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
commit 9a50033d5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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 - 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 for the right-hand side of type definitions in type sections. Previously
they would error with "invalid indentation". 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 - Compile-time define changes:
use accents as in ``defined(`a.b.c`)`` to access such defines. - `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: - [Macro pragmas](https://nim-lang.github.io/Nim/manual.html#userminusdefined-pragmas-macro-pragmas) changes:
- Templates now accept macro pragmas. - Templates now accept macro pragmas.
- Macro pragmas for var/let/const sections have been redesigned in a way that works - 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, mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym,
mNHint, mNWarning, mNError, mNHint, mNWarning, mNError,
mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2, mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2,
mNimvm, mIntDefine, mStrDefine, mBoolDefine, mRunnableExamples, mNimvm, mIntDefine, mStrDefine, mBoolDefine, mGenericDefine, mRunnableExamples,
mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf,
mSymIsInstantiationOf, mNodeId, mPrivateAccess, mZeroDefault mSymIsInstantiationOf, mNodeId, mPrivateAccess, mZeroDefault

View file

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

View file

@ -81,7 +81,8 @@ const
wGuard, wGoto, wCursor, wNoalias, wAlign} wGuard, wGoto, wCursor, wNoalias, wAlign}
constPragmas* = declPragmas + {wHeader, wMagic, constPragmas* = declPragmas + {wHeader, wMagic,
wGensym, wInject, wGensym, wInject,
wIntDefine, wStrDefine, wBoolDefine, wCompilerProc, wCore} wIntDefine, wStrDefine, wBoolDefine, wDefine,
wCompilerProc, wCore}
paramPragmas* = {wNoalias, wInject, wGensym} paramPragmas* = {wNoalias, wInject, wGensym}
letPragmas* = varPragmas letPragmas* = varPragmas
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect, procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
@ -476,8 +477,16 @@ proc processPop(c: PContext, n: PNode) =
when defined(debugOptions): when defined(debugOptions):
echo c.config $ n.info, " POP config is now ", c.config.options echo c.config $ n.info, " POP config is now ", c.config.options
proc processDefine(c: PContext, n: PNode) = proc processDefineConst(c: PContext, n: PNode, sym: PSym, kind: TMagic) =
if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): 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) defineSymbol(c.config.symbols, n[1].ident.s)
else: else:
invalidPragma(c, n) invalidPragma(c, n)
@ -1066,7 +1075,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
recordPragma(c, it, "error", s) recordPragma(c, it, "error", s)
localError(c.config, it.info, errUser, s) localError(c.config, it.info, errUser, s)
of wFatal: fatal(c.config, it.info, expectStrLit(c, it)) 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 wUndef: processUndef(c, it)
of wCompile: processCompile(c, it) of wCompile: processCompile(c, it)
of wLink: processLink(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) noVal(c, it)
sym.flags.incl sfBase sym.flags.incl sfBase
of wIntDefine: of wIntDefine:
sym.magic = mIntDefine processDefineConst(c, n, sym, mIntDefine)
of wStrDefine: of wStrDefine:
sym.magic = mStrDefine processDefineConst(c, n, sym, mStrDefine)
of wBoolDefine: of wBoolDefine:
sym.magic = mBoolDefine processDefineConst(c, n, sym, mBoolDefine)
of wUsed: of wUsed:
noVal(c, it) noVal(c, it)
if sym == nil: invalidPragma(c, it) if sym == nil: invalidPragma(c, it)

View file

@ -13,7 +13,7 @@
import import
strutils, options, ast, trees, nimsets, strutils, options, ast, trees, nimsets,
platform, math, msgs, idents, renderer, types, platform, math, msgs, idents, renderer, types,
commands, magicsys, modulegraphs, strtabs, lineinfos commands, magicsys, modulegraphs, strtabs, lineinfos, wordrecg
from system/memory import nimCStrLen 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 & localError(g.config, n.info, "cannot convert " & $value &
" to " & typeToString(n.typ)) " 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 = proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode =
let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc}) let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc})
let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc}) let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc})
@ -490,6 +495,81 @@ proc newSymNodeTypeDesc*(s: PSym; idgen: IdGenerator; info: TLineInfo): PNode =
else: else:
result.typ = s.typ 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 = proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode =
result = nil result = nil
case n.kind 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 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 mBuildCPU: result = newStrNodeT(platform.CPU[g.config.target.hostCPU].name.toLowerAscii, n, g)
of mAppType: result = getAppType(n, g) of mAppType: result = getAppType(n, g)
of mIntDefine: of mIntDefine, mStrDefine, mBoolDefine, mGenericDefine:
if isDefined(g.config, s.name.s): result = foldDefine(m, s, n, idgen, g)
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)
else: else:
result = copyTree(s.astdef) result = copyTree(s.astdef)
of skProc, skFunc, skMethod: 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 The syntax `-d:flag`:option: is actually just a shortcut for
`-d:flag=true`:option:. `-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 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 The `void` type is only valid for parameters and return types; other symbols
cannot have the type `void`. 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 Top-down type inference
======================= =======================

View file

@ -1,12 +1,23 @@
discard """ discard """
joinable: false 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 when defined(useGenericDefine):
const booldef2 {.booldefine.} = true {.pragma: booldefine2, define.}
const intdef {.intdefine.} = 0 {.pragma: intdefine2, define.}
const strdef {.strdefine.} = "" {.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(booldef)
doAssert defined(booldef2) doAssert defined(booldef2)
@ -17,17 +28,28 @@ doAssert not booldef2
doAssert intdef == 2 doAssert intdef == 2
doAssert strdef == "foobar" 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 # Intentionally not defined from command line
const booldef3 {.booldefine.} = true const booldef3 {.booldefine2.} = true
const intdef2 {.intdefine.} = 1 const intdef2 {.intdefine2.} = 1
const strdef2 {.strdefine.} = "abc" const strdef2 {.strdefine2.} = "abc"
type T = object type T = object
when booldef3: when booldef3:
field1: int field1: int
when intdef2 == 1: when intdef2 == 1:
field2: int field2: int
when strdef2 == "abc": when strdef2 == "abc":
field3: int field3: int
doAssert not defined(booldef3) doAssert not defined(booldef3)
doAssert not defined(intdef2) doAssert not defined(intdef2)
@ -35,11 +57,21 @@ doAssert not defined(strdef2)
discard T(field1: 1, field2: 2, field3: 3) discard T(field1: 1, field2: 2, field3: 3)
doAssert defined(namespaced.define) doAssert defined(namespaced.define)
const `namespaced.define` {.booldefine.} = true const `namespaced.define` {.booldefine2.} = true
doAssert not `namespaced.define` 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) doAssert defined(double.namespaced.define)
const `double.namespaced.define` {.booldefine.} = false const `double.namespaced.define` {.booldefine2.} = false
doAssert `double.namespaced.define` 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) doAssert not defined(namespaced.butnotdefined)