1<?php
2
3/**
4 * Parses an array.
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 an array.
16 *
17 * @category   Components
18 *
19 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
20 */
21class ArrayObj extends Component
22{
23    /**
24     * The array that contains the unprocessed value of each token.
25     *
26     * @var array
27     */
28    public $raw = array();
29
30    /**
31     * The array that contains the processed value of each token.
32     *
33     * @var array
34     */
35    public $values = array();
36
37    /**
38     * Constructor.
39     *
40     * @param array $raw    the unprocessed values
41     * @param array $values the processed values
42     */
43    public function __construct(array $raw = array(), array $values = array())
44    {
45        $this->raw = $raw;
46        $this->values = $values;
47    }
48
49    /**
50     * @param Parser     $parser  the parser that serves as context
51     * @param TokensList $list    the list of tokens that are being parsed
52     * @param array      $options parameters for parsing
53     *
54     * @return ArrayObj|Component[]
55     */
56    public static function parse(Parser $parser, TokensList $list, array $options = array())
57    {
58        $ret = empty($options['type']) ? new self() : array();
59
60        /**
61         * The last raw expression.
62         *
63         * @var string
64         */
65        $lastRaw = '';
66
67        /**
68         * The last value.
69         *
70         * @var string
71         */
72        $lastValue = '';
73
74        /**
75         * Counts brackets.
76         *
77         * @var int
78         */
79        $brackets = 0;
80
81        /**
82         * Last separator (bracket or comma).
83         *
84         * @var bool
85         */
86        $isCommaLast = false;
87
88        for (; $list->idx < $list->count; ++$list->idx) {
89            /**
90             * Token parsed at this moment.
91             *
92             * @var Token
93             */
94            $token = $list->tokens[$list->idx];
95
96            // End of statement.
97            if ($token->type === Token::TYPE_DELIMITER) {
98                break;
99            }
100
101            // Skipping whitespaces and comments.
102            if (($token->type === Token::TYPE_WHITESPACE)
103                || ($token->type === Token::TYPE_COMMENT)
104            ) {
105                $lastRaw .= $token->token;
106                $lastValue = trim($lastValue) . ' ';
107                continue;
108            }
109
110            if (($brackets === 0)
111                && (($token->type !== Token::TYPE_OPERATOR)
112                || ($token->value !== '('))
113            ) {
114                $parser->error('An opening bracket was expected.', $token);
115                break;
116            }
117
118            if ($token->type === Token::TYPE_OPERATOR) {
119                if ($token->value === '(') {
120                    if (++$brackets === 1) { // 1 is the base level.
121                        continue;
122                    }
123                } elseif ($token->value === ')') {
124                    if (--$brackets === 0) { // Array ended.
125                        break;
126                    }
127                } elseif ($token->value === ',') {
128                    if ($brackets === 1) {
129                        $isCommaLast = true;
130                        if (empty($options['type'])) {
131                            $ret->raw[] = trim($lastRaw);
132                            $ret->values[] = trim($lastValue);
133                            $lastRaw = $lastValue = '';
134                        }
135                    }
136                    continue;
137                }
138            }
139
140            if (empty($options['type'])) {
141                $lastRaw .= $token->token;
142                $lastValue .= $token->value;
143            } else {
144                $ret[] = $options['type']::parse(
145                    $parser,
146                    $list,
147                    empty($options['typeOptions']) ? array() : $options['typeOptions']
148                );
149            }
150        }
151
152        // Handling last element.
153        //
154        // This is treated differently to treat the following cases:
155        //
156        //           => array()
157        //      (,)  => array('', '')
158        //      ()   => array()
159        //      (a,) => array('a', '')
160        //      (a)  => array('a')
161        //
162        $lastRaw = trim($lastRaw);
163        if ((empty($options['type']))
164            && ((strlen($lastRaw) > 0) || ($isCommaLast))
165        ) {
166            $ret->raw[] = $lastRaw;
167            $ret->values[] = trim($lastValue);
168        }
169
170        return $ret;
171    }
172
173    /**
174     * @param ArrayObj|ArrayObj[] $component the component to be built
175     * @param array               $options   parameters for building
176     *
177     * @return string
178     */
179    public static function build($component, array $options = array())
180    {
181        if (is_array($component)) {
182            return implode(', ', $component);
183        } elseif (! empty($component->raw)) {
184            return '(' . implode(', ', $component->raw) . ')';
185        }
186
187        return '(' . implode(', ', $component->values) . ')';
188    }
189}
190