annotate vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 5fb285c0d0e3
children
rev   line source
Chris@13 1 <?php
Chris@13 2
Chris@13 3 namespace PhpParser;
Chris@13 4
Chris@13 5 use PhpParser\Node\Expr;
Chris@13 6 use PhpParser\Node\Scalar;
Chris@13 7
Chris@13 8 /**
Chris@13 9 * Evaluates constant expressions.
Chris@13 10 *
Chris@13 11 * This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be
Chris@13 12 * evaluated without further context. If a subexpression is not of this type, a user-provided
Chris@13 13 * fallback evaluator is invoked. To support all constant expressions that are also supported by
Chris@13 14 * PHP (and not already handled by this class), the fallback evaluator must be able to handle the
Chris@13 15 * following node types:
Chris@13 16 *
Chris@13 17 * * All Scalar\MagicConst\* nodes.
Chris@13 18 * * Expr\ConstFetch nodes. Only null/false/true are already handled by this class.
Chris@13 19 * * Expr\ClassConstFetch nodes.
Chris@13 20 *
Chris@13 21 * The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate.
Chris@13 22 *
Chris@13 23 * The evaluation is dependent on runtime configuration in two respects: Firstly, floating
Chris@13 24 * point to string conversions are affected by the precision ini setting. Secondly, they are also
Chris@13 25 * affected by the LC_NUMERIC locale.
Chris@13 26 */
Chris@13 27 class ConstExprEvaluator
Chris@13 28 {
Chris@13 29 private $fallbackEvaluator;
Chris@13 30
Chris@13 31 /**
Chris@13 32 * Create a constant expression evaluator.
Chris@13 33 *
Chris@13 34 * The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See
Chris@13 35 * class doc comment for more information.
Chris@13 36 *
Chris@13 37 * @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
Chris@13 38 */
Chris@13 39 public function __construct(callable $fallbackEvaluator = null) {
Chris@13 40 $this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
Chris@13 41 throw new ConstExprEvaluationException(
Chris@13 42 "Expression of type {$expr->getType()} cannot be evaluated"
Chris@13 43 );
Chris@13 44 };
Chris@13 45 }
Chris@13 46
Chris@13 47 /**
Chris@13 48 * Silently evaluates a constant expression into a PHP value.
Chris@13 49 *
Chris@13 50 * Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException.
Chris@13 51 * The original source of the exception is available through getPrevious().
Chris@13 52 *
Chris@13 53 * If some part of the expression cannot be evaluated, the fallback evaluator passed to the
Chris@13 54 * constructor will be invoked. By default, if no fallback is provided, an exception of type
Chris@13 55 * ConstExprEvaluationException is thrown.
Chris@13 56 *
Chris@13 57 * See class doc comment for caveats and limitations.
Chris@13 58 *
Chris@13 59 * @param Expr $expr Constant expression to evaluate
Chris@13 60 * @return mixed Result of evaluation
Chris@13 61 *
Chris@13 62 * @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
Chris@13 63 */
Chris@13 64 public function evaluateSilently(Expr $expr) {
Chris@13 65 set_error_handler(function($num, $str, $file, $line) {
Chris@13 66 throw new \ErrorException($str, 0, $num, $file, $line);
Chris@13 67 });
Chris@13 68
Chris@13 69 try {
Chris@13 70 return $this->evaluate($expr);
Chris@13 71 } catch (\Throwable $e) {
Chris@13 72 if (!$e instanceof ConstExprEvaluationException) {
Chris@13 73 $e = new ConstExprEvaluationException(
Chris@13 74 "An error occurred during constant expression evaluation", 0, $e);
Chris@13 75 }
Chris@13 76 throw $e;
Chris@13 77 } finally {
Chris@13 78 restore_error_handler();
Chris@13 79 }
Chris@13 80 }
Chris@13 81
Chris@13 82 /**
Chris@13 83 * Directly evaluates a constant expression into a PHP value.
Chris@13 84 *
Chris@13 85 * May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these
Chris@13 86 * into a ConstExprEvaluationException.
Chris@13 87 *
Chris@13 88 * If some part of the expression cannot be evaluated, the fallback evaluator passed to the
Chris@13 89 * constructor will be invoked. By default, if no fallback is provided, an exception of type
Chris@13 90 * ConstExprEvaluationException is thrown.
Chris@13 91 *
Chris@13 92 * See class doc comment for caveats and limitations.
Chris@13 93 *
Chris@13 94 * @param Expr $expr Constant expression to evaluate
Chris@13 95 * @return mixed Result of evaluation
Chris@13 96 *
Chris@13 97 * @throws ConstExprEvaluationException if the expression cannot be evaluated
Chris@13 98 */
Chris@13 99 public function evaluateDirectly(Expr $expr) {
Chris@13 100 return $this->evaluate($expr);
Chris@13 101 }
Chris@13 102
Chris@13 103 private function evaluate(Expr $expr) {
Chris@13 104 if ($expr instanceof Scalar\LNumber
Chris@13 105 || $expr instanceof Scalar\DNumber
Chris@13 106 || $expr instanceof Scalar\String_
Chris@13 107 ) {
Chris@13 108 return $expr->value;
Chris@13 109 }
Chris@13 110
Chris@13 111 if ($expr instanceof Expr\Array_) {
Chris@13 112 return $this->evaluateArray($expr);
Chris@13 113 }
Chris@13 114
Chris@13 115 // Unary operators
Chris@13 116 if ($expr instanceof Expr\UnaryPlus) {
Chris@13 117 return +$this->evaluate($expr->expr);
Chris@13 118 }
Chris@13 119 if ($expr instanceof Expr\UnaryMinus) {
Chris@13 120 return -$this->evaluate($expr->expr);
Chris@13 121 }
Chris@13 122 if ($expr instanceof Expr\BooleanNot) {
Chris@13 123 return !$this->evaluate($expr->expr);
Chris@13 124 }
Chris@13 125 if ($expr instanceof Expr\BitwiseNot) {
Chris@13 126 return ~$this->evaluate($expr->expr);
Chris@13 127 }
Chris@13 128
Chris@13 129 if ($expr instanceof Expr\BinaryOp) {
Chris@13 130 return $this->evaluateBinaryOp($expr);
Chris@13 131 }
Chris@13 132
Chris@13 133 if ($expr instanceof Expr\Ternary) {
Chris@13 134 return $this->evaluateTernary($expr);
Chris@13 135 }
Chris@13 136
Chris@13 137 if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) {
Chris@13 138 return $this->evaluate($expr->var)[$this->evaluate($expr->dim)];
Chris@13 139 }
Chris@13 140
Chris@13 141 if ($expr instanceof Expr\ConstFetch) {
Chris@13 142 return $this->evaluateConstFetch($expr);
Chris@13 143 }
Chris@13 144
Chris@13 145 return ($this->fallbackEvaluator)($expr);
Chris@13 146 }
Chris@13 147
Chris@13 148 private function evaluateArray(Expr\Array_ $expr) {
Chris@13 149 $array = [];
Chris@13 150 foreach ($expr->items as $item) {
Chris@13 151 if (null !== $item->key) {
Chris@13 152 $array[$this->evaluate($item->key)] = $this->evaluate($item->value);
Chris@13 153 } else {
Chris@13 154 $array[] = $this->evaluate($item->value);
Chris@13 155 }
Chris@13 156 }
Chris@13 157 return $array;
Chris@13 158 }
Chris@13 159
Chris@13 160 private function evaluateTernary(Expr\Ternary $expr) {
Chris@13 161 if (null === $expr->if) {
Chris@13 162 return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else);
Chris@13 163 }
Chris@13 164
Chris@13 165 return $this->evaluate($expr->cond)
Chris@13 166 ? $this->evaluate($expr->if)
Chris@13 167 : $this->evaluate($expr->else);
Chris@13 168 }
Chris@13 169
Chris@13 170 private function evaluateBinaryOp(Expr\BinaryOp $expr) {
Chris@13 171 if ($expr instanceof Expr\BinaryOp\Coalesce
Chris@13 172 && $expr->left instanceof Expr\ArrayDimFetch
Chris@13 173 ) {
Chris@13 174 // This needs to be special cased to respect BP_VAR_IS fetch semantics
Chris@13 175 return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
Chris@13 176 ?? $this->evaluate($expr->right);
Chris@13 177 }
Chris@13 178
Chris@13 179 // The evaluate() calls are repeated in each branch, because some of the operators are
Chris@13 180 // short-circuiting and evaluating the RHS in advance may be illegal in that case
Chris@13 181 $l = $expr->left;
Chris@13 182 $r = $expr->right;
Chris@13 183 switch ($expr->getOperatorSigil()) {
Chris@13 184 case '&': return $this->evaluate($l) & $this->evaluate($r);
Chris@13 185 case '|': return $this->evaluate($l) | $this->evaluate($r);
Chris@13 186 case '^': return $this->evaluate($l) ^ $this->evaluate($r);
Chris@13 187 case '&&': return $this->evaluate($l) && $this->evaluate($r);
Chris@13 188 case '||': return $this->evaluate($l) || $this->evaluate($r);
Chris@13 189 case '??': return $this->evaluate($l) ?? $this->evaluate($r);
Chris@13 190 case '.': return $this->evaluate($l) . $this->evaluate($r);
Chris@13 191 case '/': return $this->evaluate($l) / $this->evaluate($r);
Chris@13 192 case '==': return $this->evaluate($l) == $this->evaluate($r);
Chris@13 193 case '>': return $this->evaluate($l) > $this->evaluate($r);
Chris@13 194 case '>=': return $this->evaluate($l) >= $this->evaluate($r);
Chris@13 195 case '===': return $this->evaluate($l) === $this->evaluate($r);
Chris@13 196 case 'and': return $this->evaluate($l) and $this->evaluate($r);
Chris@13 197 case 'or': return $this->evaluate($l) or $this->evaluate($r);
Chris@13 198 case 'xor': return $this->evaluate($l) xor $this->evaluate($r);
Chris@13 199 case '-': return $this->evaluate($l) - $this->evaluate($r);
Chris@13 200 case '%': return $this->evaluate($l) % $this->evaluate($r);
Chris@13 201 case '*': return $this->evaluate($l) * $this->evaluate($r);
Chris@13 202 case '!=': return $this->evaluate($l) != $this->evaluate($r);
Chris@13 203 case '!==': return $this->evaluate($l) !== $this->evaluate($r);
Chris@13 204 case '+': return $this->evaluate($l) + $this->evaluate($r);
Chris@13 205 case '**': return $this->evaluate($l) ** $this->evaluate($r);
Chris@13 206 case '<<': return $this->evaluate($l) << $this->evaluate($r);
Chris@13 207 case '>>': return $this->evaluate($l) >> $this->evaluate($r);
Chris@13 208 case '<': return $this->evaluate($l) < $this->evaluate($r);
Chris@13 209 case '<=': return $this->evaluate($l) <= $this->evaluate($r);
Chris@13 210 case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
Chris@13 211 }
Chris@13 212
Chris@13 213 throw new \Exception('Should not happen');
Chris@13 214 }
Chris@13 215
Chris@13 216 private function evaluateConstFetch(Expr\ConstFetch $expr) {
Chris@13 217 $name = $expr->name->toLowerString();
Chris@13 218 switch ($name) {
Chris@13 219 case 'null': return null;
Chris@13 220 case 'false': return false;
Chris@13 221 case 'true': return true;
Chris@13 222 }
Chris@13 223
Chris@13 224 return ($this->fallbackEvaluator)($expr);
Chris@13 225 }
Chris@13 226 }