diff --git a/compiler/renderer.nim b/compiler/renderer.nim index ce47cf2197e01..4a41c12525d33 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -999,9 +999,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n, 0) gcomma(g, n, 1) of nkCommand: - accentedName(g, n[0]) - put(g, tkSpaces, Space) - gcomma(g, n, 1) + if n.len > 0: + accentedName(g, n[0]) + put(g, tkSpaces, Space) + gcomma(g, n, 1) of nkExprEqExpr, nkAsgn, nkFastAsgn: gsub(g, n, 0) put(g, tkSpaces, Space) diff --git a/compiler/vm.nim b/compiler/vm.nim index 131501380f1a5..3951f76f674a1 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -19,6 +19,7 @@ import from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate +from os import isAbsolute, parentDir, `/` from modulegraphs import ModuleGraph, PPassContext @@ -1498,7 +1499,21 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = internalAssert c.config, false regs[ra].node.info = n.info regs[ra].node.typ = n.typ - of opcNSetLineInfo: + of opcNSetLineInfoObj: + decodeB(rkNode) + let n = regs[rb].node + assert(n.kind == nkObjConstr) + assert(n.len == 4) + assert(n[1][0].sym.name.s == "filename") + var filename = n[1][1].strVal + if not isAbsolute(filename): + filename = parentDir(toFullPath(c.config, c.debug[pc])) / filename + assert(n[2][0].sym.name.s == "line") + let line = n[2][1].intVal.int + assert(n[3][0].sym.name.s == "column") + let column = n[3][1].intVal.int + regs[ra].node.info = newLineInfo(c.config, AbsoluteFile(filename), line, column) + of opcNCopyLineInfo: decodeB(rkNode) regs[ra].node.info = regs[rb].node.info of opcEqIdent: diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index a43f8dbba52a8..1a2e5c0d9ea15 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -107,7 +107,7 @@ type opcNError, opcNWarning, opcNHint, - opcNGetLineInfo, opcNSetLineInfo, + opcNGetLineInfo, opcNSetLineInfoObj, opcNCopyLineInfo, opcEqIdent, opcStrToIdent, opcGetImpl, diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index e7993dfb21270..353df363b0336 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1231,11 +1231,13 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of "getFile": genUnaryABI(c, n, dest, opcNGetLineInfo, 0) of "getLine": genUnaryABI(c, n, dest, opcNGetLineInfo, 1) of "getColumn": genUnaryABI(c, n, dest, opcNGetLineInfo, 2) + of "lineInfoObj=": genBinaryStmt(c, n, opcNSetLineInfoObj) of "copyLineInfo": internalAssert c.config, n.len == 3 unused(c, n, dest) - genBinaryStmt(c, n, opcNSetLineInfo) - else: internalAssert c.config, false + genBinaryStmt(c, n, opcNCopyLineInfo) + else: + internalAssert c.config, false of mNHint: unused(c, n, dest) genBinaryStmt(c, n, opcNHint) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 3a85324bac03a..5300766b9e301 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -489,6 +489,8 @@ proc lineInfoObj*(n: NimNode): LineInfo {.compileTime.} = proc lineInfo*(arg: NimNode): string {.compileTime.} = $arg.lineInfoObj +proc `lineInfoObj=`*(n: NimNode; lineinfo: LineInfo) {.magic: "NLineInfo".} + proc internalParseExpr(s: string): NimNode {. magic: "ParseExprToAst", noSideEffect.} diff --git a/lib/experimental/quote2.nim b/lib/experimental/quote2.nim new file mode 100644 index 0000000000000..7e91e40d3aaae --- /dev/null +++ b/lib/experimental/quote2.nim @@ -0,0 +1,241 @@ +import macros + +# template expectNimNode(arg: NimNode): NimNode = arg +# macro expectNimNode(arg: typed): NimNode = +# error("expressions needs to be of type NimNode", arg) + +proc expectNimNode[T](arg: T): NimNode = arg + +proc newTreeWithLineinfo*(kind: NimNodeKind; lineinfo: LineInfo; children: varargs[NimNode]): NimNode {.compileTime.} = + ## like ``macros.newTree``, just that the first argument is a node to take lineinfo from. + # TODO lineinfo cannot be forwarded to new node. I am forced to drop it here. + result = newNimNode(kind, nil) + result.lineinfoObj = lineinfo + result.add(children) + +# TODO restrict unquote to nimnode expressions (error message) + +const forwardLineinfo = true + +proc newTreeExpr(stmtList, exprNode, unquoteIdent: NimNode): NimNode {.compileTime.} = + # stmtList is a buffer to generate statements + if exprNode.kind in nnkLiterals: + result = newCall(bindSym"newLit", exprNode) + elif exprNode.kind == nnkIdent: + result = newCall(bindSym"ident", newLit(exprNode.strVal)) + elif exprNode.kind in nnkCallKinds and exprNode.len == 2 and exprNode[0].eqIdent unquoteIdent: + result = newCall(bindSym"expectNimNode", exprNode[1]) + elif exprNode.kind == nnkSym: + error("for quoting the ast needs to be untyped", exprNode) + elif exprNode.kind == nnkCommentStmt: + result = newCall(bindSym"newCommentStmtNode", newLit(exprNode.strVal)) + elif exprNode.kind == nnkEmpty: + # bug newTree(nnkEmpty) raises exception: + result = newCall(bindSym"newEmptyNode") + else: + if forwardLineInfo: + result = newCall(bindSym"newTreeWithLineinfo", newLit(exprNode.kind), newLit(exprNode.lineinfoObj)) + else: + result = newCall(bindSym"newTree", newLit(exprNode.kind)) + for child in exprNode: + result.add newTreeExpr(stmtList, child, unquoteIdent) + +macro quoteAst*(ast: untyped): untyped = + ## Substitute for ``quote do`` but with ``uq`` for unquoting instead of backticks. + result = newNimNode(nnkStmtListExpr) + result.add result.newTreeExpr(ast, ident"uq") + + echo "quoteAst:" + echo result.repr + +macro quoteAst*(unquoteIdent, ast: untyped): untyped = + unquoteIdent.expectKind nnkIdent + result = newStmtList() + result.add newTreeExpr(result, ast, unquoteIdent) + +proc foo(arg1: int, arg2: string): string = + "xxx" + +macro foobar(arg: untyped): untyped = + # simple generation of source code: + result = quoteAst: + echo "Hello world!" + + echo result.treeRepr + + # inject subtrees from local scope, like `` in quote do: + let world = newLit("world") + result = quoteAst: + echo "Hello ", uq(world), "!" + + echo result.treeRepr + + # inject subtree from expression: + result = quoteAst: + echo "Hello ", uq(newLit("world")), "!" + + echo result.treeRepr + + # custom name for unquote in case `uq` should collide with anything. + let x = newLit(123) + result = quoteAst myUnquote: + echo "abc ", myUnquote(x), " ", myUnquote(newLit("xyz")), " ", myUnquote(arg) + + #result = quoteAst: + # echo uq(bindSym"foo")(123, "abc") + + echo result.treeRepr + + # result = quoteAst: + # var tmp = 1 + # for x in 0 ..< 100: + # tmp *= 3 + +let myVal = "Hallo Welt!" +foobar(myVal) + +# example from #10326 + +template id*(val: int) {.pragma.} +macro m1(): untyped = + let x = newLit(10) + let r1 = quote do: + type T1 {.id(`x`).} = object + + let r2 = quoteAst: + type T1 {.id(uq(x)).} = object + + echo "from #10326:" + echo r1[0][0].treeRepr + echo r2[0][0].treeRepr + +m1() + +macro lineinfoTest(): untyped = + # line info is preserved as if the content of ``quoteAst`` is written in a template + result = quoteAst: + assert(false) + +#lineinfoTest() + +# example from #7375 + +macro fails(b: static[bool]): untyped = + echo b + result = newStmtList() + +macro works(b: static[int]): untyped = + echo b + result = newStmtList() + +macro foo(): untyped = + + var b = newLit(false) + + ## Fails + result = quote do: + fails(`b`) + + ## Works + # result = quote do: + # works(`b`) + +foo() + + +# example from #9745 (does not work yet) +# import macros + +# var +# typ {.compiletime.} = newLit("struct ABC") +# name {.compiletime.} = ident("ABC") + +# macro abc(): untyped = +# result = newNimNode(nnkStmtList) + +# let x = quoteAst: +# type +# uq(name) {.importc: uq(typ).} = object + +# echo result.repr + +# abc() + +# example from #7889 + +from streams import newStringStream, readData, writeData +import macros + +macro bindme*(): untyped = + quoteAst: + var tst = "sometext" + var ss = uq(bindSym"newStringStream")("anothertext") + uq(bindSym"writeData")(ss, tst[0].addr, 2) + discard uq(bindSym"readData")(ss, tst[0].addr, 2) # <= comment this out to make compilation successful + +# test.nim +# from binder import bindme +# bindme() + + +# example from #8220 + +macro fooA(): untyped = + result = quoteAst: + let bar = "Hello, World" + echo &"Let's interpolate {bar} in the string" + +foo() + + +# example from #7589 + +macro fooB(x: untyped): untyped = + result = quoteAst: + echo uq(bindSym"==")(3,4) # echo ["false"]' has no type (or is ambiguous) + echo result.treerepr + +# example from #7726 + +import macros + +macro fooC(): untyped = + let a = @[1, 2, 3, 4, 5] + result = quoteAst: + uq(newLit(a.len)) + +macro fooD(): untyped = + let a = @[1, 2, 3, 4, 5] + let len = a.len + result = quoteAst: + uq(newLit(len)) + +macro fooE(): untyped = + let a = @[1, 2, 3, 4, 5] + let len = a.len + result = quoteAst: + uq(newLit(a[2])) + +echo fooC() # Outputs 5 +echo fooD() # Outputs 5 +echo fooE() # Outputs 3 + + +# example from #10430 + +import macros + +macro commentTest(arg: untyped): untyped = + let tmp = quoteAst: + ## comment 1 + echo "abc" + ## comment 2 + ## still comment 2 + + doAssert tmp.treeRepr == arg.treeRepr + +commentTest: + ## comment 1 + echo "abc" + ## comment 2 + ## still comment 2