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