Proposal: lazy, context-based macro expansion #48
Replies: 11 comments
-
I'm afraid this proposal isn't clear enough to evaluate as-written. Could you work through an example macro expansion and explain the order in which everything happens? How are nested macro invocations handled? Is all well if a macro pre-expands its children? |
Beta Was this translation helpful? Give feedback.
-
I'm using my phone right now but I'll do my best to craft an example. Let Now suppose that we have the following snippet of code. var i = 3;
static if (#isa(i, int))
{
// ...
}
else
{
// ...
} The macro expansion/semantic analysis process does the following:
That's not something I had considered. Pre-expanding children could work fine for an ahead-of-time macro expansion utility like A full-stack compiler is different. It feeds the macro processor's output into semantic analysis. To analyze a node, the compiler needs to know what it's looking at, which requires the top-level node to be expanded first. To properly macro-expand a node, its ancestors need to be analyzed by semantic analysis. So the top-level node cannot be expanded last because that leads to a chicken-egg problem: it needs to be analyzed/expanded before its children can be expanded but its children can only be analyzed/expanded after it has been expanded. |
Beta Was this translation helpful? Give feedback.
-
I always thought of the Your description uses the passive voice a lot, so I can't tell what components are involved and their responsibilities. So I would add these questions:
Edit: btw, PreProcess and PreprocessChildren are used pretty often. about 22 macros use the former and 3 use the latter. (Interestingly only one macro is still using |
Beta Was this translation helpful? Give feedback.
-
Heh. Sorry about the passive voice. I sometimes find it hard to imagine data structures actually doing something. I didn't deliberately mean to be vague. I'll go over your remarks one by one:
Yes! That's because you can't (easily) tell in advance if a node needs lazy behavior. For example,
That's where the laziness comes in. Lazy macro expansion happens on-the-fly, so expanding macros "first" and then applying semantic analysis will automatically interleave the two processes. Furthermore, semantic analysis can cheaply change the macro expansion context and macro expansion can trigger semantic analysis if Gonna grab some breakfast now. I'll try to answer the remainder of your questions as soon as possible. |
Beta Was this translation helpful? Give feedback.
-
Pardon my ignorance. I have just discovered Still, this does not eliminate the discrepancy in terms of intent between Perhaps
I meant to refer to a C# local scope as created by the blocks in the |
Beta Was this translation helpful? Give feedback.
-
Maybe I should phrase more clearly what So evaluating tree |
Beta Was this translation helpful? Give feedback.
-
Okay, so Evaluate makes sense, though it leaves the question of how to implement static_if so that it also works without ecsc. Static_if should really not create a scope, though. I still don't really understand the proposed process. I would suggest trying to implement it yourself and see how things work out. |
Beta Was this translation helpful? Give feedback.
-
Thanks for your feedback! I'll give implementing this a shot in about a week or so. If this works out, then I'll send a PR with my proposed changes. Also, some thoughts on your last comment:
If
It won't —but the blocks inside a static if (...)
-> {
-> // ...
-> }
else
-> {
-> // ...
-> } |
Beta Was this translation helpful? Give feedback.
-
Er, those shouldn't create scopes either. Consider:
(You can use double-curlies
I guess so, but then what does it do in LeMP, just act the same as |
Beta Was this translation helpful? Give feedback.
-
Right. I should've clarified that I thought of my example as part of a method body, not as part of a type definition. Looking at the source code, I see that Making metaprogramming workable is more important than keeping that rule, I suppose. But it did confuse me. Is this also how the D programming language handles blocks in
That's pretty much what I had in mind. Also, LeMP's implementation of It's kind of a best-effort approach, but that has more to do with how LeMP works than with how |
Beta Was this translation helpful? Give feedback.
-
A similar argument can be made for inside methods, though.
Yes. IIRC, one of my early ideas about EC# was to have a different syntax for blocks that don't create scopes:
Okay, that makes sense. |
Beta Was this translation helpful? Give feedback.
-
Hey there. I think I may have an elegant solution to the problem of semantic macros that works well for a full compiler like
ecsc
and provides a best-effort service for macro expansion utilities likeLeMP.exe
. This proposal deals with changes to the LeMP library.My proposal
I'd like to propose a change to how LeMP expands macros. Specifically, I want it to support lazy, context-based macro expansion.
I'll try to explain what I mean by that:
LNode
implementation (say,ExpandedNode
) that behaves exactly like this result when its non-LNode
properties are accessed but automatically and lazily expandsLNode
values. The advantage of this approach is that it's entirely transparent: to an end-user, it feels like they're traversing a tree that's already been macro-expanded, but the expansion is actually happening on the fly. So this shouldn't break existing code.ExpandedNode
would also have a context implementation (the context may be defined as an interface:IMacroContext
), which should at least include a methodobject Evaluate(LNode node)
that evaluates a node. It should also be possible to take anExpandedNode
and create a newExpandedNode
with a different context from it. Since the originalExpandedNode
will likely be discarded after this, its child nodes will never be expanded (thanks to lazy expansion) and the (potentially different) tree defined by the newExpandedNode
can be traversed without incurring a serious performance overhead.So how does this solve the problem of semantic macros?
Whenever a full-stack EC# compiler stumbles across a construct that creates a new scope, it simply changes the context of the node it's evaluating. It implements this context's
object Evaluate(LNode node)
method by analyzingnode
and then evaluating the resulting IR. In other words, built-ins can be evaluated at macro-expansion time because macros are actually evaluated during the semantic analysis phase.Beta Was this translation helpful? Give feedback.
All reactions