parse() public static method

field First field to be filled. If this is not specified, it takes the value of parseField. parseField Specifies the type of the field parsed. It may be database, table or column. These expressions may not include parentheses. breakOnAlias If not empty, breaks when the alias occurs (it is not included). breakOnParentheses If not empty, breaks when the first parentheses occurs. parenthesesDelimited If not empty, breaks after last parentheses occurred.
public static parse ( Parser $parser, TokensList $list, array $options = [] ) : Expression
$parser SqlParser\Parser The parser that serves as context.
$list SqlParser\TokensList The list of tokens that are being parsed.
$options array Parameters for parsing.
return Expression
Ejemplo n.º 1
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 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;
 }
Ejemplo n.º 2
0
 /**
  * @dataProvider testParseErrProvider
  */
 public function testParseErr($expr, $error)
 {
     $parser = new Parser();
     Expression::parse($parser, $this->getTokensList($expr));
     $errors = $this->getErrorsAsArray($parser);
     $this->assertEquals($errors[0][0], $error);
 }
Ejemplo n.º 3
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 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;
 }
Ejemplo n.º 4
0
 /**
  * @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 `ALTER`.
     $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
     ++$list->idx;
     // Parsing affected table.
     $this->table = Expression::parse($parser, $list, array('parseField' => 'table', 'breakOnAlias' => true));
     ++$list->idx;
     // Skipping field.
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -----------------[ alter operation ]-----------------> 1
      *
      *      1 -------------------------[ , ]-----------------------> 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 whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             $options = array();
             if ($this->options->has('DATABASE')) {
                 $options = AlterOperation::$DB_OPTIONS;
             } elseif ($this->options->has('TABLE')) {
                 $options = AlterOperation::$TABLE_OPTIONS;
             } elseif ($this->options->has('VIEW')) {
                 $options = AlterOperation::$VIEW_OPTIONS;
             }
             $this->altered[] = AlterOperation::parse($parser, $list, $options);
             $state = 1;
         } elseif ($state === 1) {
             if ($token->type === Token::TYPE_OPERATOR && $token->value === ',') {
                 $state = 0;
             }
         }
     }
 }
Ejemplo n.º 5
0
 /**
  * @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 `ALTER`.
     $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
     // Skipping `TABLE`.
     $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'TABLE');
     // Parsing affected table.
     $this->table = Expression::parse($parser, $list, array('noAlias' => true, 'noBrackets' => true));
     ++$list->idx;
     // Skipping field.
     /**
      * The state of the parser.
      *
      * Below are the states of the parser.
      *
      *      0 -----------------[ alter operation ]-----------------> 1
      *
      *      1 -------------------------[ , ]-----------------------> 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 whitespaces and comments.
         if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
             continue;
         }
         if ($state === 0) {
             $this->altered[] = AlterOperation::parse($parser, $list);
             $state = 1;
         } else {
             if ($state === 1) {
                 if ($token->type === Token::TYPE_OPERATOR && $token->value === ',') {
                     $state = 0;
                 }
             }
         }
     }
 }
Ejemplo n.º 6
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 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 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;
 }
Ejemplo n.º 8
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 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;
 }
Ejemplo n.º 9
0
 /**
  * @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;
                 }
             }
         }
     }
 }
Ejemplo n.º 10
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 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;
 }
Ejemplo n.º 11
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 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;
 }
Ejemplo n.º 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 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;
 }
Ejemplo n.º 13
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;
 }
Ejemplo n.º 14
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 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;
 }
Ejemplo n.º 15
0
 /**
  * @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;
         }
     }
 }
Ejemplo n.º 16
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 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;
 }
Ejemplo n.º 17
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 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;
 }
Ejemplo n.º 19
0
 /**
  *
  * @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;
 }