diff options
Diffstat (limited to 'lib/core')
-rw-r--r-- | lib/core/hotcodereloading.nim | 41 | ||||
-rw-r--r-- | lib/core/locks.nim | 90 | ||||
-rw-r--r-- | lib/core/macrocache.nim | 246 | ||||
-rw-r--r-- | lib/core/macros.nim | 1812 | ||||
-rw-r--r-- | lib/core/rlocks.nim | 57 | ||||
-rw-r--r-- | lib/core/typeinfo.nim | 723 |
6 files changed, 2969 insertions, 0 deletions
diff --git a/lib/core/hotcodereloading.nim b/lib/core/hotcodereloading.nim new file mode 100644 index 000000000..3a876885c --- /dev/null +++ b/lib/core/hotcodereloading.nim @@ -0,0 +1,41 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2019 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Unstable API. + +when defined(hotcodereloading): + import + std/macros + + template beforeCodeReload*(body: untyped) = + hcrAddEventHandler(true, proc = body) {.executeOnReload.} + + template afterCodeReload*(body: untyped) = + hcrAddEventHandler(false, proc = body) {.executeOnReload.} + + macro hasModuleChanged*(module: typed): untyped = + if module.kind != nnkSym or module.symKind != nskModule: + error "hasModuleChanged expects a module symbol", module + return newCall(bindSym"hcrHasModuleChanged", newLit(module.signatureHash)) + + proc hasAnyModuleChanged*(): bool = hcrReloadNeeded() + + when not defined(js): + template performCodeReload* = + when isMainModule: + {.warning: "Code residing in the main module will not be changed from calling a code-reload".} + hcrPerformCodeReload() + else: + template performCodeReload* = discard +else: + template beforeCodeReload*(body: untyped) = discard + template afterCodeReload*(body: untyped) = discard + template hasModuleChanged*(module: typed): bool = false + proc hasAnyModuleChanged*(): bool = false + template performCodeReload*() = discard diff --git a/lib/core/locks.nim b/lib/core/locks.nim new file mode 100644 index 000000000..523727479 --- /dev/null +++ b/lib/core/locks.nim @@ -0,0 +1,90 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains Nim's support for locks and condition vars. + +#[ +for js, for now we treat locks as noop's to avoid pushing `when defined(js)` +in client code that uses locks. +]# + +when not compileOption("threads") and not defined(nimdoc): + when false: # fix #12330 + {.error: "Locks requires --threads:on option.".} + +import std/private/syslocks + +type + Lock* = SysLock ## Nim lock; whether this is re-entrant + ## or not is unspecified! + Cond* = SysCond ## Nim condition variable + +{.push stackTrace: off.} + + +proc `$`*(lock: Lock): string = + # workaround bug #14873 + result = "()" + +proc initLock*(lock: var Lock) {.inline.} = + ## Initializes the given lock. + when not defined(js): + initSysLock(lock) + +proc deinitLock*(lock: Lock) {.inline.} = + ## Frees the resources associated with the lock. + deinitSys(lock) + +proc tryAcquire*(lock: var Lock): bool {.inline.} = + ## Tries to acquire the given lock. Returns `true` on success. + result = tryAcquireSys(lock) + +proc acquire*(lock: var Lock) {.inline.} = + ## Acquires the given lock. + when not defined(js): + acquireSys(lock) + +proc release*(lock: var Lock) {.inline.} = + ## Releases the given lock. + when not defined(js): + releaseSys(lock) + + +proc initCond*(cond: var Cond) {.inline.} = + ## Initializes the given condition variable. + initSysCond(cond) + +proc deinitCond*(cond: Cond) {.inline.} = + ## Frees the resources associated with the condition variable. + deinitSysCond(cond) + +proc wait*(cond: var Cond, lock: var Lock) {.inline.} = + ## Waits on the condition variable `cond`. + waitSysCond(cond, lock) + +proc signal*(cond: var Cond) {.inline.} = + ## Sends a signal to the condition variable `cond`. + signalSysCond(cond) + +proc broadcast*(cond: var Cond) {.inline.} = + ## Unblocks all threads currently blocked on the + ## specified condition variable `cond`. + broadcastSysCond(cond) + +template withLock*(a: Lock, body: untyped) = + ## Acquires the given lock, executes the statements in body and + ## releases the lock after the statements finish executing. + acquire(a) + {.locks: [a].}: + try: + body + finally: + release(a) + +{.pop.} diff --git a/lib/core/macrocache.nim b/lib/core/macrocache.nim new file mode 100644 index 000000000..39999fa11 --- /dev/null +++ b/lib/core/macrocache.nim @@ -0,0 +1,246 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module provides an API for macros to collect compile-time information +## across module boundaries. It should be used instead of global `{.compileTime.}` +## variables as those break incremental compilation. +## +## The main feature of this module is that if you create `CacheTable`s or +## any other `Cache` types with the same name in different modules, their +## content will be shared, meaning that you can fill a `CacheTable` in +## one module, and iterate over its contents in another. + +runnableExamples: + import std/macros + + const mcTable = CacheTable"myTable" + const mcSeq = CacheSeq"mySeq" + const mcCounter = CacheCounter"myCounter" + + static: + # add new key "val" with the value `myval` + let myval = newLit("hello ic") + mcTable["val"] = myval + assert mcTable["val"].kind == nnkStrLit + + # Can access the same cache from different static contexts + # All the information is retained + static: + # get value from `mcTable` and add it to `mcSeq` + mcSeq.add(mcTable["val"]) + assert mcSeq.len == 1 + + static: + assert mcSeq[0].strVal == "hello ic" + + # increase `mcCounter` by 3 + mcCounter.inc(3) + assert mcCounter.value == 3 + + +type + CacheSeq* = distinct string + ## Compile-time sequence of `NimNode`s. + CacheTable* = distinct string + ## Compile-time table of key-value pairs. + ## + ## Keys are `string`s and values are `NimNode`s. + CacheCounter* = distinct string + ## Compile-time counter, uses `int` for storing the count. + +proc value*(c: CacheCounter): int {.magic: "NccValue".} = + ## Returns the value of a counter `c`. + runnableExamples: + static: + let counter = CacheCounter"valTest" + # default value is 0 + assert counter.value == 0 + + inc counter + assert counter.value == 1 + +proc inc*(c: CacheCounter; by = 1) {.magic: "NccInc".} = + ## Increments the counter `c` with the value `by`. + runnableExamples: + static: + let counter = CacheCounter"incTest" + inc counter + inc counter, 5 + + assert counter.value == 6 + +proc add*(s: CacheSeq; value: NimNode) {.magic: "NcsAdd".} = + ## Adds `value` to `s`. + runnableExamples: + import std/macros + const mySeq = CacheSeq"addTest" + + static: + mySeq.add(newLit(5)) + mySeq.add(newLit("hello ic")) + + assert mySeq.len == 2 + assert mySeq[1].strVal == "hello ic" + +proc incl*(s: CacheSeq; value: NimNode) {.magic: "NcsIncl".} = + ## Adds `value` to `s`. + ## + ## .. hint:: This doesn't do anything if `value` is already in `s`. + runnableExamples: + import std/macros + const mySeq = CacheSeq"inclTest" + + static: + mySeq.incl(newLit(5)) + mySeq.incl(newLit(5)) + + # still one element + assert mySeq.len == 1 + +proc len*(s: CacheSeq): int {.magic: "NcsLen".} = + ## Returns the length of `s`. + runnableExamples: + import std/macros + + const mySeq = CacheSeq"lenTest" + static: + let val = newLit("helper") + mySeq.add(val) + assert mySeq.len == 1 + + mySeq.add(val) + assert mySeq.len == 2 + +proc `[]`*(s: CacheSeq; i: int): NimNode {.magic: "NcsAt".} = + ## Returns the `i`th value from `s`. + runnableExamples: + import std/macros + + const mySeq = CacheSeq"subTest" + static: + mySeq.add(newLit(42)) + assert mySeq[0].intVal == 42 + +proc `[]`*(s: CacheSeq; i: BackwardsIndex): NimNode = + ## Returns the `i`th last value from `s`. + runnableExamples: + import std/macros + + const mySeq = CacheSeq"backTest" + static: + mySeq &= newLit(42) + mySeq &= newLit(7) + assert mySeq[^1].intVal == 7 # Last item + assert mySeq[^2].intVal == 42 # Second last item + s[s.len - int(i)] + +iterator items*(s: CacheSeq): NimNode = + ## Iterates over each item in `s`. + runnableExamples: + import std/macros + const myseq = CacheSeq"itemsTest" + + static: + myseq.add(newLit(5)) + myseq.add(newLit(42)) + + for val in myseq: + # check that all values in `myseq` are int literals + assert val.kind == nnkIntLit + + for i in 0 ..< len(s): yield s[i] + +proc `[]=`*(t: CacheTable; key: string, value: NimNode) {.magic: "NctPut".} = + ## Inserts a `(key, value)` pair into `t`. + ## + ## .. warning:: `key` has to be unique! Assigning `value` to a `key` that is already + ## in the table will result in a compiler error. + runnableExamples: + import std/macros + + const mcTable = CacheTable"subTest" + static: + # assign newLit(5) to the key "value" + mcTable["value"] = newLit(5) + + # check that we can get the value back + assert mcTable["value"].kind == nnkIntLit + +proc len*(t: CacheTable): int {.magic: "NctLen".} = + ## Returns the number of elements in `t`. + runnableExamples: + import std/macros + + const dataTable = CacheTable"lenTest" + static: + dataTable["key"] = newLit(5) + assert dataTable.len == 1 + +proc `[]`*(t: CacheTable; key: string): NimNode {.magic: "NctGet".} = + ## Retrieves the `NimNode` value at `t[key]`. + runnableExamples: + import std/macros + + const mcTable = CacheTable"subTest" + static: + mcTable["toAdd"] = newStmtList() + + # get the NimNode back + assert mcTable["toAdd"].kind == nnkStmtList + +proc hasKey*(t: CacheTable; key: string): bool = + ## Returns true if `key` is in the table `t`. + ## + ## See also: + ## * [contains proc][contains(CacheTable, string)] for use with the `in` operator + runnableExamples: + import std/macros + const mcTable = CacheTable"hasKeyEx" + static: + assert not mcTable.hasKey("foo") + mcTable["foo"] = newEmptyNode() + # Will now be true since we inserted a value + assert mcTable.hasKey("foo") + discard "Implemented in vmops" + +proc contains*(t: CacheTable; key: string): bool {.inline.} = + ## Alias of [hasKey][hasKey(CacheTable, string)] for use with the `in` operator. + runnableExamples: + import std/macros + const mcTable = CacheTable"containsEx" + static: + mcTable["foo"] = newEmptyNode() + # Will be true since we gave it a value before + assert "foo" in mcTable + t.hasKey(key) + +proc hasNext(t: CacheTable; iter: int): bool {.magic: "NctHasNext".} +proc next(t: CacheTable; iter: int): (string, NimNode, int) {.magic: "NctNext".} + +iterator pairs*(t: CacheTable): (string, NimNode) = + ## Iterates over all `(key, value)` pairs in `t`. + runnableExamples: + import std/macros + const mytabl = CacheTable"values" + + static: + mytabl["intVal"] = newLit(5) + mytabl["otherVal"] = newLit(6) + for key, val in mytabl: + # make sure that we actually get the same keys + assert key in ["intVal", "otherVal"] + + # all vals are int literals + assert val.kind == nnkIntLit + + var h = 0 + while hasNext(t, h): + let (a, b, h2) = next(t, h) + yield (a, b) + h = h2 diff --git a/lib/core/macros.nim b/lib/core/macros.nim new file mode 100644 index 000000000..7646b165c --- /dev/null +++ b/lib/core/macros.nim @@ -0,0 +1,1812 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +include "system/inclrtl" +import std/private/since + +when defined(nimPreviewSlimSystem): + import std/[assertions, formatfloat] + + +## This module contains the interface to the compiler's abstract syntax +## tree (`AST`:idx:). Macros operate on this tree. +## +## See also: +## * `macros tutorial <tut3.html>`_ +## * `macros section in Nim manual <manual.html#macros>`_ + +## .. include:: ../../doc/astspec.txt + +## .. importdoc:: system.nim + +# If you look for the implementation of the magic symbol +# ``{.magic: "Foo".}``, search for `mFoo` and `opcFoo`. + +type + NimNodeKind* = enum + nnkNone, nnkEmpty, nnkIdent, nnkSym, + nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit, + nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit, + nnkUInt16Lit, nnkUInt32Lit, nnkUInt64Lit, nnkFloatLit, + nnkFloat32Lit, nnkFloat64Lit, nnkFloat128Lit, nnkStrLit, nnkRStrLit, + nnkTripleStrLit, nnkNilLit, nnkComesFrom, nnkDotCall, + nnkCommand, nnkCall, nnkCallStrLit, nnkInfix, + nnkPrefix, nnkPostfix, nnkHiddenCallConv, + nnkExprEqExpr, + nnkExprColonExpr, nnkIdentDefs, nnkVarTuple, + nnkPar, nnkObjConstr, nnkCurly, nnkCurlyExpr, + nnkBracket, nnkBracketExpr, nnkPragmaExpr, nnkRange, + nnkDotExpr, nnkCheckedFieldExpr, nnkDerefExpr, nnkIfExpr, + nnkElifExpr, nnkElseExpr, nnkLambda, nnkDo, nnkAccQuoted, + nnkTableConstr, nnkBind, + nnkClosedSymChoice, + nnkOpenSymChoice, + nnkHiddenStdConv, + nnkHiddenSubConv, nnkConv, nnkCast, nnkStaticExpr, + nnkAddr, nnkHiddenAddr, nnkHiddenDeref, nnkObjDownConv, + nnkObjUpConv, nnkChckRangeF, nnkChckRange64, nnkChckRange, + nnkStringToCString, nnkCStringToString, nnkAsgn, + nnkFastAsgn, nnkGenericParams, nnkFormalParams, nnkOfInherit, + nnkImportAs, nnkProcDef, nnkMethodDef, nnkConverterDef, + nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch, + nnkElifBranch, nnkExceptBranch, nnkElse, + nnkAsmStmt, nnkPragma, nnkPragmaBlock, nnkIfStmt, nnkWhenStmt, + nnkForStmt, nnkParForStmt, nnkWhileStmt, nnkCaseStmt, + nnkTypeSection, nnkVarSection, nnkLetSection, nnkConstSection, + nnkConstDef, nnkTypeDef, + nnkYieldStmt, nnkDefer, nnkTryStmt, nnkFinally, nnkRaiseStmt, + nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt, + nnkDiscardStmt, nnkStmtList, + nnkImportStmt, + nnkImportExceptStmt, + nnkExportStmt, + nnkExportExceptStmt, + nnkFromStmt, + nnkIncludeStmt, + nnkBindStmt, nnkMixinStmt, nnkUsingStmt, + nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, + nnkStmtListType, nnkBlockType, + nnkWith, nnkWithout, + nnkTypeOfExpr, nnkObjectTy, + nnkTupleTy, nnkTupleClassTy, nnkTypeClassTy, nnkStaticTy, + nnkRecList, nnkRecCase, nnkRecWhen, + nnkRefTy, nnkPtrTy, nnkVarTy, + nnkConstTy, nnkOutTy, + nnkDistinctTy, + nnkProcTy, + nnkIteratorTy, # iterator type + nnkSinkAsgn, + nnkEnumTy, + nnkEnumFieldDef, + nnkArgList, nnkPattern + nnkHiddenTryStmt, + nnkClosure, + nnkGotoState, + nnkState, + nnkBreakState, + nnkFuncDef, + nnkTupleConstr, + nnkError, ## erroneous AST node + nnkModuleRef, nnkReplayAction, nnkNilRodNode ## internal IC nodes + nnkOpenSym + + NimNodeKinds* = set[NimNodeKind] + NimTypeKind* = enum # some types are no longer used, see ast.nim + ntyNone, ntyBool, ntyChar, ntyEmpty, + ntyAlias, ntyNil, ntyExpr, ntyStmt, + ntyTypeDesc, ntyGenericInvocation, ntyGenericBody, ntyGenericInst, + ntyGenericParam, ntyDistinct, ntyEnum, ntyOrdinal, + ntyArray, ntyObject, ntyTuple, ntySet, + ntyRange, ntyPtr, ntyRef, ntyVar, + ntySequence, ntyProc, ntyPointer, ntyOpenArray, + ntyString, ntyCString, ntyForward, ntyInt, + ntyInt8, ntyInt16, ntyInt32, ntyInt64, + ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128, + ntyUInt, ntyUInt8, ntyUInt16, ntyUInt32, ntyUInt64, + ntyUnused0, ntyUnused1, ntyUnused2, + ntyVarargs, + ntyUncheckedArray, + ntyError, + ntyBuiltinTypeClass, ntyUserTypeClass, ntyUserTypeClassInst, + ntyCompositeTypeClass, ntyInferred, ntyAnd, ntyOr, ntyNot, + ntyAnything, ntyStatic, ntyFromExpr, ntyOptDeprecated, ntyVoid + + TNimTypeKinds* {.deprecated.} = set[NimTypeKind] + NimSymKind* = enum + nskUnknown, nskConditional, nskDynLib, nskParam, + nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet, + nskConst, nskResult, + nskProc, nskFunc, nskMethod, nskIterator, + nskConverter, nskMacro, nskTemplate, nskField, + nskEnumField, nskForVar, nskLabel, + nskStub + + TNimSymKinds* {.deprecated.} = set[NimSymKind] + +const + nnkMutableTy* {.deprecated.} = nnkOutTy + nnkSharedTy* {.deprecated.} = nnkSinkAsgn + +type + NimIdent* {.deprecated.} = object of RootObj + ## Represents a Nim identifier in the AST. **Note**: This is only + ## rarely useful, for identifier construction from a string + ## use `ident"abc"`. + + NimSymObj = object # hidden + NimSym* {.deprecated.} = ref NimSymObj + ## Represents a Nim *symbol* in the compiler; a *symbol* is a looked-up + ## *ident*. + + +const + nnkLiterals* = {nnkCharLit..nnkNilLit} + # see matching set CallNodes below + nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, + nnkCallStrLit, nnkHiddenCallConv} + nnkPragmaCallKinds = {nnkExprColonExpr, nnkCall, nnkCallStrLit} + +{.push warnings: off.} + +proc toNimIdent*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated: + "Deprecated since version 0.18.0: Use 'ident' or 'newIdentNode' instead.".} + ## Constructs an identifier from the string `s`. + +proc `==`*(a, b: NimIdent): bool {.magic: "EqIdent", noSideEffect, deprecated: + "Deprecated since version 0.18.1; Use '==' on 'NimNode' instead.".} + ## Compares two Nim identifiers. + +proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.} + ## Compare two Nim nodes. Return true if nodes are structurally + ## equivalent. This means two independently created nodes can be equal. + +proc `==`*(a, b: NimSym): bool {.magic: "EqNimrodNode", noSideEffect, deprecated: + "Deprecated since version 0.18.1; Use '==(NimNode, NimNode)' instead.".} + ## Compares two Nim symbols. + +{.pop.} + +proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} = + ## Compares two Nim nodes' types. Return true if the types are the same, + ## e.g. true when comparing alias with original type. + discard + +proc len*(n: NimNode): int {.magic: "NLen", noSideEffect.} + ## Returns the number of children of `n`. + +proc `[]`*(n: NimNode, i: int): NimNode {.magic: "NChild", noSideEffect.} + ## Get `n`'s `i`'th child. + +proc `[]`*(n: NimNode, i: BackwardsIndex): NimNode = n[n.len - i.int] + ## Get `n`'s `i`'th child. + +template `^^`(n: NimNode, i: untyped): untyped = + (when i is BackwardsIndex: n.len - int(i) else: int(i)) + +proc `[]`*[T, U: Ordinal](n: NimNode, x: HSlice[T, U]): seq[NimNode] = + ## Slice operation for NimNode. + ## Returns a seq of child of `n` who inclusive range `[n[x.a], n[x.b]]`. + let xa = n ^^ x.a + let L = (n ^^ x.b) - xa + 1 + result = newSeq[NimNode](L) + for i in 0..<L: + result[i] = n[i + xa] + +proc `[]=`*(n: NimNode, i: int, child: NimNode) {.magic: "NSetChild", + noSideEffect.} + ## Set `n`'s `i`'th child to `child`. + +proc `[]=`*(n: NimNode, i: BackwardsIndex, child: NimNode) = + ## Set `n`'s `i`'th child to `child`. + n[n.len - i.int] = child + +template `or`*(x, y: NimNode): NimNode = + ## Evaluate `x` and when it is not an empty node, return + ## it. Otherwise evaluate to `y`. Can be used to chain several + ## expressions to get the first expression that is not empty. + ## ```nim + ## let node = mightBeEmpty() or mightAlsoBeEmpty() or fallbackNode + ## ``` + + let arg = x + if arg != nil and arg.kind != nnkEmpty: + arg + else: + y + +proc add*(father, child: NimNode): NimNode {.magic: "NAdd", discardable, + noSideEffect.} + ## Adds the `child` to the `father` node. Returns the + ## father node so that calls can be nested. + +proc add*(father: NimNode, children: varargs[NimNode]): NimNode {. + magic: "NAddMultiple", discardable, noSideEffect.} + ## Adds each child of `children` to the `father` node. + ## Returns the `father` node so that calls can be nested. + +proc del*(father: NimNode, idx = 0, n = 1) {.magic: "NDel", noSideEffect.} + ## Deletes `n` children of `father` starting at index `idx`. + +proc kind*(n: NimNode): NimNodeKind {.magic: "NKind", noSideEffect.} + ## Returns the `kind` of the node `n`. + +proc intVal*(n: NimNode): BiggestInt {.magic: "NIntVal", noSideEffect.} + ## Returns an integer value from any integer literal or enum field symbol. + +proc floatVal*(n: NimNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.} + ## Returns a float from any floating point literal. + + +proc symKind*(symbol: NimNode): NimSymKind {.magic: "NSymKind", noSideEffect.} +proc getImpl*(symbol: NimNode): NimNode {.magic: "GetImpl", noSideEffect.} + ## Returns a copy of the declaration of a symbol or `nil`. +proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.} + ## Returns the string value of an identifier, symbol, comment, or string literal. + ## + ## See also: + ## * `strVal= proc<#strVal=,NimNode,string>`_ for setting the string value. + +{.push warnings: off.} # silence `deprecated` + +proc ident*(n: NimNode): NimIdent {.magic: "NIdent", noSideEffect, deprecated: + "Deprecated since version 0.18.1; All functionality is defined on 'NimNode'.".} + +proc symbol*(n: NimNode): NimSym {.magic: "NSymbol", noSideEffect, deprecated: + "Deprecated since version 0.18.1; All functionality is defined on 'NimNode'.".} + +proc getImpl*(s: NimSym): NimNode {.magic: "GetImpl", noSideEffect, deprecated: "use `getImpl: NimNode -> NimNode` instead".} + +proc `$`*(i: NimIdent): string {.magic: "NStrVal", noSideEffect, deprecated: + "Deprecated since version 0.18.1; Use 'strVal' instead.".} + ## Converts a Nim identifier to a string. + +proc `$`*(s: NimSym): string {.magic: "NStrVal", noSideEffect, deprecated: + "Deprecated since version 0.18.1; Use 'strVal' instead.".} + ## Converts a Nim symbol to a string. + +{.pop.} + +when (NimMajor, NimMinor, NimPatch) >= (1, 3, 5) or defined(nimSymImplTransform): + proc getImplTransformed*(symbol: NimNode): NimNode {.magic: "GetImplTransf", noSideEffect.} + ## For a typed proc returns the AST after transformation pass; this is useful + ## for debugging how the compiler transforms code (e.g.: `defer`, `for`) but + ## note that code transformations are implementation dependent and subject to change. + ## See an example in `tests/macros/tmacros_various.nim`. + +proc owner*(sym: NimNode): NimNode {.magic: "SymOwner", noSideEffect, deprecated.} + ## Accepts a node of kind `nnkSym` and returns its owner's symbol. + ## The meaning of 'owner' depends on `sym`'s `NimSymKind` and declaration + ## context. For top level declarations this is an `nskModule` symbol, + ## for proc local variables an `nskProc` symbol, for enum/object fields an + ## `nskType` symbol, etc. For symbols without an owner, `nil` is returned. + ## + ## See also: + ## * `symKind proc<#symKind,NimNode>`_ to get the kind of a symbol + ## * `getImpl proc<#getImpl,NimNode>`_ to get the declaration of a symbol + +proc isInstantiationOf*(instanceProcSym, genProcSym: NimNode): bool {.magic: "SymIsInstantiationOf", noSideEffect.} + ## Checks if a proc symbol is an instance of the generic proc symbol. + ## Useful to check proc symbols against generic symbols + ## returned by `bindSym`. + +proc getType*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} + ## With 'getType' you can access the node's `type`:idx:. A Nim type is + ## mapped to a Nim AST too, so it's slightly confusing but it means the same + ## API can be used to traverse types. Recursive types are flattened for you + ## so there is no danger of infinite recursions during traversal. To + ## resolve recursive types, you have to call 'getType' again. To see what + ## kind of type it is, call `typeKind` on getType's result. + +proc getType*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} + ## Version of `getType` which takes a `typedesc`. + +proc typeKind*(n: NimNode): NimTypeKind {.magic: "NGetType", noSideEffect.} + ## Returns the type kind of the node 'n' that should represent a type, that + ## means the node should have been obtained via `getType`. + +proc getTypeInst*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} = + ## Returns the `type`:idx: of a node in a form matching the way the + ## type instance was declared in the code. + runnableExamples: + type + Vec[N: static[int], T] = object + arr: array[N, T] + Vec4[T] = Vec[4, T] + Vec4f = Vec4[float32] + var a: Vec4f + var b: Vec4[float32] + var c: Vec[4, float32] + macro dumpTypeInst(x: typed): untyped = + newLit(x.getTypeInst.repr) + doAssert(dumpTypeInst(a) == "Vec4f") + doAssert(dumpTypeInst(b) == "Vec4[float32]") + doAssert(dumpTypeInst(c) == "Vec[4, float32]") + +proc getTypeInst*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} + ## Version of `getTypeInst` which takes a `typedesc`. + +proc getTypeImpl*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} = + ## Returns the `type`:idx: of a node in a form matching the implementation + ## of the type. Any intermediate aliases are expanded to arrive at the final + ## type implementation. You can instead use `getImpl` on a symbol if you + ## want to find the intermediate aliases. + runnableExamples: + type + Vec[N: static[int], T] = object + arr: array[N, T] + Vec4[T] = Vec[4, T] + Vec4f = Vec4[float32] + var a: Vec4f + var b: Vec4[float32] + var c: Vec[4, float32] + macro dumpTypeImpl(x: typed): untyped = + newLit(x.getTypeImpl.repr) + let t = """ +object + arr: array[0 .. 3, float32]""" + doAssert(dumpTypeImpl(a) == t) + doAssert(dumpTypeImpl(b) == t) + doAssert(dumpTypeImpl(c) == t) + +proc signatureHash*(n: NimNode): string {.magic: "NSigHash", noSideEffect.} + ## Returns a stable identifier derived from the signature of a symbol. + ## The signature combines many factors such as the type of the symbol, + ## the owning module of the symbol and others. The same identifier is + ## used in the back-end to produce the mangled symbol name. + +proc symBodyHash*(s: NimNode): string {.noSideEffect.} = + ## Returns a stable digest for symbols derived not only from type signature + ## and owning module, but also implementation body. All procs/variables used in + ## the implementation of this symbol are hashed recursively as well, including + ## magics from system module. + discard + +proc getTypeImpl*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} + ## Version of `getTypeImpl` which takes a `typedesc`. + +proc `intVal=`*(n: NimNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.} +proc `floatVal=`*(n: NimNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.} + +{.push warnings: off.} + +proc `symbol=`*(n: NimNode, val: NimSym) {.magic: "NSetSymbol", noSideEffect, deprecated: + "Deprecated since version 0.18.1; Generate a new 'NimNode' with 'genSym' instead.".} + +proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect, deprecated: + "Deprecated since version 0.18.1; Generate a new 'NimNode' with 'ident(string)' instead.".} + +{.pop.} + +proc `strVal=`*(n: NimNode, val: string) {.magic: "NSetStrVal", noSideEffect.} + ## Sets the string value of a string literal or comment. + ## Setting `strVal` is disallowed for `nnkIdent` and `nnkSym` nodes; a new node + ## must be created using `ident` or `bindSym` instead. + ## + ## See also: + ## * `strVal proc<#strVal,NimNode>`_ for getting the string value. + ## * `ident proc<#ident,string>`_ for creating an identifier. + ## * `bindSym proc<#bindSym%2C%2CBindSymRule>`_ for binding a symbol. + +proc newNimNode*(kind: NimNodeKind, + lineInfoFrom: NimNode = nil): NimNode + {.magic: "NNewNimNode", noSideEffect.} + ## Creates a new AST node of the specified kind. + ## + ## The `lineInfoFrom` parameter is used for line information when the + ## produced code crashes. You should ensure that it is set to a node that + ## you are transforming. + +proc copyNimNode*(n: NimNode): NimNode {.magic: "NCopyNimNode", noSideEffect.} = + ## Creates a new AST node by copying the node `n`. Note that unlike `copyNimTree`, + ## child nodes of `n` are not copied. + runnableExamples: + macro foo(x: typed) = + var s = copyNimNode(x) + doAssert s.len == 0 + doAssert s.kind == nnkStmtList + + foo: + let x = 12 + echo x + +proc copyNimTree*(n: NimNode): NimNode {.magic: "NCopyNimTree", noSideEffect.} = + ## Creates a new AST node by recursively copying the node `n`. Note that + ## unlike `copyNimNode`, this copies `n`, the children of `n`, etc. + runnableExamples: + macro foo(x: typed) = + var s = copyNimTree(x) + doAssert s.len == 2 + doAssert s.kind == nnkStmtList + + foo: + let x = 12 + echo x + +when defined(nimHasNoReturnError): + {.pragma: errorNoReturn, noreturn.} +else: + {.pragma: errorNoReturn.} + +proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign, errorNoReturn.} + ## Writes an error message at compile time. The optional `n: NimNode` + ## parameter is used as the source for file and line number information in + ## the compilation error message. + +proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", benign.} + ## Writes a warning message at compile time. + +proc hint*(msg: string, n: NimNode = nil) {.magic: "NHint", benign.} + ## Writes a hint message at compile time. + +proc newStrLitNode*(s: string): NimNode {.noSideEffect.} = + ## Creates a string literal node from `s`. + result = newNimNode(nnkStrLit) + result.strVal = s + +proc newCommentStmtNode*(s: string): NimNode {.noSideEffect.} = + ## Creates a comment statement node. + result = newNimNode(nnkCommentStmt) + result.strVal = s + +proc newIntLitNode*(i: BiggestInt): NimNode = + ## Creates an int literal node from `i`. + result = newNimNode(nnkIntLit) + result.intVal = i + +proc newFloatLitNode*(f: BiggestFloat): NimNode = + ## Creates a float literal node from `f`. + result = newNimNode(nnkFloatLit) + result.floatVal = f + +{.push warnings: off.} + +proc newIdentNode*(i: NimIdent): NimNode {.deprecated: "use ident(string)".} = + ## Creates an identifier node from `i`. + result = newNimNode(nnkIdent) + result.ident = i + +{.pop.} + +proc newIdentNode*(i: string): NimNode {.magic: "StrToIdent", noSideEffect.} + ## Creates an identifier node from `i`. It is simply an alias for + ## `ident(string)`. Use that, it's shorter. + +proc ident*(name: string): NimNode {.magic: "StrToIdent", noSideEffect.} + ## Create a new ident node from a string. + +type + BindSymRule* = enum ## Specifies how `bindSym` behaves. The difference + ## between open and closed symbols can be found in + ## `<manual.html#symbol-lookup-in-generics-open-and-closed-symbols>`_ + brClosed, ## only the symbols in current scope are bound + brOpen, ## open for overloaded symbols, but may be a single + ## symbol if not ambiguous (the rules match that of + ## binding in generics) + brForceOpen ## same as brOpen, but it will always be open even + ## if not ambiguous (this cannot be achieved with + ## any other means in the language currently) + +proc bindSym*(ident: string | NimNode, rule: BindSymRule = brClosed): NimNode {. + magic: "NBindSym", noSideEffect.} + ## Creates a node that binds `ident` to a symbol node. The bound symbol + ## may be an overloaded symbol. + ## if `ident` is a NimNode, it must have `nnkIdent` kind. + ## If `rule == brClosed` either an `nnkClosedSymChoice` tree is + ## returned or `nnkSym` if the symbol is not ambiguous. + ## If `rule == brOpen` either an `nnkOpenSymChoice` tree is + ## returned or `nnkSym` if the symbol is not ambiguous. + ## If `rule == brForceOpen` always an `nnkOpenSymChoice` tree is + ## returned even if the symbol is not ambiguous. + ## + ## See the `manual <manual.html#macros-bindsym>`_ for more details. + +proc genSym*(kind: NimSymKind = nskLet; ident = ""): NimNode {. + magic: "NGenSym", noSideEffect.} + ## Generates a fresh symbol that is guaranteed to be unique. The symbol + ## needs to occur in a declaration context. + +proc callsite*(): NimNode {.magic: "NCallSite", benign, deprecated: + "Deprecated since v0.18.1; use `varargs[untyped]` in the macro prototype instead".} + ## Returns the AST of the invocation expression that invoked this macro. + # see https://github.com/nim-lang/RFCs/issues/387 as candidate replacement. + +proc toStrLit*(n: NimNode): NimNode = + ## Converts the AST `n` to the concrete Nim code and wraps that + ## in a string literal node. + return newStrLitNode(repr(n)) + +type + LineInfo* = object + filename*: string + line*,column*: int + +proc `$`*(arg: LineInfo): string = + ## Return a string representation in the form `filepath(line, column)`. + # BUG: without `result = `, gives compile error + result = arg.filename & "(" & $arg.line & ", " & $arg.column & ")" + +#proc lineinfo*(n: NimNode): LineInfo {.magic: "NLineInfo", noSideEffect.} +# ## returns the position the node appears in the original source file +# ## in the form filename(line, col) + +proc getLine(arg: NimNode): int {.magic: "NLineInfo", noSideEffect.} +proc getColumn(arg: NimNode): int {.magic: "NLineInfo", noSideEffect.} +proc getFile(arg: NimNode): string {.magic: "NLineInfo", noSideEffect.} + +proc copyLineInfo*(arg: NimNode, info: NimNode) {.magic: "NLineInfo", noSideEffect.} + ## Copy lineinfo from `info`. + +proc setLine(arg: NimNode, line: uint16) {.magic: "NLineInfo", noSideEffect.} +proc setColumn(arg: NimNode, column: int16) {.magic: "NLineInfo", noSideEffect.} +proc setFile(arg: NimNode, file: string) {.magic: "NLineInfo", noSideEffect.} + +proc setLineInfo*(arg: NimNode, file: string, line: int, column: int) = + ## Sets the line info on the NimNode. The file needs to exists, but can be a + ## relative path. If you want to attach line info to a block using `quote` + ## you'll need to add the line information after the quote block. + arg.setFile(file) + arg.setLine(line.uint16) + arg.setColumn(column.int16) + +proc setLineInfo*(arg: NimNode, lineInfo: LineInfo) = + ## See `setLineInfo proc<#setLineInfo,NimNode,string,int,int>`_ + setLineInfo(arg, lineInfo.filename, lineInfo.line, lineInfo.column) + +proc lineInfoObj*(n: NimNode): LineInfo = + ## Returns `LineInfo` of `n`, using absolute path for `filename`. + result = LineInfo(filename: n.getFile, line: n.getLine, column: n.getColumn) + +proc lineInfo*(arg: NimNode): string = + ## Return line info in the form `filepath(line, column)`. + $arg.lineInfoObj + +proc internalParseExpr(s, filename: string): NimNode {. + magic: "ParseExprToAst", noSideEffect.} + +proc internalParseStmt(s, filename: string): NimNode {. + magic: "ParseStmtToAst", noSideEffect.} + +proc internalErrorFlag*(): string {.magic: "NError", noSideEffect.} + ## Some builtins set an error flag. This is then turned into a proper + ## exception. **Note**: Ordinary application code should not call this. + +proc parseExpr*(s: string; filename: string = ""): NimNode {.noSideEffect.} = + ## Compiles the passed string to its AST representation. + ## Expects a single expression. Raises `ValueError` for parsing errors. + ## A filename can be given for more informative errors. + result = internalParseExpr(s, filename) + let x = internalErrorFlag() + if x.len > 0: raise newException(ValueError, x) + +proc parseStmt*(s: string; filename: string = ""): NimNode {.noSideEffect.} = + ## Compiles the passed string to its AST representation. + ## Expects one or more statements. Raises `ValueError` for parsing errors. + ## A filename can be given for more informative errors. + result = internalParseStmt(s, filename) + let x = internalErrorFlag() + if x.len > 0: raise newException(ValueError, x) + +proc getAst*(macroOrTemplate: untyped): NimNode {.magic: "ExpandToAst", noSideEffect.} + ## Obtains the AST nodes returned from a macro or template invocation. + ## See also `genasts.genAst`. + ## Example: + ## ```nim + ## macro FooMacro() = + ## var ast = getAst(BarTemplate()) + ## ``` + +proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.} = + ## Quasi-quoting operator. + ## Accepts an expression or a block and returns the AST that represents it. + ## Within the quoted AST, you are able to interpolate NimNode expressions + ## from the surrounding scope. If no operator is given, quoting is done using + ## backticks. Otherwise, the given operator must be used as a prefix operator + ## for any interpolated expression. The original meaning of the interpolation + ## operator may be obtained by escaping it (by prefixing it with itself) when used + ## as a unary operator: + ## e.g. `@` is escaped as `@@`, `&%` is escaped as `&%&%` and so on; see examples. + ## + ## A custom operator interpolation needs accent quoted (``) whenever it resolves + ## to a symbol. + ## + ## See also `genasts <genasts.html>`_ which avoids some issues with `quote`. + runnableExamples: + macro check(ex: untyped) = + # this is a simplified version of the check macro from the + # unittest module. + + # If there is a failed check, we want to make it easy for + # the user to jump to the faulty line in the code, so we + # get the line info here: + var info = ex.lineinfo + + # We will also display the code string of the failed check: + var expString = ex.toStrLit + + # Finally we compose the code to implement the check: + result = quote do: + if not `ex`: + echo `info` & ": Check failed: " & `expString` + check 1 + 1 == 2 + + runnableExamples: + # example showing how to define a symbol that requires backtick without + # quoting it. + var destroyCalled = false + macro bar() = + let s = newTree(nnkAccQuoted, ident"=destroy") + # let s = ident"`=destroy`" # this would not work + result = quote do: + type Foo = object + # proc `=destroy`(a: var Foo) = destroyCalled = true # this would not work + proc `s`(a: var Foo) = destroyCalled = true + block: + let a = Foo() + bar() + doAssert destroyCalled + + runnableExamples: + # custom `op` + var destroyCalled = false + macro bar(ident) = + var x = 1.5 + result = quote("@") do: + type Foo = object + let `@ident` = 0 # custom op interpolated symbols need quoted (``) + proc `=destroy`(a: var Foo) = + doAssert @x == 1.5 + doAssert compiles(@x == 1.5) + let b1 = @[1,2] + let b2 = @@[1,2] + doAssert $b1 == "[1, 2]" + doAssert $b2 == "@[1, 2]" + destroyCalled = true + block: + let a = Foo() + bar(someident) + doAssert destroyCalled + + proc `&%`(x: int): int = 1 + proc `&%`(x, y: int): int = 2 + + macro bar2() = + var x = 3 + result = quote("&%") do: + var y = &%x # quoting operator + doAssert &%&%y == 1 # unary operator => need to escape + doAssert y &% y == 2 # binary operator => no need to escape + doAssert y == 3 + bar2() + +proc expectKind*(n: NimNode, k: NimNodeKind) = + ## Checks that `n` is of kind `k`. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check the AST that is passed to them. + if n.kind != k: error("Expected a node of kind " & $k & ", got " & $n.kind, n) + +proc expectMinLen*(n: NimNode, min: int) = + ## Checks that `n` has at least `min` children. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check its number of arguments. + if n.len < min: error("Expected a node with at least " & $min & " children, got " & $n.len, n) + +proc expectLen*(n: NimNode, len: int) = + ## Checks that `n` has exactly `len` children. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check its number of arguments. + if n.len != len: error("Expected a node with " & $len & " children, got " & $n.len, n) + +proc expectLen*(n: NimNode, min, max: int) = + ## Checks that `n` has a number of children in the range `min..max`. + ## If this is not the case, compilation aborts with an error message. + ## This is useful for writing macros that check its number of arguments. + if n.len < min or n.len > max: + error("Expected a node with " & $min & ".." & $max & " children, got " & $n.len, n) + +proc newTree*(kind: NimNodeKind, + children: varargs[NimNode]): NimNode = + ## Produces a new node with children. + result = newNimNode(kind) + result.add(children) + +proc newCall*(theProc: NimNode, args: varargs[NimNode]): NimNode = + ## Produces a new call node. `theProc` is the proc that is called with + ## the arguments `args[0..]`. + result = newNimNode(nnkCall) + result.add(theProc) + result.add(args) + +{.push warnings: off.} + +proc newCall*(theProc: NimIdent, args: varargs[NimNode]): NimNode {.deprecated: + "Deprecated since v0.18.1; use 'newCall(string, ...)' or 'newCall(NimNode, ...)' instead".} = + ## Produces a new call node. `theProc` is the proc that is called with + ## the arguments `args[0..]`. + result = newNimNode(nnkCall) + result.add(newIdentNode(theProc)) + result.add(args) + +{.pop.} + +proc newCall*(theProc: string, + args: varargs[NimNode]): NimNode = + ## Produces a new call node. `theProc` is the proc that is called with + ## the arguments `args[0..]`. + result = newNimNode(nnkCall) + result.add(newIdentNode(theProc)) + result.add(args) + +proc newLit*(c: char): NimNode = + ## Produces a new character literal node. + result = newNimNode(nnkCharLit) + result.intVal = ord(c) + +proc newLit*(i: int): NimNode = + ## Produces a new integer literal node. + result = newNimNode(nnkIntLit) + result.intVal = i + +proc newLit*(i: int8): NimNode = + ## Produces a new integer literal node. + result = newNimNode(nnkInt8Lit) + result.intVal = i + +proc newLit*(i: int16): NimNode = + ## Produces a new integer literal node. + result = newNimNode(nnkInt16Lit) + result.intVal = i + +proc newLit*(i: int32): NimNode = + ## Produces a new integer literal node. + result = newNimNode(nnkInt32Lit) + result.intVal = i + +proc newLit*(i: int64): NimNode = + ## Produces a new integer literal node. + result = newNimNode(nnkInt64Lit) + result.intVal = i + +proc newLit*(i: uint): NimNode = + ## Produces a new unsigned integer literal node. + result = newNimNode(nnkUIntLit) + result.intVal = BiggestInt(i) + +proc newLit*(i: uint8): NimNode = + ## Produces a new unsigned integer literal node. + result = newNimNode(nnkUInt8Lit) + result.intVal = BiggestInt(i) + +proc newLit*(i: uint16): NimNode = + ## Produces a new unsigned integer literal node. + result = newNimNode(nnkUInt16Lit) + result.intVal = BiggestInt(i) + +proc newLit*(i: uint32): NimNode = + ## Produces a new unsigned integer literal node. + result = newNimNode(nnkUInt32Lit) + result.intVal = BiggestInt(i) + +proc newLit*(i: uint64): NimNode = + ## Produces a new unsigned integer literal node. + result = newNimNode(nnkUInt64Lit) + result.intVal = BiggestInt(i) + +proc newLit*(b: bool): NimNode = + ## Produces a new boolean literal node. + result = if b: bindSym"true" else: bindSym"false" + +proc newLit*(s: string): NimNode = + ## Produces a new string literal node. + result = newNimNode(nnkStrLit) + result.strVal = s + +when false: + # the float type is not really a distinct type as described in https://github.com/nim-lang/Nim/issues/5875 + proc newLit*(f: float): NimNode = + ## Produces a new float literal node. + result = newNimNode(nnkFloatLit) + result.floatVal = f + +proc newLit*(f: float32): NimNode = + ## Produces a new float literal node. + result = newNimNode(nnkFloat32Lit) + result.floatVal = f + +proc newLit*(f: float64): NimNode = + ## Produces a new float literal node. + result = newNimNode(nnkFloat64Lit) + result.floatVal = f + +when declared(float128): + proc newLit*(f: float128): NimNode = + ## Produces a new float literal node. + result = newNimNode(nnkFloat128Lit) + result.floatVal = f + +proc newLit*(arg: enum): NimNode = + result = newCall( + arg.typeof.getTypeInst[1], + newLit(int(arg)) + ) + +proc newLit*[N,T](arg: array[N,T]): NimNode +proc newLit*[T](arg: seq[T]): NimNode +proc newLit*[T](s: set[T]): NimNode +proc newLit*[T: tuple](arg: T): NimNode + +proc newLit*(arg: object): NimNode = + result = nnkObjConstr.newTree(arg.typeof.getTypeInst[1]) + for a, b in arg.fieldPairs: + result.add nnkExprColonExpr.newTree( newIdentNode(a), newLit(b) ) + +proc newLit*(arg: ref object): NimNode = + ## produces a new ref type literal node. + result = nnkObjConstr.newTree(arg.typeof.getTypeInst[1]) + for a, b in fieldPairs(arg[]): + result.add nnkExprColonExpr.newTree(newIdentNode(a), newLit(b)) + +proc newLit*[N,T](arg: array[N,T]): NimNode = + result = nnkBracket.newTree + for x in arg: + result.add newLit(x) + +proc newLit*[T](arg: seq[T]): NimNode = + let bracket = nnkBracket.newTree + for x in arg: + bracket.add newLit(x) + result = nnkPrefix.newTree( + bindSym"@", + bracket + ) + if arg.len == 0: + # add type cast for empty seq + var typ = getTypeInst(typeof(arg))[1] + result = newCall(typ,result) + +proc newLit*[T](s: set[T]): NimNode = + result = nnkCurly.newTree + for x in s: + result.add newLit(x) + if result.len == 0: + # add type cast for empty set + var typ = getTypeInst(typeof(s))[1] + result = newCall(typ,result) + +proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".} + ## See `typetraits.isNamedTuple` + +proc newLit*[T: tuple](arg: T): NimNode = + ## use -d:nimHasWorkaround14720 to restore behavior prior to PR, forcing + ## a named tuple even when `arg` is unnamed. + result = nnkTupleConstr.newTree + when defined(nimHasWorkaround14720) or isNamedTuple(T): + for a, b in arg.fieldPairs: + result.add nnkExprColonExpr.newTree(newIdentNode(a), newLit(b)) + else: + for b in arg.fields: + result.add newLit(b) + +proc nestList*(op: NimNode; pack: NimNode): NimNode = + ## Nests the list `pack` into a tree of call expressions: + ## `[a, b, c]` is transformed into `op(a, op(c, d))`. + ## This is also known as fold expression. + if pack.len < 1: + error("`nestList` expects a node with at least 1 child") + result = pack[^1] + for i in countdown(pack.len - 2, 0): + result = newCall(op, pack[i], result) + +proc nestList*(op: NimNode; pack: NimNode; init: NimNode): NimNode = + ## Nests the list `pack` into a tree of call expressions: + ## `[a, b, c]` is transformed into `op(a, op(c, d))`. + ## This is also known as fold expression. + result = init + for i in countdown(pack.len - 1, 0): + result = newCall(op, pack[i], result) + +proc eqIdent*(a: string; b: string): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. + +proc eqIdent*(a: NimNode; b: string): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. `a` can be an identifier or a + ## symbol. `a` may be wrapped in an export marker + ## (`nnkPostfix`) or quoted with backticks (`nnkAccQuoted`), + ## these nodes will be unwrapped. + +proc eqIdent*(a: string; b: NimNode): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. `b` can be an identifier or a + ## symbol. `b` may be wrapped in an export marker + ## (`nnkPostfix`) or quoted with backticks (`nnkAccQuoted`), + ## these nodes will be unwrapped. + +proc eqIdent*(a: NimNode; b: NimNode): bool {.magic: "EqIdent", noSideEffect.} + ## Style insensitive comparison. `a` and `b` can be an + ## identifier or a symbol. Both may be wrapped in an export marker + ## (`nnkPostfix`) or quoted with backticks (`nnkAccQuoted`), + ## these nodes will be unwrapped. + +const collapseSymChoice = not defined(nimLegacyMacrosCollapseSymChoice) + +proc treeTraverse(n: NimNode; res: var string; level = 0; isLisp = false, indented = false) {.benign.} = + if level > 0: + if indented: + res.add("\n") + for i in 0 .. level-1: + if isLisp: + res.add(" ") # dumpLisp indentation + else: + res.add(" ") # dumpTree indentation + else: + res.add(" ") + + if isLisp: + res.add("(") + res.add(($n.kind).substr(3)) + + case n.kind + of nnkEmpty, nnkNilLit: + discard # same as nil node in this representation + of nnkCharLit .. nnkInt64Lit: + res.add(" " & $n.intVal) + of nnkUIntLit .. nnkUInt64Lit: + res.add(" " & $cast[uint64](n.intVal)) + of nnkFloatLit .. nnkFloat64Lit: + res.add(" " & $n.floatVal) + of nnkStrLit .. nnkTripleStrLit, nnkCommentStmt, nnkIdent, nnkSym: + res.add(" " & $n.strVal.newLit.repr) + of nnkNone: + assert false + elif n.kind in {nnkOpenSymChoice, nnkClosedSymChoice} and collapseSymChoice: + res.add(" " & $n.len) + if n.len > 0: + var allSameSymName = true + for i in 0..<n.len: + if n[i].kind != nnkSym or not eqIdent(n[i], n[0]): + allSameSymName = false + break + if allSameSymName: + res.add(" " & $n[0].strVal.newLit.repr) + else: + for j in 0 ..< n.len: + n[j].treeTraverse(res, level+1, isLisp, indented) + else: + for j in 0 ..< n.len: + n[j].treeTraverse(res, level+1, isLisp, indented) + + if isLisp: + res.add(")") + +proc treeRepr*(n: NimNode): string {.benign.} = + ## Convert the AST `n` to a human-readable tree-like string. + ## + ## See also `repr`, `lispRepr`_, and `astGenRepr`_. + result = "" + n.treeTraverse(result, isLisp = false, indented = true) + +proc lispRepr*(n: NimNode; indented = false): string {.benign.} = + ## Convert the AST `n` to a human-readable lisp-like string. + ## + ## See also `repr`, `treeRepr`_, and `astGenRepr`_. + result = "" + n.treeTraverse(result, isLisp = true, indented = indented) + +proc astGenRepr*(n: NimNode): string {.benign.} = + ## Convert the AST `n` to the code required to generate that AST. + ## + ## See also `repr`_, `treeRepr`_, and `lispRepr`_. + + const + NodeKinds = {nnkEmpty, nnkIdent, nnkSym, nnkNone, nnkCommentStmt} + LitKinds = {nnkCharLit..nnkInt64Lit, nnkFloatLit..nnkFloat64Lit, nnkStrLit..nnkTripleStrLit} + + proc traverse(res: var string, level: int, n: NimNode) {.benign.} = + for i in 0..level-1: res.add " " + if n.kind in NodeKinds: + res.add("new" & ($n.kind).substr(3) & "Node(") + elif n.kind in LitKinds: + res.add("newLit(") + elif n.kind == nnkNilLit: + res.add("newNilLit()") + else: + res.add($n.kind) + + case n.kind + of nnkEmpty, nnkNilLit: discard + of nnkCharLit: res.add("'" & $chr(n.intVal) & "'") + of nnkIntLit..nnkInt64Lit: res.add($n.intVal) + of nnkFloatLit..nnkFloat64Lit: res.add($n.floatVal) + of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkIdent, nnkSym: + res.add(n.strVal.newLit.repr) + of nnkNone: assert false + elif n.kind in {nnkOpenSymChoice, nnkClosedSymChoice} and collapseSymChoice: + res.add(", # unrepresentable symbols: " & $n.len) + if n.len > 0: + res.add(" " & n[0].strVal.newLit.repr) + else: + res.add(".newTree(") + for j in 0..<n.len: + res.add "\n" + traverse(res, level + 1, n[j]) + if j != n.len-1: + res.add(",") + + res.add("\n") + for i in 0..level-1: res.add " " + res.add(")") + + if n.kind in NodeKinds+LitKinds: + res.add(")") + + result = "" + traverse(result, 0, n) + +macro dumpTree*(s: untyped): untyped = echo s.treeRepr + ## Accepts a block of nim code and prints the parsed abstract syntax + ## tree using the `treeRepr` proc. Printing is done *at compile time*. + ## + ## You can use this as a tool to explore the Nim's abstract syntax + ## tree and to discover what kind of nodes must be created to represent + ## a certain expression/statement. + ## + ## For example: + ## ```nim + ## dumpTree: + ## echo "Hello, World!" + ## ``` + ## + ## Outputs: + ## ``` + ## StmtList + ## Command + ## Ident "echo" + ## StrLit "Hello, World!" + ## ``` + ## + ## Also see `dumpAstGen` and `dumpLisp`. + +macro dumpLisp*(s: untyped): untyped = echo s.lispRepr(indented = true) + ## Accepts a block of nim code and prints the parsed abstract syntax + ## tree using the `lispRepr` proc. Printing is done *at compile time*. + ## + ## You can use this as a tool to explore the Nim's abstract syntax + ## tree and to discover what kind of nodes must be created to represent + ## a certain expression/statement. + ## + ## For example: + ## ```nim + ## dumpLisp: + ## echo "Hello, World!" + ## ``` + ## + ## Outputs: + ## ``` + ## (StmtList + ## (Command + ## (Ident "echo") + ## (StrLit "Hello, World!"))) + ## ``` + ## + ## Also see `dumpAstGen` and `dumpTree`. + +macro dumpAstGen*(s: untyped): untyped = echo s.astGenRepr + ## Accepts a block of nim code and prints the parsed abstract syntax + ## tree using the `astGenRepr` proc. Printing is done *at compile time*. + ## + ## You can use this as a tool to write macros quicker by writing example + ## outputs and then copying the snippets into the macro for modification. + ## + ## For example: + ## ```nim + ## dumpAstGen: + ## echo "Hello, World!" + ## ``` + ## + ## Outputs: + ## ``` + ## nnkStmtList.newTree( + ## nnkCommand.newTree( + ## newIdentNode("echo"), + ## newLit("Hello, World!") + ## ) + ## ) + ## ``` + ## + ## Also see `dumpTree` and `dumpLisp`. + +proc newEmptyNode*(): NimNode {.noSideEffect.} = + ## Create a new empty node. + result = newNimNode(nnkEmpty) + +proc newStmtList*(stmts: varargs[NimNode]): NimNode = + ## Create a new statement list. + result = newNimNode(nnkStmtList).add(stmts) + +proc newPar*(exprs: NimNode): NimNode = + ## Create a new parentheses-enclosed expression. + newNimNode(nnkPar).add(exprs) + +proc newPar*(exprs: varargs[NimNode]): NimNode {.deprecated: + "don't use newPar/nnkPar to construct tuple expressions; use nnkTupleConstr instead".} = + ## Create a new parentheses-enclosed expression. + newNimNode(nnkPar).add(exprs) + +proc newBlockStmt*(label, body: NimNode): NimNode = + ## Create a new block statement with label. + return newNimNode(nnkBlockStmt).add(label, body) + +proc newBlockStmt*(body: NimNode): NimNode = + ## Create a new block: stmt. + return newNimNode(nnkBlockStmt).add(newEmptyNode(), body) + +proc newVarStmt*(name, value: NimNode): NimNode = + ## Create a new var stmt. + return newNimNode(nnkVarSection).add( + newNimNode(nnkIdentDefs).add(name, newNimNode(nnkEmpty), value)) + +proc newLetStmt*(name, value: NimNode): NimNode = + ## Create a new let stmt. + return newNimNode(nnkLetSection).add( + newNimNode(nnkIdentDefs).add(name, newNimNode(nnkEmpty), value)) + +proc newConstStmt*(name, value: NimNode): NimNode = + ## Create a new const stmt. + newNimNode(nnkConstSection).add( + newNimNode(nnkConstDef).add(name, newNimNode(nnkEmpty), value)) + +proc newAssignment*(lhs, rhs: NimNode): NimNode = + return newNimNode(nnkAsgn).add(lhs, rhs) + +proc newDotExpr*(a, b: NimNode): NimNode = + ## Create new dot expression. + ## a.dot(b) -> `a.b` + return newNimNode(nnkDotExpr).add(a, b) + +proc newColonExpr*(a, b: NimNode): NimNode = + ## Create new colon expression. + ## newColonExpr(a, b) -> `a: b` + newNimNode(nnkExprColonExpr).add(a, b) + +proc newIdentDefs*(name, kind: NimNode; + default = newEmptyNode()): NimNode = + ## Creates a new `nnkIdentDefs` node of a specific kind and value. + ## + ## `nnkIdentDefs` need to have at least three children, but they can have + ## more: first comes a list of identifiers followed by a type and value + ## nodes. This helper proc creates a three node subtree, the first subnode + ## being a single identifier name. Both the `kind` node and `default` + ## (value) nodes may be empty depending on where the `nnkIdentDefs` + ## appears: tuple or object definitions will have an empty `default` node, + ## `let` or `var` blocks may have an empty `kind` node if the + ## identifier is being assigned a value. Example: + ## + ## ```nim + ## var varSection = newNimNode(nnkVarSection).add( + ## newIdentDefs(ident("a"), ident("string")), + ## newIdentDefs(ident("b"), newEmptyNode(), newLit(3))) + ## # --> var + ## # a: string + ## # b = 3 + ## ``` + ## + ## If you need to create multiple identifiers you need to use the lower level + ## `newNimNode`: + ## ```nim + ## result = newNimNode(nnkIdentDefs).add( + ## ident("a"), ident("b"), ident("c"), ident("string"), + ## newStrLitNode("Hello")) + ## ``` + newNimNode(nnkIdentDefs).add(name, kind, default) + +proc newNilLit*(): NimNode = + ## New nil literal shortcut. + result = newNimNode(nnkNilLit) + +proc last*(node: NimNode): NimNode = node[node.len-1] + ## Return the last item in nodes children. Same as `node[^1]`. + + +const + RoutineNodes* = {nnkProcDef, nnkFuncDef, nnkMethodDef, nnkDo, nnkLambda, + nnkIteratorDef, nnkTemplateDef, nnkConverterDef, nnkMacroDef} + AtomicNodes* = {nnkNone..nnkNilLit} + # see matching set nnkCallKinds above + CallNodes* = nnkCallKinds + +proc expectKind*(n: NimNode; k: set[NimNodeKind]) = + ## Checks that `n` is of kind `k`. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check the AST that is passed to them. + if n.kind notin k: error("Expected one of " & $k & ", got " & $n.kind, n) + +proc newProc*(name = newEmptyNode(); + params: openArray[NimNode] = [newEmptyNode()]; + body: NimNode = newStmtList(); + procType = nnkProcDef; + pragmas: NimNode = newEmptyNode()): NimNode = + ## Shortcut for creating a new proc. + ## + ## The `params` array must start with the return type of the proc, + ## followed by a list of IdentDefs which specify the params. + if procType notin RoutineNodes: + error("Expected one of " & $RoutineNodes & ", got " & $procType) + pragmas.expectKind({nnkEmpty, nnkPragma}) + result = newNimNode(procType).add( + name, + newEmptyNode(), + newEmptyNode(), + newNimNode(nnkFormalParams).add(params), + pragmas, + newEmptyNode(), + body) + +proc newIfStmt*(branches: varargs[tuple[cond, body: NimNode]]): NimNode = + ## Constructor for `if` statements. + ## ```nim + ## newIfStmt( + ## (Ident, StmtList), + ## ... + ## ) + ## ``` + result = newNimNode(nnkIfStmt) + if len(branches) < 1: + error("If statement must have at least one branch") + for i in branches: + result.add(newTree(nnkElifBranch, i.cond, i.body)) + +proc newEnum*(name: NimNode, fields: openArray[NimNode], + public, pure: bool): NimNode = + + ## Creates a new enum. `name` must be an ident. Fields are allowed to be + ## either idents or EnumFieldDef: + ## ```nim + ## newEnum( + ## name = ident("Colors"), + ## fields = [ident("Blue"), ident("Red")], + ## public = true, pure = false) + ## + ## # type Colors* = Blue Red + ## ``` + + expectKind name, nnkIdent + if len(fields) < 1: + error("Enum must contain at least one field") + for field in fields: + expectKind field, {nnkIdent, nnkEnumFieldDef} + + let enumBody = newNimNode(nnkEnumTy).add(newEmptyNode()).add(fields) + var typeDefArgs = [name, newEmptyNode(), enumBody] + + if public: + let postNode = newNimNode(nnkPostfix).add( + newIdentNode("*"), typeDefArgs[0]) + + typeDefArgs[0] = postNode + + if pure: + let pragmaNode = newNimNode(nnkPragmaExpr).add( + typeDefArgs[0], + add(newNimNode(nnkPragma), newIdentNode("pure"))) + + typeDefArgs[0] = pragmaNode + + let + typeDef = add(newNimNode(nnkTypeDef), typeDefArgs) + typeSect = add(newNimNode(nnkTypeSection), typeDef) + + return typeSect + +proc copyChildrenTo*(src, dest: NimNode) = + ## Copy all children from `src` to `dest`. + for i in 0 ..< src.len: + dest.add src[i].copyNimTree + +template expectRoutine(node: NimNode) = + expectKind(node, RoutineNodes) + +proc name*(someProc: NimNode): NimNode = + someProc.expectRoutine + result = someProc[0] + if result.kind == nnkPostfix: + if result[1].kind == nnkAccQuoted: + result = result[1][0] + else: + result = result[1] + elif result.kind == nnkAccQuoted: + result = result[0] + +proc `name=`*(someProc: NimNode; val: NimNode) = + someProc.expectRoutine + if someProc[0].kind == nnkPostfix: + someProc[0][1] = val + else: someProc[0] = val + +proc params*(someProc: NimNode): NimNode = + if someProc.kind == nnkProcTy: + someProc[0] + else: + someProc.expectRoutine + someProc[3] + +proc `params=`* (someProc: NimNode; params: NimNode) = + expectKind(params, nnkFormalParams) + if someProc.kind == nnkProcTy: + someProc[0] = params + else: + someProc.expectRoutine + someProc[3] = params + +proc pragma*(someProc: NimNode): NimNode = + ## Get the pragma of a proc type. + ## These will be expanded. + if someProc.kind == nnkProcTy: + result = someProc[1] + else: + someProc.expectRoutine + result = someProc[4] +proc `pragma=`*(someProc: NimNode; val: NimNode) = + ## Set the pragma of a proc type. + expectKind(val, {nnkEmpty, nnkPragma}) + if someProc.kind == nnkProcTy: + someProc[1] = val + else: + someProc.expectRoutine + someProc[4] = val + +proc addPragma*(someProc, pragma: NimNode) = + ## Adds pragma to routine definition. + someProc.expectKind(RoutineNodes + {nnkProcTy}) + var pragmaNode = someProc.pragma + if pragmaNode.isNil or pragmaNode.kind == nnkEmpty: + pragmaNode = newNimNode(nnkPragma) + someProc.pragma = pragmaNode + pragmaNode.add(pragma) + +template badNodeKind(n, f) = + error("Invalid node kind " & $n.kind & " for macros.`" & $f & "`", n) + +proc body*(someProc: NimNode): NimNode = + case someProc.kind: + of RoutineNodes: + return someProc[6] + of nnkBlockStmt, nnkWhileStmt: + return someProc[1] + of nnkForStmt: + return someProc.last + else: + badNodeKind someProc, "body" + +proc `body=`*(someProc: NimNode, val: NimNode) = + case someProc.kind + of RoutineNodes: + someProc[6] = val + of nnkBlockStmt, nnkWhileStmt: + someProc[1] = val + of nnkForStmt: + someProc[len(someProc)-1] = val + else: + badNodeKind someProc, "body=" + +proc basename*(a: NimNode): NimNode = + ## Pull an identifier from prefix/postfix expressions. + case a.kind + of nnkIdent: result = a + of nnkPostfix, nnkPrefix: result = a[1] + of nnkPragmaExpr: result = basename(a[0]) + else: + error("Do not know how to get basename of (" & treeRepr(a) & ")\n" & + repr(a), a) + +proc `$`*(node: NimNode): string = + ## Get the string of an identifier node. + case node.kind + of nnkPostfix: + result = node.basename.strVal & "*" + of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkSym, nnkIdent: + result = node.strVal + of nnkOpenSymChoice, nnkClosedSymChoice, nnkOpenSym: + result = $node[0] + of nnkAccQuoted: + result = "" + for i in 0 ..< node.len: + result.add(repr(node[i])) + else: + badNodeKind node, "$" + +iterator items*(n: NimNode): NimNode {.inline.} = + ## Iterates over the children of the NimNode `n`. + for i in 0 ..< n.len: + yield n[i] + +iterator pairs*(n: NimNode): (int, NimNode) {.inline.} = + ## Iterates over the children of the NimNode `n` and its indices. + for i in 0 ..< n.len: + yield (i, n[i]) + +iterator children*(n: NimNode): NimNode {.inline.} = + ## Iterates over the children of the NimNode `n`. + for i in 0 ..< n.len: + yield n[i] + +template findChild*(n: NimNode; cond: untyped): NimNode {.dirty.} = + ## Find the first child node matching condition (or nil). + ## ```nim + ## var res = findChild(n, it.kind == nnkPostfix and + ## it.basename.ident == ident"foo") + ## ``` + block: + var res: NimNode + for it in n.children: + if cond: + res = it + break + res + +proc insert*(a: NimNode; pos: int; b: NimNode) = + ## Insert node `b` into node `a` at `pos`. + if len(a)-1 < pos: + # add some empty nodes first + for i in len(a)-1..pos-2: + a.add newEmptyNode() + a.add b + else: + # push the last item onto the list again + # and shift each item down to pos up one + a.add(a[a.len-1]) + for i in countdown(len(a) - 3, pos): + a[i + 1] = a[i] + a[pos] = b + +proc `basename=`*(a: NimNode; val: string) = + case a.kind + of nnkIdent: + a.strVal = val + of nnkPostfix, nnkPrefix: + a[1] = ident(val) + of nnkPragmaExpr: `basename=`(a[0], val) + else: + error("Do not know how to get basename of (" & treeRepr(a) & ")\n" & + repr(a), a) + +proc postfix*(node: NimNode; op: string): NimNode = + newNimNode(nnkPostfix).add(ident(op), node) + +proc prefix*(node: NimNode; op: string): NimNode = + newNimNode(nnkPrefix).add(ident(op), node) + +proc infix*(a: NimNode; op: string; + b: NimNode): NimNode = + newNimNode(nnkInfix).add(ident(op), a, b) + +proc unpackPostfix*(node: NimNode): tuple[node: NimNode; op: string] = + node.expectKind nnkPostfix + result = (node[1], $node[0]) + +proc unpackPrefix*(node: NimNode): tuple[node: NimNode; op: string] = + node.expectKind nnkPrefix + result = (node[1], $node[0]) + +proc unpackInfix*(node: NimNode): tuple[left: NimNode; op: string; right: NimNode] = + expectKind(node, nnkInfix) + result = (node[1], $node[0], node[2]) + +proc copy*(node: NimNode): NimNode = + ## An alias for `copyNimTree<#copyNimTree,NimNode>`_. + return node.copyNimTree() + +proc expectIdent*(n: NimNode, name: string) {.since: (1,1).} = + ## Check that `eqIdent(n,name)` holds true. If this is not the + ## case, compilation aborts with an error message. This is useful + ## for writing macros that check the AST that is passed to them. + if not eqIdent(n, name): + error("Expected identifier to be `" & name & "` here", n) + +proc hasArgOfName*(params: NimNode; name: string): bool = + ## Search `nnkFormalParams` for an argument. + expectKind(params, nnkFormalParams) + for i in 1..<params.len: + for j in 0..<params[i].len-2: + if name.eqIdent($params[i][j]): + return true + +proc addIdentIfAbsent*(dest: NimNode, ident: string) = + ## Add `ident` to `dest` if it is not present. This is intended for use + ## with pragmas. + for node in dest.children: + case node.kind + of nnkIdent: + if ident.eqIdent($node): return + of nnkExprColonExpr: + if ident.eqIdent($node[0]): return + else: discard + dest.add(ident(ident)) + +proc boolVal*(n: NimNode): bool {.noSideEffect.} = + if n.kind == nnkIntLit: n.intVal != 0 + else: n == bindSym"true" # hacky solution for now + +proc nodeID*(n: NimNode): int {.magic: "NodeId".} + ## Returns the id of `n`, when the compiler has been compiled + ## with the flag `-d:useNodeids`, otherwise returns `-1`. This + ## proc is for the purpose to debug the compiler only. + +macro expandMacros*(body: typed): untyped = + ## Expands one level of macro - useful for debugging. + ## Can be used to inspect what happens when a macro call is expanded, + ## without altering its result. + ## + ## For instance, + ## + ## ```nim + ## import std/[sugar, macros] + ## + ## let + ## x = 10 + ## y = 20 + ## expandMacros: + ## dump(x + y) + ## ``` + ## + ## will actually dump `x + y`, but at the same time will print at + ## compile time the expansion of the `dump` macro, which in this + ## case is `debugEcho ["x + y", " = ", x + y]`. + echo body.toStrLit + result = body + +proc extractTypeImpl(n: NimNode): NimNode = + ## attempts to extract the type definition of the given symbol + case n.kind + of nnkSym: # can extract an impl + result = n.getImpl.extractTypeImpl() + of nnkObjectTy, nnkRefTy, nnkPtrTy: result = n + of nnkBracketExpr: + if n.typeKind == ntyTypeDesc: + result = n[1].extractTypeImpl() + else: + doAssert n.typeKind == ntyGenericInst + result = n[0].getImpl() + of nnkTypeDef: + result = n[2] + else: error("Invalid node to retrieve type implementation of: " & $n.kind) + +proc customPragmaNode(n: NimNode): NimNode = + expectKind(n, {nnkSym, nnkDotExpr, nnkBracketExpr, nnkTypeOfExpr, nnkType, nnkCheckedFieldExpr}) + let + typ = n.getTypeInst() + + if typ.kind == nnkBracketExpr and typ.len > 1 and typ[1].kind == nnkProcTy: + return typ[1][1] + elif typ.typeKind == ntyTypeDesc: + let impl = getImpl( + if kind(typ[1]) == nnkBracketExpr: typ[1][0] + else: typ[1] + ) + if impl.kind == nnkNilLit: + return impl + elif impl[0].kind == nnkPragmaExpr: + return impl[0][1] + else: + return impl[0] # handle types which don't have macro at all + + if n.kind == nnkSym: # either an variable or a proc + let impl = n.getImpl() + if impl.kind in RoutineNodes: + return impl.pragma + elif impl.kind in {nnkIdentDefs, nnkConstDef} and impl[0].kind == nnkPragmaExpr: + return impl[0][1] + else: + let timpl = getImpl(if typ.kind == nnkBracketExpr: typ[0] else: typ) + if timpl.len>0 and timpl[0].len>1: + return timpl[0][1] + else: + return timpl + + if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}: + let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1]) + var typInst = getTypeInst(if n.kind == nnkCheckedFieldExpr or n[0].kind == nnkHiddenDeref: n[0][0] else: n[0]) + while typInst.kind in {nnkVarTy, nnkBracketExpr}: typInst = typInst[0] + var typDef = getImpl(typInst) + while typDef != nil: + typDef.expectKind(nnkTypeDef) + let typ = typDef[2].extractTypeImpl() + if typ.kind notin {nnkRefTy, nnkPtrTy, nnkObjectTy}: break + let isRef = typ.kind in {nnkRefTy, nnkPtrTy} + if isRef and typ[0].kind in {nnkSym, nnkBracketExpr}: # defines ref type for another object(e.g. X = ref X) + typDef = getImpl(typ[0]) + else: # object definition, maybe an object directly defined as a ref type + let + obj = (if isRef: typ[0] else: typ) + var identDefsStack = newSeq[NimNode](obj[2].len) + for i in 0..<identDefsStack.len: identDefsStack[i] = obj[2][i] + while identDefsStack.len > 0: + var identDefs = identDefsStack.pop() + + case identDefs.kind + of nnkRecList: + for child in identDefs.children: + identDefsStack.add(child) + of nnkRecCase: + # Add condition definition + identDefsStack.add(identDefs[0]) + # Add branches + for i in 1 ..< identDefs.len: + identDefsStack.add(identDefs[i].last) + else: + for i in 0 .. identDefs.len - 3: + let varNode = identDefs[i] + if varNode.kind == nnkPragmaExpr: + var varName = varNode[0] + if varName.kind == nnkPostfix: + # This is a public field. We are skipping the postfix * + varName = varName[1] + if eqIdent($varName, name): + return varNode[1] + + if obj[1].kind == nnkOfInherit: # explore the parent object + typDef = getImpl(obj[1][0]) + else: + typDef = nil + +macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped = + ## Expands to `true` if expression `n` which is expected to be `nnkDotExpr` + ## (if checking a field), a proc or a type has custom pragma `cp`. + ## + ## See also `getCustomPragmaVal`_. + ## + ## ```nim + ## template myAttr() {.pragma.} + ## type + ## MyObj = object + ## myField {.myAttr.}: int + ## + ## proc myProc() {.myAttr.} = discard + ## + ## var o: MyObj + ## assert(o.myField.hasCustomPragma(myAttr)) + ## assert(myProc.hasCustomPragma(myAttr)) + ## ``` + let pragmaNode = customPragmaNode(n) + for p in pragmaNode: + if (p.kind == nnkSym and p == cp) or + (p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp): + return newLit(true) + return newLit(false) + +macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = + ## Expands to value of custom pragma `cp` of expression `n` which is expected + ## to be `nnkDotExpr`, a proc or a type. + ## + ## See also `hasCustomPragma`_. + ## + ## ```nim + ## template serializationKey(key: string) {.pragma.} + ## type + ## MyObj {.serializationKey: "mo".} = object + ## myField {.serializationKey: "mf".}: int + ## var o: MyObj + ## assert(o.myField.getCustomPragmaVal(serializationKey) == "mf") + ## assert(o.getCustomPragmaVal(serializationKey) == "mo") + ## assert(MyObj.getCustomPragmaVal(serializationKey) == "mo") + ## ``` + result = nil + let pragmaNode = customPragmaNode(n) + for p in pragmaNode: + if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp: + if p.len == 2 or (p.len == 3 and p[1].kind == nnkSym and p[1].symKind == nskType): + result = p[1] + else: + let def = p[0].getImpl[3] + result = newTree(nnkPar) + for i in 1 ..< def.len: + let key = def[i][0] + let val = p[i] + result.add newTree(nnkExprColonExpr, key, val) + break + if result.kind == nnkEmpty: + error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error, + +macro unpackVarargs*(callee: untyped; args: varargs[untyped]): untyped = + ## Calls `callee` with `args` unpacked as individual arguments. + ## This is useful in 2 cases: + ## * when forwarding `varargs[T]` for some typed `T` + ## * when forwarding `varargs[untyped]` when `args` can potentially be empty, + ## due to a compiler limitation + runnableExamples: + template call1(fun: typed; args: varargs[untyped]): untyped = + unpackVarargs(fun, args) + # when varargsLen(args) > 0: fun(args) else: fun() # this would also work + template call2(fun: typed; args: varargs[typed]): untyped = + unpackVarargs(fun, args) + proc fn1(a = 0, b = 1) = discard (a, b) + call1(fn1, 10, 11) + call1(fn1) # `args` is empty in this case + if false: call2(echo, 10, 11) # would print 1011 + result = newCall(callee) + for i in 0 ..< args.len: + result.add args[i] + +proc getProjectPath*(): string = discard + ## Returns the path to the currently compiling project. + ## + ## This is not to be confused with `system.currentSourcePath <system.html#currentSourcePath.t>`_ + ## which returns the path of the source file containing that template + ## call. + ## + ## For example, assume a `dir1/foo.nim` that imports a `dir2/bar.nim`, + ## have the `bar.nim` print out both `getProjectPath` and + ## `currentSourcePath` outputs. + ## + ## Now when `foo.nim` is compiled, the `getProjectPath` from + ## `bar.nim` will return the `dir1/` path, while the `currentSourcePath` + ## will return the path to the `bar.nim` source file. + ## + ## Now when `bar.nim` is compiled directly, the `getProjectPath` + ## will now return the `dir2/` path, and the `currentSourcePath` + ## will still return the same path, the path to the `bar.nim` source + ## file. + ## + ## The path returned by this proc is set at compile time. + ## + ## See also: + ## * `getCurrentDir proc <os.html#getCurrentDir>`_ + +proc getSize*(arg: NimNode): int {.magic: "NSizeOf", noSideEffect.} = + ## Returns the same result as `system.sizeof` if the size is + ## known by the Nim compiler. Returns a negative value if the Nim + ## compiler does not know the size. +proc getAlign*(arg: NimNode): int {.magic: "NSizeOf", noSideEffect.} = + ## Returns the same result as `system.alignof` if the alignment + ## is known by the Nim compiler. It works on `NimNode` for use + ## in macro context. Returns a negative value if the Nim compiler + ## does not know the alignment. +proc getOffset*(arg: NimNode): int {.magic: "NSizeOf", noSideEffect.} = + ## Returns the same result as `system.offsetof` if the offset is + ## known by the Nim compiler. It expects a resolved symbol node + ## from a field of a type. Therefore it only requires one argument + ## instead of two. Returns a negative value if the Nim compiler + ## does not know the offset. + +proc isExported*(n: NimNode): bool {.noSideEffect.} = + ## Returns whether the symbol is exported or not. + +proc extractDocCommentsAndRunnables*(n: NimNode): NimNode = + ## returns a `nnkStmtList` containing the top-level doc comments and + ## runnableExamples in `a`, stopping at the first child that is neither. + ## Example: + ## + ## ```nim + ## import std/macros + ## macro transf(a): untyped = + ## result = quote do: + ## proc fun2*() = discard + ## let header = extractDocCommentsAndRunnables(a.body) + ## # correct usage: rest is appended + ## result.body = header + ## result.body.add quote do: discard # just an example + ## # incorrect usage: nesting inside a nnkStmtList: + ## # result.body = quote do: (`header`; discard) + ## + ## proc fun*() {.transf.} = + ## ## first comment + ## runnableExamples: discard + ## runnableExamples: discard + ## ## last comment + ## discard # first statement after doc comments + runnableExamples + ## ## not docgen'd + ## ``` + + result = newStmtList() + for ni in n: + case ni.kind + of nnkCommentStmt: + result.add ni + of nnkCall, nnkCommand: + if ni[0].kind == nnkIdent and ni[0].eqIdent "runnableExamples": + result.add ni + else: break + else: break diff --git a/lib/core/rlocks.nim b/lib/core/rlocks.nim new file mode 100644 index 000000000..8cb0cef05 --- /dev/null +++ b/lib/core/rlocks.nim @@ -0,0 +1,57 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Anatoly Galiulin +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains Nim's support for reentrant locks. + + +when not compileOption("threads") and not defined(nimdoc): + when false: + # make rlocks modlue consistent with locks module, + # so they can replace each other seamlessly. + {.error: "Rlocks requires --threads:on option.".} + +import std/private/syslocks + +type + RLock* = SysLock ## Nim lock, re-entrant + +proc initRLock*(lock: var RLock) {.inline.} = + ## Initializes the given lock. + when defined(posix): + var a: SysLockAttr + initSysLockAttr(a) + setSysLockType(a, SysLockType_Reentrant) + initSysLock(lock, a.addr) + else: + initSysLock(lock) + +proc deinitRLock*(lock: RLock) {.inline.} = + ## Frees the resources associated with the lock. + deinitSys(lock) + +proc tryAcquire*(lock: var RLock): bool {.inline.} = + ## Tries to acquire the given lock. Returns `true` on success. + result = tryAcquireSys(lock) + +proc acquire*(lock: var RLock) {.inline.} = + ## Acquires the given lock. + acquireSys(lock) + +proc release*(lock: var RLock) {.inline.} = + ## Releases the given lock. + releaseSys(lock) + +template withRLock*(lock: RLock, code: untyped) = + ## Acquires the given lock and then executes the code. + acquire(lock) + {.locks: [lock].}: + try: + code + finally: + release(lock) diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim new file mode 100644 index 000000000..f2fee91c4 --- /dev/null +++ b/lib/core/typeinfo.nim @@ -0,0 +1,723 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2013 Dominik Picheta, Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements an interface to Nim's `runtime type information`:idx: +## (`RTTI`:idx:). See the `marshal <marshal.html>`_ module for an example of +## what this allows you to do. +## +## .. note:: Even though `Any` and its operations hide the nasty low level +## details from its users, it remains inherently unsafe! Also, Nim's +## runtime type information will evolve and may eventually be deprecated. +## As an alternative approach to programmatically understanding and +## manipulating types, consider using the `macros <macros.html>`_ module to +## work with the types' AST representation at compile time. See for example +## the `getTypeImpl proc <macros.html#getTypeImpl,NimNode>`_. As an alternative +## approach to storing arbitrary types at runtime, consider using generics. + +runnableExamples: + var x: Any + + var i = 42 + x = i.toAny + assert x.kind == akInt + assert x.getInt == 42 + + var s = @[1, 2, 3] + x = s.toAny + assert x.kind == akSequence + assert x.len == 3 + +{.push hints: off.} + +include "system/inclrtl.nim" +include "system/hti.nim" + +{.pop.} + +when defined(nimPreviewSlimSystem): + import std/assertions + + +type + AnyKind* = enum ## The kind of `Any`. + akNone = 0, ## invalid + akBool = 1, ## bool + akChar = 2, ## char + akEnum = 14, ## enum + akArray = 16, ## array + akObject = 17, ## object + akTuple = 18, ## tuple + akSet = 19, ## set + akRange = 20, ## range + akPtr = 21, ## ptr + akRef = 22, ## ref + akSequence = 24, ## sequence + akProc = 25, ## proc + akPointer = 26, ## pointer + akString = 28, ## string + akCString = 29, ## cstring + akInt = 31, ## int + akInt8 = 32, ## int8 + akInt16 = 33, ## int16 + akInt32 = 34, ## int32 + akInt64 = 35, ## int64 + akFloat = 36, ## float + akFloat32 = 37, ## float32 + akFloat64 = 38, ## float64 + akFloat128 = 39, ## float128 + akUInt = 40, ## uint + akUInt8 = 41, ## uint8 + akUInt16 = 42, ## uin16 + akUInt32 = 43, ## uint32 + akUInt64 = 44, ## uint64 +# akOpt = 44+18 ## the builtin 'opt' type. + + Any* = object + ## A type that can represent any nim value. + ## + ## .. danger:: The wrapped value can be modified with its wrapper! This means + ## that `Any` keeps a non-traced pointer to its wrapped value and + ## **must not** live longer than its wrapped value. + value: pointer + when defined(js): + rawType: PNimType + else: + rawTypePtr: pointer + + ppointer = ptr pointer + pbyteArray = ptr array[0xffff, uint8] + +when not defined(gcDestructors): + type + TGenericSeq {.importc.} = object + len, space: int + when defined(gogc): + elemSize: int + PGenSeq = ptr TGenericSeq + + when defined(gogc): + const GenericSeqSize = 3 * sizeof(int) + else: + const GenericSeqSize = 2 * sizeof(int) + +else: + include system/seqs_v2_reimpl + +from std/private/strimpl import cmpNimIdentifier + +when not defined(js): + template rawType(x: Any): PNimType = + cast[PNimType](x.rawTypePtr) + + template `rawType=`(x: var Any, p: PNimType) = + x.rawTypePtr = cast[pointer](p) + +proc genericAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc.} + +when not defined(gcDestructors): + proc genericShallowAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc.} + proc incrSeq(seq: PGenSeq, elemSize, elemAlign: int): PGenSeq {.importCompilerProc.} + proc newObj(typ: PNimType, size: int): pointer {.importCompilerProc.} + proc newSeq(typ: PNimType, len: int): pointer {.importCompilerProc.} + proc objectInit(dest: pointer, typ: PNimType) {.importCompilerProc.} +else: + proc nimNewObj(size, align: int): pointer {.importCompilerProc.} + proc newSeqPayload(cap, elemSize, elemAlign: int): pointer {.importCompilerProc.} + proc prepareSeqAddUninit(len: int; p: pointer; addlen, elemSize, elemAlign: int): pointer {. + importCompilerProc.} + proc zeroNewElements(len: int; p: pointer; addlen, elemSize, elemAlign: int) {. + importCompilerProc.} + +template `+!!`(a, b): untyped = cast[pointer](cast[int](a) + b) + +proc getDiscriminant(aa: pointer, n: ptr TNimNode): int = + assert(n.kind == nkCase) + var d: int + let a = cast[int](aa) + case n.typ.size + of 1: d = int(cast[ptr uint8](a +% n.offset)[]) + of 2: d = int(cast[ptr uint16](a +% n.offset)[]) + of 4: d = int(cast[ptr uint32](a +% n.offset)[]) + of 8: d = int(cast[ptr uint64](a +% n.offset)[]) + else: assert(false) + return d + +proc selectBranch(aa: pointer, n: ptr TNimNode): ptr TNimNode = + let discr = getDiscriminant(aa, n) + if discr <% n.len: + result = n.sons[discr] + if result == nil: result = n.sons[n.len] + # n.sons[n.len] contains the `else` part (but may be nil) + else: + result = n.sons[n.len] + +proc newAny(value: pointer, rawType: PNimType): Any {.inline.} = + result.value = value + result.rawType = rawType + +proc toAny*[T](x: var T): Any {.inline.} = + ## Constructs an `Any` object from `x`. This captures `x`'s address, so + ## `x` can be modified with its `Any` wrapper! The caller needs to ensure + ## that the wrapper **does not** live longer than `x`! + newAny(addr(x), cast[PNimType](getTypeInfo(x))) + +proc kind*(x: Any): AnyKind {.inline.} = + ## Gets the type kind. + result = AnyKind(ord(x.rawType.kind)) + +proc size*(x: Any): int {.inline.} = + ## Returns the size of `x`'s type. + result = x.rawType.size + +proc baseTypeKind*(x: Any): AnyKind {.inline.} = + ## Gets the base type's kind. If `x` has no base type, `akNone` is returned. + if x.rawType.base != nil: + result = AnyKind(ord(x.rawType.base.kind)) + +proc baseTypeSize*(x: Any): int {.inline.} = + ## Returns the size of `x`'s base type. If `x` has no base type, 0 is returned. + if x.rawType.base != nil: + result = x.rawType.base.size + +proc invokeNew*(x: Any) = + ## Performs `new(x)`. `x` needs to represent a `ref`. + assert x.rawType.kind == tyRef + when defined(gcDestructors): + cast[ppointer](x.value)[] = nimNewObj(x.rawType.base.size, x.rawType.base.align) + else: + var z = newObj(x.rawType, x.rawType.base.size) + genericAssign(x.value, addr(z), x.rawType) + +proc invokeNewSeq*(x: Any, len: int) = + ## Performs `newSeq(x, len)`. `x` needs to represent a `seq`. + assert x.rawType.kind == tySequence + when defined(gcDestructors): + var s = cast[ptr NimSeqV2Reimpl](x.value) + s.len = len + let elem = x.rawType.base + s.p = cast[ptr NimSeqPayloadReimpl](newSeqPayload(len, elem.size, elem.align)) + else: + var z = newSeq(x.rawType, len) + genericShallowAssign(x.value, addr(z), x.rawType) + +proc extendSeq*(x: Any) = + ## Performs `setLen(x, x.len+1)`. `x` needs to represent a `seq`. + assert x.rawType.kind == tySequence + when defined(gcDestructors): + var s = cast[ptr NimSeqV2Reimpl](x.value) + let elem = x.rawType.base + if s.p == nil or s.p.cap < s.len+1: + s.p = cast[ptr NimSeqPayloadReimpl](prepareSeqAddUninit(s.len, s.p, 1, elem.size, elem.align)) + zeroNewElements(s.len, s.p, 1, elem.size, elem.align) + inc s.len + else: + var y = cast[ptr PGenSeq](x.value)[] + var z = incrSeq(y, x.rawType.base.size, x.rawType.base.align) + # 'incrSeq' already freed the memory for us and copied over the RC! + # So we simply copy the raw pointer into 'x.value': + cast[ppointer](x.value)[] = z + #genericShallowAssign(x.value, addr(z), x.rawType) + +proc setObjectRuntimeType*(x: Any) = + ## This needs to be called to set `x`'s runtime object type field. + assert x.rawType.kind == tyObject + when defined(gcDestructors): + cast[ppointer](x.value)[] = x.rawType.typeInfoV2 + else: + objectInit(x.value, x.rawType) + +proc skipRange(x: PNimType): PNimType {.inline.} = + result = x + if result.kind == tyRange: result = result.base + +proc align(address, alignment: int): int = + result = (address + (alignment - 1)) and not (alignment - 1) + +proc `[]`*(x: Any, i: int): Any = + ## Accessor for an any `x` that represents an array or a sequence. + case x.rawType.kind + of tyArray: + let bs = x.rawType.base.size + if i >=% x.rawType.size div bs: + raise newException(IndexDefect, formatErrorIndexBound(i, x.rawType.size div bs)) + return newAny(x.value +!! i*bs, x.rawType.base) + of tySequence: + when defined(gcDestructors): + var s = cast[ptr NimSeqV2Reimpl](x.value) + if i >=% s.len: + raise newException(IndexDefect, formatErrorIndexBound(i, s.len-1)) + let bs = x.rawType.base.size + let ba = x.rawType.base.align + let headerSize = align(sizeof(int), ba) + return newAny(s.p +!! (headerSize+i*bs), x.rawType.base) + else: + var s = cast[ppointer](x.value)[] + if s == nil: raise newException(ValueError, "sequence is nil") + let bs = x.rawType.base.size + if i >=% cast[PGenSeq](s).len: + raise newException(IndexDefect, formatErrorIndexBound(i, cast[PGenSeq](s).len-1)) + return newAny(s +!! (align(GenericSeqSize, x.rawType.base.align)+i*bs), x.rawType.base) + else: assert false + +proc `[]=`*(x: Any, i: int, y: Any) = + ## Accessor for an any `x` that represents an array or a sequence. + case x.rawType.kind + of tyArray: + var bs = x.rawType.base.size + if i >=% x.rawType.size div bs: + raise newException(IndexDefect, formatErrorIndexBound(i, x.rawType.size div bs)) + assert y.rawType == x.rawType.base + genericAssign(x.value +!! i*bs, y.value, y.rawType) + of tySequence: + when defined(gcDestructors): + var s = cast[ptr NimSeqV2Reimpl](x.value) + if i >=% s.len: + raise newException(IndexDefect, formatErrorIndexBound(i, s.len-1)) + let bs = x.rawType.base.size + let ba = x.rawType.base.align + let headerSize = align(sizeof(int), ba) + assert y.rawType == x.rawType.base + genericAssign(s.p +!! (headerSize+i*bs), y.value, y.rawType) + else: + var s = cast[ppointer](x.value)[] + if s == nil: raise newException(ValueError, "sequence is nil") + var bs = x.rawType.base.size + if i >=% cast[PGenSeq](s).len: + raise newException(IndexDefect, formatErrorIndexBound(i, cast[PGenSeq](s).len-1)) + assert y.rawType == x.rawType.base + genericAssign(s +!! (align(GenericSeqSize, x.rawType.base.align)+i*bs), y.value, y.rawType) + else: assert false + +proc len*(x: Any): int = + ## `len` for an any `x` that represents an array or a sequence. + case x.rawType.kind + of tyArray: + result = x.rawType.size div x.rawType.base.size + of tySequence: + when defined(gcDestructors): + result = cast[ptr NimSeqV2Reimpl](x.value).len + else: + let pgenSeq = cast[PGenSeq](cast[ppointer](x.value)[]) + if isNil(pgenSeq): + result = 0 + else: + result = pgenSeq.len + else: assert false + + +proc base*(x: Any): Any = + ## Returns the base type of `x` (useful for inherited object types). + result.rawType = x.rawType.base + result.value = x.value + + +proc isNil*(x: Any): bool = + ## `isNil` for an `x` that represents a cstring, proc or + ## some pointer type. + assert x.rawType.kind in {tyCstring, tyRef, tyPtr, tyPointer, tyProc} + result = isNil(cast[ppointer](x.value)[]) + +const pointerLike = + when defined(gcDestructors): {tyCstring, tyRef, tyPtr, tyPointer, tyProc} + else: {tyString, tyCstring, tyRef, tyPtr, tyPointer, tySequence, tyProc} + +proc getPointer*(x: Any): pointer = + ## Retrieves the pointer value out of `x`. `x` needs to be of kind + ## `akString`, `akCString`, `akProc`, `akRef`, `akPtr`, + ## `akPointer` or `akSequence`. + assert x.rawType.kind in pointerLike + result = cast[ppointer](x.value)[] + +proc setPointer*(x: Any, y: pointer) = + ## Sets the pointer value of `x`. `x` needs to be of kind + ## `akString`, `akCString`, `akProc`, `akRef`, `akPtr`, + ## `akPointer` or `akSequence`. + assert x.rawType.kind in pointerLike + if y != nil and x.rawType.kind != tyPointer: + genericAssign(x.value, y, x.rawType) + else: + cast[ppointer](x.value)[] = y + +proc fieldsAux(p: pointer, n: ptr TNimNode, + ret: var seq[tuple[name: cstring, any: Any]]) = + case n.kind + of nkNone: assert(false) + of nkSlot: + ret.add((n.name, newAny(p +!! n.offset, n.typ))) + assert ret[ret.len()-1][0] != nil + of nkList: + for i in 0..n.len-1: fieldsAux(p, n.sons[i], ret) + of nkCase: + var m = selectBranch(p, n) + ret.add((n.name, newAny(p +!! n.offset, n.typ))) + if m != nil: fieldsAux(p, m, ret) + +iterator fields*(x: Any): tuple[name: string, any: Any] = + ## Iterates over every active field of `x`. `x` needs to represent an object + ## or a tuple. + assert x.rawType.kind in {tyTuple, tyObject} + let p = x.value + var t = x.rawType + # XXX BUG: does not work yet, however is questionable anyway + when false: + if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[] + var ret: seq[tuple[name: cstring, any: Any]] + if t.kind == tyObject: + while true: + fieldsAux(p, t.node, ret) + t = t.base + if t.isNil: break + else: + fieldsAux(p, t.node, ret) + for name, any in items(ret): + yield ($name, any) + +proc getFieldNode(p: pointer, n: ptr TNimNode, name: cstring): ptr TNimNode = + case n.kind + of nkNone: assert(false) + of nkSlot: + if cmpNimIdentifier(n.name, name) == 0: + result = n + of nkList: + for i in 0..n.len-1: + result = getFieldNode(p, n.sons[i], name) + if result != nil: break + of nkCase: + if cmpNimIdentifier(n.name, name) == 0: + result = n + else: + let m = selectBranch(p, n) + if m != nil: result = getFieldNode(p, m, name) + +proc `[]=`*(x: Any, fieldName: string, value: Any) = + ## Sets a field of `x`. `x` needs to represent an object or a tuple. + var t = x.rawType + # XXX BUG: does not work yet, however is questionable anyway + when false: + if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[] + assert x.rawType.kind in {tyTuple, tyObject} + let n = getFieldNode(x.value, t.node, fieldName) + if n != nil: + assert n.typ == value.rawType + genericAssign(x.value +!! n.offset, value.value, value.rawType) + else: + raise newException(ValueError, "invalid field name: " & fieldName) + +proc `[]`*(x: Any, fieldName: string): Any = + ## Gets a field of `x`. `x` needs to represent an object or a tuple. + var t = x.rawType + # XXX BUG: does not work yet, however is questionable anyway + when false: + if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[] + assert x.rawType.kind in {tyTuple, tyObject} + let n = getFieldNode(x.value, t.node, fieldName) + if n != nil: + result.value = x.value +!! n.offset + result.rawType = n.typ + elif x.rawType.kind == tyObject and x.rawType.base != nil: + return `[]`(newAny(x.value, x.rawType.base), fieldName) + else: + raise newException(ValueError, "invalid field name: " & fieldName) + +proc `[]`*(x: Any): Any = + ## Dereference operator for `Any`. `x` needs to represent a ptr or a ref. + assert x.rawType.kind in {tyRef, tyPtr} + result.value = cast[ppointer](x.value)[] + result.rawType = x.rawType.base + +proc `[]=`*(x, y: Any) = + ## Dereference operator for `Any`. `x` needs to represent a ptr or a ref. + assert x.rawType.kind in {tyRef, tyPtr} + assert y.rawType == x.rawType.base + genericAssign(cast[ppointer](x.value)[], y.value, y.rawType) + +proc getInt*(x: Any): int = + ## Retrieves the `int` value out of `x`. `x` needs to represent an `int`. + assert skipRange(x.rawType).kind == tyInt + result = cast[ptr int](x.value)[] + +proc getInt8*(x: Any): int8 = + ## Retrieves the `int8` value out of `x`. `x` needs to represent an `int8`. + assert skipRange(x.rawType).kind == tyInt8 + result = cast[ptr int8](x.value)[] + +proc getInt16*(x: Any): int16 = + ## Retrieves the `int16` value out of `x`. `x` needs to represent an `int16`. + assert skipRange(x.rawType).kind == tyInt16 + result = cast[ptr int16](x.value)[] + +proc getInt32*(x: Any): int32 = + ## Retrieves the `int32` value out of `x`. `x` needs to represent an `int32`. + assert skipRange(x.rawType).kind == tyInt32 + result = cast[ptr int32](x.value)[] + +proc getInt64*(x: Any): int64 = + ## Retrieves the `int64` value out of `x`. `x` needs to represent an `int64`. + assert skipRange(x.rawType).kind == tyInt64 + result = cast[ptr int64](x.value)[] + +proc getBiggestInt*(x: Any): BiggestInt = + ## Retrieves the integer value out of `x`. `x` needs to represent + ## some integer, a bool, a char, an enum or a small enough bit set. + ## The value might be sign-extended to `BiggestInt`. + let t = skipRange(x.rawType) + case t.kind + of tyInt: result = BiggestInt(cast[ptr int](x.value)[]) + of tyInt8: result = BiggestInt(cast[ptr int8](x.value)[]) + of tyInt16: result = BiggestInt(cast[ptr int16](x.value)[]) + of tyInt32: result = BiggestInt(cast[ptr int32](x.value)[]) + of tyInt64, tyUInt64: result = BiggestInt(cast[ptr int64](x.value)[]) + of tyBool: result = BiggestInt(cast[ptr bool](x.value)[]) + of tyChar: result = BiggestInt(cast[ptr char](x.value)[]) + of tyEnum, tySet: + case t.size + of 1: result = int64(cast[ptr uint8](x.value)[]) + of 2: result = int64(cast[ptr uint16](x.value)[]) + of 4: result = BiggestInt(cast[ptr int32](x.value)[]) + of 8: result = BiggestInt(cast[ptr int64](x.value)[]) + else: assert false + of tyUInt: result = BiggestInt(cast[ptr uint](x.value)[]) + of tyUInt8: result = BiggestInt(cast[ptr uint8](x.value)[]) + of tyUInt16: result = BiggestInt(cast[ptr uint16](x.value)[]) + of tyUInt32: result = BiggestInt(cast[ptr uint32](x.value)[]) + else: assert false + +proc setBiggestInt*(x: Any, y: BiggestInt) = + ## Sets the integer value of `x`. `x` needs to represent + ## some integer, a bool, a char, an enum or a small enough bit set. + let t = skipRange(x.rawType) + case t.kind + of tyInt: cast[ptr int](x.value)[] = int(y) + of tyInt8: cast[ptr int8](x.value)[] = int8(y) + of tyInt16: cast[ptr int16](x.value)[] = int16(y) + of tyInt32: cast[ptr int32](x.value)[] = int32(y) + of tyInt64, tyUInt64: cast[ptr int64](x.value)[] = int64(y) + of tyBool: cast[ptr bool](x.value)[] = y != 0 + of tyChar: cast[ptr char](x.value)[] = chr(y.int) + of tyEnum, tySet: + case t.size + of 1: cast[ptr uint8](x.value)[] = uint8(y.int) + of 2: cast[ptr uint16](x.value)[] = uint16(y.int) + of 4: cast[ptr int32](x.value)[] = int32(y) + of 8: cast[ptr int64](x.value)[] = y + else: assert false + of tyUInt: cast[ptr uint](x.value)[] = uint(y) + of tyUInt8: cast[ptr uint8](x.value)[] = uint8(y) + of tyUInt16: cast[ptr uint16](x.value)[] = uint16(y) + of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) + else: assert false + +proc getUInt*(x: Any): uint = + ## Retrieves the `uint` value out of `x`. `x` needs to represent a `uint`. + assert skipRange(x.rawType).kind == tyUInt + result = cast[ptr uint](x.value)[] + +proc getUInt8*(x: Any): uint8 = + ## Retrieves the `uint8` value out of `x`. `x` needs to represent a `uint8`. + assert skipRange(x.rawType).kind == tyUInt8 + result = cast[ptr uint8](x.value)[] + +proc getUInt16*(x: Any): uint16 = + ## Retrieves the `uint16` value out of `x`. `x` needs to represent a `uint16`. + assert skipRange(x.rawType).kind == tyUInt16 + result = cast[ptr uint16](x.value)[] + +proc getUInt32*(x: Any): uint32 = + ## Retrieves the `uint32` value out of `x`. `x` needs to represent a `uint32`. + assert skipRange(x.rawType).kind == tyUInt32 + result = cast[ptr uint32](x.value)[] + +proc getUInt64*(x: Any): uint64 = + ## Retrieves the `uint64` value out of `x`. `x` needs to represent a `uint64`. + assert skipRange(x.rawType).kind == tyUInt64 + result = cast[ptr uint64](x.value)[] + +proc getBiggestUint*(x: Any): uint64 = + ## Retrieves the unsigned integer value out of `x`. `x` needs to + ## represent an unsigned integer. + let t = skipRange(x.rawType) + case t.kind + of tyUInt: result = uint64(cast[ptr uint](x.value)[]) + of tyUInt8: result = uint64(cast[ptr uint8](x.value)[]) + of tyUInt16: result = uint64(cast[ptr uint16](x.value)[]) + of tyUInt32: result = uint64(cast[ptr uint32](x.value)[]) + of tyUInt64: result = uint64(cast[ptr uint64](x.value)[]) + else: assert false + +proc setBiggestUint*(x: Any; y: uint64) = + ## Sets the unsigned integer value of `x`. `x` needs to represent an + ## unsigned integer. + let t = skipRange(x.rawType) + case t.kind: + of tyUInt: cast[ptr uint](x.value)[] = uint(y) + of tyUInt8: cast[ptr uint8](x.value)[] = uint8(y) + of tyUInt16: cast[ptr uint16](x.value)[] = uint16(y) + of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) + of tyUInt64: cast[ptr uint64](x.value)[] = uint64(y) + else: assert false + +proc getChar*(x: Any): char = + ## Retrieves the `char` value out of `x`. `x` needs to represent a `char`. + let t = skipRange(x.rawType) + assert t.kind == tyChar + result = cast[ptr char](x.value)[] + +proc getBool*(x: Any): bool = + ## Retrieves the `bool` value out of `x`. `x` needs to represent a `bool`. + let t = skipRange(x.rawType) + assert t.kind == tyBool + result = cast[ptr bool](x.value)[] + +proc skipRange*(x: Any): Any = + ## Skips the range information of `x`. + assert x.rawType.kind == tyRange + result.rawType = x.rawType.base + result.value = x.value + +proc getEnumOrdinal*(x: Any, name: string): int = + ## Gets the enum field ordinal from `name`. `x` needs to represent an enum + ## but is only used to access the type information. In case of an error + ## `low(int)` is returned. + let typ = skipRange(x.rawType) + assert typ.kind == tyEnum + let n = typ.node + let s = n.sons + for i in 0 .. n.len-1: + if cmpNimIdentifier($s[i].name, name) == 0: + if ntfEnumHole notin typ.flags: + return i + else: + return s[i].offset + result = low(int) + +proc getEnumField*(x: Any, ordinalValue: int): string = + ## Gets the enum field name as a string. `x` needs to represent an enum + ## but is only used to access the type information. The field name of + ## `ordinalValue` is returned. + let typ = skipRange(x.rawType) + assert typ.kind == tyEnum + let e = ordinalValue + if ntfEnumHole notin typ.flags: + if e <% typ.node.len: + return $typ.node.sons[e].name + else: + # ugh we need a slow linear search: + let n = typ.node + let s = n.sons + for i in 0 .. n.len-1: + if s[i].offset == e: return $s[i].name + result = $e + +proc getEnumField*(x: Any): string = + ## Gets the enum field name as a string. `x` needs to represent an enum. + result = getEnumField(x, getBiggestInt(x).int) + +proc getFloat*(x: Any): float = + ## Retrieves the `float` value out of `x`. `x` needs to represent a `float`. + assert skipRange(x.rawType).kind == tyFloat + result = cast[ptr float](x.value)[] + +proc getFloat32*(x: Any): float32 = + ## Retrieves the `float32` value out of `x`. `x` needs to represent a `float32`. + assert skipRange(x.rawType).kind == tyFloat32 + result = cast[ptr float32](x.value)[] + +proc getFloat64*(x: Any): float64 = + ## Retrieves the `float64` value out of `x`. `x` needs to represent a `float64`. + assert skipRange(x.rawType).kind == tyFloat64 + result = cast[ptr float64](x.value)[] + +proc getBiggestFloat*(x: Any): BiggestFloat = + ## Retrieves the float value out of `x`. `x` needs to represent + ## some float. The value is extended to `BiggestFloat`. + case skipRange(x.rawType).kind + of tyFloat: result = BiggestFloat(cast[ptr float](x.value)[]) + of tyFloat32: result = BiggestFloat(cast[ptr float32](x.value)[]) + of tyFloat64: result = BiggestFloat(cast[ptr float64](x.value)[]) + else: assert false + +proc setBiggestFloat*(x: Any, y: BiggestFloat) = + ## Sets the float value of `x`. `x` needs to represent + ## some float. + case skipRange(x.rawType).kind + of tyFloat: cast[ptr float](x.value)[] = y + of tyFloat32: cast[ptr float32](x.value)[] = y.float32 + of tyFloat64: cast[ptr float64](x.value)[] = y + else: assert false + +proc getString*(x: Any): string = + ## Retrieves the `string` value out of `x`. `x` needs to represent a `string`. + assert x.rawType.kind == tyString + when defined(gcDestructors): + result = cast[ptr string](x.value)[] + else: + if not isNil(cast[ptr pointer](x.value)[]): + result = cast[ptr string](x.value)[] + +proc setString*(x: Any, y: string) = + ## Sets the `string` value of `x`. `x` needs to represent a `string`. + assert x.rawType.kind == tyString + cast[ptr string](x.value)[] = y # also correct for gcDestructors + +proc getCString*(x: Any): cstring = + ## Retrieves the `cstring` value out of `x`. `x` needs to represent a `cstring`. + assert x.rawType.kind == tyCstring + result = cast[ptr cstring](x.value)[] + +proc assign*(x, y: Any) = + ## Copies the value of `y` to `x`. The assignment operator for `Any` + ## does NOT do this; it performs a shallow copy instead! + assert y.rawType == x.rawType + genericAssign(x.value, y.value, y.rawType) + +iterator elements*(x: Any): int = + ## Iterates over every element of `x`. `x` needs to represent a `set`. + assert x.rawType.kind == tySet + let typ = x.rawType + let p = x.value + # "typ.slots.len" field is for sets the "first" field + var u: int64 + case typ.size + of 1: u = int64(cast[ptr uint8](p)[]) + of 2: u = int64(cast[ptr uint16](p)[]) + of 4: u = int64(cast[ptr uint32](p)[]) + of 8: u = cast[ptr int64](p)[] + else: + let a = cast[pbyteArray](p) + for i in 0 .. typ.size*8-1: + if (int(a[i div 8]) and (1 shl (i mod 8))) != 0: + yield i + typ.node.len + if typ.size <= 8: + for i in 0..sizeof(int64)*8-1: + if (u and (1'i64 shl int64(i))) != 0'i64: + yield i + typ.node.len + +proc inclSetElement*(x: Any, elem: int) = + ## Includes an element `elem` in `x`. `x` needs to represent a Nim bitset. + assert x.rawType.kind == tySet + let typ = x.rawType + let p = x.value + # "typ.slots.len" field is for sets the "first" field + let e = elem - typ.node.len + case typ.size + of 1: + var a = cast[ptr int8](p) + a[] = a[] or (1'i8 shl int8(e)) + of 2: + var a = cast[ptr int16](p) + a[] = a[] or (1'i16 shl int16(e)) + of 4: + var a = cast[ptr int32](p) + a[] = a[] or (1'i32 shl int32(e)) + of 8: + var a = cast[ptr int64](p) + a[] = a[] or (1'i64 shl e) + else: + var a = cast[pbyteArray](p) + a[e shr 3] = a[e shr 3] or uint8(1 shl (e and 7)) |