diff --git a/CHANGELOG.md b/CHANGELOG.md index 90baf40..0150be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ Version 4.0.0-dev * Added `getComments()`, `getStartLine()`, `getEndLine()`, `getStartTokenPos()`, `getEndTokenPos()`, `getStartFilePos()` and `getEndFilePos()` methods to `Node`. These provide a more obvious access point for the already existing attributes of the same name. +* Added `ConstExprEvaluator` to evaluate constant expressions to PHP values. +* Added `Expr\BinaryOp::getOperatorSigil()`, returning `+` for `Expr\BinaryOp\Plus`, etc. ### Changed diff --git a/lib/PhpParser/ConstExprEvaluationException.php b/lib/PhpParser/ConstExprEvaluationException.php new file mode 100644 index 0000000..9f912ce --- /dev/null +++ b/lib/PhpParser/ConstExprEvaluationException.php @@ -0,0 +1,5 @@ +fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) { + throw new ConstExprEvaluationException( + "Expression of type {$expr->getType()} cannot be evaluated" + ); + }; + } + + /** + * Evaluates a constant expression into a PHP value. + * + * If some part of the expression cannot be evaluated, the fallback evaluator passed to the + * constructor will be invoked. By default, if no fallback is provided, an exception of type + * ConstExprEvaluationException is thrown. + * + * See class doc comment for caveats and limitations. + * + * @param Expr $expr Constant expression to evaluate + * @return mixed Result of evaluation + * @throws ConstExprEvaluationException if the expression cannot be evaluated + */ + public function evaluate(Expr $expr) { + if ($expr instanceof Scalar\LNumber + || $expr instanceof Scalar\DNumber + || $expr instanceof Scalar\String_ + ) { + return $expr->value; + } + + if ($expr instanceof Expr\Array_) { + return $this->evaluateArray($expr); + } + + // Unary operators + if ($expr instanceof Expr\UnaryPlus) { + return +$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\UnaryMinus) { + return -$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BooleanNot) { + return !$this->evaluate($expr->expr); + } + if ($expr instanceof Expr\BitwiseNot) { + return ~$this->evaluate($expr->expr); + } + + if ($expr instanceof Expr\BinaryOp) { + return $this->evaluateBinaryOp($expr); + } + + if ($expr instanceof Expr\Ternary) { + return $this->evaluateTernary($expr); + } + + if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) { + return $this->evaluate($expr->var)[$this->evaluate($expr->dim)]; + } + + if ($expr instanceof Expr\ConstFetch) { + return $this->evaluateConstFetch($expr); + } + + return ($this->fallbackEvaluator)($expr); + } + + private function evaluateArray(Expr\Array_ $expr) { + $array = []; + foreach ($expr->items as $item) { + if (null !== $item->key) { + $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } else { + $array[] = $this->evaluate($item->value); + } + } + return $array; + } + + private function evaluateTernary(Expr\Ternary $expr) { + if (null === $expr->if) { + return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else); + } + + return $this->evaluate($expr->cond) + ? $this->evaluate($expr->if) + : $this->evaluate($expr->else); + } + + private function evaluateBinaryOp(Expr\BinaryOp $expr) { + if ($expr instanceof Expr\BinaryOp\Coalesce + && $expr->left instanceof Expr\ArrayDimFetch + ) { + // This needs to be special cased to respect BP_VAR_IS fetch semantics + return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] + ?? $this->evaluate($expr->right); + } + + // The evaluate() calls are repeated in each branch, because some of the operators are + // short-circuiting and evaluating the RHS in advance may be illegal in that case + $l = $expr->left; + $r = $expr->right; + switch ($expr->getOperatorSigil()) { + case '&': return $this->evaluate($l) & $this->evaluate($r); + case '|': return $this->evaluate($l) | $this->evaluate($r); + case '^': return $this->evaluate($l) ^ $this->evaluate($r); + case '&&': return $this->evaluate($l) && $this->evaluate($r); + case '||': return $this->evaluate($l) || $this->evaluate($r); + case '??': return $this->evaluate($l) ?? $this->evaluate($r); + case '.': return $this->evaluate($l) . $this->evaluate($r); + case '/': return $this->evaluate($l) / $this->evaluate($r); + case '==': return $this->evaluate($l) == $this->evaluate($r); + case '>': return $this->evaluate($l) > $this->evaluate($r); + case '>=': return $this->evaluate($l) >= $this->evaluate($r); + case '===': return $this->evaluate($l) === $this->evaluate($r); + case 'and': return $this->evaluate($l) and $this->evaluate($r); + case 'or': return $this->evaluate($l) or $this->evaluate($r); + case 'xor': return $this->evaluate($l) xor $this->evaluate($r); + case '-': return $this->evaluate($l) - $this->evaluate($r); + case '%': return $this->evaluate($l) % $this->evaluate($r); + case '*': return $this->evaluate($l) * $this->evaluate($r); + case '!=': return $this->evaluate($l) != $this->evaluate($r); + case '!==': return $this->evaluate($l) !== $this->evaluate($r); + case '+': return $this->evaluate($l) + $this->evaluate($r); + case '**': return $this->evaluate($l) ** $this->evaluate($r); + case '<<': return $this->evaluate($l) << $this->evaluate($r); + case '>>': return $this->evaluate($l) >> $this->evaluate($r); + case '<': return $this->evaluate($l) < $this->evaluate($r); + case '<=': return $this->evaluate($l) <= $this->evaluate($r); + case '<=>': return $this->evaluate($l) <=> $this->evaluate($r); + } + + throw new \Exception('Should not happen'); + } + + private function evaluateConstFetch(Expr\ConstFetch $expr) { + $name = $expr->name->toLowerString(); + switch ($name) { + case 'null': return null; + case 'false': return false; + case 'true': return true; + } + + return ($this->fallbackEvaluator)($expr); + } +} \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp.php b/lib/PhpParser/Node/Expr/BinaryOp.php index f871f16..d6c9a20 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp.php +++ b/lib/PhpParser/Node/Expr/BinaryOp.php @@ -27,4 +27,14 @@ abstract class BinaryOp extends Expr public function getSubNodeNames() : array { return ['left', 'right']; } + + /** + * Get the operator sigil for this binary operation. + * + * In the case there are multiple possible sigils for an operator, this method does not + * necessarily return the one used in the parsed code. + * + * @return string + */ + abstract public function getOperatorSigil() : string; } diff --git a/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php b/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php index a3b3986..edcabe1 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class BitwiseAnd extends BinaryOp { + public function getOperatorSigil() : string { + return '&'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php b/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php index 1b5a76a..1686797 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class BitwiseOr extends BinaryOp { + public function getOperatorSigil() : string { + return '|'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php b/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php index f1a0d13..64b9ed7 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class BitwiseXor extends BinaryOp { + public function getOperatorSigil() : string { + return '^'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php b/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php index 5040e9d..5bfcae9 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class BooleanAnd extends BinaryOp { + public function getOperatorSigil() : string { + return '&&'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php b/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php index b93570d..1e90baa 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class BooleanOr extends BinaryOp { + public function getOperatorSigil() : string { + return '||'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php b/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php index e8f84cb..dcbd1b1 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Coalesce extends BinaryOp { + public function getOperatorSigil() : string { + return '??'; + } } diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Concat.php b/lib/PhpParser/Node/Expr/BinaryOp/Concat.php index 09652a4..8f30d56 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Concat.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Concat.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Concat extends BinaryOp { + public function getOperatorSigil() : string { + return '.'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Div.php b/lib/PhpParser/Node/Expr/BinaryOp/Div.php index f0a0850..d07da43 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Div.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Div.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Div extends BinaryOp { + public function getOperatorSigil() : string { + return '/'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Equal.php b/lib/PhpParser/Node/Expr/BinaryOp/Equal.php index 4be5e1f..2dba911 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Equal.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Equal.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Equal extends BinaryOp { + public function getOperatorSigil() : string { + return '=='; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Greater.php b/lib/PhpParser/Node/Expr/BinaryOp/Greater.php index 6c7ec0b..2de4972 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Greater.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Greater.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Greater extends BinaryOp { + public function getOperatorSigil() : string { + return '>'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php b/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php index 86cfd6d..fbc6082 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class GreaterOrEqual extends BinaryOp { + public function getOperatorSigil() : string { + return '>='; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Identical.php b/lib/PhpParser/Node/Expr/BinaryOp/Identical.php index e57ee9f..68269ec 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Identical.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Identical.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Identical extends BinaryOp { + public function getOperatorSigil() : string { + return '==='; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php b/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php index 9286829..c6e75c3 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class LogicalAnd extends BinaryOp { + public function getOperatorSigil() : string { + return 'and'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php b/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php index 8aa4fda..c9004c2 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class LogicalOr extends BinaryOp { + public function getOperatorSigil() : string { + return 'or'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php b/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php index 296cd4f..7293760 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class LogicalXor extends BinaryOp { + public function getOperatorSigil() : string { + return 'xor'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Minus.php b/lib/PhpParser/Node/Expr/BinaryOp/Minus.php index c9996bd..7f32efc 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Minus.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Minus.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Minus extends BinaryOp { + public function getOperatorSigil() : string { + return '-'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Mod.php b/lib/PhpParser/Node/Expr/BinaryOp/Mod.php index 20fd16d..999697b 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Mod.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Mod.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Mod extends BinaryOp { + public function getOperatorSigil() : string { + return '%'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Mul.php b/lib/PhpParser/Node/Expr/BinaryOp/Mul.php index 5cdd457..4c971dc 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Mul.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Mul.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Mul extends BinaryOp { + public function getOperatorSigil() : string { + return '*'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php b/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php index a55e90e..c4eca29 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class NotEqual extends BinaryOp { + public function getOperatorSigil() : string { + return '!='; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php b/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php index 77a195e..12095ee 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class NotIdentical extends BinaryOp { + public function getOperatorSigil() : string { + return '!=='; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Plus.php b/lib/PhpParser/Node/Expr/BinaryOp/Plus.php index c9a7dd9..217de35 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Plus.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Plus.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Plus extends BinaryOp { + public function getOperatorSigil() : string { + return '+'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Pow.php b/lib/PhpParser/Node/Expr/BinaryOp/Pow.php index 0886f90..cdade5a 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Pow.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Pow.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Pow extends BinaryOp { + public function getOperatorSigil() : string { + return '**'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php b/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php index df1d836..24eba97 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class ShiftLeft extends BinaryOp { + public function getOperatorSigil() : string { + return '<<'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php b/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php index c61b620..163cb5c 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class ShiftRight extends BinaryOp { + public function getOperatorSigil() : string { + return '>>'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php b/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php index cd30607..eeeb238 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Smaller extends BinaryOp { + public function getOperatorSigil() : string { + return '<'; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php b/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php index ee50c90..885cac1 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class SmallerOrEqual extends BinaryOp { + public function getOperatorSigil() : string { + return '<='; + } } \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php b/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php index 2b004c3..6c34b51 100644 --- a/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php +++ b/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php @@ -6,4 +6,7 @@ use PhpParser\Node\Expr\BinaryOp; class Spaceship extends BinaryOp { + public function getOperatorSigil() : string { + return '<=>'; + } } diff --git a/test/PhpParser/ConstExprEvaluatorTest.php b/test/PhpParser/ConstExprEvaluatorTest.php new file mode 100644 index 0000000..6e28cc9 --- /dev/null +++ b/test/PhpParser/ConstExprEvaluatorTest.php @@ -0,0 +1,97 @@ +parse('expr; + $evaluator = new ConstExprEvaluator(); + $this->assertSame($expected, $evaluator->evaluate($expr)); + } + + public function provideTestEvaluate() { + return [ + ['1', 1], + ['1.0', 1.0], + ['"foo"', "foo"], + ['[0, 1]', [0, 1]], + ['["foo" => "bar"]', ["foo" => "bar"]], + ['NULL', null], + ['False', false], + ['true', true], + ['+1', 1], + ['-1', -1], + ['~0', -1], + ['!true', false], + ['[0][0]', 0], + ['"a"[0]', "a"], + ['true ? 1 : (1/0)', 1], + ['false ? (1/0) : 1', 1], + ['42 ?: (1/0)', 42], + ['false ?: 42', 42], + ['false ?? 42', false], + ['null ?? 42', 42], + ['[0][0] ?? 42', 0], + ['[][0] ?? 42', 42], + ['0b11 & 0b10', 0b10], + ['0b11 | 0b10', 0b11], + ['0b11 ^ 0b10', 0b01], + ['1 << 2', 4], + ['4 >> 2', 1], + ['"a" . "b"', "ab"], + ['4 + 2', 6], + ['4 - 2', 2], + ['4 * 2', 8], + ['4 / 2', 2], + ['4 % 2', 0], + ['4 ** 2', 16], + ['1 == 1.0', true], + ['1 != 1.0', false], + ['1 < 2.0', true], + ['1 <= 2.0', true], + ['1 > 2.0', false], + ['1 >= 2.0', false], + ['1 <=> 2.0', -1], + ['1 === 1.0', false], + ['1 !== 1.0', true], + ['true && true', true], + ['true and true', true], + ['false && (1/0)', false], + ['false and (1/0)', false], + ['false || false', false], + ['false or false', false], + ['true || (1/0)', true], + ['true or (1/0)', true], + ['true xor false', true], + ]; + } + + /** + * @expectedException \PhpParser\ConstExprEvaluationException + * @expectedExceptionMessage Expression of type Expr_Variable cannot be evaluated + */ + public function testEvaluateFails() { + $evaluator = new ConstExprEvaluator(); + $evaluator->evaluate(new Expr\Variable('a')); + } + + public function testEvaluateFallback() { + $evaluator = new ConstExprEvaluator(function(Expr $expr) { + if ($expr instanceof Scalar\MagicConst\Line) { + return 42; + } + throw new ConstExprEvaluationException(); + }); + $expr = new Expr\BinaryOp\Plus( + new Scalar\LNumber(8), + new Scalar\MagicConst\Line() + ); + $this->assertSame(50, $evaluator->evaluate($expr)); + } +} \ No newline at end of file