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