For future use in case someone needs it, here is a bit of feedback from my experiments.
1. Keep in mind that template is only **code rewriting**. 2. Keep templates for **simple** rewriting rules. 3. Don't use imbricated templates to manage scoping rules! 4. Use `block:` to manage scopes (and [declaredInScope](https://nim-lang.org/docs/system.html#declaredInScope%2Cuntyped) can be useful) 5. If you need complex rules, learn macros (but beware, they bite too!) 6. Experiments with code snippets before applying what you've learned in your thousands lines project. 7. Repeat after me: **template = text code rewriting**! Use it for that only! As a demonstration of point #3 import macros template dsl(dslBody: untyped): untyped = block: template options(optionsBody: untyped): untyped = block: template foo(fooBody: untyped): untyped = echo "Enter foo" fooBody echo "Leave foo" echo "Enter options" optionsBody echo "Leave options" template options(status: string; optionsBody: untyped): untyped = block: echo "Enter options with " & status options(optionsBody) echo "Leave options with " & status echo "Enter dsl" dslBody echo "Leave dsl" expandMacros: dsl: options: echo "Youpi!" #foo: #<== Undeclared identifier # echo "1000 $!!!" options "Oops!": echo "I failed" foo: echo "1000 $!!!" Run You see that you can't call the first `foo` as the compiler greats you with a `Error: Undeclared identifier` message but accepts the second one even if `foo` is not declared in that block. What's the heck! Looking at the output gives you a clue: block: template options(optionsBody`gensym14461030: untyped): untyped = block: template foo(fooBody`gensym14461031_14465004: untyped): untyped = echo "Enter foo" fooBody`gensym14461031_14465004 echo "Leave foo" echo "Enter options" optionsBody`gensym14461030 echo "Leave options" template options(status`gensym14461032: string; optionsBody`gensym14461033: untyped): untyped = block: echo "Enter options with " & status`gensym14461032 options(optionsBody`gensym14461033) echo "Leave options with " & status`gensym14461032 echo ["Enter dsl"] block: template foo(fooBody`gensym14461031`gensym14465018: untyped): untyped = echo "Enter foo" fooBody`gensym14461031`gensym14465018 echo "Leave foo" echo ["Enter options"] echo ["Youpi!"] echo ["Leave options"] block: echo ["Enter options with Oops!"] block: template foo(fooBody`gensym14461031`gensym14475005: untyped): untyped = echo "Enter foo" fooBody`gensym14461031`gensym14475005 echo "Leave foo" echo ["Enter options"] echo ["I failed"] echo ["Enter foo"] echo ["1000 $!!!"] echo ["Leave foo"] echo ["Leave options"] echo ["Leave options with Oops!"] echo ["Leave dsl"] Run The templates rewrites have been more prolific than expected! Using non-imbricated templates is simpler to reason about: import macros template foo(fooBody: untyped): untyped = echo "Enter foo" fooBody echo "Leave foo" template options(optionsBody: untyped): untyped = block: echo "Enter options" optionsBody echo "Leave options" template options(status: string; optionsBody: untyped): untyped = block: echo "Enter options with " & status options(optionsBody) echo "Leave options with " & status template dsl(dslBody: untyped): untyped = block: echo "Enter dsl" dslBody echo "Leave dsl" expandMacros: dsl: options: echo "Youpi!" foo: echo "1000 $!!!" options "Oops!": echo "I failed" foo: echo "1000 $!!!" Run And the result matches expectations block: echo ["Enter dsl"] block: echo ["Enter options"] echo ["Youpi!"] echo ["Leave options"] block: echo ["Enter options with Oops!"] block: echo ["Enter options"] echo ["I failed"] echo ["Enter foo"] echo ["1000 $!!!"] echo ["Leave foo"] echo ["Leave options"] echo ["Leave options with Oops!"] echo ["Leave dsl"] Run So how do you solve the initial template scoping problem? I have to validate it in my project to confirm, but you can try something like: import macros template foo(fooBody: untyped): untyped = when not declaredInScope(inOptions): {. fatal: "foo car be used only in `options:` block" .} echo "Enter foo" fooBody echo "Leave foo" template options(optionsBody: untyped): untyped = when not declaredInScope(inDsl): {. fatal: "options car be used only in `dsl:` block" .} block: let inOptions {. inject, used .} = true echo "Enter options" optionsBody echo "Leave options" template dsl(dslBody: untyped): untyped = block: let inDsl {. inject, used .} = true echo "Enter dsl" dslBody echo "Leave dsl" expandMacros: dsl: options: echo "Youpi!" foo: echo "1000 $!!!" #options: <== Will not compile, not in dsl! # echo "I failed" #foo: <== Will not compile, not in options! # echo "Not better" Run
