discard """
action: compile
"""
# this test should ensure that the AST doesn't change slightly without it getting noticed.
import ../ast_pattern_matching
template expectNimNode(arg: untyped): NimNode = arg
## This template here is just to be injected by `myquote`, so that
## a nice error message appears when the captured symbols are not of
## type `NimNode`.
proc substitudeComments(symbols, values, n: NimNode): NimNode =
## substitutes all nodes of kind nnkCommentStmt to parameter
## symbols. Consumes the argument `n`.
if n.kind == nnkCommentStmt:
values.add newCall(bindSym"newCommentStmtNode", newLit(n.strVal))
# Gensym doesn't work for parameters. These identifiers won't
# clash unless an argument is constructed to clash here.
symbols.add ident("comment" & $values.len & "_XObBdOnh6meCuJK2smZV")
return symbols[^1]
for i in 0 ..< n.len:
n[i] = substitudeComments(symbols, values, n[i])
return n
macro myquote*(args: varargs[untyped]): untyped =
expectMinLen(args, 1)
# This is a workaround for #10430 where comments are removed in
# template expansions. This workaround lifts all comments
# statements to be arguments of the temporary template.
let extraCommentSymbols = newNimNode(nnkBracket)
let extraCommentGenExpr = newNimNode(nnkBracket)
let body = substitudeComments(
extraCommentSymbols, extraCommentGenExpr, args[^1]
)
let formalParams = nnkFormalParams.newTree(ident"untyped")
for i in 0 ..< args.len-1:
formalParams.add nnkIdentDefs.newTree(
args[i], ident"untyped", newEmptyNode()
)
for sym in extraCommentSymbols:
formalParams.add nnkIdentDefs.newTree(
sym, ident"untyped", newEmptyNode()
)
let templateSym = genSym(nskTemplate)
let templateDef = nnkTemplateDef.newTree(
templateSym,
newEmptyNode(),
newEmptyNode(),
formalParams,
nnkPragma.newTree(ident"dirty"),
newEmptyNode(),
args[^1]
)
let templateCall = newCall(templateSym)
for i in 0 ..< args.len-1:
let symName = args[i]
# identifiers and quoted identifiers are allowed.
if symName.kind == nnkAccQuoted:
symName.expectLen 1
symName[0].expectKind nnkIdent
else:
symName.expectKind nnkIdent
templateCall.add newCall(bindSym"expectNimNode", symName)
for expr in extraCommentGenExpr:
templateCall.add expr
let getAstCall = newCall(bindSym"getAst", templateCall)
result = newStmtList(templateDef, getAstCall)
macro testAddrAst(arg: typed): bool =
arg.expectKind nnkStmtListExpr
arg[0].expectKind(nnkVarSection)
arg[1].expectKind({nnkAddr, nnkCall})
result = newLit(arg[1].kind == nnkCall)
const newAddrAst: bool = testAddrAst((var x: int; addr(x)))
static:
echo "new addr ast: ", newAddrAst
# TODO test on matching failures
proc peelOff*(arg: NimNode, kinds: set[NimNodeKind]): NimNode {.compileTime.} =
## Peel off nodes of a specific kinds.
if arg.len == 1 and arg.kind in kinds:
arg[0].peelOff(kinds)
else:
arg
proc peelOff*(arg: NimNode, kind: NimNodeKind): NimNode {.compileTime.} =
## Peel off nodes of a specific kind.
if arg.len == 1 and arg.kind == kind:
arg[0].peelOff(kind)
else:
arg
static:
template testPattern(pattern, astArg: untyped): untyped =
let ast = quote do: `astArg`
ast.matchAst:
of `pattern`:
echo "ok"
template testPatternFail(pattern, astArg: untyped): untyped =
let ast = quote do: `astArg`
ast.matchAst:
of `pattern`:
error("this should not match", ast)
else:
echo "OK"
testPattern nnkIntLit(intVal = 42), 42
testPattern nnkInt8Lit(intVal = 42), 42'i8
testPattern nnkInt16Lit(intVal = 42), 42'i16
testPattern nnkInt32Lit(intVal = 42), 42'i32
testPattern nnkInt64Lit(intVal = 42), 42'i64
testPattern nnkUInt8Lit(intVal = 42), 42'u8
testPattern nnkUInt16Lit(intVal = 42), 42'u16
testPattern nnkUInt32Lit(intVal = 42), 42'u32
testPattern nnkUInt64Lit(intVal = 42), 42'u64
#testPattern nnkFloat64Lit(floatVal = 42.0), 42.0
testPattern nnkFloat32Lit(floatVal = 42.0), 42.0'f32
#testPattern nnkFloat64Lit(floatVal = 42.0), 42.0'f64
testPattern nnkStrLit(strVal = "abc"), "abc"
testPattern nnkRStrLit(strVal = "abc"), r"abc"
testPattern nnkTripleStrLit(strVal = "abc"), """abc"""
testPattern nnkCharLit(intVal = 32), ' '
testPattern nnkNilLit(), nil
testPattern nnkIdent(strVal = "myIdentifier"), myIdentifier
testPatternFail nnkInt8Lit(intVal = 42), 42'i16
testPatternFail nnkInt16Lit(intVal = 42), 42'i8
# this should be just `block` but it doesn't work that way anymore because of VM.
macro scope(arg: untyped): untyped =
let procSym = genSym(nskProc)
result = quote do:
proc `procSym`() {.compileTime.} =
`arg`
`procSym`()
static:
## Command call
scope:
let ast = myquote:
echo "abc", "xyz"
ast.matchAst:
of nnkCommand(ident"echo", "abc", "xyz"):
echo "ok"
## Call with ``()``
scope:
let ast = myquote:
echo("abc", "xyz")
ast.matchAst:
of nnkCall(ident"echo", "abc", "xyz"):
echo "ok"
## Infix operator call
macro testInfixOperatorCall(ast: untyped): untyped =
ast.matchAst(errorSym):
of nnkInfix(
ident"&",
nnkStrLit(strVal = "abc"),
nnkStrLit(strVal = "xyz")
):
echo "ok1"
of nnkInfix(
ident"+",
nnkIntLit(intVal = 5),
nnkInfix(
ident"*",
nnkIntLit(intVal = 3),
nnkIntLit(intVal = 4)
)
):
echo "ok2"
of nnkCall(
nnkAccQuoted(
ident"+"
),
nnkIntLit(intVal = 3),
nnkIntLit(intVal = 4)
):
echo "ok3"
testInfixOperatorCall("abc" & "xyz")
testInfixOperatorCall(5 + 3 * 4)
testInfixOperatorCall(`+`(3, 4))
## Prefix operator call
scope:
let ast = myquote:
? "xyz"
ast.matchAst(err):
of nnkPrefix(
ident"?",
nnkStrLit(strVal = "xyz")
):
echo "ok"
## Postfix operator call
scope:
let ast = myquote:
proc identifier*
ast[0].matchAst(err):
of nnkPostfix(
ident"*",
ident"identifier"
):
echo "ok"
## Call with named arguments
macro testCallWithNamedArguments(ast: untyped): untyped =
ast.peelOff(nnkStmtList).matchAst:
of nnkCall(
ident"writeLine",
nnkExprEqExpr(
ident"file",
ident"stdout"
),
nnkStrLit(strVal = "hallo")
):
echo "ok"
testCallWithNamedArguments:
writeLine(file=stdout, "hallo")
## Call with raw string literal
scope:
let ast = myquote:
echo"abc"
ast.matchAst(err):
of nnkCallStrLit(
ident"echo",
nnkRStrLit(strVal = "abc")
):
echo "ok"
## Dereference operator ``[]``
scope:
# The dereferece operator exists only on a typed ast.
macro testDereferenceOperator(ast: typed): untyped =
ast.matchAst(err):
of nnkDerefExpr(_):
echo "ok"
var x: ptr int
testDereferenceOperator(x[])
## Addr operator
scope:
# The addr operator exists only on a typed ast.
macro testAddrOperator(ast: untyped): untyped =
echo ast.treeRepr
ast.matchAst(err):
of nnkAddr(ident"x"):
echo "old nim"
of nnkCall(ident"addr", ident"x"):
echo "ok"
var x: int
testAddrOperator(addr(x))
## Cast operator
scope:
let ast = myquote:
cast[T](x)
ast.matchAst:
of nnkCast(ident"T", ident"x"):
echo "ok"
## Object access operator ``.``
scope:
let ast = myquote:
x.y
ast.matchAst:
of nnkDotExpr(ident"x", ident"y"):
echo "ok"
## Array access operator ``[]``
macro testArrayAccessOperator(ast: untyped): untyped =
ast.matchAst:
of nnkBracketExpr(ident"x", ident"y"):
echo "ok"
testArrayAccessOperator(x[y])
## Parentheses
scope:
let ast = myquote:
(a + b) * c
ast.matchAst:
of nnkInfix(ident"*", nnkPar(nnkInfix(ident"+", ident"a", ident"b")), ident"c"):
echo "parentheses ok"
## Tuple Constructors
scope:
let ast = myquote:
(1, 2, 3)
(a: 1, b: 2, c: 3)
(1,)
(a: 1)
()
for it in ast:
echo it.lispRepr
it.matchAst:
of nnkTupleConstr(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkIntLit(intVal = 3)):
echo "simple tuple ok"
of nnkTupleConstr(
nnkExprColonExpr(ident"a", nnkIntLit(intVal = 1)),
nnkExprColonExpr(ident"b", nnkIntLit(intVal = 2)),
nnkExprColonExpr(ident"c", nnkIntLit(intVal = 3))
):
echo "named tuple ok"
of nnkTupleConstr(nnkIntLit(intVal = 1)):
echo "one tuple ok"
of nnkTupleConstr(nnkExprColonExpr(ident"a", nnkIntLit(intVal = 1))):
echo "named one tuple ok"
of nnkTupleConstr():
echo "empty tuple ok"
## Curly braces
scope:
let ast = myquote:
{1, 2, 3}
ast.matchAst:
of nnkCurly(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkIntLit(intVal = 3)):
echo "ok"
scope:
let ast = myquote:
{a: 3, b: 5}
ast.matchAst:
of nnkTableConstr(
nnkExprColonExpr(ident"a", nnkIntLit(intVal = 3)),
nnkExprColonExpr(ident"b", nnkIntLit(intVal = 5))
):
echo "ok"
## Brackets
scope:
let ast = myquote:
[1, 2, 3]
ast.matchAst:
of nnkBracket(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkIntLit(intVal = 3)):
echo "ok"
## Ranges
scope:
let ast = myquote:
1..3
ast.matchAst:
of nnkInfix(
ident"..",
nnkIntLit(intVal = 1),
nnkIntLit(intVal = 3)
):
echo "ok"
## If expression
scope:
let ast = myquote:
if cond1: expr1 elif cond2: expr2 else: expr3
ast.matchAst:
of {nnkIfExpr, nnkIfStmt}(
{nnkElifExpr, nnkElifBranch}(`cond1`, `expr1`),
{nnkElifExpr, nnkElifBranch}(`cond2`, `expr2`),
{nnkElseExpr, nnkElse}(`expr3`)
):
echo "ok"
## Documentation Comments
scope:
let ast = myquote:
## This is a comment
## This is part of the first comment
stmt1
## Yet another
ast.matchAst:
of nnkStmtList(
nnkCommentStmt(),
`stmt1`,
nnkCommentStmt()
):
echo "ok"
else:
echo "warning!"
echo ast.treeRepr
echo "TEST causes no fail, because of a regression in Nim."
scope:
let ast = myquote:
{.emit: "#include <stdio.h>".}
ast.matchAst:
of nnkPragma(
nnkExprColonExpr(
ident"emit",
nnkStrLit(strVal = "#include <stdio.h>") # the "argument"
)
):
echo "ok"
scope:
let ast = myquote:
{.pragma: cdeclRename, cdecl.}
ast.matchAst:
of nnkPragma(
nnkExprColonExpr(
ident"pragma", # this is always first when declaring a new pragma
ident"cdeclRename" # the name of the pragma
),
ident"cdecl"
):
echo "ok"
scope:
let ast = myquote:
if cond1:
stmt1
elif cond2:
stmt2
elif cond3:
stmt3
else:
stmt4
ast.matchAst:
of nnkIfStmt(
nnkElifBranch(`cond1`, `stmt1`),
nnkElifBranch(`cond2`, `stmt2`),
nnkElifBranch(`cond3`, `stmt3`),
nnkElse(`stmt4`)
):
echo "ok"
scope:
let ast = myquote:
x = 42
ast.matchAst:
of nnkAsgn(ident"x", nnkIntLit(intVal = 42)):
echo "ok"
scope:
let ast = myquote:
stmt1
stmt2
stmt3
ast.matchAst:
of nnkStmtList(`stmt1`, `stmt2`, `stmt3`):
assert stmt1.strVal == "stmt1"
assert stmt2.strVal == "stmt2"
assert stmt3.strVal == "stmt3"
echo "ok"
## Case statement
scope:
let ast = myquote:
case expr1
of expr2, expr3..expr4:
stmt1
of expr5:
stmt2
elif cond1:
stmt3
else:
stmt4
ast.matchAst:
of nnkCaseStmt(
`expr1`,
nnkOfBranch(`expr2`, {nnkRange, nnkInfix}(_, `expr3`, `expr4`), `stmt1`),
nnkOfBranch(`expr5`, `stmt2`),
nnkElifBranch(`cond1`, `stmt3`),
nnkElse(`stmt4`)
):
echo "ok"
## While statement
scope:
let ast = myquote:
while expr1:
stmt1
ast.matchAst:
of nnkWhileStmt(`expr1`, `stmt1`):
echo "ok"
## For statement
scope:
let ast = myquote:
for ident1, ident2 in expr1:
stmt1
ast.matchAst:
of nnkForStmt(`ident1`, `ident2`, `expr1`, `stmt1`):
echo "ok"
## Try statement
scope:
let ast = myquote:
try:
stmt1
except e1, e2:
stmt2
except e3:
stmt3
except:
stmt4
finally:
stmt5
ast.matchAst:
of nnkTryStmt(
`stmt1`,
nnkExceptBranch(`e1`, `e2`, `stmt2`),
nnkExceptBranch(`e3`, `stmt3`),
nnkExceptBranch(`stmt4`),
nnkFinally(`stmt5`)
):
echo "ok"
## Return statement
scope:
let ast = myquote:
return expr1
ast.matchAst:
of nnkReturnStmt(`expr1`):
echo "ok"
## Continue statement
scope:
let ast = myquote:
continue
ast.matchAst:
of nnkContinueStmt:
echo "ok"
## Break statement
scope:
let ast = myquote:
break otherLocation
ast.matchAst:
of nnkBreakStmt(ident"otherLocation"):
echo "ok"
## Block statement
scope:
template blockStatement {.dirty.} =
block name:
discard
let ast = getAst(blockStatement())
ast.matchAst:
of nnkBlockStmt(ident"name", nnkStmtList):
echo "ok"
## Asm statement
scope:
let ast = myquote:
asm """some asm"""
ast.matchAst:
of nnkAsmStmt(
nnkEmpty(), # for pragmas
nnkTripleStrLit(strVal = "some asm"),
):
echo "ok"
## Import section
scope:
let ast = myquote:
import math
ast.matchAst:
of nnkImportStmt(ident"math"):
echo "ok"
scope:
let ast = myquote:
import math except pow
ast.matchAst:
of nnkImportExceptStmt(ident"math",ident"pow"):
echo "ok"
scope:
let ast = myquote:
import strutils as su
ast.matchAst:
of nnkImportStmt(
nnkInfix(
ident"as",
ident"strutils",
ident"su"
)
):
echo "ok"
## From statement
scope:
let ast = myquote:
from math import pow
ast.matchAst:
of nnkFromStmt(ident"math", ident"pow"):
echo "ok"
## Export statement
scope:
let ast = myquote:
export unsigned
ast.matchAst:
of nnkExportStmt(ident"unsigned"):
echo "ok"
scope:
let ast = myquote:
export math except pow # we're going to implement our own exponentiation
ast.matchAst:
of nnkExportExceptStmt(ident"math",ident"pow"):
echo "ok"
## Include statement
scope:
let ast = myquote:
include blocks
ast.matchAst:
of nnkIncludeStmt(ident"blocks"):
echo "ok"
## Var section
scope:
let ast = myquote:
var a = 3
ast.matchAst:
of nnkVarSection(
nnkIdentDefs(
ident"a",
nnkEmpty(), # or nnkIdent(...) if the variable declares the type
nnkIntLit(intVal = 3),
)
):
echo "ok"
## Let section
scope:
let ast = myquote:
let a = 3
ast.matchAst:
of nnkLetSection(
nnkIdentDefs(
ident"a",
nnkEmpty(), # or nnkIdent(...) for the type
nnkIntLit(intVal = 3),
)
):
echo "ok"
## Const section
scope:
let ast = myquote:
const a = 3
ast.matchAst:
of nnkConstSection(
nnkConstDef( # not nnkConstDefs!
ident"a",
nnkEmpty(), # or nnkIdent(...) if the variable declares the type
nnkIntLit(intVal = 3), # required in a const declaration!
)
):
echo "ok"
## Type section
scope:
let ast = myquote:
type A = int
ast.matchAst:
of nnkTypeSection(
nnkTypeDef(
ident"A",
nnkEmpty(),
ident"int"
)
):
echo "ok"
scope:
let ast = myquote:
type MyInt = distinct int
ast.peelOff({nnkTypeSection}).matchAst:
of# ...
nnkTypeDef(
ident"MyInt",
nnkEmpty(),
nnkDistinctTy(
ident"int"
)
):
echo "ok"
scope:
let ast = myquote:
type A[T] = expr1
ast.matchAst:
of nnkTypeSection(
nnkTypeDef(
ident"A",
nnkGenericParams(
nnkIdentDefs(
ident"T",
nnkEmpty(), # if the type is declared with options, like
# ``[T: SomeInteger]``, they are given here
nnkEmpty()
)
),
`expr1`
)
):
echo "ok"
scope:
let ast = myquote:
type IO = object of RootObj
ast.peelOff(nnkTypeSection).matchAst:
of nnkTypeDef(
ident"IO",
nnkEmpty(),
nnkObjectTy(
nnkEmpty(), # no pragmas here
nnkOfInherit(
ident"RootObj" # inherits from RootObj
),
nnkEmpty()
)
):
echo "ok"
scope:
macro testRecCase(ast: untyped): untyped =
ast.peelOff({nnkStmtList, nnkTypeSection}).matchAst:
of nnkTypeDef(
nnkPragmaExpr(
ident"Obj",
nnkPragma(ident"inheritable")
),
nnkGenericParams(
nnkIdentDefs(
ident"T",
nnkEmpty(),
nnkEmpty())
),
nnkObjectTy(
nnkEmpty(),
nnkEmpty(),
nnkRecList( # list of object parameters
nnkIdentDefs(
ident"name",
ident"string",
nnkEmpty()
),
nnkRecCase( # case statement within object (not nnkCaseStmt)
nnkIdentDefs(
ident"isFat",
ident"bool",
nnkEmpty()
),
nnkOfBranch(
ident"true",
nnkRecList( # again, a list of object parameters
nnkIdentDefs(
ident"m",
nnkBracketExpr(
ident"array",
nnkIntLit(intVal = 100000),
ident"T"
),
nnkEmpty()
)
)
),
nnkOfBranch(
ident"false",
nnkRecList(
nnkIdentDefs(
ident"m",
nnkBracketExpr(
ident"array",
nnkIntLit(intVal = 10),
ident"T"
),
nnkEmpty()
)
)
)
)
)
)
):
echo "ok"
testRecCase:
type Obj[T] {.inheritable.} = object
name: string
case isFat: bool
of true:
m: array[100_000, T]
of false:
m: array[10, T]
scope:
let ast = myquote:
type X = enum
First
ast.peelOff({nnkStmtList, nnkTypeSection})[2].matchAst:
of nnkEnumTy(
nnkEmpty(),
ident"First" # you need at least one nnkIdent or the compiler complains
):
echo "ok"
scope:
let ast = myquote:
type Con = concept x,y,z
(x & y & z) is string
ast.peelOff({nnkStmtList, nnkTypeSection}).matchAst:
of nnkTypeDef(_, _, nnkTypeClassTy(nnkArgList, _, _, nnkStmtList)):
# note this isn't nnkConceptTy!
echo "ok"
scope:
let astX = myquote:
type
A[T: static[int]] = object
let ast = astX.peelOff({nnkStmtList, nnkTypeSection})
ast.matchAst(err): # this is a sub ast for this a findAst or something like that is useful
of nnkTypeDef(_, nnkGenericParams( nnkIdentDefs( ident"T", nnkCall( ident"[]", ident"static", _ ), _ )), _):
echo "ok"
else:
echo "foobar"
echo ast.treeRepr
scope:
let ast = myquote:
type MyProc[T] = proc(x: T)
ast.peelOff({nnkStmtList, nnkTypeSection}).matchAst(err):
of nnkTypeDef(
ident"MyProc",
nnkGenericParams, # here, not with the proc
nnkProcTy( # behaves like a procedure declaration from here on
nnkFormalParams, _
)
):
echo "ok"
## Mixin statement
macro testMixinStatement(ast: untyped): untyped =
ast.peelOff(nnkStmtList).matchAst:
of nnkMixinStmt(ident"x"):
echo "ok"
testMixinStatement:
mixin x
## Bind statement
macro testBindStmt(ast: untyped): untyped =
ast[0].matchAst:
of `node` @ nnkBindStmt(ident"x"):
echo "ok"
testBindStmt:
bind x
## Procedure declaration
macro testProcedureDeclaration(ast: untyped): untyped =
# NOTE this is wrong in astdef
ast.peelOff(nnkStmtList).matchAst:
of nnkProcDef(
nnkPostfix(ident"*", ident"hello"), # the exported proc name
nnkEmpty, # patterns for term rewriting in templates and macros (not procs)
nnkGenericParams( # generic type parameters, like with type declaration
nnkIdentDefs(
ident"T",
ident"SomeInteger", _
)
),
nnkFormalParams(
ident"int", # the first FormalParam is the return type. nnkEmpty if there is none
nnkIdentDefs(
ident"x",
ident"int", # type type (required for procs, not for templates)
nnkIntLit(intVal = 3) # a default value
),
nnkIdentDefs(
ident"y",
ident"float32",
nnkEmpty
)
),
nnkPragma(ident"inline"),
nnkEmpty, # reserved slot for future use
`meat` @ nnkStmtList # the meat of the proc
):
echo "ok got meat: ", meat.lispRepr
testProcedureDeclaration:
proc hello*[T: SomeInteger](x: int = 3, y: float32): int {.inline.} = discard
scope:
var ast = myquote:
proc foobar(a, b: int): void
ast = ast[3]
ast.matchAst: # sub expression
of nnkFormalParams(
_, # return would be here
nnkIdentDefs(
ident"a", # the first parameter
ident"b", # directly to the second parameter
ident"int", # their shared type identifier
nnkEmpty, # default value would go here
)
):
echo "ok"
scope:
let ast = myquote:
proc hello(): var int
ast[3].matchAst: # subAst
of nnkFormalParams(
nnkVarTy(
ident"int"
)
):
echo "ok"
## Iterator declaration
scope:
let ast = myquote:
iterator nonsense[T](x: seq[T]): float {.closure.} =
discard
ast.matchAst:
of nnkIteratorDef(ident"nonsense", nnkEmpty, _, _, _, _, _):
echo "ok"
## Converter declaration
scope:
let ast = myquote:
converter toBool(x: float): bool
ast.matchAst:
of nnkConverterDef(ident"toBool",_,_,_,_,_,_):
echo "ok"
## Template declaration
scope:
let ast = myquote:
template optOpt{expr1}(a: int): int
ast.matchAst:
of nnkTemplateDef(ident"optOpt", nnkStmtList(`expr1`), _, _, _, _, _):
echo "ok"