What I'm trying to do is to parse a string within a macro, and determine the type of the resulting expression. A simplified example:
macro stringParseTest(s: string): stmt =
let trueString = s.strVal
let ne = parseExpr(trueString)
let ns = parseStmt(trueString)
echo treeRepr(ne)
echo treeRepr(ns)
# looking good, e.g. for ne:
# Infix
# Ident !"+"
# Ident !"y"
# IntLit 1
#
# But how to get the type of the parsed node here? (int in the example)
# even ns.getType yields Error: node has no type
# let t = ns.getType
# echo "Type is: ", t.repr
result = quote do:
echo "does nothing"
var y = 1
var (a, b) = ("a", "b")
stringParseTest("y+1")
stringParseTest("a & b")
Note: This example might suggest to simply pass the expression directly as a typed expression, i.e., stringParseTest(y+1), but for my use case I really only have a raw string available in the macro.
You cannot possibly determine the type of an arbitrary expression without knowing the context it's being used in.
Example:
template example(body: untyped) =
block:
template `&`(a, b: int): int = a and b
body
template example2(body: untyped) =
block:
body
example:
let
a = 3
b = 5
echo a & b
example2:
let
a = "3"
b = "5"
echo a & b
Both examples use a & b, but the types are different each time.
This makes sense. Maybe my question should be: Can I evaluate the expression "in the context of the caller scope"?
On IRC Araq suggested it should be possible by a nested macro approach...
What's the problem?
import macros
macro parse(x: string): untyped =
result = parseExpr(x.strVal)
macro typecheck(x: typed): untyped =
echo repr x.getType()
result = newStmtList()
template stringParseTest(x) = typecheck parse x
var y = 1
var (a, b) = ("a", "b")
stringParseTest("y+1")
stringParseTest("a & b")
Sorry for bumping an old thread, but it's for a good reason (the string interpolation PR). I'm still struggling to achieve the same from within a macro:
macro parse(x: string): untyped =
echo "calling parse on: ", x
result = parseExpr(x.strVal)
macro typecheck(x: typed): untyped =
echo "calling typecheck on: ", x.getType().repr()
result = newStmtList()
template stringParseTest(x: string) = typecheck parse x
macro fmt*(fmt: static[string]): untyped =
let fields = fmt.split(":")
assert fields.len == 2
let lhs = fields[0]
let rhs = fields[1]
echo "Trying to get type of: ", lhs
# Neither of the following works, because the ``x.strVal`` produces:
# Error: field 'strVal' cannot be found
# Also, it is strange that the value of x within parse is "lhs" and not
# its actual value.
when false:
let lhsType = stringParseTest(lhs)
when false:
let lhsType = typecheck(parse(lhs))
when false: # in general calling parse doesn't seems to work due to the value issue:
parse(lhs)
# So it looks like I have to call parseExpr directly here. This gives the expected
# result for the expression, but calling typecheck on it does not work, because of:
# Error: expression '' has no type (or is ambiguous)
when false:
let e = parseExpr(lhs)
echo e.treeRepr
let lhsType = typecheck(e)
# Similarly any attempt to call getType() directly on the expressions fails with
# node has no type.
when false:
let e = parseExpr(lhs)
echo e.treeRepr
let lhsType = e.getType()
block:
let x = 1
fmt"x + x:<format-string>"
block:
let x = "test"
fmt"x & x:<format-string>"
I feel like I'm just missing the right kind of nesting. Or maybe it isn't possible when the outer-most thing is a macro, and I really need to wrap everything into another outer template?
Ah, I think I found a solution:
import macros, strutils
macro typecheckedAst(x: typed): untyped =
echo "calling typecheck on expression: ", x.repr
echo "with type: ", x.getType().treeRepr()
# We can build a type specific AST here:
let typ = x.getType()
if sameType(typ, bindSym"int"):
echo "it's an int"
result = newStrLitNode("AST for int")
elif sameType(typ, bindSym"string"):
echo "it's a string"
result = newStrLitNode("AST for string")
else:
echo "unknown type"
echo " => resulting type dependent AST: ", result.repr
macro fmt*(fmt: static[string]): untyped =
let fields = fmt.split(":")
assert fields.len == 2
let lhs = fields[0]
let rhs = fields[1]
# We probably can't get the type of `e` here, but we can delegate
# all type specific AST generation into the `typecheckedAst` macro
let e = parseExpr(lhs)
result = newCall(ident"typecheckedAst", e)
block:
let x = 1
echo fmt"x + x:<format-string>"
block:
let x = "test"
echo fmt"x & x:<format-string>"
So the trick is to use parseExpr in the outer macro and delegate the type specific AST generation into a sub-macro which takes a typed expression.