Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[experiment] implementation for quote ast proposal #10446

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions compiler/renderer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 16 additions & 1 deletion compiler/vm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import

from semfold import leValueConv, ordinalValToString
from evaltempl import evalTemplate
from os import isAbsolute, parentDir, `/`

from modulegraphs import ModuleGraph, PPassContext

Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion compiler/vmdef.nim
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ type
opcNError,
opcNWarning,
opcNHint,
opcNGetLineInfo, opcNSetLineInfo,
opcNGetLineInfo, opcNSetLineInfoObj, opcNCopyLineInfo,
opcEqIdent,
opcStrToIdent,
opcGetImpl,
Expand Down
6 changes: 4 additions & 2 deletions compiler/vmgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions lib/core/macros.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.}

Expand Down
241 changes: 241 additions & 0 deletions lib/experimental/quote2.nim
Original file line number Diff line number Diff line change
@@ -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