-
Notifications
You must be signed in to change notification settings - Fork 1
Function
A function is identified by its unique name. Every function has any non-negative number of inputs and a numeric or boolean output. The inputs and outputs are either a float
type, an int
type or a bool
type (i.e., int|float|bool
).
A function may be registered for a given parser before expressions are parsed by invoking FunctionRegistry::registerFunction()
.
$registry = $parser->function_registry;
$registry->registerFunction("intp1", static fn(int $x) : int => $x + 1);
$registry->registerFunction("num2", static fn(float $x) : float => $x * 2);
$registry->registerFunction("dist2d", static fn(float $x1, float $y1, float $x2, float $y2) : float => sqrt($x1 * $y1 + $x2 * $y2));
$registry->registerFunction("is_valid_level", static fn(int $level) : bool => $level >= 1 && $level <= 5);
$parser->parse("intp1(2)")->evaluate(); // 3
$parser->parse("num2(num2(2))")->evaluate(); // 8
$parser->parse("dist2d(2, 3, 4, 5)")->evaluate(); // 5.0990195135927845
$parser->parse("is_valid_level(4)")->evaluate(); // true
Functions accept any number of numeric or boolean inputs (including a varying number of inputs (variadic)), and return a numeric or a boolean output. Below are some examples of valid function signatures:
fn() : int
fn() : float
fn() : int|float
fn() : int|float|bool
fn(int $x, int|float $y) : int
fn(int ...$values) : float
fn(int $x, int $y, float ...$values) : float
fn(int|bool $value) : float
A function may be registered as commutative, meaning it yields the same output regardless of the order of input parameters. For example, the built-in function min
is registered as commutative because min(1, 2, 3) === min(3, 1, 2) === min(1, 3, 2)...
. The commutative property is an optimization hint which allows the optimizer to precompute a value such as min(x, y) - min(y, x)
into 0
during the parsing stage. To define a commutative function, the flag FunctionFlags::COMMUTATIVE
must be set at the time of registration.
$registry->registerFunction("sum", static fn(int|float $x, int|float $y) : int|float => $x + $y, FunctionFlags::COMMUTATIVE);
A function may be registered as deterministic so that it is expected to return the same result for the same set of inputs (the meaning of deterministic here is the same as it's meaning in the context of SQL). To define a deterministic function, the flag FunctionFlags::DETERMINISTIC
must be set at the time of registration.
$registry->registerFunction("sum", static fn(int|float $x, int|float$y) : int|float=> $x + $y, FunctionFlags::COMMUTATIVE | FunctionFlags::DETERMINISTIC);
Function calls to deterministic functions having deterministic inputs are computed during the parsing stage rather than the evaluation stage. Some of the example instances of this optimization are listed below:
- The expression
max(sum(2, 3), sum(4, 5))
is pre-computed as9
. The functionsmax()
andsum()
are invoked only during the parsing stage and no function call occurs during evaluation. - The expression
max(sum(x, 3), sum(4, 5))
is pre-computed asmax(sum(x, 3), 9)
. The function callsum(4, 5)
is resolved during the parsing stage, while the other function calls are resolved during evaluation. - No optimization transformation occurs on the expression
max(sum(x, y), sum(z, w))
.
A function may be registered as idempotent so that applying the same function multiple times on the same parameter has no change on the initially computed result (i.e., f(f(x)) === f(x)
). To define an idempotent function, the flag FunctionFlags::IDEMPOTENT
must be set at the time of registration.
$registry->registerFunction("modulus", static fn(int|float $x) : int|float => abs($x), FunctionFlags::IDEMPOTENT);
Multiple function calls to idempotent functions are reduced based on their commutative property. If an idempotent function is non-commutative, only instances of f(f(x))
are transformed to f(x)
(this also includes f(f(f(x))), f(f(f(f(x)))), ...
). Below are some examples on the behaviour of optimizing a non-commutative idempotent function — round()
:
-
round(round(x))
,round(round(round(x)))
,...
are transformed toround(x)
-
round(round(round(x, 2)))
is transformed toround(round(x, 2))
- No transformation occurs on the expression
round(round(x, 2))
- No transformation occurs on the expression
round(round(x), 2)
If an idempotent function is commutative, calls to the same function have their parameter list expanded during transformation. Below are some examples on the behaviour of optimizing a commutative idempotent function — min()
(FunctionFlags::COMMUTATIVE | FunctionFlags::IDEMPOTENT
):
-
min(min(x))
,min(min(min(x)))
,...
are transformed tomin(x)
-
min(min(x, y))
is transformed tomin(x, y)
-
min(min(x, y), z, w)
is transformed tomin(x, y, z, w)
-
min(min(x, y), min(z, w))
is transformed tomin(x, y, z, w)
A function call is made by mentioning the function name followed by an open and a close curve bracket containing the function arguments. Function arguments must be separated by a comma, such as fn()
, fn(x)
, fn(x, y)
, etc. If no argument is supplied following a comma, the function's default value for the parameter is used.
For example, for the given function fn(int $x = 1, int $y = 2) : int => $x + $y
:
-
fn()
resolves to3
. -
fn(2)
resolves to4
. -
fn(2, 3)
resolves to5
. -
fn(2, )
resolves to4
. -
fn(,)
resolves to3
. -
fn(, 1)
resolves to2
.
For the given function fn(int $x = 1, int $y) : int => $x + $y
:
-
fn()
throws aParseException
as the parameter$y
does not have a default value. -
fn(1)
throws aParseException
as the parameter$y
does not have a default value. -
fn(1,)
throws aParseException
as the parameter$y
does not have a default value. -
fn(, 1)
resolves to2
.
Below is a list of functions provided by arithmexp
out-of-the-box. These functions are directly ported from PHP's list of math functions and behave the same way.
-
abs(number num) : number
— Absolute value -
acos(number num) : float
— Arc cosine -
acosh(number num) : float
— Inverse hyperbolic cosine -
asin(number num) : float
— Arc sine -
asinh(number num) : float
— Inverse hyperbolic sine -
atan2(number y, number x) : float
— Arc tangent of two variables -
atan(number num) : float
— Arc tangent -
atanh(number num) : float
— Inverse hyperbolic tangent -
boolval(mixed value) : bool
— Get the boolean value of a variable -
ceil(number num) : float
— Round fractions up -
cos(number num) : float
— Cosine -
cosh(number num) : float
— Hyperbolic cosine -
deg2rad(number num) : float
— Converts the number in degrees to the radian equivalent -
exp(number num) : float
— Calculates the exponent of e -
expm1(number num) : float
— Returns exp(number) - 1, computed in a way that is accurate even when the value of number is close to zero -
fdiv(number num1, number num2) : float
— Divides two numbers, according to IEEE 754 -
floatval(mixed value) : float
— Get float value of a variable -
floor(number num) : float
— Round fractions down -
fmod(number num1, number num2) : float
— Returns the floating point remainder (modulo) of the division of the arguments -
hypot(number x, number y) : float
— Calculate the length of the hypotenuse of a right-angle triangle -
intdiv(int num1, int num2) : int
— Integer division -
intval(mixed value, int base = 10) : int
— Get the integer value of a variable -
is_bool(mixed value) : bool
— Finds out whether a variable is a boolean -
is_float(mixed value) : bool
— Finds whether the type of a variable is float -
is_finite(number value) : bool
— Checks whether a float is finite -
is_infinite(number value) : bool
— Checks whether a float is infinite -
is_nan(number value) : bool
— Checks whether a float is NAN -
lcg_value() : float
— Combined linear congruential generator -
log10(number num) : float
— Base-10 logarithm -
log1p(number num) : float
— Returns log(1 + number), computed in a way that is accurate even when the value of number is close to zero -
log(number num, number base = M_E) : float
— Natural logarithm -
max(number ...values) : number
— Find highest value -
min(number ...values) : number
— Find lowest value -
mt_getrandmax() : int
— Show largest possible random value -
mt_rand() : int
|mt_rand(int min, int max) : int
— Generate a random value via the Mersenne Twister Random Number Generator -
pi() : float
— Get value of pi -
pow(number num, number exponent) : number
— Exponential expression -
rad2deg(number num) : float
— Converts the radian number to the equivalent number in degrees -
round(number num, int precision = 0, int mode = HALF_UP) : float
— Rounds a float (availablemode
constants:HALF_UP
,HALF_DOWN
,HALF_ODD
,HALF_EVEN
) -
sin(number num) : float
— Sine -
sinh(number num) : float
— Hyperbolic sine -
sqrt(number num) : float
— Square root -
tan(number num) : float
— Tangent -
tanh(number num) : float
— Hyperbolic tangent
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