Author: Dan Ungureanu (udan1107@gmail.com)
Inheritance: extends SqlParser\Component
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return Expression[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ----------------------[ array ]---------------------> 1
      *
      *      1 ------------------------[ , ]------------------------> 0
      *      1 -----------------------[ else ]----------------------> (END)
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED && ~$token->flags & Token::FLAG_KEYWORD_FUNCTION && $token->value !== 'DUAL' && $token->value !== 'NULL' && $token->value !== 'CASE') {
             // No keyword is expected.
             break;
         }
         if ($state === 0) {
             if ($token->type === Token::TYPE_KEYWORD && $token->value === 'CASE') {
                 $expr = CaseExpression::parse($parser, $list, $options);
             } else {
                 $expr = Expression::parse($parser, $list, $options);
             }
             if ($expr === null) {
                 break;
             }
             $ret[] = $expr;
             $state = 1;
         } elseif ($state === 1) {
             if ($token->value === ',') {
                 $state = 0;
             } else {
                 break;
             }
         }
     }
     if ($state === 0) {
         $parser->error(__('An expression was expected.'), $list->tokens[$list->idx]);
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return OrderKeyword[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     $expr = new OrderKeyword();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ----------------------[ field ]----------------------> 1
      *
      *      1 ------------------------[ , ]------------------------> 0
      *      1 -------------------[ ASC / DESC ]--------------------> 1
      *
      * @var int
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             $expr->field = Expression::parse($parser, $list);
             $state = 1;
         } elseif ($state === 1) {
             if ($token->type === Token::TYPE_KEYWORD && ($token->value === 'ASC' || $token->value === 'DESC')) {
                 $expr->type = $token->value;
             } elseif ($token->type === Token::TYPE_OPERATOR && $token->value === ',') {
                 if (!empty($expr->field)) {
                     $ret[] = $expr;
                 }
                 $expr = new OrderKeyword();
                 $state = 0;
             } else {
                 break;
             }
         }
     }
     // Last iteration was not processed.
     if (!empty($expr->field)) {
         $ret[] = $expr;
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return Expression[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     $expr = null;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED) {
             // No keyword is expected.
             break;
         }
         if ($token->type === Token::TYPE_OPERATOR && $token->value === ',') {
             $ret[] = $expr;
         } else {
             $expr = Expression::parse($parser, $list, $options);
             if ($expr === null) {
                 break;
             }
         }
     }
     // Last iteration was not processed.
     if ($expr !== null) {
         $ret[] = $expr;
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return JoinKeyword[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     $expr = new JoinKeyword();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -----------------------[ JOIN ]----------------------> 1
      *
      *      1 -----------------------[ expr ]----------------------> 2
      *
      *      2 ------------------------[ ON ]-----------------------> 3
      *      2 -----------------------[ USING ]---------------------> 4
      *
      *      3 --------------------[ conditions ]-------------------> 0
      *
      *      4 ----------------------[ columns ]--------------------> 0
      *
      * @var int $state
      */
     $state = 0;
     // By design, the parser will parse first token after the keyword.
     // In this case, the keyword must be analyzed too, in order to determine
     // the type of this join.
     if ($list->idx > 0) {
         --$list->idx;
     }
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             if ($token->type === Token::TYPE_KEYWORD && !empty(static::$JOINS[$token->value])) {
                 $expr->type = static::$JOINS[$token->value];
                 $state = 1;
             } else {
                 break;
             }
         } elseif ($state === 1) {
             $expr->expr = Expression::parse($parser, $list, array('field' => 'table'));
             $state = 2;
         } elseif ($state === 2) {
             if ($token->type === Token::TYPE_KEYWORD) {
                 if ($token->value === 'ON') {
                     $state = 3;
                 } elseif ($token->value === 'USING') {
                     $state = 4;
                 }
             }
         } elseif ($state === 3) {
             $expr->on = Condition::parse($parser, $list);
             $ret[] = $expr;
             $expr = new JoinKeyword();
             $state = 0;
         } elseif ($state === 4) {
             $expr->using = ArrayObj::parse($parser, $list);
             $ret[] = $expr;
             $expr = new JoinKeyword();
             $state = 0;
         }
     }
     if (!empty($expr->type)) {
         $ret[] = $expr;
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return AlterOperation
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new AlterOperation();
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ---------------------[ options ]---------------------> 1
      *
      *      1 ----------------------[ field ]----------------------> 2
      *
      *      2 -------------------------[ , ]-----------------------> 0
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping comments.
         if ($token->type === Token::TYPE_COMMENT) {
             continue;
         }
         // Skipping whitespaces.
         if ($token->type === Token::TYPE_WHITESPACE) {
             if ($state === 2) {
                 // When parsing the unknown part, the whitespaces are
                 // included to not break anything.
                 $ret->unknown[] = $token;
             }
             continue;
         }
         if ($state === 0) {
             $ret->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
             $state = 1;
         } elseif ($state === 1) {
             $ret->field = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true));
             if ($ret->field === null) {
                 // No field was read. We go back one token so the next
                 // iteration will parse the same token, but in state 2.
                 --$list->idx;
             }
             $state = 2;
         } elseif ($state === 2) {
             if ($token->type === Token::TYPE_OPERATOR) {
                 if ($token->value === '(') {
                     ++$brackets;
                 } elseif ($token->value === ')') {
                     --$brackets;
                 } elseif ($token->value === ',' && $brackets === 0) {
                     break;
                 }
             }
             $ret->unknown[] = $token;
         }
     }
     if ($ret->options->isEmpty()) {
         $parser->error(__('Unrecognized alter operation.'), $list->tokens[$list->idx]);
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param AlterOperation $component The component to be built.
  *
  * @return string
  */
 public static function build($component)
 {
     $ret = OptionsArray::build($component->options) . ' ';
     if (!empty($component->field)) {
         $ret .= Expression::build($component->field) . ' ';
     }
     $ret .= TokensList::build($component->unknown);
     return $ret;
 }
 /**
  * @param RenameOperation $component The component to be built.
  *
  * @return string
  */
 public static function build($component)
 {
     if (is_array($component)) {
         $values = array();
         foreach ($component as $c) {
             $values[] = static::build($c);
         }
         return implode(', ', $values);
     } else {
         return Expression::build($component->old) . ' TO ' . Expression::build($component->new);
     }
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return IntoKeyword
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new IntoKeyword();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -----------------------[ name ]----------------------> 1
      *      0 ---------------------[ OUTFILE ]---------------------> 2
      *
      *      1 ------------------------[ ( ]------------------------> (END)
      *
      *      2 ---------------------[ filename ]--------------------> 1
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED) {
             if ($state === 0 && $token->value === 'OUTFILE') {
                 $ret->type = 'OUTFILE';
                 $state = 2;
                 continue;
             }
             // No other keyword is expected.
             break;
         }
         if ($state === 0) {
             $ret->dest = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
             $state = 1;
         } elseif ($state === 1) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') {
                 $ret->columns = ArrayObj::parse($parser, $list)->values;
                 ++$list->idx;
             }
             break;
         } elseif ($state === 2) {
             $ret->dest = $token->value;
             ++$list->idx;
             break;
         }
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser The instance that requests parsing.
  * @param TokensList $list   The list of tokens to be parsed.
  *
  * @return void
  */
 public function parse(Parser $parser, TokensList $list)
 {
     ++$list->idx;
     // Skipping `CREATE`.
     // Parsing options.
     $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
     ++$list->idx;
     // Skipping last option.
     // Parsing the field name.
     $this->name = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
     if (!isset($this->name) || $this->name === '') {
         $parser->error(__('The name of the entity was expected.'), $list->tokens[$list->idx]);
     } else {
         ++$list->idx;
         // Skipping field.
     }
     if ($this->options->has('DATABASE')) {
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$DB_OPTIONS);
     } elseif ($this->options->has('TABLE')) {
         $this->fields = CreateDefinition::parse($parser, $list);
         if (empty($this->fields)) {
             $parser->error(__('At least one column definition was expected.'), $list->tokens[$list->idx]);
         }
         ++$list->idx;
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$TABLE_OPTIONS);
         /**
          * The field that is being filled (`partitionBy` or
          * `subpartitionBy`).
          *
          * @var string $field
          */
         $field = null;
         /**
          * The number of brackets. `false` means no bracket was found
          * previously. At least one bracket is required to validate the
          * expression.
          *
          * @var int|bool $brackets
          */
         $brackets = false;
         /*
          * Handles partitions.
          */
         for (; $list->idx < $list->count; ++$list->idx) {
             /**
              * Token parsed at this moment.
              *
              * @var Token $token
              */
             $token = $list->tokens[$list->idx];
             // End of statement.
             if ($token->type === Token::TYPE_DELIMITER) {
                 break;
             }
             // Skipping comments.
             if ($token->type === Token::TYPE_COMMENT) {
                 continue;
             }
             if ($token->type === Token::TYPE_KEYWORD && $token->value === 'PARTITION BY') {
                 $field = 'partitionBy';
                 $brackets = false;
             } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'SUBPARTITION BY') {
                 $field = 'subpartitionBy';
                 $brackets = false;
             } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'PARTITIONS') {
                 $token = $list->getNextOfType(Token::TYPE_NUMBER);
                 --$list->idx;
                 // `getNextOfType` also advances one position.
                 $this->partitionsNum = $token->value;
             } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'SUBPARTITIONS') {
                 $token = $list->getNextOfType(Token::TYPE_NUMBER);
                 --$list->idx;
                 // `getNextOfType` also advances one position.
                 $this->subpartitionsNum = $token->value;
             } elseif (!empty($field)) {
                 /*
                  * Handling the content of `PARTITION BY` and `SUBPARTITION BY`.
                  */
                 // Counting brackets.
                 if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') {
                     // This is used instead of `++$brackets` because,
                     // initially, `$brackets` is `false` cannot be
                     // incremented.
                     $brackets = $brackets + 1;
                 } elseif ($token->type === Token::TYPE_OPERATOR && $token->value === ')') {
                     --$brackets;
                 }
                 // Building the expression used for partitioning.
                 $this->{$field} .= $token->type === Token::TYPE_WHITESPACE ? ' ' : $token->token;
                 // Last bracket was read, the expression ended.
                 // Comparing with `0` and not `false`, because `false` means
                 // that no bracket was found and at least one must is
                 // required.
                 if ($brackets === 0) {
                     $this->{$field} = trim($this->{$field});
                     $field = null;
                 }
             } elseif ($token->type === Token::TYPE_OPERATOR && $token->value === '(') {
                 if (!empty($this->partitionBy)) {
                     $this->partitions = ArrayObj::parse($parser, $list, array('type' => 'SqlParser\\Components\\PartitionDefinition'));
                 }
                 break;
             }
         }
     } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) {
         $this->parameters = ParameterDefinition::parse($parser, $list);
         if ($this->options->has('FUNCTION')) {
             $token = $list->getNextOfType(Token::TYPE_KEYWORD);
             if ($token->value !== 'RETURNS') {
                 $parser->error(__('A "RETURNS" keyword was expected.'), $token);
             } else {
                 ++$list->idx;
                 $this->return = DataType::parse($parser, $list);
             }
         }
         ++$list->idx;
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$FUNC_OPTIONS);
         ++$list->idx;
         for (; $list->idx < $list->count; ++$list->idx) {
             $token = $list->tokens[$list->idx];
             $this->body[] = $token;
         }
     } elseif ($this->options->has('VIEW')) {
         $token = $list->getNext();
         // Skipping whitespaces and comments.
         // Parsing columns list.
         if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') {
             --$list->idx;
             // getNext() also goes forward one field.
             $this->fields = ArrayObj::parse($parser, $list);
             ++$list->idx;
             // Skipping last token from the array.
             $list->getNext();
         }
         // Parsing the `AS` keyword.
         for (; $list->idx < $list->count; ++$list->idx) {
             $token = $list->tokens[$list->idx];
             if ($token->type === Token::TYPE_DELIMITER) {
                 break;
             }
             $this->body[] = $token;
         }
     } elseif ($this->options->has('TRIGGER')) {
         // Parsing the time and the event.
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$TRIGGER_OPTIONS);
         ++$list->idx;
         $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON');
         ++$list->idx;
         // Skipping `ON`.
         // Parsing the name of the table.
         $this->table = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
         ++$list->idx;
         $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW');
         ++$list->idx;
         // Skipping `FOR EACH ROW`.
         for (; $list->idx < $list->count; ++$list->idx) {
             $token = $list->tokens[$list->idx];
             $this->body[] = $token;
         }
     } else {
         for (; $list->idx < $list->count; ++$list->idx) {
             $token = $list->tokens[$list->idx];
             if ($token->type === Token::TYPE_DELIMITER) {
                 break;
             }
             $this->body[] = $token;
         }
     }
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return RenameOperation[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     $expr = new RenameOperation();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ---------------------[ old name ]--------------------> 1
      *
      *      1 ------------------------[ TO ]-----------------------> 2
      *
      *      2 ---------------------[ old name ]--------------------> 3
      *
      *      3 ------------------------[ , ]------------------------> 0
      *      3 -----------------------[ else ]----------------------> (END)
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             $expr->old = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
             if (empty($expr->old)) {
                 $parser->error(__('The old name of the table was expected.'), $token);
             }
             $state = 1;
         } elseif ($state === 1) {
             if ($token->type === Token::TYPE_KEYWORD && $token->value === 'TO') {
                 $state = 2;
             } else {
                 $parser->error(__('Keyword "TO" was expected.'), $token);
                 break;
             }
         } elseif ($state === 2) {
             $expr->new = Expression::parse($parser, $list, array('noBrackets' => true, 'skipColumn' => true, 'noAlias' => true));
             if (empty($expr->new)) {
                 $parser->error(__('The new name of the table was expected.'), $token);
             }
             $state = 3;
         } elseif ($state === 3) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === ',') {
                 $ret[] = $expr;
                 $expr = new RenameOperation();
                 $state = 0;
             } else {
                 break;
             }
         }
     }
     if ($state !== 3) {
         $parser->error(__('A rename operation was expected.'), $list->tokens[$list->idx - 1]);
     }
     // Last iteration was not saved.
     if (!empty($expr->old)) {
         $ret[] = $expr;
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return AlterOperation
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new AlterOperation();
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ---------------------[ options ]---------------------> 1
      *
      *      1 ----------------------[ field ]----------------------> 2
      *
      *      2 -------------------------[ , ]-----------------------> 0
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping comments.
         if ($token->type === Token::TYPE_COMMENT) {
             continue;
         }
         // Skipping whitespaces.
         if ($token->type === Token::TYPE_WHITESPACE) {
             if ($state === 2) {
                 // When parsing the unknown part, the whitespaces are
                 // included to not break anything.
                 $ret->unknown[] = $token;
             }
             continue;
         }
         if ($state === 0) {
             $ret->options = OptionsArray::parse($parser, $list, $options);
             if ($ret->options->has('AS')) {
                 for (; $list->idx < $list->count; ++$list->idx) {
                     if ($list->tokens[$list->idx]->type === Token::TYPE_DELIMITER) {
                         break;
                     }
                     $ret->unknown[] = $list->tokens[$list->idx];
                 }
                 break;
             }
             $state = 1;
         } elseif ($state === 1) {
             $ret->field = Expression::parse($parser, $list, array('breakOnAlias' => true, 'parseField' => 'column'));
             if ($ret->field === null) {
                 // No field was read. We go back one token so the next
                 // iteration will parse the same token, but in state 2.
                 --$list->idx;
             }
             $state = 2;
         } elseif ($state === 2) {
             if ($token->type === Token::TYPE_OPERATOR) {
                 if ($token->value === '(') {
                     ++$brackets;
                 } elseif ($token->value === ')') {
                     --$brackets;
                 } elseif ($token->value === ',' && $brackets === 0) {
                     break;
                 }
             } elseif (!empty(Parser::$STATEMENT_PARSERS[$token->value])) {
                 // We have reached the end of ALTER operation and suddenly found
                 // a start to new statement, but have not find a delimiter between them
                 if (!($token->value == 'SET' && $list->tokens[$list->idx - 1]->value == 'CHARACTER')) {
                     $parser->error(__('A new statement was found, but no delimiter between it and the previous one.'), $token);
                     break;
                 }
             }
             $ret->unknown[] = $token;
         }
     }
     if ($ret->options->isEmpty()) {
         $parser->error(__('Unrecognized alter operation.'), $list->tokens[$list->idx]);
     }
     --$list->idx;
     return $ret;
 }
Exemple #12
0
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return SetOperation[]
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     $expr = new SetOperation();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -------------------[ column name ]-------------------> 1
      *
      *      1 ------------------------[ , ]------------------------> 0
      *      1 ----------------------[ value ]----------------------> 1
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         // No keyword is expected.
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED && $state == 0) {
             break;
         }
         if ($state === 0) {
             if ($token->token === '=') {
                 $state = 1;
             } elseif ($token->value !== ',') {
                 $expr->column .= $token->token;
             }
         } elseif ($state === 1) {
             $tmp = Expression::parse($parser, $list, array('breakOnAlias' => true));
             if ($tmp == null) {
                 break;
             }
             $expr->column = trim($expr->column);
             $expr->value = $tmp->expr;
             $ret[] = $expr;
             $expr = new SetOperation();
             $state = 0;
         }
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return RenameOperation
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = array();
     $expr = new RenameOperation();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ---------------------[ old name ]--------------------> 1
      *
      *      1 ------------------------[ TO ]-----------------------> 2
      *
      *      2 ---------------------[ old name ]--------------------> 3
      *
      *      3 ------------------------[ , ]------------------------> 0
      *      3 -----------------------[ else ]----------------------> -1
      *
      * @var int
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($token->type === Token::TYPE_KEYWORD && $token->flags & Token::FLAG_KEYWORD_RESERVED) {
             if ($state === 1 && $token->value === 'TO') {
                 $state = 2;
                 continue;
             }
             // No other keyword is expected.
             break;
         }
         if ($token->type === Token::TYPE_OPERATOR) {
             if ($state === 3 && $token->value === ',') {
                 $ret[] = $expr;
                 $expr = new RenameOperation();
                 $state = 0;
                 continue;
             }
             // No other operator is expected.
             break;
         }
         if ($state == 0) {
             $expr->old = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
             $state = 1;
         } elseif ($state == 2) {
             $expr->new = Expression::parse($parser, $list, array('noBrackets' => true, 'skipColumn' => true, 'noAlias' => true));
             $state = 3;
         }
     }
     // Last iteration was not saved.
     if (!empty($expr->old)) {
         $ret[] = $expr;
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param IntoKeyword $component The component to be built.
  *
  * @return string
  */
 public static function build($component)
 {
     if ($component->dest instanceof Expression) {
         $columns = !empty($component->columns) ? '(' . implode(', ', $component->columns) . ')' : '';
         return Expression::build($component->dest) . $columns;
     } else {
         return 'OUTFILE "' . $component->dest . '"';
     }
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return OptionsArray
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new OptionsArray();
     /**
      * The ID that will be assigned to duplicate options.
      *
      * @var int $lastAssignedId
      */
     $lastAssignedId = count($options) + 1;
     /**
      * The option that was processed last time.
      *
      * @var array $lastOption
      */
     $lastOption = null;
     /**
      * The index of the option that was processed last time.
      *
      * @var int $lastOptionId
      */
     $lastOptionId = 0;
     /**
      * Counts brackets.
      *
      * @var int $brackets
      */
     $brackets = 0;
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ---------------------[ option ]----------------------> 1
      *
      *      1 -------------------[ = (optional) ]------------------> 2
      *
      *      2 ----------------------[ value ]----------------------> 0
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping comments.
         if ($token->type === Token::TYPE_COMMENT) {
             continue;
         }
         // Skipping whitespace if not parsing value.
         if ($token->type === Token::TYPE_WHITESPACE && $brackets === 0) {
             continue;
         }
         if ($lastOption === null) {
             $upper = strtoupper($token->token);
             if (isset($options[$upper])) {
                 $lastOption = $options[$upper];
                 $lastOptionId = is_array($lastOption) ? $lastOption[0] : $lastOption;
                 $state = 0;
                 // Checking for option conflicts.
                 // For example, in `SELECT` statements the keywords `ALL`
                 // and `DISTINCT` conflict and if used together, they
                 // produce an invalid query.
                 //
                 // Usually, tokens can be identified in the array by the
                 // option ID, but if conflicts occur, a generated option ID
                 // is used.
                 //
                 // The first pseudo duplicate ID is the maximum value of the
                 // real options (e.g.  if there are 5 options, the first
                 // fake ID is 6).
                 if (isset($ret->options[$lastOptionId])) {
                     $parser->error(sprintf(__('This option conflicts with "%1$s".'), is_array($ret->options[$lastOptionId]) ? $ret->options[$lastOptionId]['name'] : $ret->options[$lastOptionId]), $token);
                     $lastOptionId = $lastAssignedId++;
                 }
             } else {
                 // There is no option to be processed.
                 break;
             }
         }
         if ($state === 0) {
             if (!is_array($lastOption)) {
                 // This is a just keyword option without any value.
                 // This is the beginning and the end of it.
                 $ret->options[$lastOptionId] = $token->value;
                 $lastOption = null;
                 $state = 0;
             } elseif ($lastOption[1] === 'var' || $lastOption[1] === 'var=') {
                 // This is a keyword that is followed by a value.
                 // This is only the beginning. The value is parsed in state
                 // 1 and 2. State 1 is used to skip the first equals sign
                 // and state 2 to parse the actual value.
                 $ret->options[$lastOptionId] = array('name' => $token->value, 'equals' => $lastOption[1] === 'var=', 'expr' => '', 'value' => '');
                 $state = 1;
             } elseif ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
                 // This is a keyword that is followed by an expression.
                 // The expression is used by the specialized parser.
                 // Skipping this option in order to parse the expression.
                 ++$list->idx;
                 $ret->options[$lastOptionId] = array('name' => $token->value, 'equals' => $lastOption[1] === 'expr=', 'expr' => '');
                 $state = 1;
             }
         } elseif ($state === 1) {
             $state = 2;
             if ($token->token === '=') {
                 $ret->options[$lastOptionId]['equals'] = true;
                 continue;
             }
         }
         // This is outside the `elseif` group above because the change might
         // change this iteration.
         if ($state === 2) {
             if ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
                 $ret->options[$lastOptionId]['expr'] = Expression::parse($parser, $list, empty($lastOption[2]) ? array() : $lastOption[2]);
                 $ret->options[$lastOptionId]['value'] = $ret->options[$lastOptionId]['expr']->expr;
                 $lastOption = null;
                 $state = 0;
             } else {
                 if ($token->token === '(') {
                     ++$brackets;
                 } elseif ($token->token === ')') {
                     --$brackets;
                 }
                 $ret->options[$lastOptionId]['expr'] .= $token->token;
                 if (!($token->token === '(' && $brackets === 1 || $token->token === ')' && $brackets === 0)) {
                     // First pair of brackets is being skipped.
                     $ret->options[$lastOptionId]['value'] .= $token->value;
                 }
                 // Checking if we finished parsing.
                 if ($brackets === 0) {
                     $lastOption = null;
                 }
             }
         }
     }
     /*
      * We reached the end of statement without getting a value
      * for an option for which a value was required
      */
     if ($state === 1 && $lastOption && ($lastOption[1] == 'expr' || $lastOption[1] == 'var' || $lastOption[1] == 'var=' || $lastOption[1] == 'expr=')) {
         $parser->error(sprintf(__('Value/Expression for the option %1$s was expected'), $ret->options[$lastOptionId]['name']), $list->tokens[$list->idx - 1]);
     }
     if (empty($options['_UNSORTED'])) {
         ksort($ret->options);
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return PartitionDefinition
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new PartitionDefinition();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -------------[ PARTITION | SUBPARTITION ]------------> 1
      *
      *      1 -----------------------[ name ]----------------------> 2
      *
      *      2 ----------------------[ VALUES ]---------------------> 3
      *
      *      3 ---------------------[ LESS THAN ]-------------------> 4
      *      3 ------------------------[ IN ]-----------------------> 4
      *
      *      4 -----------------------[ expr ]----------------------> 5
      *
      *      5 ----------------------[ options ]--------------------> 6
      *
      *      6 ------------------[ subpartitions ]------------------> (END)
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             $ret->isSubpartition = $token->type === Token::TYPE_KEYWORD && $token->value === 'SUBPARTITION';
             $state = 1;
         } elseif ($state === 1) {
             $ret->name = $token->value;
             $state = $ret->isSubpartition ? 5 : 2;
         } elseif ($state === 2) {
             $state = 3;
         } elseif ($state === 3) {
             $ret->type = $token->value;
             $state = 4;
         } elseif ($state === 4) {
             if ($token->value === 'MAXVALUE') {
                 $ret->expr = $token->value;
             } else {
                 $ret->expr = Expression::parse($parser, $list, array('bracketsDelimited' => true, 'noAlias' => true));
             }
             $state = 5;
         } elseif ($state === 5) {
             $ret->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
             $state = 6;
         } elseif ($state === 6) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') {
                 $ret->subpartitions = ArrayObj::parse($parser, $list, array('type' => 'SqlParser\\Components\\PartitionDefinition'));
                 ++$list->idx;
             }
             break;
         }
     }
     --$list->idx;
     return $ret;
 }
 /**
  * @param Parser     $parser The instance that requests parsing.
  * @param TokensList $list   The list of tokens to be parsed.
  *
  * @return void
  */
 public function parse(Parser $parser, TokensList $list)
 {
     ++$list->idx;
     // Skipping `CREATE`.
     // Parsing options.
     $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
     ++$list->idx;
     // Skipping last option.
     // Parsing the field name.
     $this->name = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
     ++$list->idx;
     // Skipping field.
     if ($this->options->has('DATABASE')) {
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$DB_OPTIONS);
     } elseif ($this->options->has('TABLE')) {
         $this->fields = FieldDefinition::parse($parser, $list);
         ++$list->idx;
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$TABLE_OPTIONS);
     } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) {
         $this->parameters = ParameterDefinition::parse($parser, $list);
         if ($this->options->has('FUNCTION')) {
             $token = $list->getNextOfType(Token::TYPE_KEYWORD);
             if ($token->value !== 'RETURNS') {
                 $parser->error('\'RETURNS\' keyword was expected.', $token);
             } else {
                 ++$list->idx;
                 $this->return = DataType::parse($parser, $list);
             }
         }
         ++$list->idx;
         $this->entityOptions = OptionsArray::parse($parser, $list, static::$FUNC_OPTIONS);
         ++$list->idx;
         for (; $list->idx < $list->count; ++$list->idx) {
             $token = $list->tokens[$list->idx];
             $this->body[] = $token;
         }
     } else {
         if ($this->options->has('VIEW')) {
             $token = $list->getNext();
             // Skipping whitespaces and comments.
             // Parsing columns list.
             if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') {
                 --$list->idx;
                 // getNext() also goes forward one field.
                 $this->fields = ArrayObj::parse($parser, $list);
                 ++$list->idx;
                 // Skipping last token from the array.
                 $list->getNext();
             }
             // Parsing the `AS` keyword.
             for (; $list->idx < $list->count; ++$list->idx) {
                 $token = $list->tokens[$list->idx];
                 if ($token->type === Token::TYPE_DELIMITER) {
                     break;
                 }
                 $this->body[] = $token;
             }
         } else {
             if ($this->options->has('TRIGGER')) {
                 // Parsing the time and the event.
                 $this->entityOptions = OptionsArray::parse($parser, $list, static::$TRIGGER_OPTIONS);
                 ++$list->idx;
                 $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON');
                 ++$list->idx;
                 // Skipping `ON`.
                 // Parsing the name of the table.
                 $this->table = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true, 'skipColumn' => true));
                 ++$list->idx;
                 $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW');
                 ++$list->idx;
                 // Skipping `FOR EACH ROW`.
                 for (; $list->idx < $list->count; ++$list->idx) {
                     $token = $list->tokens[$list->idx];
                     $this->body[] = $token;
                 }
             }
         }
     }
 }
Exemple #18
0
 /**
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  * @param array      $options Parameters for parsing.
  *
  * @return Reference
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new Reference();
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 ----------------------[ table ]---------------------> 1
      *
      *      1 ---------------------[ columns ]--------------------> 2
      *
      *      2 ---------------------[ options ]--------------------> (END)
      *
      * @var int $state
      */
     $state = 0;
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // End of statement.
         if ($token->type === Token::TYPE_DELIMITER) {
             break;
         }
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             $ret->table = Expression::parse($parser, $list, array('parseField' => 'table', 'breakOnAlias' => true));
             $state = 1;
         } elseif ($state === 1) {
             $ret->columns = ArrayObj::parse($parser, $list)->values;
             $state = 2;
         } elseif ($state === 2) {
             $ret->options = OptionsArray::parse($parser, $list, static::$REFERENCES_OPTIONS);
             ++$list->idx;
             break;
         }
     }
     --$list->idx;
     return $ret;
 }
 public function testBuild()
 {
     $component = array(new Expression('1 + 2', 'three'), new Expression('1 + 3', 'four'));
     $this->assertEquals(Expression::build($component), '1 + 2 AS `three`, 1 + 3 AS `four`');
 }
Exemple #20
0
 /**
  * @param JoinKeyword[] $component The component to be built.
  *
  * @return string
  */
 public static function build($component)
 {
     $ret = array();
     foreach ($component as $c) {
         $ret[] = ($c->type === 'JOIN' ? 'JOIN ' : $c->type . ' JOIN ') . Expression::build($c->expr) . ' ON ' . Condition::build($c->on);
     }
     return implode(' ', $ret);
 }
Exemple #21
0
 /**
  * @param OrderKeyword $component The component to be built.
  *
  * @return string
  */
 public static function build($component)
 {
     if (is_array($component)) {
         $ret = array();
         foreach ($component as $c) {
             $ret[] = static::build($c);
         }
         return implode(", ", $ret);
     } else {
         return Expression::build($component->field) . ' ' . $component->type;
     }
 }
 /**
  * @return string
  */
 public function build()
 {
     $tmp = array();
     foreach ($this->altered as $altered) {
         $tmp[] = $altered::build($altered);
     }
     return 'ALTER ' . OptionsArray::build($this->options) . ' TABLE ' . Expression::build($this->table) . ' ' . implode(', ', $tmp);
 }
 /**
  * Gets a list of all tables used in this statement.
  *
  * @param Statement $statement Statement to be scanned.
  *
  * @return array
  */
 public static function getTables($statement)
 {
     $expressions = array();
     if ($statement instanceof InsertStatement || $statement instanceof ReplaceStatement) {
         $expressions = array($statement->into->dest);
     } elseif ($statement instanceof UpdateStatement) {
         $expressions = $statement->tables;
     } elseif ($statement instanceof SelectStatement || $statement instanceof DeleteStatement) {
         $expressions = $statement->from;
     } elseif ($statement instanceof AlterStatement || $statement instanceof TruncateStatement) {
         $expressions = array($statement->table);
     } elseif ($statement instanceof DropStatement) {
         if (!$statement->options->has('TABLE')) {
             // No tables are dropped.
             return array();
         }
         $expressions = $statement->fields;
     } elseif ($statement instanceof RenameStatement) {
         foreach ($statement->renames as $rename) {
             $expressions[] = $rename->old;
         }
     }
     $ret = array();
     foreach ($expressions as $expr) {
         if (!empty($expr->table)) {
             $expr->expr = null;
             // Force rebuild.
             $expr->alias = null;
             // Aliases are not required.
             $ret[] = Expression::build($expr);
         }
     }
     return $ret;
 }
 /**
  *
  * @param Parser     $parser  The parser that serves as context.
  * @param TokensList $list    The list of tokens that are being parsed.
  *
  * @return Expression
  */
 public static function parse(Parser $parser, TokensList $list, array $options = array())
 {
     $ret = new CaseExpression();
     /**
      * State of parser
      *
      * @var int $parser
      */
     $state = 0;
     /**
      * Syntax type (type 0 or type 1)
      *
      * @var int $type
      */
     $type = 0;
     ++$list->idx;
     // Skip 'CASE'
     for (; $list->idx < $list->count; ++$list->idx) {
         /**
          * Token parsed at this moment.
          *
          * @var Token $token
          */
         $token = $list->tokens[$list->idx];
         // Skipping whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             if ($token->type === Token::TYPE_KEYWORD && $token->value === 'WHEN') {
                 ++$list->idx;
                 // Skip 'WHEN'
                 $new_condition = Condition::parse($parser, $list);
                 $type = 1;
                 $state = 1;
                 $ret->conditions[] = $new_condition;
             } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'ELSE') {
                 ++$list->idx;
                 // Skip 'ELSE'
                 $ret->else_result = Expression::parse($parser, $list);
                 $state = 0;
                 // last clause of CASE expression
             } elseif ($token->type === Token::TYPE_KEYWORD && ($token->value === 'END' || $token->value === 'end')) {
                 $state = 3;
                 // end of CASE expression
                 ++$list->idx;
                 break;
             } elseif ($token->type === Token::TYPE_KEYWORD) {
                 $parser->error(__('Unexpected keyword.'), $token);
                 break;
             } else {
                 $ret->value = Expression::parse($parser, $list);
                 $type = 0;
                 $state = 1;
             }
         } elseif ($state === 1) {
             if ($type === 0) {
                 if ($token->type === Token::TYPE_KEYWORD && $token->value === 'WHEN') {
                     ++$list->idx;
                     // Skip 'WHEN'
                     $new_value = Expression::parse($parser, $list);
                     $state = 2;
                     $ret->compare_values[] = $new_value;
                 } elseif ($token->type === Token::TYPE_KEYWORD && $token->value === 'ELSE') {
                     ++$list->idx;
                     // Skip 'ELSE'
                     $ret->else_result = Expression::parse($parser, $list);
                     $state = 0;
                     // last clause of CASE expression
                 } elseif ($token->type === Token::TYPE_KEYWORD && ($token->value === 'END' || $token->value === 'end')) {
                     $state = 3;
                     // end of CASE expression
                     ++$list->idx;
                     break;
                 } elseif ($token->type === Token::TYPE_KEYWORD) {
                     $parser->error(__('Unexpected keyword.'), $token);
                     break;
                 }
             } else {
                 if ($token->type === Token::TYPE_KEYWORD && $token->value === 'THEN') {
                     ++$list->idx;
                     // Skip 'THEN'
                     $new_result = Expression::parse($parser, $list);
                     $state = 0;
                     $ret->results[] = $new_result;
                 } elseif ($token->type === Token::TYPE_KEYWORD) {
                     $parser->error(__('Unexpected keyword.'), $token);
                     break;
                 }
             }
         } elseif ($state === 2) {
             if ($type === 0) {
                 if ($token->type === Token::TYPE_KEYWORD && $token->value === 'THEN') {
                     ++$list->idx;
                     // Skip 'THEN'
                     $new_result = Expression::parse($parser, $list);
                     $ret->results[] = $new_result;
                     $state = 1;
                 } elseif ($token->type === Token::TYPE_KEYWORD) {
                     $parser->error(__('Unexpected keyword.'), $token);
                     break;
                 }
             }
         }
     }
     if ($state !== 3) {
         $parser->error(__('Unexpected end of CASE expression'), $list->tokens[$list->idx - 1]);
     }
     --$list->idx;
     return $ret;
 }