GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( facd84...731f7f )
by Zordius
07:00
created

Parser::getPartialName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4286
cc 3
eloc 4
nc 3
nop 2
crap 3
1
<?php
2
/*
3
4
Copyrights for code authored by Yahoo! Inc. is licensed under the following terms:
5
MIT License
6
Copyright (c) 2013-2015 Yahoo! Inc. All Rights Reserved.
7
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
11
Origin: https://github.com/zordius/lightncandy
12
*/
13
14
/**
15
 * file to keep LightnCandy Parser
16
 *
17
 * @package    LightnCandy
18
 * @author     Zordius <[email protected]>
19
 */
20
21
namespace LightnCandy;
22
23
use \LightnCandy\Token;
24
use \LightnCandy\SafeString;
25
26
/**
27
 * LightnCandy Parser
28
 */
29
class Parser extends Token
30
{
31
    // Compile time error handling flags
32
    const BLOCKPARAM = 9999;
33
    const LITERAL = -1;
34
    const SUBEXP = -2;
35
36
    /**
37
     * Get block params and fix the variable list
38
     *
39
     * @param array<boolean|integer|array> $vars parsed token
40
     *
41
     * @return array<string>|null Return list of block params or null
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
42
     *
43
     */
44 200
    public static function getBlockParams(&$vars) {
45 200
        if (isset($vars[static::BLOCKPARAM])) {
46 8
            $list = $vars[static::BLOCKPARAM];
47 8
            unset($vars[static::BLOCKPARAM]);
48 8
            return $list;
49
        }
50 195
    }
51
52
    /**
53
     * Return array presentation for an expression
54
     *
55
     * @param string $name variable name.
56
     * @param boolean $asis keep the name as is or not
57
     * @param boolean $quote add single quote or not
58
     *
59
     * @return array<integer|string> Return variable name array
60
     *
61
     */
62 105
    protected static function getLiteral($name, $asis, $quote = false) {
63 105
        return $asis ? array($name) : array(static::LITERAL, $quote ? "'$name'" : $name);
64
    }
65
66
    /**
67
     * Return array presentation for an expression
68
     *
69
     * @param string $v analyzed expression names.
70
     * @param array<string,array|string|integer> $context Current compile content.
71
     * @param integer $pos expression position
72
     *
73
     * @return array<integer,string> Return variable name array
74
     *
75
     * @expect array('this') when input 'this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0)), 0
76
     * @expect array() when input 'this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1)), 0
77
     * @expect array(1) when input '../', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
78
     * @expect array(1) when input '../.', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
79
     * @expect array(1) when input '../this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
80
     * @expect array(1, 'a') when input '../a', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
81
     * @expect array(2, 'a', 'b') when input '../../a.b', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
82
     * @expect array(2, '[a]', 'b') when input '../../[a].b', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
83
     * @expect array(2, 'a', 'b') when input '../../[a].b', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
84
     * @expect array(0, 'id') when input 'this.id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
85
     * @expect array('this', 'id') when input 'this.id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
86
     * @expect array(0, 'id') when input './id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
87
     * @expect array(-1, '\'a.b\'') when input '"a.b"', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1
88
     * @expect array(-1, '123') when input '123', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1
89
     * @expect array(-1, 'null') when input 'null', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1
90
     */
91 665
    protected static function getExpression($v, &$context, $pos) {
92 665
        $asis = ($pos === 0);
93
94
        // handle number
95 665
        if (is_numeric($v)) {
96 29
            return static::getLiteral(strval(1 * $v), $asis);
97
        }
98
99
        // handle double quoted string
100 660
        if (preg_match('/^"(.*)"$/', $v, $matched)) {
101 56
            return static::getLiteral(preg_replace('/([^\\\\])\\\\\\\\"/', '$1"', preg_replace('/^\\\\\\\\"/', '"', $matched[1])), $asis, true);
102
        }
103
104
        // handle single quoted string
105 654
        if (preg_match('/^\\\\\'(.*)\\\\\'$/', $v, $matched)) {
106 21
            return static::getLiteral($matched[1], $asis, true);
107
        }
108
109
        // handle boolean, null and undefined
110 651
        if (preg_match('/^(true|false|null|undefined)$/', $v)) {
111 29
            return static::getLiteral($v, $asis);
112
        }
113
114 646
        $ret = array();
115 646
        $levels = 0;
116
117
        // handle ..
118 646
        if ($v === '..') {
119 6
            $v = '../';
120
        }
121
122
        // Trace to parent for ../ N times
123 646
        $v = preg_replace_callback('/\\.\\.\\//', function() use (&$levels) {
124 46
            $levels++;
125 46
            return '';
126 646
        }, trim($v));
127
128
        // remove ./ in path
129 646
        $v = preg_replace('/\\.\\//', '', $v, -1, $scoped);
130
131 646
        $strp = (($pos !== 0) && $context['flags']['strpar']);
132 646
        if ($levels && !$strp) {
133 43
            $ret[] = $levels;
134 43
            if (!$context['flags']['parent']) {
135 4
                $context['error'][] = 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag';
136
            }
137 43
            $context['usedFeature']['parent'] ++;
138
        }
139
140 646
        if ($context['flags']['advar'] && preg_match('/\\]/', $v)) {
141 30
            preg_match_all(static::VARNAME_SEARCH, $v, $matchedall);
142
        } else {
143 622
            preg_match_all('/([^\\.\\/]+)/', $v, $matchedall);
144
        }
145
146 646
        foreach ($matchedall[1] as $m) {
147 639
            if ($context['flags']['advar'] && substr($m, 0, 1) === '[') {
148 24
                $ret[] = substr($m, 1, -1);
149 633
            } else if ((!$context['flags']['this'] || ($m !== 'this')) && ($m !== '.')) {
150 631
                $ret[] = $m;
151
            } else {
152 639
                $scoped++;
153
            }
154
        }
155
156 646
        if ($strp) {
157 10
            return array(static::LITERAL, "'" . implode('.', $ret) . "'");
158
        }
159
160 646
        if (($scoped > 0) && ($levels === 0) && (count($ret) > 0)) {
161 9
            array_unshift($ret, 0);
162
        }
163
164 646
        return $ret;
165
    }
166
167
    /**
168
     * Parse the token and return parsed result.
169
     *
170
     * @param array<string> $token preg_match results
171
     * @param array<string,array|string|integer> $context current compile context
172
     *
173
     * @return array<boolean|integer|array> Return parsed result
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<boolean|array<boolean|integer|array>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
174
     *
175
     * @expect array(false, array(array())) when input array(0,0,0,0,0,0,0,''), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
176
     * @expect array(true, array(array())) when input array(0,0,0,'{{',0,'{',0,''), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
177
     * @expect array(true, array(array())) when input array(0,0,0,0,0,0,0,''), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 1), 'rawblock' => false)
178
     * @expect array(false, array(array('a'))) when input array(0,0,0,0,0,0,0,'a'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
179
     * @expect array(false, array(array('a'), array('b'))) when input array(0,0,0,0,0,0,0,'a  b'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
180
     * @expect array(false, array(array('a'), array('"b'), array('c"'))) when input array(0,0,0,0,0,0,0,'a "b c"'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
181
     * @expect array(false, array(array('a'), array(-1, '\'b c\''))) when input array(0,0,0,0,0,0,0,'a "b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
182
     * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
183
     * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
184
     * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
185
     * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
186
     * @expect array(false, array(array('a'), 'q' => array('b c'))) when input array(0,0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
187
     * @expect array(false, array(array('a'), array('q=[b c'))) when input array(0,0,0,0,0,0,0,'a [q=[b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
188
     * @expect array(false, array(array('a'), 'q' => array('[b'), array('c]'))) when input array(0,0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
189
     * @expect array(false, array(array('a'), 'q' => array('b'), array('c'))) when input array(0,0,0,0,0,0,0,'a [q]=b c'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
190
     * @expect array(false, array(array('a'), 'q' => array(-1, '\'b c\''))) when input array(0,0,0,0,0,0,0,'a q="b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
191
     * @expect array(false, array(array(-2, array(array('foo'), array('bar')), '(foo bar)'))) when input array(0,0,0,0,0,0,0,'(foo bar)'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 1, 'lambda' => 0), 'ops' => array('seperator' => ''), 'usedFeature' => array('subexp' => 0), 'rawblock' => false)
192
     * @expect array(false, array(array('foo'), array("'=='"), array('bar'))) when input array(0,0,0,0,0,0,0,"foo '==' bar"), array('flags' => array('strpar' => 0, 'advar' => 1, 'namev' => 1, 'noesc' => 0, 'this' => 0), 'rawblock' => false)
193
     * @expect array(false, array(array(-2, array(array('foo'), array('bar')), '( foo bar)'))) when input array(0,0,0,0,0,0,0,'( foo bar)'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 1, 'lambda' => 0), 'ops' => array('seperator' => ''), 'usedFeature' => array('subexp' => 0), 'rawblock' => false)
194
     * @expect array(false, array(array('a'), array(-1, '\' b c\''))) when input array(0,0,0,0,0,0,0,'a " b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false)
195
     * @expect array(false, array(array('a'), 'q' => array(-1, '\' b c\''))) when input array(0,0,0,0,0,0,0,'a q=" b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
196
     * @expect array(false, array(array('foo'), array(-1, "' =='"), array('bar'))) when input array(0,0,0,0,0,0,0,"foo \' ==\' bar"), array('flags' => array('strpar' => 0, 'advar' => 1, 'namev' => 1, 'noesc' => 0, 'this' => 0), 'rawblock' => false)
197
     * @expect array(false, array(array('a'), array(' b c'))) when input array(0,0,0,0,0,0,0,'a [ b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
198
     * @expect array(false, array(array('a'), 'q' => array(-1, "' d e'"))) when input array(0,0,0,0,0,0,0,"a q=\' d e\'"), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false)
199
     * @expect array(false, array('q' => array(-2, array(array('foo'), array('bar')), '( foo bar)'))) when input array(0,0,0,0,0,0,0,'q=( foo bar)'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false)
200
     * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','foo'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false)
201
     * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','"foo"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false)
202
     * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','[foo] '), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false)
203
     * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','\\\'foo\\\''), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false)
204
     */
205 671
    public static function parse(&$token, &$context) {
206 671
        $vars = static::analyze($token[static::POS_INNERTAG], $context);
207 671
        if ($token[static::POS_OP] === '>') {
208 89
            $fn = static::getPartialName($vars);
209 646
        } else if ($token[static::POS_OP] === '#*') {
210 13
            $fn = static::getPartialName($vars, 1);
211
        }
212
213 671
        $avars = static::advancedVariable($vars, $context, static::toString($token));
214
215 671
        if (isset($fn) && ($fn !== null)) {
216 86
            if ($token[static::POS_OP] === '>') {
217 84
                $avars[0] = $fn;
218 12
            } else if ($token[static::POS_OP] === '#*') {
219 12
                $avars[1] = $fn;
220
            }
221
        }
222
223 671
        return array(($token[static::POS_BEGINRAW] === '{') || ($token[static::POS_OP] === '&') || $context['flags']['noesc'] || $context['rawblock'], $avars);
224
    }
225
226
    /**
227
     * Get block params and fix the variable list
228
     *
229
     * @param array<boolean|integer|array> $vars parsed token
230
     * @param integer $pos position of partial name
231
     *
232
     * @return array<string>|null Return list of block params or null
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string|string[]>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
233
     *
234
     */
235 91
    public static function getPartialName(&$vars, $pos = 0) {
236 91
        if (!isset($vars[$pos])) {
237 1
            return;
238
        }
239 90
        return preg_match(SafeString::IS_SUBEXP_SEARCH, $vars[$pos]) ? null : array(preg_replace('/^("(.+)")|(\\[(.+)\\])|(\\\\\'(.+)\\\\\')$/', '$2$4$6', $vars[$pos]));
240
    }
241
242
    /**
243
     * Parse a subexpression then return parsed result.
244
     *
245
     * @param string $expression the full string of a sub expression
246
     * @param array<string,array|string|integer> $context current compile context
247
     *
248
     * @return array<boolean|integer|array> Return parsed result
0 ignored issues
show
Documentation introduced by
Should the return type not be array<integer|array<bool...|integer|array>|string>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
249
     */
250 43
    public static function subexpression($expression, &$context) {
251 43
        $context['usedFeature']['subexp']++;
252 43
        $vars = static::analyze(substr($expression, 1, -1), $context);
253 43
        $avars = static::advancedVariable($vars, $context, $expression);
254 43
        if (isset($avars[0][0]) && !$context['flags']['exhlp']) {
255 21
            if (!Validator::helper($context, $avars[0][0])) {
256 2
                $context['error'][] = "Can not find custom helper function defination {$avars[0][0]}() !";
257
            }
258
        }
259 43
        return array(static::SUBEXP, $avars, $expression);
260
    }
261
262
    /**
263
     * Check a parsed result is a subexpression or not
264
     *
265
     * @param array<string|integer|array> $var
266
     *
267
     * @return boolean return true when input is a subexpression
268
     */
269 377
    public static function isSubExp($var) {
270 377
        return is_array($var) && (count($var) === 3) && ($var[0] === static::SUBEXP) && is_string($var[2]);
271
    }
272
273
    /**
274
     * Analyze parsed token for advanved variables.
275
     *
276
     * @param array<boolean|integer|array> $vars parsed token
277
     * @param array<string,array|string|integer> $context current compile context
278
     * @param string $token original token
279
     *
280
     * @return array<boolean|integer|array> Return parsed result
281
     *
282
     */
283 670
    protected static function advancedVariable($vars, &$context, $token) {
284 670
        $ret = array();
285 670
        $i = 0;
286 670
        foreach ($vars as $idx => $var) {
287
            // handle (...)
288 664
            if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) {
289 37
                $ret[$i] = static::subexpression($var, $context);
290 37
                $i++;
291 37
                continue;
292
            }
293
294
            // handle |...|
295 664
            if (preg_match(SafeString::IS_BLOCKPARAM_SEARCH, $var, $matched)) {
296 8
                $ret[static::BLOCKPARAM] = explode(' ', $matched[1]);
297 8
                continue;
298
            }
299
300 664
            if ($context['flags']['namev']) {
301 453
                if (preg_match('/^((\\[([^\\]]+)\\])|([^=^["\']+))=(.+)$/', $var, $m)) {
302 39
                    if (!$context['flags']['advar'] && $m[3]) {
303 1
                        $context['error'][] = "Wrong argument name as '[$m[3]]' in $token ! You should fix your template or compile with LightnCandy::FLAG_ADVARNAME flag.";
304
                    }
305 39
                    $idx = $m[3] ? $m[3] : $m[4];
306 39
                    $var = $m[5];
307
                    // handle foo=(...)
308 39
                    if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) {
309 7
                        $ret[$idx] = static::subexpression($var, $context);
310 7
                        continue;
311
                    }
312
                }
313
            }
314
315 664
            if ($context['flags']['advar'] && !preg_match("/^(\"|\\\\')(.*)(\"|\\\\')$/", $var)) {
316
                    // foo]  Rule 1: no starting [ or [ not start from head
317 468
                if (preg_match('/^[^\\[\\.]+[\\]\\[]/', $var)
318
                    // [bar  Rule 2: no ending ] or ] not in the end
319 462
                    || preg_match('/[\\[\\]][^\\]\\.]+$/', $var)
320
                    // ]bar. Rule 3: middle ] not before .
321 460
                    || preg_match('/\\][^\\]\\[\\.]+\\./', $var)
322
                    // .foo[ Rule 4: middle [ not after .
323 468
                    || preg_match('/\\.[^\\]\\[\\.]+\\[/', preg_replace('/^(..\\/)+/', '', preg_replace('/\\[[^\\]]+\\]/', '[XXX]', $var)))
324
                ) {
325 12
                    $context['error'][] = "Wrong variable naming as '$var' in $token !";
326
                } else {
327 456
                    $name = preg_replace('/(\\[.+?\\])/', '', $var);
328
                    // Scan for invalid charactors which not be protected by [ ]
329
                    // now make ( and ) pass, later fix
330 456
                    if (preg_match('/[!"#%\'*+,;<=>{|}~]/', $name)) {
331 2
                        $context['error'][] = "Wrong variable naming as '$var' in $token ! You should wrap ! \" # % & ' * + , ; < = > { | } ~ into [ ]";
332
                    }
333
                }
334
            }
335
336 664
            $var = static::getExpression($var, $context, $idx);
337
338 664
            if (is_string($idx)) {
339 33
                $ret[$idx] = $var;
340
            } else {
341 663
                $ret[$i] = $var;
342 664
                $i++;
343
            }
344
        }
345 670
        return $ret;
346
    }
347
348
    /**
349
     * Analyze a token string and return parsed result.
350
     *
351
     * @param string $token preg_match results
352
     * @param array<string,array|string|integer> $context current compile context
353
     *
354
     * @return array<boolean|integer|array> Return parsed result
355
     *
356
     */
357 670
    protected static function analyze($token, &$context) {
358 670
        $count = preg_match_all('/(\s*)([^\s]+)/', $token, $matchedall);
359
        // Parse arguments and deal with "..." or [...] or (...) or \'...\' or |...|
360 670
        if (($count > 0) && $context['flags']['advar']) {
361 481
            $vars = array();
362 481
            $prev = '';
363 481
            $expect = 0;
364 481
            $stack = 0;
365
366 481
            foreach ($matchedall[2] as $index => $t) {
367
                // continue from previous match when expect something
368 481
                if ($expect) {
369 53
                    $prev .= "{$matchedall[1][$index]}$t";
370 53
                    if (($stack > 0) && (substr($t, 0, 1) === '(')) {
371 10
                        $stack++;
372
                    }
373
                    // end an argument when end with expected charactor
374 53
                    if (substr($t, -1, 1) === $expect) {
375 53
                        if ($stack > 0) {
376 42
                            preg_match('/(\\)+)$/', $t, $matchedq);
377 42
                            $stack -= isset($matchedq[0]) ? strlen($matchedq[0]) : 1;
378 42
                            if ($stack > 0) {
379 4
                                continue;
380
                            }
381 42
                            if ($stack < 0) {
382 1
                                $context['error'][] = "Unexcepted ')' in expression '$token' !!";
383 1
                                break;
384
                            }
385
                        }
386 52
                        $vars[] = $prev;
387 52
                        $prev = '';
388 52
                        $expect = 0;
389
                    }
390 53
                    continue;
391
                }
392
393
                // continue to next match when begin with '(' without ending ')'
394 481
                if (preg_match('/^\([^\)]*$/', $t)) {
395 30
                    $prev = $t;
396 30
                    $expect = ')';
397 30
                    $stack=1;
398 30
                    continue;
399
                }
400
401
                // continue to next match when begin with '"' without ending '"'
402 481
                if (preg_match('/^"[^"]*$/', $t)) {
403 7
                    $prev = $t;
404 7
                    $expect = '"';
405 7
                    continue;
406
                }
407
408
                // continue to next match when begin with \' without ending '
409 480
                if (preg_match('/^\\\\\'[^\']*$/', $t)) {
410 6
                    $prev = $t;
411 6
                    $expect = '\'';
412 6
                    continue;
413
                }
414
415
                // continue to next match when '="' exists without ending '"'
416 479
                if (preg_match('/^[^"]*="[^"]*$/', $t)) {
417 2
                    $prev = $t;
418 2
                    $expect = '"';
419 2
                    continue;
420
                }
421
422
                // continue to next match when '[' exists without ending ']'
423 479
                if (preg_match('/^([^"\'].+)?\\[[^\\]]*$/', $t)) {
424 9
                    $prev = $t;
425 9
                    $expect = ']';
426 9
                    continue;
427
                }
428
429
                // continue to next match when =\' exists without ending '
430 471
                if (preg_match('/^[^\']*=\\\\\'[^\']*$/', $t)) {
431 1
                    $prev = $t;
432 1
                    $expect = '\'';
433 1
                    continue;
434
                }
435
436
                // continue to next match when =( exists without ending )
437 471
                if (preg_match('/.+\([^\)]*$/', $t)) {
438 5
                    $prev = $t;
439 5
                    $expect = ')';
440 5
                    $stack=1;
441 5
                    continue;
442
                }
443
444
                // continue to next match when 'as' without ending '|'
445 471
                if (($t === 'as') && (count($vars) > 0)) {
446 8
                    $prev = '';
447 8
                    $expect = '|';
448 8
                    $stack=1;
449 8
                    continue;
450
                }
451
452 471
                $vars[] = $t;
453
            }
454 481
            return $vars;
455
        }
456 212
        return ($count > 0) ? $matchedall[2] : explode(' ', $token);
457
    }
458
}
459
460