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:
metagn
2022-12-13 23:20:55 +03:00
committed by GitHub
parent 2564b5c938
commit 9a50033d5b
8 changed files with 218 additions and 53 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -149,3 +149,5 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasOutParams")
defineSymbol("nimHasSystemRaisesDefect")
defineSymbol("nimHasWarnUnnamedBreak")
defineSymbol("nimHasGenericDefine")
defineSymbol("nimHasDefineAliases")

View File

@@ -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)

View File

@@ -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:

View File

@@ -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
====================

View File

@@ -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
=======================

View File

@@ -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)