diff options
-rw-r--r-- | changelog.md | 3 | ||||
-rw-r--r-- | compiler/ast.nim | 1 | ||||
-rw-r--r-- | compiler/pragmas.nim | 112 | ||||
-rw-r--r-- | compiler/semexprs.nim | 3 | ||||
-rw-r--r-- | compiler/semstmts.nim | 3 | ||||
-rw-r--r-- | compiler/semtempl.nim | 5 | ||||
-rw-r--r-- | doc/manual/pragmas.txt | 67 | ||||
-rw-r--r-- | lib/core/macros.nim | 54 | ||||
-rw-r--r-- | tests/pragmas/custom_pragma.nim | 5 | ||||
-rw-r--r-- | tests/pragmas/tcustom_pragma.nim | 43 |
10 files changed, 254 insertions, 42 deletions
diff --git a/changelog.md b/changelog.md index 21ab2b87a..993923e5c 100644 --- a/changelog.md +++ b/changelog.md @@ -190,3 +190,6 @@ let - Added support for casting between integers of same bitsize in VM (compile time and nimscript). This allow to among other things to reinterpret signed integers as unsigned. +- Pragmas now support call syntax, for example: ``{.exportc"myname".}`` and ``{.exportc("myname").}`` +- Custom pragmas are now supported using pragma ``pragma``, please see language manual for details + diff --git a/compiler/ast.nim b/compiler/ast.nim index 54c33a038..69f2eb1c7 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -305,6 +305,7 @@ const sfEscapes* = sfProcvar # param escapes sfBase* = sfDiscriminant sfIsSelf* = sfOverriden # param is 'self' + sfCustomPragma* = sfRegister # symbol is custom pragma template const # getting ready for the future expr/stmt merge diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index bdaecf91d..b6229796f 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -17,6 +17,7 @@ import const FirstCallConv* = wNimcall LastCallConv* = wNoconv + nkPragmaCallKinds = {nkExprColonExpr, nkCall, nkCallStrLit} const procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, @@ -29,7 +30,7 @@ const converterPragmas* = procPragmas methodPragmas* = procPragmas+{wBase}-{wImportCpp} templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty, - wDelegator, wExportNims, wUsed} + wDelegator, wExportNims, wUsed, wPragma} macroPragmas* = {FirstCallConv..LastCallConv, wImmediate, wImportc, wExportc, wNodecl, wMagic, wNosideeffect, wCompilerProc, wCore, wDeprecated, wExtern, wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wDelegator, @@ -74,7 +75,7 @@ proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode = let p = procAst[pragmasPos] if p.kind == nkEmpty: return nil for it in p: - if it.kind == nkExprColonExpr and it[0].kind == nkIdent and + if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent and it[0].ident.id == ord(name): return it[1] @@ -89,7 +90,7 @@ proc pragmaAsm*(c: PContext, n: PNode): char = if n != nil: for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] - if it.kind == nkExprColonExpr and it.sons[0].kind == nkIdent: + if it.kind in nkPragmaCallKinds and it.len == 2 and it.sons[0].kind == nkIdent: case whichKeyword(it.sons[0].ident) of wSubsChar: if it.sons[1].kind == nkCharLit: result = chr(int(it.sons[1].intVal)) @@ -151,7 +152,7 @@ proc newEmptyStrNode(n: PNode): PNode {.noinline.} = result.strVal = "" proc getStrLitNode(c: PContext, n: PNode): PNode = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) # error correction: result = newEmptyStrNode(n) @@ -168,7 +169,7 @@ proc expectStrLit(c: PContext, n: PNode): string = result = getStrLitNode(c, n).strVal proc expectIntLit(c: PContext, n: PNode): int = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errIntLiteralExpected) else: n.sons[1] = c.semConstExpr(c, n.sons[1]) @@ -177,7 +178,7 @@ proc expectIntLit(c: PContext, n: PNode): int = else: localError(n.info, errIntLiteralExpected) proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string = - if n.kind == nkExprColonExpr: result = expectStrLit(c, n) + if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n) else: result = defaultStr proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) = @@ -186,7 +187,7 @@ proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) = proc processMagic(c: PContext, n: PNode, s: PSym) = #if sfSystemModule notin c.module.flags: # liMessage(n.info, errMagicOnlyInSystem) - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) return var v: string @@ -204,7 +205,7 @@ proc wordToCallConv(sw: TSpecialWord): TCallingConvention = result = TCallingConvention(ord(ccDefault) + ord(sw) - ord(wNimcall)) proc isTurnedOn(c: PContext, n: PNode): bool = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: let x = c.semConstBoolExpr(c, n.sons[1]) n.sons[1] = x if x.kind == nkIntLit: return x.intVal != 0 @@ -223,7 +224,7 @@ proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = else: excl(c.module.flags, flag) proc processCallConv(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): + if n.kind in nkPragmaCallKinds and n.len == 2 and n.sons[1].kind == nkIdent: var sw = whichKeyword(n.sons[1].ident) case sw of FirstCallConv..LastCallConv: @@ -244,7 +245,7 @@ proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib = result.isOverriden = options.isDynlibOverride(path.strVal) proc expectDynlibNode(c: PContext, n: PNode): PNode = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) # error correction: result = newEmptyStrNode(n) @@ -264,7 +265,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = if not lib.isOverriden: c.optionStack[^1].dynlib = lib else: - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds: var lib = getLib(c, libDynamic, expectDynlibNode(c, n)) if not lib.isOverriden: addToLib(lib, sym) @@ -279,7 +280,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = sym.typ.callConv = ccCDecl proc processNote(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (sonsLen(n) == 2) and + if (n.kind in nkPragmaCallKinds) and (sonsLen(n) == 2) and (n.sons[0].kind == nkBracketExpr) and (n.sons[0].sons.len == 2) and (n.sons[0].sons[1].kind == nkIdent) and @@ -307,7 +308,7 @@ proc processNote(c: PContext, n: PNode) = invalidPragma(n) proc processOption(c: PContext, n: PNode): bool = - if n.kind != nkExprColonExpr: result = true + if n.kind notin nkPragmaCallKinds or n.len != 2: result = true elif n.sons[0].kind == nkBracketExpr: processNote(c, n) elif n.sons[0].kind != nkIdent: result = true else: @@ -355,8 +356,8 @@ proc processOption(c: PContext, n: PNode): bool = else: result = true proc processPush(c: PContext, n: PNode, start: int) = - if n.sons[start-1].kind == nkExprColonExpr: - localError(n.info, errGenerated, "':' after 'push' not supported") + if n.sons[start-1].kind in nkPragmaCallKinds: + localError(n.info, errGenerated, "'push' can't have arguments") var x = newOptionEntry() var y = c.optionStack[^1] x.options = gOptions @@ -381,14 +382,14 @@ proc processPop(c: PContext, n: PNode) = c.optionStack.setLen(c.optionStack.len - 1) proc processDefine(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): + if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent): defineSymbol(n.sons[1].ident.s) message(n.info, warnDeprecated, "define") else: invalidPragma(n) proc processUndef(c: PContext, n: PNode) = - if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): + if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent): undefSymbol(n.sons[1].ident.s) message(n.info, warnDeprecated, "undef") else: @@ -420,7 +421,7 @@ proc processCompile(c: PContext, n: PNode) = localError(n.info, errStringLiteralExpected) result = "" - let it = if n.kind == nkExprColonExpr: n.sons[1] else: n + let it = if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] else: n if it.kind == nkPar and it.len == 2: let s = getStrLit(c, it, 0) let dest = getStrLit(c, it, 1) @@ -453,7 +454,7 @@ proc pragmaBreakpoint(c: PContext, n: PNode) = discard getOptionalStr(c, n, "") proc pragmaWatchpoint(c: PContext, n: PNode) = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] = c.semExpr(c, n.sons[1]) else: invalidPragma(n) @@ -494,7 +495,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = result = newNode(nkAsmStmt, n.info) proc pragmaEmit(c: PContext, n: PNode) = - if n.kind != nkExprColonExpr: + if n.kind notin nkPragmaCallKinds or n.len != 2: localError(n.info, errStringLiteralExpected) else: let n1 = n[1] @@ -512,12 +513,12 @@ proc pragmaEmit(c: PContext, n: PNode) = localError(n.info, errStringLiteralExpected) proc noVal(n: PNode) = - if n.kind == nkExprColonExpr: invalidPragma(n) + if n.kind in nkPragmaCallKinds and n.len > 1: invalidPragma(n) proc pragmaUnroll(c: PContext, n: PNode) = if c.p.nestedLoopCounter <= 0: invalidPragma(n) - elif n.kind == nkExprColonExpr: + elif n.kind in nkPragmaCallKinds and n.len == 2: var unrollFactor = expectIntLit(c, n) if unrollFactor <% 32: n.sons[1] = newIntNode(nkIntLit, unrollFactor) @@ -525,10 +526,11 @@ proc pragmaUnroll(c: PContext, n: PNode) = invalidPragma(n) proc pragmaLine(c: PContext, n: PNode) = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] = c.semConstExpr(c, n.sons[1]) let a = n.sons[1] if a.kind == nkPar: + # unpack the tuple var x = a.sons[0] var y = a.sons[1] if x.kind == nkExprColonExpr: x = x.sons[1] @@ -549,7 +551,7 @@ proc pragmaLine(c: PContext, n: PNode) = proc processPragma(c: PContext, n: PNode, i: int) = var it = n.sons[i] - if it.kind != nkExprColonExpr: invalidPragma(n) + if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(n) elif it.sons[0].kind != nkIdent: invalidPragma(n) elif it.sons[1].kind != nkIdent: invalidPragma(n) @@ -566,7 +568,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = localError(x.info, errGenerated, "invalid type for raises/tags list") x.typ = t - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: let it = n.sons[1] if it.kind notin {nkCurly, nkBracket}: processExc(c, it) @@ -576,7 +578,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) = invalidPragma(n) proc pragmaLockStmt(c: PContext; it: PNode) = - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: invalidPragma(it) else: let n = it[1] @@ -587,7 +589,7 @@ proc pragmaLockStmt(c: PContext; it: PNode) = n.sons[i] = c.semExpr(c, n.sons[i]) proc pragmaLocks(c: PContext, it: PNode): TLockLevel = - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: invalidPragma(it) else: case it[1].kind @@ -604,7 +606,7 @@ proc pragmaLocks(c: PContext, it: PNode): TLockLevel = result = TLockLevel(x) proc typeBorrow(sym: PSym, n: PNode) = - if n.kind == nkExprColonExpr: + if n.kind in nkPragmaCallKinds and n.len == 2: let it = n.sons[1] if it.kind != nkAccQuoted: localError(n.info, "a type can only borrow `.` for now") @@ -624,7 +626,7 @@ proc deprecatedStmt(c: PContext; pragma: PNode) = if pragma.kind != nkBracket: localError(pragma.info, "list of key:value pairs expected"); return for n in pragma: - if n.kind in {nkExprColonExpr, nkExprEqExpr}: + if n.kind in nkPragmaCallKinds and n.len == 2: let dest = qualifiedLookUp(c, n[1], {checkUndeclared}) if dest == nil or dest.kind in routineKinds: localError(n.info, warnUser, "the .deprecated pragma is unreliable for routines") @@ -638,7 +640,7 @@ proc deprecatedStmt(c: PContext; pragma: PNode) = localError(n.info, "key:value pair expected") proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym = - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: invalidPragma(it); return let n = it[1] if n.kind == nkSym: @@ -655,13 +657,36 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym = else: result = qualifiedLookUp(c, n, {checkUndeclared}) +proc semCustomPragma(c: PContext, n: PNode): PNode = + assert(n.kind in nkPragmaCallKinds + {nkIdent}) + + if n.kind == nkIdent: + result = newTree(nkCall, n) + elif n.kind == nkExprColonExpr: + # pragma: arg -> pragma(arg) + result = newTree(nkCall, n[0], n[1]) + else: + result = n + + result = c.semOverloadedCall(c, result, n, {skTemplate}, {}) + if sfCustomPragma notin result[0].sym.flags: + invalidPragma(n) + + if n.kind == nkIdent: + result = result[0] + elif n.kind == nkExprColonExpr: + result.kind = n.kind # pragma(arg) -> pragma: arg + proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, validPragmas: TSpecialWords): bool = var it = n.sons[i] - var key = if it.kind == nkExprColonExpr: it.sons[0] else: it + var key = if it.kind in nkPragmaCallKinds and it.len > 1: it.sons[0] else: it if key.kind == nkBracketExpr: processNote(c, it) return + elif key.kind notin nkIdentKinds: + n.sons[i] = semCustomPragma(c, it) + return let ident = considerQuotedIdent(key) var userPragma = strTableGet(c.userPragmas, ident) if userPragma != nil: @@ -785,7 +810,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wExplain: sym.flags.incl sfExplain of wDeprecated: - if it.kind == nkExprColonExpr: deprecatedStmt(c, it) + if it.kind in nkPragmaCallKinds: deprecatedStmt(c, it) elif sym != nil: incl(sym.flags, sfDeprecated) else: incl(c.module.flags, sfDeprecated) of wVarargs: @@ -864,8 +889,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, result = true of wPop: processPop(c, it) of wPragma: - processPragma(c, n, i) - result = true + if not sym.isNil and sym.kind == skTemplate: + sym.flags.incl sfCustomPragma + else: + processPragma(c, n, i) + result = true of wDiscardable: noVal(it) if sym != nil: incl(sym.flags, sfDiscardable) @@ -939,7 +967,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, elif sym.typ == nil: invalidPragma(it) else: sym.typ.lockLevel = pragmaLocks(c, it) of wBitsize: - if sym == nil or sym.kind != skField or it.kind != nkExprColonExpr: + if sym == nil or sym.kind != skField: invalidPragma(it) else: sym.bitsize = expectIntLit(c, it) @@ -957,7 +985,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, if sym == nil: invalidPragma(it) else: magicsys.registerNimScriptSymbol(sym) of wInjectStmt: - if it.kind != nkExprColonExpr: + if it.kind notin nkPragmaCallKinds or it.len != 2: localError(it.info, errExprExpected) else: it.sons[1] = c.semExpr(c, it.sons[1]) @@ -968,10 +996,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, else: localError(it.info, "'experimental' pragma only valid as toplevel statement") of wThis: - if it.kind == nkExprColonExpr: + if it.kind in nkPragmaCallKinds and it.len == 2: c.selfName = considerQuotedIdent(it[1]) - else: + elif it.kind == nkIdent or it.len == 1: c.selfName = getIdent("self") + else: + localError(it.info, "'this' pragma is allowed to have zero or one arguments") of wNoRewrite: noVal(it) of wBase: @@ -987,7 +1017,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, else: sym.flags.incl sfUsed of wLiftLocals: discard else: invalidPragma(it) - else: invalidPragma(it) + else: + n.sons[i] = semCustomPragma(c, it) + proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = @@ -1015,7 +1047,7 @@ proc hasPragma*(n: PNode, pragma: TSpecialWord): bool = return false for p in n.sons: - var key = if p.kind == nkExprColonExpr: p[0] else: p + var key = if p.kind in nkPragmaCallKinds and p.len > 1: p[0] else: p if key.kind == nkIdent and whichKeyword(key.ident) == pragma: return true diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 62489bd36..e737f7676 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -951,7 +951,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = else: result = semMacroExpr(c, n, n, s, flags) of skTemplate: - if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: + if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or + sfCustomPragma in sym.flags: markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 096cf99de..ccddabcbe 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1153,6 +1153,9 @@ proc semProcAnnotation(c: PContext, prc: PNode; else: localError(prc.info, errOnlyACallOpCanBeDelegator) continue + elif sfCustomPragma in m.flags: + continue # semantic check for custom pragma happens later in semProcAux + # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and # let the semantic checker deal with it: var x = newNodeI(nkCall, n.info) diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index f90dff8f1..454dadec0 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -608,7 +608,10 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = popOwner(c) s.ast = n result = n - if n.sons[bodyPos].kind == nkEmpty: + if sfCustomPragma in s.flags: + if n.sons[bodyPos].kind != nkEmpty: + localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) + elif n.sons[bodyPos].kind == nkEmpty: localError(n.info, errImplOfXexpected, s.name.s) var proto = searchForProc(c, c.currentScope, s) if proto == nil: diff --git a/doc/manual/pragmas.txt b/doc/manual/pragmas.txt index 835b6909d..cd26a9448 100644 --- a/doc/manual/pragmas.txt +++ b/doc/manual/pragmas.txt @@ -1087,3 +1087,70 @@ In the above example, providing the -d flag causes the symbol ``FooBar`` to be overwritten at compile time, printing out 42. If the ``-d:FooBar=42`` were to be omitted, the default value of 5 would be used. + + +Custom annotations +------------------ +It is possible to define custom typed pragmas. Custom pragmas do not effect +code generation directly, but their presence can be detected by macros. +Custom pragmas are defined using templates annotated with pragma ``pragma``: + +.. code-block:: nim + template dbTable(name: string, table_space: string = nil) {.pragma.} + template dbKey(name: string = nil, primary_key: bool = false) {.pragma.} + template dbForeignKey(t: typedesc) {.pragma.} + template dbIgnore {.pragma.} + + +Consider stylized example of possible Object Relation Mapping (ORM) implementation: + +.. code-block:: nim + const tblspace {.strdefine.} = "dev" # switch for dev, test and prod environments + + type + User {.dbTable("users", tblspace).} = object + id {.dbKey(primary_key = true).}: int + name {.dbKey"full_name".}: string + is_cached {.dbIgnore.}: bool + age: int + + UserProfile {.dbTable("profiles", tblspace).} = object + id {.dbKey(primary_key = true).}: int + user_id {.dbForeignKey: User.}: int + read_access: bool + write_access: bool + admin_acess: bool + +In this example custom pragmas are used to describe how Nim objects are +mapped to the schema of the relational database. Custom pragmas can have +zero or more arguments. In order to pass multiple arguments use one of +template call syntaxes. All arguments are typed and follow standard +overload resolution rules for templates. Therefore, it is possible to have +default values for arguments, pass by name, varargs, etc. + +Custom pragmas can be used in all locations where ordinary pragmas can be +specified. It is possible to annotate procs, templates, type and variable +definitions, statements, etc. + +Macros module includes helpers which can be used to simplify custom pragma +access `hasCustomPragma`, `getCustomPragmaVal`. Please consult macros module +documentation for details. These macros are no magic, they don't do anything +you cannot do yourself by walking AST object representation. + +More examples with custom pragmas: + - Better serialization/deserialization control: + + .. code-block:: nim + type MyObj = object + a {.dontSerialize.}: int + b {.defaultDeserialize: 5.}: int + c {.serializationKey: "_c".}: string + + - Adopting type for gui inspector in a game engine: + + .. code-block:: nim + type MyComponent = object + position {.editable, animatable.}: Vector3 + alpha {.editRange: [0.0..1.0], animatable.}: float32 + + diff --git a/lib/core/macros.nim b/lib/core/macros.nim index b08a2198e..ed9c304fe 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -130,6 +130,7 @@ const nnkLiterals* = {nnkCharLit..nnkNilLit} nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, nnkCallStrLit} + nnkPragmaCallKinds = {nnkExprColonExpr, nnkCall, nnkCallStrLit} proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.} ## constructs an identifier from the string `s` @@ -1213,6 +1214,59 @@ macro expandMacros*(body: typed): untyped = result = getAst(inner(body)) echo result.toStrLit +proc customPragmaNode(n: NimNode): NimNode = + expectKind(n, {nnkSym, nnkDotExpr}) + if n.kind == nnkSym: + let sym = n.symbol.getImpl() + sym.expectRoutine() + result = sym.pragma + elif n.kind == nnkDotExpr: + let typDef = getImpl(getTypeInst(n[0]).symbol) + typDef.expectKind(nnkTypeDef) + typDef[2].expectKind(nnkObjectTy) + let recList = typDef[2][2] + for identDefs in recList: + for i in 0 .. identDefs.len - 3: + if identDefs[i].kind == nnkPragmaExpr and + identDefs[i][0].kind == nnkIdent and $identDefs[i][0] == $n[1]: + return identDefs[i][1] + +macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped = + ## Expands to `true` if expression `n` which is expected to be `nnkDotExpr` + ## has custom pragma `cp`. + ## + ## .. code-block:: nim + ## template myAttr() {.pragma.} + ## type + ## MyObj = object + ## myField {.myAttr.}: int + ## var o: MyObj + ## assert(o.myField.hasCustomPragma(myAttr) == 0) + 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`. + ## + ## .. code-block:: nim + ## template serializationKey(key: string) {.pragma.} + ## type + ## MyObj = object + ## myField {.serializationKey: "mf".}: int + ## var o: MyObj + ## assert(o.myField.getCustomPragmaVal(serializationKey) == "mf") + 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: + return p[1] + return newEmptyNode() + + when not defined(booting): template emit*(e: static[string]): untyped {.deprecated.} = ## accepts a single string argument and treats it as nim code diff --git a/tests/pragmas/custom_pragma.nim b/tests/pragmas/custom_pragma.nim new file mode 100644 index 000000000..9e8f51deb --- /dev/null +++ b/tests/pragmas/custom_pragma.nim @@ -0,0 +1,5 @@ +# imported by tcustom_pragmas to test scoping + +template serializationKey*(s: string) {.pragma.} +template defaultValue*(V: typed) {.pragma.} +template alternativeKey*(s: string = nil, V: typed) {.pragma.} \ No newline at end of file diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim new file mode 100644 index 000000000..a2380522f --- /dev/null +++ b/tests/pragmas/tcustom_pragma.nim @@ -0,0 +1,43 @@ +import macros + +block: + template myAttr() {.pragma.} + + proc myProc():int {.myAttr.} = 2 + const myAttrIdx = myProc.hasCustomPragma(myAttr) + static: + assert(myAttrIdx) + +block: + template myAttr(a: string) {.pragma.} + + type MyObj = object + myField1, myField2 {.myAttr: "hi".}: int + var o: MyObj + static: + assert o.myField2.hasCustomPragma(myAttr) + assert(not o.myField1.hasCustomPragma(myAttr)) + +import custom_pragma +block: # A bit more advanced case + type + Subfield = object + c {.serializationKey: "cc".}: float + + MySerializable = object + a {.serializationKey"asdf", defaultValue: 5.} : int + b {.custom_pragma.defaultValue"hello".} : int + field: Subfield + d {.alternativeKey("df", 5).}: float + e {.alternativeKey(V = 5).}: seq[bool] + + var s: MySerializable + + const aDefVal = s.a.getCustomPragmaVal(defaultValue) + static: assert(aDefVal == 5) + + const aSerKey = s.a.getCustomPragmaVal(serializationKey) + static: assert(aSerKey == "asdf") + + const cSerKey = getCustomPragmaVal(s.field.c, serializationKey) + static: assert(cSerKey == "cc") |