let x = 1; y = 2; z = 3 logvars(x, y, z) # Output: x = 1, y = 2, z = 3
- Use
macro logvars(args: varargs[untyped])
.- Loop over
args
and emit anecho
.
Two different macro approaches: logvars1
is more idiomatic for Nim; logvars2
is educationally rich.
Use astToStr
inside macros, to to convert each variable to a "(identifier) = " string dynamically.
Accessing identifiers via backticks.
stdout.write
for fine-grained control (like Python's end=" "
).
quote do:
captures whole statements. Conditional logic must live outside it.
Distinction between AST construction, vs runtime evaluation, i.e: You're not running code - you're building the structure of it, piece by piece - i.e. 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 assembling output in result using stdout.write
. You're making decisions outside the quote do
.
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)