Skip to content
Muqsit Rayyan edited this page Nov 15, 2022 · 13 revisions

A macro is a function that maps a set of inputs to an executable output. Macros work in ways similar to a function (in writing, a macro call is indistinguishable from a function call). In arithmexp, a macro consists of the following attributes:

  1. Identifier: A unique name given to the macro
  2. Base: A Closure that defines the macro call's signature and acts as a fallback function call
  3. 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
  4. Flags: A FunctionFlags bitmask (learn more about function flags on the function page)

Similar to a function call, the identifier property is used when executing a macro call. A 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 macro may be registered for a given parser before expressions are parsed by invoking FunctionRegistry::registerMacro().

$registry = $parser->getFunctionRegistry();
$registry->registerMacro(
	"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)

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.

1. Quick Start

1.1. Installation

2. Documentation Notes

2.1. Constant
2.2. Function
2.3. Macro
2.4. Operator
2.5. Variable
2.6. Expression Optimization
2.7. Implementation Internals

Clone this wiki locally