Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generic define pragma + string alias #20979

Merged
merged 4 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,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
Expand Down
2 changes: 1 addition & 1 deletion compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,5 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasOutParams")
defineSymbol("nimHasSystemRaisesDefect")
defineSymbol("nimHasWarnUnnamedBreak")
defineSymbol("nimHasGenericDefine")
defineSymbol("nimHasDefineAliases")
23 changes: 16 additions & 7 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
109 changes: 83 additions & 26 deletions compiler/semfold.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
18 changes: 18 additions & 0 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -8032,6 +8032,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
====================

Expand Down
23 changes: 23 additions & 0 deletions doc/manual_experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
=======================

Expand Down
64 changes: 48 additions & 16 deletions tests/misc/tdefine.nim
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -17,29 +28,50 @@ 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)
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)