Log the following using macros:
let x = 1; y = 2; z = 3 logvars(x, y, z) # Output: x = 1, y = 2, z = 3
Hint:
macro logvars(args: varargs[untyped])
Loop over
args
and 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)