Here, this should reify some stuff... I'm not sure exactly what you were going
for, so hopefully this helps some. I certainly found writing it enlightening.
**Examples**
* Asterisks denote that the remark is open to discussion, especially since I
introduce a lot of new ideas in this post (sorry! I got excited). I likely
touch on the remark again later in the post.
1\. Simple case
Simple functionality; can use a template/macro from the same module.
# bod.nim
template whatIsYourFavoriteColor*(body: untyped): untyped =
echo("Blue")
body
echo("AAAUUUUGHH")
when isMainModule:
wrapwith whatIsYourFavoriteColor
echo("Yello--")
Run
c -r bod.nim
Blue
Yell--
AAAUUUUGHH
Run
2\. Inner scope
Wrap statements allowed in any statement list.*
# inn.nim
import bod
block:
wrapwith whatIsYourFavoriteColor
echo("Purple!")
echo("It was green...")
Run
c -r inn.nim
Blue
Purple!
AAAUUUUGHH
It was green...
Run
3\. Multiple arguments
Arguments are allowed* and are passed before the statement list.
# wrapargs.nim
import macros
func replaceImpl(source, dest, body: NimNode): NimNode =
result = body.copyNimTree
for i, son in result:
if son == source:
result[i] = dest
else:
result[i] = replaceImpl(source, dest, son)
macro replace(source, dest, body: untyped): untyped =
replaceImpl(source, dest, body)
wrapwith replace(2, 3)
echo(2 + 1 + 2)
echo($2)
Run
c -r wrapargs.nim
7
3
Run
4\. Chaining
wrapwith A
wrapwith B
Run
is resolved in order and `A` receives `wrapwith B` as part of the argument
statement list.
wrapwith A, B
Run
is resolved in order and `A` does not receive B.
# tricky.nim
import wrapargs
wrapwith replace(wrapwith replace(2, 3), echo("gotcha!"))
wrapwith replace(2, 3)
echo(2)
Run
c -r tricky.nim
gotcha!
2
Run
However:
# fancy.nim
import wrapargs
wrapwith replace(wrapwith replace(2, 3), echo("gotcha!")), replace(2, 3)
echo(2)
Run
c -r fancy.nim
3
Run
**Grammar**
A wrap statement would grammatically be (I think) `wrapStmt = 'wrapwith'
qualifiedIdent par (comma qualifiedIdent par)`* and be added as a statement.
* I think there is an argument to be made that only fully-qualified identifiers
should be allowed. This would make debugging wrap statements and wrap
statement-related issues much easier. However, it would break precedent and be
generally inconsistent with Nim's lax nature regarding qualification
(`{.pure.}` notwithstanding).
**Placement**
Wrap statements may or may not be placed in a top-level statement*,
Wrap statements must, in order to be easily locatable, come before all other
statements in the statement list except imports, pragmas, docstrings, macro
definitions, and template definitions**. Wrap statements are allowed after
templates and macro definitions so that a wrap statement may use a macro
located in the same module as it appears in***.
Wrap statements must appear within a `stmtList`.
* As I wrote this I realized that there's no technical reason not to allow wrap
statements to appear in any statement list, so I include it as a topic of
discussion here.
** Or maybe this restriction should only apply to top-level wrap statements,
given that scopes should generally be much, much smaller and much, much easier
to rake through than the root scope.
*** This is a questionable call and needs discussion
**Semantics**
For semantics, consider `wrapwith X` in:
before
wrapwith X
after
Run
Where `before` and after signify a sequence of statements. Note, however, that
the code snippet only necessitates one actual `stmtList`, of which `before` and
`after` are subsequences.
`X` is invoked as a macro/template normally with all the statements in `after`
as a `stmtList`. The result, `X'`, must either be a `stmtList` or a statement*.
If `X'` is a `stmtList`, then the code snippet is replaced with a `stmtList`
containing all the statements in `before` followed by all the statements in
`X'`. Colloquially, the snippet becomes `before & X(after)`.
If `X'` is not a `stmtList`, but instead a statement, the code snippet is
replaced with a `stmtList` of all the statements in `before` followed by `X'`.
Colloquially, the snippet becomes `before & [X(after)]`.
* I include the restriction that it must be a statement (if not a `stmtList`)
because my understanding is that that will work best with Nim's current way of
doing things. Please correct me if I'm wrong and an expression would actually
work fine.
**Semantics (multiple macros)**
` wrapwith` statements are resolved in their order in the code, which also
handles the case:
wrapwith A
wrapwith B
Run
In this case, `A` receives the statement `wrapwith B` as part of the `stmtList`
its passed.
To avoid this, use the following syntax:
before
wrapwith A, B
after
Run
If a wrap statement is supplied multiple macros/templates, they are resolved in
order*
In this case, the statements in `after` are passed to `A`, producing `A'`. If
`A'` is a statement, it is wrapped in a `stmtList`; either way, it is then
passed to `B`, producing `B'`. The statements in `B'` then replace `wrapwith A,
B` and `after` in the code snippet. Colloquially, the snippet becomes the
result of:
var A' = A(after)
if A'.kind isnot nnkStmtList:
A' = nnkStmtList.newTree(A')
var B' = B(A')
if B'.kind isnot nnkStmtList:
B' = nnkStmtList.newTree(B')
return before & B'
Run
* This should also be discussed. I personally see no reason to not resolve them
in order, but I know (for example) Python decorators are resolved in reverse
order, from the inside out.
**Semantics (arguments)**
In a wrap statement, the templates/macros may be supplied arguments* which will
precede** the statement list in the template/macro invocation. For instance,
block:
wrapwith A(x, y)
body
Run
Is essentially the same as
block:
A(x, y, body)
Run
* Perhaps worth discussion? I see no reason not to allow this.
** There is an argument to be made that arguments should come after the body,
so that `body.macro(arg)` can be idiomatically used. The precedent, however,
set by templates, is that the body comes last.