-
Notifications
You must be signed in to change notification settings - Fork 1
Macro
A macro maps a set of inputs to executable output tokens. In arithmexp
, macros come in two kinds — function-like macros and object-like macros.
Function-like macros look and work in ways similar to a function (in writing, a function-like macro call is indistinguishable from a function call). In arithmexp
, a function-like macro consists of the following attributes:
- Identifier: A unique name given to the macro
-
Base: A
Closure
that defines the macro call's signature and acts as a fallback function call -
Resolver: A
Closure
that takes in an array of tokens as its input arguments, and returns an array of tokens, or null to fallback to a function call -
Flags: A
FunctionFlags
bitmask (learn more about function flags on the function page)
Similar to a function call, the identifier property is used when specifying the macro call. The macro's resolver is executed during the parsing stage, and returns an array of replacement tokens to substitute the macro call with, or alternatively null
to substitute the macro call with a function call to its base function.
A function-like macro may be registered for a given parser before expressions are parsed by invoking MacroRegistry::registerFunction()
.
$registry = $parser->getMacroRegistry();
$registry->registerFunction(
"sum",
fn(int|float ...$nums) : int|float => array_sum($nums),
function(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : ?array{
if(count($args) === 0){
throw ParseException::unresolvableFcallTooLessParams($expression, $token->getPos(), 1, 0);
}
if(count($args) === 1){ // resolve "sum(x)" to "x"
return [$args[0]];
}
if(count($args) === 2){ // resolve "sum(x, y)" to "x + y"
return [
$args[0],
$args[1],
new BinaryOperatorToken($token->getPos(), "+")
];
}
return null; // replace macro with an fcall to the base function (`array_sum($nums)`)
}
);
$vars = ["x" => 1, "y" => 2];
$parser->parse("sum(x, y)")->evaluate($vars); // int(3)
$parser->parse("sum(sum(x, y))")->evaluate($vars); // int(3)
$parser->parse("sum(sum(x, y), sum(x), sum(y))")->evaluate($vars); // int(6)
Object-like macros are simply an identifier that resolves into executable tokens (in writing, an object-like macro call is indistinguishable from a constant or a variable). An object-like macro consists of the following attributes:
- Identifier: A unique name given to the macro
-
Resolver: A
Closure
that takes in an array of tokens as its input arguments, and returns an array of tokens.
An object-like macro may be registered for a given parser before expressions are parsed by invoking MacroRegistry::registerObject()
.
$registry = $parser->getMacroRegistry();
$registry->registerObject("default", fn(Parser $parser, string $expression, IdentifierToken $token) : array => [
new NumericLiteralToken($token->getPos(), 2),
new NumericLiteralToken($token->getPos(), 3),
new BinaryOperatorToken($token->getPos(), "+"),
]);
$parser->parse("default + 1")->evaluate(); // int(6)
The capability of macros to manipulate tokens within a macro-call make macros powerful. As such, macros can reduce the evaluation overhead by preprocessing tokens and shaping how they must be evaluated. Furthermore, optimizers can more effectively apply their methods when a macro such as sqrt(x) => x ** 0.5
is executed (with optimizations, sqrt(x) / (x ** 0.5)
gets resolved to a ConstantExpression(value: 1.0)
). However, defining a macro is more complex than a simple function. A macro resolver must adhere to the following:
- The return value of the resolver must either be
null
or a non-empty array. - The return value of the resolver must be a postfix-formatted array of tokens (all available tokens are listed under the muqsit/arithmexp/token namespace)
// return: 2 * 3 return [ new NumericLiteralToken($token->getPos(), 2), new NumericLiteralToken($token->getPos(), 3), new BinaryOperatorToken($token->getPos(), "*") ];
- Parenthesis token must not be used. As the resulting array uses a postfix notation, parentheses are not needed.
Try out arithmexp
on the demo site!
1.1. Installation
2.1. Constant
2.2. Function
2.3. Macro
2.4. Operator
2.5. Variable
2.6. Expression Optimization
2.7. Implementation Internals