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

Reply via email to