Log the following using macros:
let x = 1; y = 2; z = 3 logvars(x, y, z) # Output: x = 1, y = 2, z = 3Hint:
macro logvars(args: varargs[untyped])Loop over
argsand emit anecho.
I've approached this macro exercise two different ways:
logvars1, as being more idiomatic in Nim;
logvars2, as an educationally-rich curiosity.
Use astToStr inside macros, to convert each variable to a "(identifier) = " string dynamically:
Access identifiers via backticks.
Use stdout.write for fine-grained control (like Python's end=" ").
Conditional logic must live outside quote do:, since that syntax captures whole statements.
Distinguish AST construction vs runtime evaluation, i.e: you're not running code - you're building code structure, piece by piece - i.e. and constructing formatted expressions via formatted_parts, to be spliced into actual Nim code later. This makes the macro logic easier to follow especially when debugging or extending it.
You're making decisions outside the quote do. Assemble output in the result using stdout.write.
import macros
# ATTEMPT-1
macro logvars1(args: varargs[untyped]): untyped =
result = newStmtList()
var parts: seq[NimNode] = @[]
for arg in args:
parts.add(newStrLitNode($arg & " = "))
parts.add(arg)
parts.add(newStrLitNode(", "))
if parts.len >= 1:
discard parts.pop()
result.add(newCall(ident("echo"), parts))
## ATTEMPT-2
macro logvars2(args: varargs[untyped]): untyped =
result = newStmtList()
var parts: seq[NimNode] = @[] # [!] We work on AST nodes, not strings yet
for arg in args:
parts.add(arg)
var formatted_parts: seq[NimNode] = @[]
for part in parts:
formatted_parts.add quote do:
astToStr(`part`) & " = " & $`part`
for i, c in formatted_parts:
if i < (formatted_parts.len - 1):
result.add quote do:
stdout.write $`c` & ", "
else:
result.add quote do:
stdout.write $`c`
# Handling last-line
if i == (formatted_parts.len - 1):
result.add quote do:
stdout.write "\n"
let x = 1
let y = 2
let z = 3
logvars1(x, y, z)
logvars2(x, y, z)