Skip to content
Muqsit Rayyan edited this page Jun 2, 2023 · 13 revisions

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 Macro

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:

  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 tokens as its input, 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 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->macro_registry;
$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 Macro

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:

  1. Identifier: A unique name given to the macro
  2. Resolver: A Closure that 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->macro_registry;
$registry->registerObject("default", fn(Parser $parser, string $expression, IdentifierToken $token) : array => [
	// resolves to "2 + 3"
	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.

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