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