Proc with a "NimNode" input are automatically compile-time, otherwise you can tag them with {.compiletime.} pragma.
Regarding implementing your own AST with an Expr AST -> Nim AST, you might also be interested into my step-by-step experiments into writing a compiler in Nim macros: https://github.com/mratsim/compute-graph-optim which now lives as a PoC tensor/matrix operation compiler: https://github.com/numforge/laser/tree/master/laser/lux_compiler.
The AST implementation is described here: https://github.com/numforge/laser/blob/master/laser/lux_compiler/core/low_level_design_considerations.md
It's still unfinished so there is only about 1000 lines so should be pretty easy to follow.