it would require introducing into the target environment a run-time dependency on LavaScript's eval
Ohhhh, nice limitation.
Technically, if your compile phase ran the code in a LavaScript-capable environment and you kept a running total of all the compiled code in order to output it at the end, then macros should be fine. They're almost never called by the compiled code (just by the thing that was compiling that code earlier), so they're just cruft. I'd be surprised if there weren't a minifier that could cut them out of the result automatically.
On the other hand, if you did that, it would mean running the code in order to set up any definitions the macros use, and thus you would have to have all your run time libraries loaded at compile time--perhaps including the DOM and such--so maybe that's not what you're going for.
Your templating macro system is something that doesn't take advantage of any run time definitions, and is therefore usable from a completely different compile-time environment. Macros that can't execute arbitrary code are pretty dull, though. Maybe what you need is a phase control macro:
## (This is just a sketch. Please don't bother crediting me.)
realMacros = {}
realMacros.atCompileTime = (body) ->
each body, (expr) ->
eval lc(expr)
'null' ## or whatever makes sense as an ignored result
(->
orig = lc
lc = (s) ->
if isList(s) and (s[0] of realMacros)
realMacros[s[0]](s[1..])
else orig(s)
)()
From here, people oughta be able to use (atCompileTime ...) forms to work in compiler-space, where they can modify the realMacros table directly, perhaps to implement a more convenient macro definition macro.
I like your atCompileTime approach. It would seem to open up the kinds of things the compiler can do while still not requiring anything special about the run-time environment. I'm leaning toward something like this.
> Technically, if your compile phase ran the code in a LavaScript-capable environment and you kept a running total of all the compiled code in order to output it at the end, then macros should be fine.
To be sure I understand, would this be kind of like if LavaScript both compiled every expression to JavaScript (as it does now) and ran it through the atCompileTime evaluator, so as to make it available for use in the compiler space?
To be sure I understand, would this be kind of like if LavaScript both compiled every expression to JavaScript (as it does now) and ran it through the atCompileTime evaluator, so as to make it available for use in the compiler space?
That and atCompileTime were almost independent trains of thought, but yeah, I think you understand it.
The compiler would compile a command, write the compiled command to the output file, evaluate the command itself, then repeat with the next command.
for command in input
let compiled = compile( command ) in
output-file.write compiled
execute compiled
Like I said, I don't know if this applies to your case very well, since the compiler's environment may not adequately simulate the actual application conditions.