My understanding is that include is like template but for file: in-place code replacement, just like C #include. I broke my big file into small includes, and now inapprehensible errors pop up... This example presents the reverse of what I see with my code: big file compiling while includes don't.
myinclude.nim
type
A = object
str: string
template conc(s1: A, s2: string): string =
s1.str & s2
foo.nim
template foo*(sFoo: untyped) =
block:
include myinclude
template bar(sBar: untyped) =
echo "In bar"
sBar
echo "In foo"
sFoo
echo "completed foo"
foo:
bar:
echo conc(A(str: "hello"), "world")
This compiles and outputs
In foo
In bar
helloworld
completed foo
Now, if instead of including the file, I copy its content replacing the include myinclude, to get foo.nim
template foo*(sFoo: untyped) =
block:
type A = object
str: string
template conc(s1: A, s2: string): string =
s1.str & s2
template bar(sBar: untyped) =
echo "In bar"
sBar
echo "In foo"
sFoo
echo "completed foo"
foo:
bar:
echo conc(A(str: "hello"), "world")
It does not compile anymore, with Error: undeclared identifier: 'A'
What does include do exactly?
Also as a side note, if you don't declare the type ``A``, Nim compiler optimize the string concatenation and both versions compile...
You are doing "interesting" things.
First, I can not remember having seen nested templates at all in Nim, at least I never used that construct.
And putting a include inside a template is also "interesting".
From my memory an include should be identical to copying the content at that location.
Let's look at a minimal code sample which reproduces the compiler error:
template foo*(sFoo: untyped) =
type A = object
str: string
sFoo
foo:
echo A(str: "hello").str
Type definitions inside a template are by default invisible to code from the instantiation scope (or gensym ed, see the manual chapter @Hlaaftana linked). sFoo comes from the instantiation scope, thus the error, which can be avoided by giving A an {.inject.} pragma.
When the definition of A is included instead, the include is apparently resolved after template substitution: A is not defined in the template body, but immediatly inside the instantiating context.
You are doing "interesting" things.
First, I can not remember having seen nested templates at all in Nim, at least I never used that construct.
And putting a include inside a template is also "interesting".
My goal is to produce precise error message to the user of templates. One way I'm exploring is to control when a template can be used. I'll explain here what I was trying to do...
mymodule.nim
template foo*(...) =
# Visible and usable externaly
template bar*(...) =
# How to control that bar can be used externaly by a client
# only in the context of foo?
I want to prevent a consumer of mymodule use bar and get an error message like Syntax error: ctx variable not defined. This client must not know that ctx has been injected by foo. So using englobing template defines context when a template can be used: bar is not usable outside of a foo block.
But writing multi-levels tens of nested templates inside a big one is not that easy, and I turned to include for code management. The tricky part is that templates don't define scope as they are just code replacement and that's where include don't play well with visibility rules...
mymodule.nim
type
A = object
# A custom object that is used by bar
template foo*(...) =
# Visible and usable externaly
block:
template bar(...) =
# Now bar can be used only within foo.
In the code snippet above, if bar uses special types, like A, it would be simpler if bar is stored in another file to be included, that A definition goes into the same file too. And as everything was inside a block:, I thought that it declared somewhat a new scope where I can have type declarations. Well, I was wrong because of template hygiene...
This is becoming tricky as I mix visibility rules for consumers (* sufix), scope of objects/templates/macros, and code management.
Thanks @Hlaaftana and @gemath, this makes a lot of sense. For the moment, I reverted back all the pieces of code in a single file. It will be easier to debug. I'll try to break it again in pieces when it is debugged.
That's what I'm presently doing. I'm using the following pattern (presented top-down for easier reading):
template dsl*(bodyDsl: untyped) =
# Structure of the DSL
block:
# Inject context variables and do init...
template action(bodyAction: untyped) =
# Here in action context.
actionImpl(bodyAction)
...
macro actionImpl(body: untyped): untyped =
# Delegate syntax processing to procs
... extract pieces of syntax
proc1(var1, code1)
proc2(var2, code2)
...
proc proc1(var1: int; body: NimNode) =
# Procs can mix different type of arguments and
# easier to use in proc body.
...
v = op(v1, v2)
template op(v1: Type1; v2: Type2) =
# Optimize operators at the lowest level
Using procs below the macro level means that I don't have to take care of typed/untyped arguments and automatic macro untyped conversions. And I use again templates at the lowest level, for small operations or optimizations. I hope this pattern will make sense and that the code will be easy to understand...