]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - extensions/ParserFunctions/Expr.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / extensions / ParserFunctions / Expr.php
1 <?php
2
3 use UtfNormal\Validator;
4
5 if ( !defined( 'MEDIAWIKI' ) ) {
6         die( 'This file is a MediaWiki extension, it is not a valid entry point' );
7 }
8
9 // Character classes
10 define( 'EXPR_WHITE_CLASS', " \t\r\n" );
11 define( 'EXPR_NUMBER_CLASS', '0123456789.' );
12
13 // Token types
14 define( 'EXPR_WHITE', 1 );
15 define( 'EXPR_NUMBER', 2 );
16 define( 'EXPR_NEGATIVE', 3 );
17 define( 'EXPR_POSITIVE', 4 );
18 define( 'EXPR_PLUS', 5 );
19 define( 'EXPR_MINUS', 6 );
20 define( 'EXPR_TIMES', 7 );
21 define( 'EXPR_DIVIDE', 8 );
22 define( 'EXPR_MOD', 9 );
23 define( 'EXPR_OPEN', 10 );
24 define( 'EXPR_CLOSE', 11 );
25 define( 'EXPR_AND', 12 );
26 define( 'EXPR_OR', 13 );
27 define( 'EXPR_NOT', 14 );
28 define( 'EXPR_EQUALITY', 15 );
29 define( 'EXPR_LESS', 16 );
30 define( 'EXPR_GREATER', 17 );
31 define( 'EXPR_LESSEQ', 18 );
32 define( 'EXPR_GREATEREQ', 19 );
33 define( 'EXPR_NOTEQ', 20 );
34 define( 'EXPR_ROUND', 21 );
35 define( 'EXPR_EXPONENT', 22 );
36 define( 'EXPR_SINE', 23 );
37 define( 'EXPR_COSINE', 24 );
38 define( 'EXPR_TANGENS', 25 );
39 define( 'EXPR_ARCSINE', 26 );
40 define( 'EXPR_ARCCOS', 27 );
41 define( 'EXPR_ARCTAN', 28 );
42 define( 'EXPR_EXP', 29 );
43 define( 'EXPR_LN', 30 );
44 define( 'EXPR_ABS', 31 );
45 define( 'EXPR_FLOOR', 32 );
46 define( 'EXPR_TRUNC', 33 );
47 define( 'EXPR_CEIL', 34 );
48 define( 'EXPR_POW', 35 );
49 define( 'EXPR_PI', 36 );
50 define( 'EXPR_FMOD', 37 );
51 define( 'EXPR_SQRT', 38 );
52
53 class ExprError extends Exception {
54         /**
55          * @param $msg string
56          * @param $parameter string
57          */
58         public function __construct( $msg, $parameter = '' ) {
59                 // Give grep a chance to find the usages:
60                 // pfunc_expr_stack_exhausted, pfunc_expr_unexpected_number, pfunc_expr_preg_match_failure,
61                 // pfunc_expr_unrecognised_word, pfunc_expr_unexpected_operator, pfunc_expr_missing_operand,
62                 // pfunc_expr_unexpected_closing_bracket, pfunc_expr_unrecognised_punctuation,
63                 // pfunc_expr_unclosed_bracket, pfunc_expr_division_by_zero, pfunc_expr_invalid_argument,
64                 // pfunc_expr_invalid_argument_ln, pfunc_expr_unknown_error, pfunc_expr_not_a_number
65                 $this->message = wfMessage( "pfunc_expr_$msg", $parameter )->inContentLanguage()->text();
66         }
67 }
68
69 class ExprParser {
70         public $maxStackSize = 100;
71
72         public $precedence = [
73                 EXPR_NEGATIVE => 10,
74                 EXPR_POSITIVE => 10,
75                 EXPR_EXPONENT => 10,
76                 EXPR_SINE => 9,
77                 EXPR_COSINE => 9,
78                 EXPR_TANGENS => 9,
79                 EXPR_ARCSINE => 9,
80                 EXPR_ARCCOS => 9,
81                 EXPR_ARCTAN => 9,
82                 EXPR_EXP => 9,
83                 EXPR_LN => 9,
84                 EXPR_ABS => 9,
85                 EXPR_FLOOR => 9,
86                 EXPR_TRUNC => 9,
87                 EXPR_CEIL => 9,
88                 EXPR_NOT => 9,
89                 EXPR_SQRT => 9,
90                 EXPR_POW => 8,
91                 EXPR_TIMES => 7,
92                 EXPR_DIVIDE => 7,
93                 EXPR_MOD => 7,
94                 EXPR_FMOD => 7,
95                 EXPR_PLUS => 6,
96                 EXPR_MINUS => 6,
97                 EXPR_ROUND => 5,
98                 EXPR_EQUALITY => 4,
99                 EXPR_LESS => 4,
100                 EXPR_GREATER => 4,
101                 EXPR_LESSEQ => 4,
102                 EXPR_GREATEREQ => 4,
103                 EXPR_NOTEQ => 4,
104                 EXPR_AND => 3,
105                 EXPR_OR => 2,
106                 EXPR_PI => 0,
107                 EXPR_OPEN => -1,
108                 EXPR_CLOSE => -1,
109         ];
110
111         public $names = [
112                 EXPR_NEGATIVE => '-',
113                 EXPR_POSITIVE => '+',
114                 EXPR_NOT => 'not',
115                 EXPR_TIMES => '*',
116                 EXPR_DIVIDE => '/',
117                 EXPR_MOD => 'mod',
118                 EXPR_FMOD => 'fmod',
119                 EXPR_PLUS => '+',
120                 EXPR_MINUS => '-',
121                 EXPR_ROUND => 'round',
122                 EXPR_EQUALITY => '=',
123                 EXPR_LESS => '<',
124                 EXPR_GREATER => '>',
125                 EXPR_LESSEQ => '<=',
126                 EXPR_GREATEREQ => '>=',
127                 EXPR_NOTEQ => '<>',
128                 EXPR_AND => 'and',
129                 EXPR_OR => 'or',
130                 EXPR_EXPONENT => 'e',
131                 EXPR_SINE => 'sin',
132                 EXPR_COSINE => 'cos',
133                 EXPR_TANGENS => 'tan',
134                 EXPR_ARCSINE => 'asin',
135                 EXPR_ARCCOS => 'acos',
136                 EXPR_ARCTAN => 'atan',
137                 EXPR_LN => 'ln',
138                 EXPR_EXP => 'exp',
139                 EXPR_ABS => 'abs',
140                 EXPR_FLOOR => 'floor',
141                 EXPR_TRUNC => 'trunc',
142                 EXPR_CEIL => 'ceil',
143                 EXPR_POW => '^',
144                 EXPR_PI => 'pi',
145                 EXPR_SQRT => 'sqrt',
146         ];
147
148         public $words = [
149                 'mod' => EXPR_MOD,
150                 'fmod' => EXPR_FMOD,
151                 'and' => EXPR_AND,
152                 'or' => EXPR_OR,
153                 'not' => EXPR_NOT,
154                 'round' => EXPR_ROUND,
155                 'div' => EXPR_DIVIDE,
156                 'e' => EXPR_EXPONENT,
157                 'sin' => EXPR_SINE,
158                 'cos' => EXPR_COSINE,
159                 'tan' => EXPR_TANGENS,
160                 'asin' => EXPR_ARCSINE,
161                 'acos' => EXPR_ARCCOS,
162                 'atan' => EXPR_ARCTAN,
163                 'exp' => EXPR_EXP,
164                 'ln' => EXPR_LN,
165                 'abs' => EXPR_ABS,
166                 'trunc' => EXPR_TRUNC,
167                 'floor' => EXPR_FLOOR,
168                 'ceil' => EXPR_CEIL,
169                 'pi' => EXPR_PI,
170                 'sqrt' => EXPR_SQRT,
171         ];
172
173         /**
174          * Evaluate a mathematical expression
175          *
176          * The algorithm here is based on the infix to RPN algorithm given in
177          * http://montcs.bloomu.edu/~bobmon/Information/RPN/infix2rpn.shtml
178          * It's essentially the same as Dijkstra's shunting yard algorithm.
179          * @param $expr string
180          * @throws ExprError
181          * @return string
182          */
183         public function doExpression( $expr ) {
184                 $operands = [];
185                 $operators = [];
186
187                 # Unescape inequality operators
188                 $expr = strtr( $expr, [ '&lt;' => '<', '&gt;' => '>',
189                         '&minus;' => '-', '−' => '-' ] );
190
191                 $p = 0;
192                 $end = strlen( $expr );
193                 $expecting = 'expression';
194                 $name = '';
195
196                 while ( $p < $end ) {
197                         if ( count( $operands ) > $this->maxStackSize || count( $operators ) > $this->maxStackSize ) {
198                                 throw new ExprError( 'stack_exhausted' );
199                         }
200                         $char = $expr[$p];
201                         $char2 = substr( $expr, $p, 2 );
202
203                         // Mega if-elseif-else construct
204                         // Only binary operators fall through for processing at the bottom, the rest
205                         // finish their processing and continue
206
207                         // First the unlimited length classes
208
209                         if ( false !== strpos( EXPR_WHITE_CLASS, $char ) ) {
210                                 // Whitespace
211                                 $p += strspn( $expr, EXPR_WHITE_CLASS, $p );
212                                 continue;
213                         } elseif ( false !== strpos( EXPR_NUMBER_CLASS, $char ) ) {
214                                 // Number
215                                 if ( $expecting !== 'expression' ) {
216                                         throw new ExprError( 'unexpected_number' );
217                                 }
218
219                                 // Find the rest of it
220                                 $length = strspn( $expr, EXPR_NUMBER_CLASS, $p );
221                                 // Convert it to float, silently removing double decimal points
222                                 $operands[] = (float)substr( $expr, $p, $length );
223                                 $p += $length;
224                                 $expecting = 'operator';
225                                 continue;
226                         } elseif ( ctype_alpha( $char ) ) {
227                                 // Word
228                                 // Find the rest of it
229                                 $remaining = substr( $expr, $p );
230                                 if ( !preg_match( '/^[A-Za-z]*/', $remaining, $matches ) ) {
231                                         // This should be unreachable
232                                         throw new ExprError( 'preg_match_failure' );
233                                 }
234                                 $word = strtolower( $matches[0] );
235                                 $p += strlen( $word );
236
237                                 // Interpret the word
238                                 if ( !isset( $this->words[$word] ) ) {
239                                         throw new ExprError( 'unrecognised_word', $word );
240                                 }
241                                 $op = $this->words[$word];
242                                 switch ( $op ) {
243                                 // constant
244                                 case EXPR_EXPONENT:
245                                         if ( $expecting !== 'expression' ) {
246                                                 continue;
247                                         }
248                                         $operands[] = exp( 1 );
249                                         $expecting = 'operator';
250                                         continue 2;
251                                 case EXPR_PI:
252                                         if ( $expecting !== 'expression' ) {
253                                                 throw new ExprError( 'unexpected_number' );
254                                         }
255                                         $operands[] = pi();
256                                         $expecting = 'operator';
257                                         continue 2;
258                                 // Unary operator
259                                 case EXPR_NOT:
260                                 case EXPR_SINE:
261                                 case EXPR_COSINE:
262                                 case EXPR_TANGENS:
263                                 case EXPR_ARCSINE:
264                                 case EXPR_ARCCOS:
265                                 case EXPR_ARCTAN:
266                                 case EXPR_EXP:
267                                 case EXPR_LN:
268                                 case EXPR_ABS:
269                                 case EXPR_FLOOR:
270                                 case EXPR_TRUNC:
271                                 case EXPR_CEIL:
272                                 case EXPR_SQRT:
273                                         if ( $expecting !== 'expression' ) {
274                                                 throw new ExprError( 'unexpected_operator', $word );
275                                         }
276                                         $operators[] = $op;
277                                         continue 2;
278                                 }
279                                 // Binary operator, fall through
280                                 $name = $word;
281                         }
282
283                         // Next the two-character operators
284  elseif ( $char2 === '<=' ) {
285                                 $name = $char2;
286                                 $op = EXPR_LESSEQ;
287                                 $p += 2;
288         } elseif ( $char2 === '>=' ) {
289                                 $name = $char2;
290                                 $op = EXPR_GREATEREQ;
291                                 $p += 2;
292         } elseif ( $char2 === '<>' || $char2 === '!=' ) {
293                                 $name = $char2;
294                                 $op = EXPR_NOTEQ;
295                                 $p += 2;
296         }
297
298                         // Finally the single-character operators
299  elseif ( $char === '+' ) {
300                                 ++$p;
301                                 if ( $expecting === 'expression' ) {
302                                         // Unary plus
303                                         $operators[] = EXPR_POSITIVE;
304                                         continue;
305                                 } else {
306                                         // Binary plus
307                                         $op = EXPR_PLUS;
308                                 }
309         } elseif ( $char === '-' ) {
310                                 ++$p;
311                                 if ( $expecting === 'expression' ) {
312                                         // Unary minus
313                                         $operators[] = EXPR_NEGATIVE;
314                                         continue;
315                                 } else {
316                                         // Binary minus
317                                         $op = EXPR_MINUS;
318                                 }
319         } elseif ( $char === '*' ) {
320                                 $name = $char;
321                                 $op = EXPR_TIMES;
322                                 ++$p;
323         } elseif ( $char === '/' ) {
324                                 $name = $char;
325                                 $op = EXPR_DIVIDE;
326                                 ++$p;
327         } elseif ( $char === '^' ) {
328                                 $name = $char;
329                                 $op = EXPR_POW;
330                                 ++$p;
331         } elseif ( $char === '(' ) {
332                                 if ( $expecting === 'operator' ) {
333                                         throw new ExprError( 'unexpected_operator', '(' );
334                                 }
335                                 $operators[] = EXPR_OPEN;
336                                 ++$p;
337                                 continue;
338         } elseif ( $char === ')' ) {
339                                 $lastOp = end( $operators );
340                                 while ( $lastOp && $lastOp != EXPR_OPEN ) {
341                                         $this->doOperation( $lastOp, $operands );
342                                         array_pop( $operators );
343                                         $lastOp = end( $operators );
344                                 }
345                                 if ( $lastOp ) {
346                                         array_pop( $operators );
347                                 } else {
348                                         throw new ExprError( 'unexpected_closing_bracket' );
349                                 }
350                                 $expecting = 'operator';
351                                 ++$p;
352                                 continue;
353         } elseif ( $char === '=' ) {
354                                 $name = $char;
355                                 $op = EXPR_EQUALITY;
356                                 ++$p;
357         } elseif ( $char === '<' ) {
358                                 $name = $char;
359                                 $op = EXPR_LESS;
360                                 ++$p;
361         } elseif ( $char === '>' ) {
362                                 $name = $char;
363                                 $op = EXPR_GREATER;
364                                 ++$p;
365         } else {
366                                 throw new ExprError( 'unrecognised_punctuation', Validator::cleanUp( $char ) );
367         }
368
369                         // Binary operator processing
370                         if ( $expecting === 'expression' ) {
371                                 throw new ExprError( 'unexpected_operator', $name );
372                         }
373
374                         // Shunting yard magic
375                         $lastOp = end( $operators );
376                         while ( $lastOp && $this->precedence[$op] <= $this->precedence[$lastOp] ) {
377                                 $this->doOperation( $lastOp, $operands );
378                                 array_pop( $operators );
379                                 $lastOp = end( $operators );
380                         }
381                         $operators[] = $op;
382                         $expecting = 'expression';
383                 }
384
385                 // Finish off the operator array
386                 // @codingStandardsIgnoreStart
387                 while ( $op = array_pop( $operators ) ) {
388                 // @codingStandardsIgnoreEnd
389                         if ( $op == EXPR_OPEN ) {
390                                 throw new ExprError( 'unclosed_bracket' );
391                         }
392                         $this->doOperation( $op, $operands );
393                 }
394
395                 return implode( "<br />\n", $operands );
396         }
397
398         /**
399          * @param $op int
400          * @param $stack array
401          * @throws ExprError
402          */
403         public function doOperation( $op, &$stack ) {
404                 switch ( $op ) {
405                         case EXPR_NEGATIVE:
406                                 if ( count( $stack ) < 1 ) {
407                                         throw new ExprError( 'missing_operand', $this->names[$op] );
408                                 }
409                                 $arg = array_pop( $stack );
410                                 $stack[] = -$arg;
411                                 break;
412                         case EXPR_POSITIVE:
413                                 if ( count( $stack ) < 1 ) {
414                                         throw new ExprError( 'missing_operand', $this->names[$op] );
415                                 }
416                                 break;
417                         case EXPR_TIMES:
418                                 if ( count( $stack ) < 2 ) {
419                                         throw new ExprError( 'missing_operand', $this->names[$op] );
420                                 }
421                                 $right = array_pop( $stack );
422                                 $left = array_pop( $stack );
423                                 $stack[] = $left * $right;
424                                         break;
425                         case EXPR_DIVIDE:
426                                 if ( count( $stack ) < 2 ) {
427                                         throw new ExprError( 'missing_operand', $this->names[$op] );
428                                 }
429                                 $right = array_pop( $stack );
430                                 $left = array_pop( $stack );
431                                 if ( !$right ) {
432                                         throw new ExprError( 'division_by_zero', $this->names[$op] );
433                                 }
434                                 $stack[] = $left / $right;
435                                 break;
436                         case EXPR_MOD:
437                                 if ( count( $stack ) < 2 ) {
438                                         throw new ExprError( 'missing_operand', $this->names[$op] );
439                                 }
440                                 $right = (int)array_pop( $stack );
441                                 $left = (int)array_pop( $stack );
442                                 if ( !$right ) {
443                                         throw new ExprError( 'division_by_zero', $this->names[$op] );
444                                 }
445                                 $stack[] = $left % $right;
446                                 break;
447                         case EXPR_FMOD:
448                                 if ( count( $stack ) < 2 ) {
449                                         throw new ExprError( 'missing_operand', $this->names[$op] );
450                                 }
451                                 $right = (double)array_pop( $stack );
452                                 $left = (double)array_pop( $stack );
453                                 if ( !$right ) {
454                                         throw new ExprError( 'division_by_zero', $this->names[$op] );
455                                 }
456                                 $stack[] = fmod( $left, $right );
457                                 break;
458                         case EXPR_PLUS:
459                                 if ( count( $stack ) < 2 ) {
460                                         throw new ExprError( 'missing_operand', $this->names[$op] );
461                                 }
462                                 $right = array_pop( $stack );
463                                 $left = array_pop( $stack );
464                                 $stack[] = $left + $right;
465                                 break;
466                         case EXPR_MINUS:
467                                 if ( count( $stack ) < 2 ) {
468                                         throw new ExprError( 'missing_operand', $this->names[$op] );
469                                 }
470                                 $right = array_pop( $stack );
471                                 $left = array_pop( $stack );
472                                 $stack[] = $left - $right;
473                                 break;
474                         case EXPR_AND:
475                                 if ( count( $stack ) < 2 ) {
476                                         throw new ExprError( 'missing_operand', $this->names[$op] );
477                                 }
478                                 $right = array_pop( $stack );
479                                 $left = array_pop( $stack );
480                                 $stack[] = ( $left && $right ) ? 1 : 0;
481                                 break;
482                         case EXPR_OR:
483                                 if ( count( $stack ) < 2 ) {
484                                         throw new ExprError( 'missing_operand', $this->names[$op] );
485                                 }
486                                 $right = array_pop( $stack );
487                                 $left = array_pop( $stack );
488                                 $stack[] = ( $left || $right ) ? 1 : 0;
489                                 break;
490                         case EXPR_EQUALITY:
491                                 if ( count( $stack ) < 2 ) {
492                                         throw new ExprError( 'missing_operand', $this->names[$op] );
493                                 }
494                                 $right = array_pop( $stack );
495                                 $left = array_pop( $stack );
496                                 $stack[] = ( $left == $right ) ? 1 : 0;
497                                 break;
498                         case EXPR_NOT:
499                                 if ( count( $stack ) < 1 ) {
500                                         throw new ExprError( 'missing_operand', $this->names[$op] );
501                                 }
502                                 $arg = array_pop( $stack );
503                                 $stack[] = ( !$arg ) ? 1 : 0;
504                                 break;
505                         case EXPR_ROUND:
506                                 if ( count( $stack ) < 2 ) {
507                                         throw new ExprError( 'missing_operand', $this->names[$op] );
508                                 }
509                                 $digits = (int)array_pop( $stack );
510                                 $value = array_pop( $stack );
511                                 $stack[] = round( $value, $digits );
512                                 break;
513                         case EXPR_LESS:
514                                 if ( count( $stack ) < 2 ) {
515                                         throw new ExprError( 'missing_operand', $this->names[$op] );
516                                 }
517                                 $right = array_pop( $stack );
518                                 $left = array_pop( $stack );
519                                 $stack[] = ( $left < $right ) ? 1 : 0;
520                                 break;
521                         case EXPR_GREATER:
522                                 if ( count( $stack ) < 2 ) {
523                                         throw new ExprError( 'missing_operand', $this->names[$op] );
524                                 }
525                                 $right = array_pop( $stack );
526                                 $left = array_pop( $stack );
527                                 $stack[] = ( $left > $right ) ? 1 : 0;
528                                 break;
529                         case EXPR_LESSEQ:
530                                 if ( count( $stack ) < 2 ) {
531                                         throw new ExprError( 'missing_operand', $this->names[$op] );
532                                 }
533                                 $right = array_pop( $stack );
534                                 $left = array_pop( $stack );
535                                 $stack[] = ( $left <= $right ) ? 1 : 0;
536                                 break;
537                         case EXPR_GREATEREQ:
538                                 if ( count( $stack ) < 2 ) {
539                                         throw new ExprError( 'missing_operand', $this->names[$op] );
540                                 }
541                                 $right = array_pop( $stack );
542                                 $left = array_pop( $stack );
543                                 $stack[] = ( $left >= $right ) ? 1 : 0;
544                                 break;
545                         case EXPR_NOTEQ:
546                                 if ( count( $stack ) < 2 ) {
547                                         throw new ExprError( 'missing_operand', $this->names[$op] );
548                                 }
549                                 $right = array_pop( $stack );
550                                 $left = array_pop( $stack );
551                                 $stack[] = ( $left != $right ) ? 1 : 0;
552                                 break;
553                         case EXPR_EXPONENT:
554                                 if ( count( $stack ) < 2 ) {
555                                         throw new ExprError( 'missing_operand', $this->names[$op] );
556                                 }
557                                 $right = array_pop( $stack );
558                                 $left = array_pop( $stack );
559                                 $stack[] = $left * pow( 10, $right );
560                                 break;
561                         case EXPR_SINE:
562                                 if ( count( $stack ) < 1 ) {
563                                         throw new ExprError( 'missing_operand', $this->names[$op] );
564                                 }
565                                 $arg = array_pop( $stack );
566                                 $stack[] = sin( $arg );
567                                 break;
568                         case EXPR_COSINE:
569                                 if ( count( $stack ) < 1 ) {
570                                         throw new ExprError( 'missing_operand', $this->names[$op] );
571                                 }
572                                 $arg = array_pop( $stack );
573                                 $stack[] = cos( $arg );
574                                 break;
575                         case EXPR_TANGENS:
576                                 if ( count( $stack ) < 1 ) {
577                                         throw new ExprError( 'missing_operand', $this->names[$op] );
578                                 }
579                                 $arg = array_pop( $stack );
580                                 $stack[] = tan( $arg );
581                                 break;
582                         case EXPR_ARCSINE:
583                                 if ( count( $stack ) < 1 ) {
584                                         throw new ExprError( 'missing_operand', $this->names[$op] );
585                                 }
586                                 $arg = array_pop( $stack );
587                                 if ( $arg < -1 || $arg > 1 ) {
588                                         throw new ExprError( 'invalid_argument', $this->names[$op] );
589                                 }
590                                 $stack[] = asin( $arg );
591                                 break;
592                         case EXPR_ARCCOS:
593                                 if ( count( $stack ) < 1 ) {
594                                         throw new ExprError( 'missing_operand', $this->names[$op] );
595                                 }
596                                 $arg = array_pop( $stack );
597                                 if ( $arg < -1 || $arg > 1 ) {
598                                         throw new ExprError( 'invalid_argument', $this->names[$op] );
599                                 }
600                                 $stack[] = acos( $arg );
601                                 break;
602                         case EXPR_ARCTAN:
603                                 if ( count( $stack ) < 1 ) {
604                                         throw new ExprError( 'missing_operand', $this->names[$op] );
605                                 }
606                                 $arg = array_pop( $stack );
607                                 $stack[] = atan( $arg );
608                                 break;
609                         case EXPR_EXP:
610                                 if ( count( $stack ) < 1 ) {
611                                         throw new ExprError( 'missing_operand', $this->names[$op] );
612                                 }
613                                 $arg = array_pop( $stack );
614                                 $stack[] = exp( $arg );
615                                 break;
616                         case EXPR_LN:
617                                 if ( count( $stack ) < 1 ) {
618                                         throw new ExprError( 'missing_operand', $this->names[$op] );
619                                 }
620                                 $arg = array_pop( $stack );
621                                 if ( $arg <= 0 ) {
622                                         throw new ExprError( 'invalid_argument_ln', $this->names[$op] );
623                                 }
624                                 $stack[] = log( $arg );
625                                 break;
626                         case EXPR_ABS:
627                                 if ( count( $stack ) < 1 ) {
628                                         throw new ExprError( 'missing_operand', $this->names[$op] );
629                                 }
630                                 $arg = array_pop( $stack );
631                                 $stack[] = abs( $arg );
632                                 break;
633                         case EXPR_FLOOR:
634                                 if ( count( $stack ) < 1 ) {
635                                         throw new ExprError( 'missing_operand', $this->names[$op] );
636                                 }
637                                 $arg = array_pop( $stack );
638                                 $stack[] = floor( $arg );
639                                 break;
640                         case EXPR_TRUNC:
641                                 if ( count( $stack ) < 1 ) {
642                                         throw new ExprError( 'missing_operand', $this->names[$op] );
643                                 }
644                                 $arg = array_pop( $stack );
645                                 $stack[] = (int)$arg;
646                                 break;
647                         case EXPR_CEIL:
648                                 if ( count( $stack ) < 1 ) {
649                                         throw new ExprError( 'missing_operand', $this->names[$op] );
650                                 }
651                                 $arg = array_pop( $stack );
652                                 $stack[] = ceil( $arg );
653                                 break;
654                         case EXPR_POW:
655                                 if ( count( $stack ) < 2 ) {
656                                         throw new ExprError( 'missing_operand', $this->names[$op] );
657                                 }
658                                 $right = array_pop( $stack );
659                                 $left = array_pop( $stack );
660                                 $result = pow( $left, $right );
661                                 if ( $result === false ) {
662                                         throw new ExprError( 'division_by_zero', $this->names[$op] );
663                                 }
664                                 $stack[] = $result;
665                                 break;
666                         case EXPR_SQRT:
667                                 if ( count( $stack ) < 1 ) {
668                                         throw new ExprError( 'missing_operand', $this->names[$op] );
669                                 }
670                                 $arg = array_pop( $stack );
671                                 $result = sqrt( $arg );
672                                 if ( is_nan( $result ) ) {
673                                         throw new ExprError( 'not_a_number', $this->names[$op] );
674                                 }
675                                 $stack[] = $result;
676                                 break;
677                         default:
678                                 // Should be impossible to reach here.
679                                 throw new ExprError( 'unknown_error' );
680                 }
681         }
682 }