Calculator
An expression evaluator with operator precedence, brackets and unary minus. The result is a number, not a tree - the reducers do the arithmetic as the rules are reduced.
2 + 2 * 2 // 6
(2 + 2) * 2 // 8
-(2 + 3) // -5
Precedence is not configured anywhere: it comes out of the shape of the
grammar. The loosest operator sits at the top and each rule is written in
terms of the one below it, so 2 + 2 * 2 can only group one way.
Grammar
%skip T_WHITESPACE \s++
%token T_NUMBER \d++(?:\.\d++)?
%token T_PLUS \+
%token T_MINUS \-
%token T_MUL \*
%token T_DIV /
%token T_PARENTHESIS_OPEN \(
%token T_PARENTHESIS_CLOSE \)
%pragma root Expression
// a + b - c
Expression -> {
if (!\is_array($children)) {
return $children;
}
$result = \array_shift($children);
while ($children !== []) {
$operator = \array_shift($children);
$right = \array_shift($children);
$result = $operator->value === '+' ? $result + $right : $result - $right;
}
return $result;
}
: Term() ((<T_PLUS> | <T_MINUS>) Term())*
;
// a * b / c
Term -> {
if (!\is_array($children)) {
return $children;
}
$result = \array_shift($children);
while ($children !== []) {
$operator = \array_shift($children);
$right = \array_shift($children);
$result = $operator->value === '*' ? $result * $right : $result / $right;
}
return $result;
}
: Unary() ((<T_MUL> | <T_DIV>) Unary())*
;
// -a
Unary -> {
if (!\is_array($children)) {
return $children;
}
return -$children[1];
}
: <T_MINUS> Unary()
| Primary()
;
// The brackets are read but dropped, so a group is a list of one value
Primary -> { return \is_array($children) ? $children[0] : $children; }
: ::T_PARENTHESIS_OPEN:: Expression() ::T_PARENTHESIS_CLOSE::
| Number()
;
Number -> { return (float) $children->value; }
: <T_NUMBER>
;
Usage
use Phplrt\Compiler\Compiler;
use Phplrt\Source\File;
use Phplrt\Source\Source;
$parser = new Compiler()
->load(new File(__DIR__ . '/grammar.pp3'))
->getParser();
echo $parser->parse(new Source('(2 + 2) * 2')); // 8
Every reducer starts with an is_array() check because a rule that can match
one thing or a sequence hands you one thing or an array: 2 reaches
Expression as a single value, 2 + 2 as [2.0, Token('+'), 2.0].