This library evaluates arbitrarily nested arrow functions with macro
as only argument, rewriting the AST with the result of the call.
It also exposes an API to manipulate the AST during macro evaluation, and to handle pieces of syntax in the macro itself as values.
macro => <js expression>
Macro expressions are evaluated in a different environment from runtime code. No variable defined outside of the macro can be accessed;
state must be managed through the macro
object API.
As macro expressions are plain JavaScript functions, any computation can be performed in them. You can return any value except functions (which require macro.literal
),
and it will be injected in the AST in place of the macro arrow function.
const fibonacci = macro => {
const v = [];
v[0] = 0;
v[1] = 1;
for (let i = 2; i < 10; i++) {
v[i] = v[i - 1] + v[i - 2];
}
return v
}
console.log(fibonacci)
generates
const fibonacci = [
0,
1,
1,
2,
3,
5,
8,
13,
21,
34
];
console.log(fibonacci);
module.exports = () => {
macro => macro.define('operator', '+')
const operator = macro => {
const func = macro.operator === '+' ? macro.literal((a, b) => {
return a + b
}) : macro.literal((a, b) => {
return a * b
})
return macro.inject(func)
}
return operator(1, 2)
}
generates
module.exports = () => {
const operator = (a, b) => {
return a + b
}
return operator(1, 2)
}
The AST is exposed via macro.ast
and macro.node
, so it can be manipulated manually. It also exposes the recast API
via macro.types
. See examples/convert-var.js for an example of AST manipulations, and recast#usage for the recast types API.
The entire API belongs to the macro
arrow function argument. The returned value is merged into the AST (except functions, which must be returned using macro.literal
and macro.inject
);
Objects that represent AST nodes replace the current node instead of being inserted as object literals.
-
macro.literal(value: any) => ASTNode
: the main method of injecting syntactic fragments into the AST. The given argument is converted to its AST representation before macro evaluation. -
macro.inject(value: any) => ASTNode
: converts values to AST nodes at macro expansion time, aftermacro.literal
has been expanded. -
macro.injectCall(value: Function)
: injects a call with no arguments to the passed function. Used to inject a block of code. -
macro.identity(value: T): T
: returns the value. Useless, but can prevent warnings about unusedmacro
with eslint. -
macro.define(name: string, value: any)
: define a variable in the macro-expansion environment, which can be accessed by name as property ofmacro
. -
macro.ast
: the entire AST. -
macro.node
: current node (the macro arrow function). -
macro.require
: exposesrequire
, which is not available inside macros by default. -
macro.expand(node: ASTNode)
: calls the macro expander, for whatever reason. -
macro.types
: recast.types API -
macro.parse(src: string)
: parse string containing JavaScript source code
macro => 5
or
macro => macro.inject(5)
or
macro => macro.inject(macro.literal(5))
Functions cannot be returned by value, as they cannot be converted to literals by value. They must be explicitly converted to literals and injected.
macro => macro.inject(macro.literal(() => 5))
Not meant for actual usage (production or not).
./bin/js-macros <input-file>
returns the output via stdout.
-
exports.parse(src: string): ASTNode
: parse source code -
exports.parseFile(filename: string): ASTNode
: parse file; appends .js if .js or .jsx are missing infilename
-
exports.transform(ASTNode): ASTNode
: expand macros -
exports.emit(ast: ASTNode): string
: generate code from AST -
exports.compile(src: string): string
: load, expand, emit source -
exports.compileFile(filename: string): Promise<string>
: load, expand, emit source
- Improve API
- Replace
deepFind
withesquery
oresprima-selector
- Refactor the whole thing
- Find a way to implicitly decompile runtime functions as values
This software is released under the MIT license. See LICENSE for more informations.