1<?php
2
3/**
4 * Parses a list of expressions delimited by a comma.
5 */
6
7namespace PhpMyAdmin\SqlParser\Components;
8
9use PhpMyAdmin\SqlParser\Component;
10use PhpMyAdmin\SqlParser\Parser;
11use PhpMyAdmin\SqlParser\Token;
12use PhpMyAdmin\SqlParser\TokensList;
13
14/**
15 * Parses a list of expressions delimited by a comma.
16 *
17 * @category   Keywords
18 *
19 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20 */
21class ExpressionArray extends Component
22{
23    /**
24     * @param Parser     $parser  the parser that serves as context
25     * @param TokensList $list    the list of tokens that are being parsed
26     * @param array      $options parameters for parsing
27     *
28     * @return Expression[]
29     * @throws \PhpMyAdmin\SqlParser\Exceptions\ParserException
30     */
31    public static function parse(Parser $parser, TokensList $list, array $options = array())
32    {
33        $ret = array();
34
35        /**
36         * The state of the parser.
37         *
38         * Below are the states of the parser.
39         *
40         *      0 ----------------------[ array ]---------------------> 1
41         *
42         *      1 ------------------------[ , ]------------------------> 0
43         *      1 -----------------------[ else ]----------------------> (END)
44         *
45         * @var int
46         */
47        $state = 0;
48
49        for (; $list->idx < $list->count; ++$list->idx) {
50            /**
51             * Token parsed at this moment.
52             *
53             * @var Token
54             */
55            $token = $list->tokens[$list->idx];
56
57            // End of statement.
58            if ($token->type === Token::TYPE_DELIMITER) {
59                break;
60            }
61
62            // Skipping whitespaces and comments.
63            if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
64                continue;
65            }
66
67            if (($token->type === Token::TYPE_KEYWORD)
68                && ($token->flags & Token::FLAG_KEYWORD_RESERVED)
69                && ((~$token->flags & Token::FLAG_KEYWORD_FUNCTION))
70                && ($token->value !== 'DUAL')
71                && ($token->value !== 'NULL')
72                && ($token->value !== 'CASE')
73            ) {
74                // No keyword is expected.
75                break;
76            }
77
78            if ($state === 0) {
79                if ($token->type === Token::TYPE_KEYWORD
80                    && $token->value === 'CASE'
81                ) {
82                    $expr = CaseExpression::parse($parser, $list, $options);
83                } else {
84                    $expr = Expression::parse($parser, $list, $options);
85                }
86
87                if ($expr === null) {
88                    break;
89                }
90                $ret[] = $expr;
91                $state = 1;
92            } elseif ($state === 1) {
93                if ($token->value === ',') {
94                    $state = 0;
95                } else {
96                    break;
97                }
98            }
99        }
100
101        if ($state === 0) {
102            $parser->error(
103                'An expression was expected.',
104                $list->tokens[$list->idx]
105            );
106        }
107
108        --$list->idx;
109
110        if (is_array($ret)) {
111            $retIndex = count($ret) - 1;
112            if (isset($ret[$retIndex])) {
113                $expr = $ret[$retIndex]->expr;
114                if (preg_match('/\s*--\s.*$/', $expr, $matches)) {
115                    $found = $matches[0];
116                    $ret[$retIndex]->expr = substr($expr, 0, strlen($expr) - strlen($found));
117                }
118            }
119        }
120
121        return $ret;
122    }
123
124    /**
125     * @param Expression[] $component the component to be built
126     * @param array        $options   parameters for building
127     *
128     * @return string
129     */
130    public static function build($component, array $options = array())
131    {
132        $ret = array();
133        foreach ($component as $frag) {
134            $ret[] = $frag::build($frag);
135        }
136
137        return implode(', ', $ret);
138    }
139}
140