Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Calculation;
   4  
   5  /**
   6   * PARTLY BASED ON:
   7   * Copyright (c) 2007 E. W. Bachtal, Inc.
   8   *
   9   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
  10   * and associated documentation files (the "Software"), to deal in the Software without restriction,
  11   * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
  12   * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
  13   * subject to the following conditions:
  14   *
  15   * The above copyright notice and this permission notice shall be included in all copies or substantial
  16   * portions of the Software.
  17   *
  18   * The software is provided "as is", without warranty of any kind, express or implied, including but not
  19   * limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
  20   * no event shall the authors or copyright holders be liable for any claim, damages or other liability,
  21   * whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
  22   * software or the use or other dealings in the software.
  23   *
  24   * https://ewbi.blogs.com/develops/2007/03/excel_formula_p.html
  25   * https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
  26   */
  27  class FormulaParser
  28  {
  29      // Character constants
  30      const QUOTE_DOUBLE = '"';
  31      const QUOTE_SINGLE = '\'';
  32      const BRACKET_CLOSE = ']';
  33      const BRACKET_OPEN = '[';
  34      const BRACE_OPEN = '{';
  35      const BRACE_CLOSE = '}';
  36      const PAREN_OPEN = '(';
  37      const PAREN_CLOSE = ')';
  38      const SEMICOLON = ';';
  39      const WHITESPACE = ' ';
  40      const COMMA = ',';
  41      const ERROR_START = '#';
  42  
  43      const OPERATORS_SN = '+-';
  44      const OPERATORS_INFIX = '+-*/^&=><';
  45      const OPERATORS_POSTFIX = '%';
  46  
  47      /**
  48       * Formula.
  49       *
  50       * @var string
  51       */
  52      private $formula;
  53  
  54      /**
  55       * Tokens.
  56       *
  57       * @var FormulaToken[]
  58       */
  59      private $tokens = [];
  60  
  61      /**
  62       * Create a new FormulaParser.
  63       *
  64       * @param ?string $formula Formula to parse
  65       */
  66      public function __construct($formula = '')
  67      {
  68          // Check parameters
  69          if ($formula === null) {
  70              throw new Exception('Invalid parameter passed: formula');
  71          }
  72  
  73          // Initialise values
  74          $this->formula = trim($formula);
  75          // Parse!
  76          $this->parseToTokens();
  77      }
  78  
  79      /**
  80       * Get Formula.
  81       *
  82       * @return string
  83       */
  84      public function getFormula()
  85      {
  86          return $this->formula;
  87      }
  88  
  89      /**
  90       * Get Token.
  91       *
  92       * @param int $id Token id
  93       */
  94      public function getToken(int $id = 0): FormulaToken
  95      {
  96          if (isset($this->tokens[$id])) {
  97              return $this->tokens[$id];
  98          }
  99  
 100          throw new Exception("Token with id $id does not exist.");
 101      }
 102  
 103      /**
 104       * Get Token count.
 105       *
 106       * @return int
 107       */
 108      public function getTokenCount()
 109      {
 110          return count($this->tokens);
 111      }
 112  
 113      /**
 114       * Get Tokens.
 115       *
 116       * @return FormulaToken[]
 117       */
 118      public function getTokens()
 119      {
 120          return $this->tokens;
 121      }
 122  
 123      /**
 124       * Parse to tokens.
 125       */
 126      private function parseToTokens(): void
 127      {
 128          // No attempt is made to verify formulas; assumes formulas are derived from Excel, where
 129          // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions.
 130  
 131          // Check if the formula has a valid starting =
 132          $formulaLength = strlen($this->formula);
 133          if ($formulaLength < 2 || $this->formula[0] != '=') {
 134              return;
 135          }
 136  
 137          // Helper variables
 138          $tokens1 = $tokens2 = $stack = [];
 139          $inString = $inPath = $inRange = $inError = false;
 140          $token = $previousToken = $nextToken = null;
 141  
 142          $index = 1;
 143          $value = '';
 144  
 145          $ERRORS = ['#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A'];
 146          $COMPARATORS_MULTI = ['>=', '<=', '<>'];
 147  
 148          while ($index < $formulaLength) {
 149              // state-dependent character evaluation (order is important)
 150  
 151              // double-quoted strings
 152              // embeds are doubled
 153              // end marks token
 154              if ($inString) {
 155                  if ($this->formula[$index] == self::QUOTE_DOUBLE) {
 156                      if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_DOUBLE)) {
 157                          $value .= self::QUOTE_DOUBLE;
 158                          ++$index;
 159                      } else {
 160                          $inString = false;
 161                          $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_TEXT);
 162                          $value = '';
 163                      }
 164                  } else {
 165                      $value .= $this->formula[$index];
 166                  }
 167                  ++$index;
 168  
 169                  continue;
 170              }
 171  
 172              // single-quoted strings (links)
 173              // embeds are double
 174              // end does not mark a token
 175              if ($inPath) {
 176                  if ($this->formula[$index] == self::QUOTE_SINGLE) {
 177                      if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_SINGLE)) {
 178                          $value .= self::QUOTE_SINGLE;
 179                          ++$index;
 180                      } else {
 181                          $inPath = false;
 182                      }
 183                  } else {
 184                      $value .= $this->formula[$index];
 185                  }
 186                  ++$index;
 187  
 188                  continue;
 189              }
 190  
 191              // bracked strings (R1C1 range index or linked workbook name)
 192              // no embeds (changed to "()" by Excel)
 193              // end does not mark a token
 194              if ($inRange) {
 195                  if ($this->formula[$index] == self::BRACKET_CLOSE) {
 196                      $inRange = false;
 197                  }
 198                  $value .= $this->formula[$index];
 199                  ++$index;
 200  
 201                  continue;
 202              }
 203  
 204              // error values
 205              // end marks a token, determined from absolute list of values
 206              if ($inError) {
 207                  $value .= $this->formula[$index];
 208                  ++$index;
 209                  if (in_array($value, $ERRORS)) {
 210                      $inError = false;
 211                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_ERROR);
 212                      $value = '';
 213                  }
 214  
 215                  continue;
 216              }
 217  
 218              // scientific notation check
 219              if (strpos(self::OPERATORS_SN, $this->formula[$index]) !== false) {
 220                  if (strlen($value) > 1) {
 221                      if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) {
 222                          $value .= $this->formula[$index];
 223                          ++$index;
 224  
 225                          continue;
 226                      }
 227                  }
 228              }
 229  
 230              // independent character evaluation (order not important)
 231  
 232              // establish state-dependent character evaluations
 233              if ($this->formula[$index] == self::QUOTE_DOUBLE) {
 234                  if (strlen($value) > 0) {
 235                      // unexpected
 236                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
 237                      $value = '';
 238                  }
 239                  $inString = true;
 240                  ++$index;
 241  
 242                  continue;
 243              }
 244  
 245              if ($this->formula[$index] == self::QUOTE_SINGLE) {
 246                  if (strlen($value) > 0) {
 247                      // unexpected
 248                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
 249                      $value = '';
 250                  }
 251                  $inPath = true;
 252                  ++$index;
 253  
 254                  continue;
 255              }
 256  
 257              if ($this->formula[$index] == self::BRACKET_OPEN) {
 258                  $inRange = true;
 259                  $value .= self::BRACKET_OPEN;
 260                  ++$index;
 261  
 262                  continue;
 263              }
 264  
 265              if ($this->formula[$index] == self::ERROR_START) {
 266                  if (strlen($value) > 0) {
 267                      // unexpected
 268                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
 269                      $value = '';
 270                  }
 271                  $inError = true;
 272                  $value .= self::ERROR_START;
 273                  ++$index;
 274  
 275                  continue;
 276              }
 277  
 278              // mark start and end of arrays and array rows
 279              if ($this->formula[$index] == self::BRACE_OPEN) {
 280                  if (strlen($value) > 0) {
 281                      // unexpected
 282                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
 283                      $value = '';
 284                  }
 285  
 286                  $tmp = new FormulaToken('ARRAY', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
 287                  $tokens1[] = $tmp;
 288                  $stack[] = clone $tmp;
 289  
 290                  $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
 291                  $tokens1[] = $tmp;
 292                  $stack[] = clone $tmp;
 293  
 294                  ++$index;
 295  
 296                  continue;
 297              }
 298  
 299              if ($this->formula[$index] == self::SEMICOLON) {
 300                  if (strlen($value) > 0) {
 301                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 302                      $value = '';
 303                  }
 304  
 305                  $tmp = array_pop($stack);
 306                  $tmp->setValue('');
 307                  $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
 308                  $tokens1[] = $tmp;
 309  
 310                  $tmp = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
 311                  $tokens1[] = $tmp;
 312  
 313                  $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
 314                  $tokens1[] = $tmp;
 315                  $stack[] = clone $tmp;
 316  
 317                  ++$index;
 318  
 319                  continue;
 320              }
 321  
 322              if ($this->formula[$index] == self::BRACE_CLOSE) {
 323                  if (strlen($value) > 0) {
 324                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 325                      $value = '';
 326                  }
 327  
 328                  $tmp = array_pop($stack);
 329                  $tmp->setValue('');
 330                  $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
 331                  $tokens1[] = $tmp;
 332  
 333                  $tmp = array_pop($stack);
 334                  $tmp->setValue('');
 335                  $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
 336                  $tokens1[] = $tmp;
 337  
 338                  ++$index;
 339  
 340                  continue;
 341              }
 342  
 343              // trim white-space
 344              if ($this->formula[$index] == self::WHITESPACE) {
 345                  if (strlen($value) > 0) {
 346                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 347                      $value = '';
 348                  }
 349                  $tokens1[] = new FormulaToken('', FormulaToken::TOKEN_TYPE_WHITESPACE);
 350                  ++$index;
 351                  while (($this->formula[$index] == self::WHITESPACE) && ($index < $formulaLength)) {
 352                      ++$index;
 353                  }
 354  
 355                  continue;
 356              }
 357  
 358              // multi-character comparators
 359              if (($index + 2) <= $formulaLength) {
 360                  if (in_array(substr($this->formula, $index, 2), $COMPARATORS_MULTI)) {
 361                      if (strlen($value) > 0) {
 362                          $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 363                          $value = '';
 364                      }
 365                      $tokens1[] = new FormulaToken(substr($this->formula, $index, 2), FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_LOGICAL);
 366                      $index += 2;
 367  
 368                      continue;
 369                  }
 370              }
 371  
 372              // standard infix operators
 373              if (strpos(self::OPERATORS_INFIX, $this->formula[$index]) !== false) {
 374                  if (strlen($value) > 0) {
 375                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 376                      $value = '';
 377                  }
 378                  $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORINFIX);
 379                  ++$index;
 380  
 381                  continue;
 382              }
 383  
 384              // standard postfix operators (only one)
 385              if (strpos(self::OPERATORS_POSTFIX, $this->formula[$index]) !== false) {
 386                  if (strlen($value) > 0) {
 387                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 388                      $value = '';
 389                  }
 390                  $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX);
 391                  ++$index;
 392  
 393                  continue;
 394              }
 395  
 396              // start subexpression or function
 397              if ($this->formula[$index] == self::PAREN_OPEN) {
 398                  if (strlen($value) > 0) {
 399                      $tmp = new FormulaToken($value, FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
 400                      $tokens1[] = $tmp;
 401                      $stack[] = clone $tmp;
 402                      $value = '';
 403                  } else {
 404                      $tmp = new FormulaToken('', FormulaToken::TOKEN_TYPE_SUBEXPRESSION, FormulaToken::TOKEN_SUBTYPE_START);
 405                      $tokens1[] = $tmp;
 406                      $stack[] = clone $tmp;
 407                  }
 408                  ++$index;
 409  
 410                  continue;
 411              }
 412  
 413              // function, subexpression, or array parameters, or operand unions
 414              if ($this->formula[$index] == self::COMMA) {
 415                  if (strlen($value) > 0) {
 416                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 417                      $value = '';
 418                  }
 419  
 420                  $tmp = array_pop($stack);
 421                  $tmp->setValue('');
 422                  $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
 423                  $stack[] = $tmp;
 424  
 425                  if ($tmp->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
 426                      $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_UNION);
 427                  } else {
 428                      $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
 429                  }
 430                  ++$index;
 431  
 432                  continue;
 433              }
 434  
 435              // stop subexpression
 436              if ($this->formula[$index] == self::PAREN_CLOSE) {
 437                  if (strlen($value) > 0) {
 438                      $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 439                      $value = '';
 440                  }
 441  
 442                  $tmp = array_pop($stack);
 443                  $tmp->setValue('');
 444                  $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
 445                  $tokens1[] = $tmp;
 446  
 447                  ++$index;
 448  
 449                  continue;
 450              }
 451  
 452              // token accumulation
 453              $value .= $this->formula[$index];
 454              ++$index;
 455          }
 456  
 457          // dump remaining accumulation
 458          if (strlen($value) > 0) {
 459              $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
 460          }
 461  
 462          // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections
 463          $tokenCount = count($tokens1);
 464          for ($i = 0; $i < $tokenCount; ++$i) {
 465              $token = $tokens1[$i];
 466              if (isset($tokens1[$i - 1])) {
 467                  $previousToken = $tokens1[$i - 1];
 468              } else {
 469                  $previousToken = null;
 470              }
 471              if (isset($tokens1[$i + 1])) {
 472                  $nextToken = $tokens1[$i + 1];
 473              } else {
 474                  $nextToken = null;
 475              }
 476  
 477              if ($token === null) {
 478                  continue;
 479              }
 480  
 481              if ($token->getTokenType() != FormulaToken::TOKEN_TYPE_WHITESPACE) {
 482                  $tokens2[] = $token;
 483  
 484                  continue;
 485              }
 486  
 487              if ($previousToken === null) {
 488                  continue;
 489              }
 490  
 491              if (
 492                  !(
 493                      (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
 494                  (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
 495                  ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
 496                  )
 497              ) {
 498                  continue;
 499              }
 500  
 501              if ($nextToken === null) {
 502                  continue;
 503              }
 504  
 505              if (
 506                  !(
 507                      (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) ||
 508                  (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) ||
 509                  ($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
 510                  )
 511              ) {
 512                  continue;
 513              }
 514  
 515              $tokens2[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_INTERSECTION);
 516          }
 517  
 518          // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators
 519          // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names
 520          $this->tokens = [];
 521  
 522          $tokenCount = count($tokens2);
 523          for ($i = 0; $i < $tokenCount; ++$i) {
 524              $token = $tokens2[$i];
 525              if (isset($tokens2[$i - 1])) {
 526                  $previousToken = $tokens2[$i - 1];
 527              } else {
 528                  $previousToken = null;
 529              }
 530              if (isset($tokens2[$i + 1])) {
 531                  $nextToken = $tokens2[$i + 1];
 532              } else {
 533                  $nextToken = null;
 534              }
 535  
 536              if ($token === null) {
 537                  continue;
 538              }
 539  
 540              if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') {
 541                  if ($i == 0) {
 542                      $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
 543                  } elseif (
 544                      (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) &&
 545                          ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
 546                      (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) &&
 547                          ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
 548                      ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
 549                      ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
 550                  ) {
 551                      $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
 552                  } else {
 553                      $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
 554                  }
 555  
 556                  $this->tokens[] = $token;
 557  
 558                  continue;
 559              }
 560  
 561              if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') {
 562                  if ($i == 0) {
 563                      continue;
 564                  } elseif (
 565                      (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) &&
 566                          ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
 567                      (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) &&
 568                          ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
 569                      ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
 570                      ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
 571                  ) {
 572                      $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
 573                  } else {
 574                      continue;
 575                  }
 576  
 577                  $this->tokens[] = $token;
 578  
 579                  continue;
 580              }
 581  
 582              if (
 583                  $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX &&
 584                  $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING
 585              ) {
 586                  if (strpos('<>=', substr($token->getValue(), 0, 1)) !== false) {
 587                      $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
 588                  } elseif ($token->getValue() == '&') {
 589                      $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_CONCATENATION);
 590                  } else {
 591                      $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
 592                  }
 593  
 594                  $this->tokens[] = $token;
 595  
 596                  continue;
 597              }
 598  
 599              if (
 600                  $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND &&
 601                  $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING
 602              ) {
 603                  if (!is_numeric($token->getValue())) {
 604                      if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') {
 605                          $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
 606                      } else {
 607                          $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_RANGE);
 608                      }
 609                  } else {
 610                      $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_NUMBER);
 611                  }
 612  
 613                  $this->tokens[] = $token;
 614  
 615                  continue;
 616              }
 617  
 618              if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
 619                  if (strlen($token->getValue()) > 0) {
 620                      if (substr($token->getValue(), 0, 1) == '@') {
 621                          $token->setValue(substr($token->getValue(), 1));
 622                      }
 623                  }
 624              }
 625  
 626              $this->tokens[] = $token;
 627          }
 628      }
 629  }