mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 08:54:53 +00:00
generic define pragma + string alias (#20979)
* generic `define` pragma + string alias * clean * add tests and document * remove char/float, minimize changelog
This commit is contained in:
30
changelog.md
30
changelog.md
@@ -221,9 +221,33 @@
|
||||
- Full command syntax and block arguments i.e. `foo a, b: c` are now allowed
|
||||
for the right-hand side of type definitions in type sections. Previously
|
||||
they would error with "invalid indentation".
|
||||
- `defined` now accepts identifiers separated by dots, i.e. `defined(a.b.c)`.
|
||||
In the command line, this is defined as `-d:a.b.c`. Older versions can
|
||||
use accents as in ``defined(`a.b.c`)`` to access such defines.
|
||||
|
||||
- Compile-time define changes:
|
||||
- `defined` now accepts identifiers separated by dots, i.e. `defined(a.b.c)`.
|
||||
In the command line, this is defined as `-d:a.b.c`. Older versions can
|
||||
use accents as in ``defined(`a.b.c`)`` to access such defines.
|
||||
- [Define pragmas for constants](https://nim-lang.github.io/Nim/manual.html#implementation-specific-pragmas-compileminustime-define-pragmas)
|
||||
now support a string argument for qualified define names.
|
||||
|
||||
```nim
|
||||
# -d:package.FooBar=42
|
||||
const FooBar {.intdefine: "package.FooBar".}: int = 5
|
||||
echo FooBar # 42
|
||||
```
|
||||
|
||||
This was added to help disambiguate similar define names for different packages.
|
||||
In older versions, this could only be achieved with something like the following:
|
||||
|
||||
```nim
|
||||
const FooBar = block:
|
||||
const `package.FooBar` {.intdefine.}: int = 5
|
||||
`package.FooBar`
|
||||
```
|
||||
- A generic `define` pragma for constants has been added that interprets
|
||||
the value of the define based on the type of the constant value.
|
||||
See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#generic-define-pragma)
|
||||
for a list of supported types.
|
||||
|
||||
- [Macro pragmas](https://nim-lang.github.io/Nim/manual.html#userminusdefined-pragmas-macro-pragmas) changes:
|
||||
- Templates now accept macro pragmas.
|
||||
- Macro pragmas for var/let/const sections have been redesigned in a way that works
|
||||
|
||||
@@ -712,7 +712,7 @@ type
|
||||
mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym,
|
||||
mNHint, mNWarning, mNError,
|
||||
mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2,
|
||||
mNimvm, mIntDefine, mStrDefine, mBoolDefine, mRunnableExamples,
|
||||
mNimvm, mIntDefine, mStrDefine, mBoolDefine, mGenericDefine, mRunnableExamples,
|
||||
mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf,
|
||||
mSymIsInstantiationOf, mNodeId, mPrivateAccess, mZeroDefault
|
||||
|
||||
|
||||
@@ -149,3 +149,5 @@ proc initDefines*(symbols: StringTableRef) =
|
||||
defineSymbol("nimHasOutParams")
|
||||
defineSymbol("nimHasSystemRaisesDefect")
|
||||
defineSymbol("nimHasWarnUnnamedBreak")
|
||||
defineSymbol("nimHasGenericDefine")
|
||||
defineSymbol("nimHasDefineAliases")
|
||||
|
||||
@@ -81,7 +81,8 @@ const
|
||||
wGuard, wGoto, wCursor, wNoalias, wAlign}
|
||||
constPragmas* = declPragmas + {wHeader, wMagic,
|
||||
wGensym, wInject,
|
||||
wIntDefine, wStrDefine, wBoolDefine, wCompilerProc, wCore}
|
||||
wIntDefine, wStrDefine, wBoolDefine, wDefine,
|
||||
wCompilerProc, wCore}
|
||||
paramPragmas* = {wNoalias, wInject, wGensym}
|
||||
letPragmas* = varPragmas
|
||||
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
|
||||
@@ -476,8 +477,16 @@ proc processPop(c: PContext, n: PNode) =
|
||||
when defined(debugOptions):
|
||||
echo c.config $ n.info, " POP config is now ", c.config.options
|
||||
|
||||
proc processDefine(c: PContext, n: PNode) =
|
||||
if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent):
|
||||
proc processDefineConst(c: PContext, n: PNode, sym: PSym, kind: TMagic) =
|
||||
sym.magic = kind
|
||||
if n.kind in nkPragmaCallKinds and n.len == 2:
|
||||
# could also use TLib
|
||||
n[1] = getStrLitNode(c, n)
|
||||
|
||||
proc processDefine(c: PContext, n: PNode, sym: PSym) =
|
||||
if sym != nil and sym.kind == skConst:
|
||||
processDefineConst(c, n, sym, mGenericDefine)
|
||||
elif (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent):
|
||||
defineSymbol(c.config.symbols, n[1].ident.s)
|
||||
else:
|
||||
invalidPragma(c, n)
|
||||
@@ -1066,7 +1075,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
recordPragma(c, it, "error", s)
|
||||
localError(c.config, it.info, errUser, s)
|
||||
of wFatal: fatal(c.config, it.info, expectStrLit(c, it))
|
||||
of wDefine: processDefine(c, it)
|
||||
of wDefine: processDefine(c, it, sym)
|
||||
of wUndef: processUndef(c, it)
|
||||
of wCompile: processCompile(c, it)
|
||||
of wLink: processLink(c, it)
|
||||
@@ -1213,11 +1222,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||
noVal(c, it)
|
||||
sym.flags.incl sfBase
|
||||
of wIntDefine:
|
||||
sym.magic = mIntDefine
|
||||
processDefineConst(c, n, sym, mIntDefine)
|
||||
of wStrDefine:
|
||||
sym.magic = mStrDefine
|
||||
processDefineConst(c, n, sym, mStrDefine)
|
||||
of wBoolDefine:
|
||||
sym.magic = mBoolDefine
|
||||
processDefineConst(c, n, sym, mBoolDefine)
|
||||
of wUsed:
|
||||
noVal(c, it)
|
||||
if sym == nil: invalidPragma(c, it)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import
|
||||
strutils, options, ast, trees, nimsets,
|
||||
platform, math, msgs, idents, renderer, types,
|
||||
commands, magicsys, modulegraphs, strtabs, lineinfos
|
||||
commands, magicsys, modulegraphs, strtabs, lineinfos, wordrecg
|
||||
|
||||
from system/memory import nimCStrLen
|
||||
|
||||
@@ -371,6 +371,11 @@ proc rangeCheck(n: PNode, value: Int128; g: ModuleGraph) =
|
||||
localError(g.config, n.info, "cannot convert " & $value &
|
||||
" to " & typeToString(n.typ))
|
||||
|
||||
proc floatRangeCheck(n: PNode, value: BiggestFloat; g: ModuleGraph) =
|
||||
if value < firstFloat(n.typ) or value > lastFloat(n.typ):
|
||||
localError(g.config, n.info, "cannot convert " & $value &
|
||||
" to " & typeToString(n.typ))
|
||||
|
||||
proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode =
|
||||
let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc})
|
||||
let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc})
|
||||
@@ -490,6 +495,81 @@ proc newSymNodeTypeDesc*(s: PSym; idgen: IdGenerator; info: TLineInfo): PNode =
|
||||
else:
|
||||
result.typ = s.typ
|
||||
|
||||
proc foldDefine(m, s: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode =
|
||||
var name = s.name.s
|
||||
let prag = extractPragma(s)
|
||||
if prag != nil:
|
||||
for it in prag:
|
||||
if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent:
|
||||
let word = whichKeyword(it[0].ident)
|
||||
if word in {wStrDefine, wIntDefine, wBoolDefine, wDefine}:
|
||||
# should be processed in pragmas.nim already
|
||||
if it[1].kind in {nkStrLit, nkRStrLit, nkTripleStrLit}:
|
||||
name = it[1].strVal
|
||||
if isDefined(g.config, name):
|
||||
let str = g.config.symbols[name]
|
||||
case s.magic
|
||||
of mIntDefine:
|
||||
try:
|
||||
result = newIntNodeT(toInt128(str.parseInt), n, idgen, g)
|
||||
except ValueError:
|
||||
localError(g.config, s.info,
|
||||
"{.intdefine.} const was set to an invalid integer: '" &
|
||||
str & "'")
|
||||
of mStrDefine:
|
||||
result = newStrNodeT(str, n, g)
|
||||
of mBoolDefine:
|
||||
try:
|
||||
result = newIntNodeT(toInt128(str.parseBool.int), n, idgen, g)
|
||||
except ValueError:
|
||||
localError(g.config, s.info,
|
||||
"{.booldefine.} const was set to an invalid bool: '" &
|
||||
str & "'")
|
||||
of mGenericDefine:
|
||||
let rawTyp = s.typ
|
||||
# pretend we don't support distinct types
|
||||
let typ = rawTyp.skipTypes(abstractVarRange-{tyDistinct})
|
||||
try:
|
||||
template intNode(value): PNode =
|
||||
let val = toInt128(value)
|
||||
rangeCheck(n, val, g)
|
||||
newIntNodeT(val, n, idgen, g)
|
||||
case typ.kind
|
||||
of tyString, tyCstring:
|
||||
result = newStrNodeT(str, n, g)
|
||||
of tyInt..tyInt64:
|
||||
result = intNode(str.parseBiggestInt)
|
||||
of tyUInt..tyUInt64:
|
||||
result = intNode(str.parseBiggestUInt)
|
||||
of tyBool:
|
||||
result = intNode(str.parseBool.int)
|
||||
of tyEnum:
|
||||
# compile time parseEnum
|
||||
let ident = getIdent(g.cache, str)
|
||||
for e in typ.n:
|
||||
if e.kind != nkSym: internalError(g.config, "foldDefine for enum")
|
||||
let es = e.sym
|
||||
let match =
|
||||
if es.ast.isNil:
|
||||
es.name.id == ident.id
|
||||
else:
|
||||
es.ast.strVal == str
|
||||
if match:
|
||||
result = intNode(es.position)
|
||||
break
|
||||
if result.isNil:
|
||||
raise newException(ValueError, "invalid enum value: " & str)
|
||||
else:
|
||||
localError(g.config, s.info, "unsupported type $1 for define '$2'" %
|
||||
[name, typeToString(rawTyp)])
|
||||
except ValueError as e:
|
||||
localError(g.config, s.info,
|
||||
"could not process define '$1' of type $2; $3" %
|
||||
[name, typeToString(rawTyp), e.msg])
|
||||
else: result = copyTree(s.astdef) # unreachable
|
||||
else:
|
||||
result = copyTree(s.astdef)
|
||||
|
||||
proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode =
|
||||
result = nil
|
||||
case n.kind
|
||||
@@ -509,31 +589,8 @@ proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode
|
||||
of mBuildOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.hostOS].name), n, g)
|
||||
of mBuildCPU: result = newStrNodeT(platform.CPU[g.config.target.hostCPU].name.toLowerAscii, n, g)
|
||||
of mAppType: result = getAppType(n, g)
|
||||
of mIntDefine:
|
||||
if isDefined(g.config, s.name.s):
|
||||
try:
|
||||
result = newIntNodeT(toInt128(g.config.symbols[s.name.s].parseInt), n, idgen, g)
|
||||
except ValueError:
|
||||
localError(g.config, s.info,
|
||||
"{.intdefine.} const was set to an invalid integer: '" &
|
||||
g.config.symbols[s.name.s] & "'")
|
||||
else:
|
||||
result = copyTree(s.astdef)
|
||||
of mStrDefine:
|
||||
if isDefined(g.config, s.name.s):
|
||||
result = newStrNodeT(g.config.symbols[s.name.s], n, g)
|
||||
else:
|
||||
result = copyTree(s.astdef)
|
||||
of mBoolDefine:
|
||||
if isDefined(g.config, s.name.s):
|
||||
try:
|
||||
result = newIntNodeT(toInt128(g.config.symbols[s.name.s].parseBool.int), n, idgen, g)
|
||||
except ValueError:
|
||||
localError(g.config, s.info,
|
||||
"{.booldefine.} const was set to an invalid bool: '" &
|
||||
g.config.symbols[s.name.s] & "'")
|
||||
else:
|
||||
result = copyTree(s.astdef)
|
||||
of mIntDefine, mStrDefine, mBoolDefine, mGenericDefine:
|
||||
result = foldDefine(m, s, n, idgen, g)
|
||||
else:
|
||||
result = copyTree(s.astdef)
|
||||
of skProc, skFunc, skMethod:
|
||||
|
||||
@@ -8049,6 +8049,24 @@ used. To see if a value was provided, `defined(FooBar)` can be used.
|
||||
The syntax `-d:flag`:option: is actually just a shortcut for
|
||||
`-d:flag=true`:option:.
|
||||
|
||||
These pragmas also accept an optional string argument for qualified
|
||||
define names.
|
||||
|
||||
```nim
|
||||
const FooBar {.intdefine: "package.FooBar".}: int = 5
|
||||
echo FooBar
|
||||
```
|
||||
|
||||
```cmd
|
||||
nim c -d:package.FooBar=42 foobar.nim
|
||||
```
|
||||
|
||||
This helps disambiguate define names in different packages.
|
||||
|
||||
See also the [generic `define` pragma](manual_experimental.html#generic-define-pragma)
|
||||
for a version of these pragmas that detects the type of the define based on
|
||||
the constant value.
|
||||
|
||||
User-defined pragmas
|
||||
====================
|
||||
|
||||
|
||||
@@ -65,6 +65,29 @@ However, a `void` type cannot be inferred in generic code:
|
||||
The `void` type is only valid for parameters and return types; other symbols
|
||||
cannot have the type `void`.
|
||||
|
||||
Generic `define` pragma
|
||||
=======================
|
||||
|
||||
Aside the [typed define pragmas for constants](manual.html#implementation-specific-pragmas-compileminustime-define-pragmas),
|
||||
there is a generic `{.define.}` pragma that interprets the value of the define
|
||||
based on the type of the constant value.
|
||||
|
||||
```nim
|
||||
const foo {.define: "package.foo".} = 123
|
||||
const bar {.define: "package.bar".} = false
|
||||
```
|
||||
|
||||
```cmd
|
||||
nim c -d:package.foo=456 -d:package.bar foobar.nim
|
||||
```
|
||||
|
||||
The following types are supported:
|
||||
|
||||
* `string` and `cstring`
|
||||
* Signed and unsigned integer types
|
||||
* `bool`
|
||||
* Enums
|
||||
|
||||
Top-down type inference
|
||||
=======================
|
||||
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
discard """
|
||||
joinable: false
|
||||
cmd: "nim c -d:booldef -d:booldef2=false -d:intdef=2 -d:strdef=foobar -d:namespaced.define=false -d:double.namespaced.define -r $file"
|
||||
cmd: "nim c $options -d:booldef -d:booldef2=false -d:intdef=2 -d:strdef=foobar -d:namespaced.define=false -d:double.namespaced.define -r $file"
|
||||
matrix: "; -d:useGenericDefine"
|
||||
"""
|
||||
|
||||
const booldef {.booldefine.} = false
|
||||
const booldef2 {.booldefine.} = true
|
||||
const intdef {.intdefine.} = 0
|
||||
const strdef {.strdefine.} = ""
|
||||
when defined(useGenericDefine):
|
||||
{.pragma: booldefine2, define.}
|
||||
{.pragma: intdefine2, define.}
|
||||
{.pragma: strdefine2, define.}
|
||||
else:
|
||||
|
||||
{.pragma: booldefine2, booldefine.}
|
||||
{.pragma: intdefine2, intdefine.}
|
||||
{.pragma: strdefine2, strdefine.}
|
||||
|
||||
const booldef {.booldefine2.} = false
|
||||
const booldef2 {.booldefine2.} = true
|
||||
const intdef {.intdefine2.} = 0
|
||||
const strdef {.strdefine2.} = ""
|
||||
|
||||
doAssert defined(booldef)
|
||||
doAssert defined(booldef2)
|
||||
@@ -17,17 +28,28 @@ doAssert not booldef2
|
||||
doAssert intdef == 2
|
||||
doAssert strdef == "foobar"
|
||||
|
||||
when defined(useGenericDefine):
|
||||
block:
|
||||
const uintdef {.define: "intdef".}: uint = 17
|
||||
doAssert intdef == int(uintdef)
|
||||
const cstrdef {.define: "strdef".}: cstring = "not strdef"
|
||||
doAssert $cstrdef == strdef
|
||||
type FooBar = enum foo, bar, foobar
|
||||
const enumdef {.define: "strdef".} = foo
|
||||
doAssert $enumdef == strdef
|
||||
doAssert enumdef == foobar
|
||||
|
||||
# Intentionally not defined from command line
|
||||
const booldef3 {.booldefine.} = true
|
||||
const intdef2 {.intdefine.} = 1
|
||||
const strdef2 {.strdefine.} = "abc"
|
||||
const booldef3 {.booldefine2.} = true
|
||||
const intdef2 {.intdefine2.} = 1
|
||||
const strdef2 {.strdefine2.} = "abc"
|
||||
type T = object
|
||||
when booldef3:
|
||||
field1: int
|
||||
when intdef2 == 1:
|
||||
field2: int
|
||||
when strdef2 == "abc":
|
||||
field3: int
|
||||
when booldef3:
|
||||
field1: int
|
||||
when intdef2 == 1:
|
||||
field2: int
|
||||
when strdef2 == "abc":
|
||||
field3: int
|
||||
|
||||
doAssert not defined(booldef3)
|
||||
doAssert not defined(intdef2)
|
||||
@@ -35,11 +57,21 @@ doAssert not defined(strdef2)
|
||||
discard T(field1: 1, field2: 2, field3: 3)
|
||||
|
||||
doAssert defined(namespaced.define)
|
||||
const `namespaced.define` {.booldefine.} = true
|
||||
const `namespaced.define` {.booldefine2.} = true
|
||||
doAssert not `namespaced.define`
|
||||
when defined(useGenericDefine):
|
||||
const aliasToNamespacedDefine {.define: "namespaced.define".} = not `namespaced.define`
|
||||
else:
|
||||
const aliasToNamespacedDefine {.booldefine: "namespaced.define".} = not `namespaced.define`
|
||||
doAssert aliasToNamespacedDefine == `namespaced.define`
|
||||
|
||||
doAssert defined(double.namespaced.define)
|
||||
const `double.namespaced.define` {.booldefine.} = false
|
||||
const `double.namespaced.define` {.booldefine2.} = false
|
||||
doAssert `double.namespaced.define`
|
||||
when defined(useGenericDefine):
|
||||
const aliasToDoubleNamespacedDefine {.define: "double.namespaced.define".} = not `double.namespaced.define`
|
||||
else:
|
||||
const aliasToDoubleNamespacedDefine {.booldefine: "double.namespaced.define".} = not `double.namespaced.define`
|
||||
doAssert aliasToDoubleNamespacedDefine == `double.namespaced.define`
|
||||
|
||||
doAssert not defined(namespaced.butnotdefined)
|
||||
|
||||
Reference in New Issue
Block a user