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 ( 74e0f9...15201d )
by Zordius
04:34
created

Parser::detectQuote()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 36
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 16
cts 16
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 15
nc 8
nop 1
crap 8
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-2016 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 PARTIALBLOCK = 9998;
34
    const LITERAL = -1;
35
    const SUBEXP = -2;
36
37
    /**
38
     * Get partial block id and fix the variable list
39
     *
40
     * @param array<boolean|integer|string|array> $vars parsed token
41
     *
42
     * @return integer Return partial block id
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|string|array? 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...
43
     *
44
     */
45 97
    public static function getPartialBlock(&$vars) {
46 97
        if (isset($vars[static::PARTIALBLOCK])) {
47 21
            $id = $vars[static::PARTIALBLOCK];
48 21
            unset($vars[static::PARTIALBLOCK]);
49 21
            return $id;
50
        }
51 91
        return 0;
52
    }
53
54
    /**
55
     * Get block params and fix the variable list
56
     *
57
     * @param array<boolean|integer|string|array> $vars parsed token
58
     *
59
     * @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|string|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...
60
     *
61
     */
62 222
    public static function getBlockParams(&$vars) {
63 222
        if (isset($vars[static::BLOCKPARAM])) {
64 9
            $list = $vars[static::BLOCKPARAM];
65 9
            unset($vars[static::BLOCKPARAM]);
66 9
            return $list;
67
        }
68 216
    }
69
70
    /**
71
     * Return array presentation for a literal
72
     *
73
     * @param string $name variable name.
74
     * @param boolean $asis keep the name as is or not
75
     * @param boolean $quote add single quote or not
76
     *
77
     * @return array<integer|string> Return variable name array
78
     *
79
     */
80 119
    protected static function getLiteral($name, $asis, $quote = false) {
81 119
        return $asis ? array($name) : array(static::LITERAL, $quote ? "'$name'" : $name);
82
    }
83
84
    /**
85
     * Return array presentation for an expression
86
     *
87
     * @param string $v analyzed expression names.
88
     * @param array<string,array|string|integer> $context Current compile content.
89
     * @param integer $pos expression position
90
     *
91
     * @return array<integer,string> Return variable name array
92
     *
93
     * @expect array('this') when input 'this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0)), 0
94
     * @expect array() when input 'this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1)), 0
95
     * @expect array(1) when input '..', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
96
     * @expect array(1) when input '../', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
97
     * @expect array(1) when input '../.', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
98
     * @expect array(1) when input '../this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
99
     * @expect array(1, 'a') when input '../a', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
100
     * @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
101
     * @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
102
     * @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
103
     * @expect array(0, 'id') when input 'this.id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
104
     * @expect array('this', 'id') when input 'this.id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
105
     * @expect array(0, 'id') when input './id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0
106
     * @expect array(\LightnCandy\Parser::LITERAL, '\'a.b\'') when input '"a.b"', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1
107
     * @expect array(\LightnCandy\Parser::LITERAL, '123') when input '123', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1
108
     * @expect array(\LightnCandy\Parser::LITERAL, 'null') when input 'null', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1
109
     */
110 710
    protected static function getExpression($v, &$context, $pos) {
111 710
        $asis = ($pos === 0);
112
113
        // handle number
114 710
        if (is_numeric($v)) {
115 34
            return static::getLiteral(strval(1 * $v), $asis);
116
        }
117
118
        // handle double quoted string
119 704
        if (preg_match('/^"(.*)"$/', $v, $matched)) {
120 65
            return static::getLiteral(preg_replace('/([^\\\\])\\\\\\\\"/', '$1"', preg_replace('/^\\\\\\\\"/', '"', $matched[1])), $asis, true);
121
        }
122
123
        // handle single quoted string
124 698
        if (preg_match('/^\\\\\'(.*)\\\\\'$/', $v, $matched)) {
125 21
            return static::getLiteral($matched[1], $asis, true);
126
        }
127
128
        // handle boolean, null and undefined
129 695
        if (preg_match('/^(true|false|null|undefined)$/', $v)) {
130 29
            return static::getLiteral($v, $asis);
131
        }
132
133 690
        $ret = array();
134 690
        $levels = 0;
135
136
        // handle ..
137 690
        if ($v === '..') {
138 7
            $v = '../';
139
        }
140
141
        // Trace to parent for ../ N times
142 690
        $v = preg_replace_callback('/\\.\\.\\//', function() use (&$levels) {
143 48
            $levels++;
144 48
            return '';
145 690
        }, trim($v));
146
147
        // remove ./ in path
148 690
        $v = preg_replace('/\\.\\//', '', $v, -1, $scoped);
149
150 690
        $strp = (($pos !== 0) && $context['flags']['strpar']);
151 690
        if ($levels && !$strp) {
152 45
            $ret[] = $levels;
153 45
            if (!$context['flags']['parent']) {
154 4
                $context['error'][] = 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag';
155
            }
156 45
            $context['usedFeature']['parent'] ++;
157
        }
158
159 690
        if ($context['flags']['advar'] && preg_match('/\\]/', $v)) {
160 30
            preg_match_all(static::VARNAME_SEARCH, $v, $matchedall);
161
        } else {
162 666
            preg_match_all('/([^\\.\\/]+)/', $v, $matchedall);
163
        }
164
165 690
        if ($v !== '.') {
166 684
            $vv = implode('.', $matchedall[1]);
167 684
            if (strlen($v) !== strlen($vv)) {
168 8
                $context['error'][] = "Unexpected charactor in '$v' ! (should it be '$vv' ?)";
169
            }
170
        }
171
172 690
        foreach ($matchedall[1] as $m) {
173 683
            if ($context['flags']['advar'] && substr($m, 0, 1) === '[') {
174 24
                $ret[] = substr($m, 1, -1);
175 677
            } else if ((!$context['flags']['this'] || ($m !== 'this')) && ($m !== '.')) {
176 675
                $ret[] = $m;
177
            } else {
178 683
                $scoped++;
179
            }
180
        }
181
182 690
        if ($strp) {
183 10
            return array(static::LITERAL, "'" . implode('.', $ret) . "'");
184
        }
185
186 690
        if (($scoped > 0) && ($levels === 0) && (count($ret) > 0)) {
187 9
            array_unshift($ret, 0);
188
        }
189
190 690
        return $ret;
191
    }
192
193
    /**
194
     * Parse the token and return parsed result.
195
     *
196
     * @param array<string> $token preg_match results
197
     * @param array<string,array|string|integer> $context current compile context
198
     *
199
     * @return array<boolean|integer|array> Return parsed result
200
     *
201
     * @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)
202
     * @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)
203
     * @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)
204
     * @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)
205
     * @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)
206
     * @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)
207
     * @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)
208
     * @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)
209
     * @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)
210
     * @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)
211
     * @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)
212
     * @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)
213
     * @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)
214
     * @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)
215
     * @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)
216
     * @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)
217
     * @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)
218
     * @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)
219
     * @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)
220
     * @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)
221
     * @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)
222
     * @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)
223
     * @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)
224
     * @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)
225
     * @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, 'helperresolver' => 0)
226
     * @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)
227
     * @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)
228
     * @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)
229
     * @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)
230
     */
231 716
    public static function parse(&$token, &$context) {
232 716
        $vars = static::analyze($token[static::POS_INNERTAG], $context);
233 716
        if ($token[static::POS_OP] === '>') {
234 99
            $fn = static::getPartialName($vars);
235 688
        } else if ($token[static::POS_OP] === '#*') {
236 14
            $fn = static::getPartialName($vars, 1);
237
        }
238
239 716
        $avars = static::advancedVariable($vars, $context, static::toString($token));
240
241 716
        if (isset($fn) && ($fn !== null)) {
242 96
            if ($token[static::POS_OP] === '>') {
243 93
                $avars[0] = $fn;
244 13
            } else if ($token[static::POS_OP] === '#*') {
245 13
                $avars[1] = $fn;
246
            }
247
        }
248
249 716
        return array(($token[static::POS_BEGINRAW] === '{') || ($token[static::POS_OP] === '&') || $context['flags']['noesc'] || $context['rawblock'], $avars);
250
    }
251
252
    /**
253
     * Get partial name from "foo" or [foo] or \'foo\'
254
     *
255
     * @param array<boolean|integer|array> $vars parsed token
256
     * @param integer $pos position of partial name
257
     *
258
     * @return array<string>|null Return one element partial name array
259
     *
260
     * @expect null when input array()
261
     * @expect array('foo') when input array('foo')
262
     * @expect array('foo') when input array('"foo"')
263
     * @expect array('foo') when input array('[foo]')
264
     * @expect array('foo') when input array("\\'foo\\'")
265
     * @expect array('foo') when input array(0, 'foo'), 1
266
     */
267 103
    public static function getPartialName(&$vars, $pos = 0) {
268 103
        if (!isset($vars[$pos])) {
269 2
            return;
270
        }
271 102
        return preg_match(SafeString::IS_SUBEXP_SEARCH, $vars[$pos]) ? null : array(preg_replace('/^("(.+)")|(\\[(.+)\\])|(\\\\\'(.+)\\\\\')$/', '$2$4$6', $vars[$pos]));
272
    }
273
274
    /**
275
     * Parse a subexpression then return parsed result.
276
     *
277
     * @param string $expression the full string of a sub expression
278
     * @param array<string,array|string|integer> $context current compile context
279
     *
280
     * @return array<boolean|integer|array> Return parsed result
281
     *
282
     * @expect array(\LightnCandy\Parser::SUBEXP, array(array('a'), array('b')), '(a b)') when input '(a b)', array('usedFeature' => array('subexp' => 0), 'flags' => array('advar' => 0, 'namev' => 0, 'this' => 0, 'exhlp' => 1, 'strpar' => 0))
283
     */
284 48
    public static function subexpression($expression, &$context) {
285 48
        $context['usedFeature']['subexp']++;
286 48
        $vars = static::analyze(substr($expression, 1, -1), $context);
287 48
        $avars = static::advancedVariable($vars, $context, $expression);
288 48
        if (isset($avars[0][0]) && !$context['flags']['exhlp']) {
289 25
            if (!Validator::helper($context, $avars, true)) {
290 2
                $context['error'][] = "Can not find custom helper function defination {$avars[0][0]}() !";
291
            }
292
        }
293 48
        return array(static::SUBEXP, $avars, $expression);
294
    }
295
296
    /**
297
     * Check a parsed result is a subexpression or not
298
     *
299
     * @param array<string|integer|array> $var
300
     *
301
     * @return boolean return true when input is a subexpression
302
     *
303
     * @expect false when input 0
304
     * @expect false when input array()
305
     * @expect false when input array(\LightnCandy\Parser::SUBEXP, 0)
306
     * @expect false when input array(\LightnCandy\Parser::SUBEXP, 0, 0)
307
     * @expect false when input array(\LightnCandy\Parser::SUBEXP, 0, '', 0)
308
     * @expect true when input array(\LightnCandy\Parser::SUBEXP, 0, '')
309
     */
310 412
    public static function isSubExp($var) {
311 412
        return is_array($var) && (count($var) === 3) && ($var[0] === static::SUBEXP) && is_string($var[2]);
312
    }
313
314
    /**
315
     * Analyze parsed token for advanved variables.
316
     *
317
     * @param array<boolean|integer|array> $vars parsed token
318
     * @param array<string,array|string|integer> $context current compile context
319
     * @param string $token original token
320
     *
321
     * @return array<boolean|integer|array> Return parsed result
322
     *
323
     * @expect array(array('this')) when input array('this'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0,)), 0
324
     * @expect array(array()) when input array('this'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 1)), 0
325
     * @expect array(array('a')) when input array('a'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0, 'strpar' => 0)), 0
326
     * @expect array(array('a'), array('b')) when input array('a', 'b'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0, 'strpar' => 0)), 0
327
     * @expect array('a' => array('b')) when input array('a=b'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0, 'strpar' => 0)), 0
328
     * @expect array('fo o' => array(\LightnCandy\Parser::LITERAL, '123')) when input array('[fo o]=123'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0)), 0
329
     * @expect array('fo o' => array(\LightnCandy\Parser::LITERAL, '\'bar\'')) when input array('[fo o]="bar"'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0)), 0
330
     */
331 716
    protected static function advancedVariable($vars, &$context, $token) {
332 716
        $ret = array();
333 716
        $i = 0;
334 716
        foreach ($vars as $idx => $var) {
335
            // handle (...)
336 710
            if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) {
337 40
                $ret[$i] = static::subexpression($var, $context);
338 40
                $i++;
339 40
                continue;
340
            }
341
342
            // handle |...|
343 710
            if (preg_match(SafeString::IS_BLOCKPARAM_SEARCH, $var, $matched)) {
344 9
                $ret[static::BLOCKPARAM] = explode(' ', $matched[1]);
345 9
                continue;
346
            }
347
348 710
            if ($context['flags']['namev']) {
349 485
                if (preg_match('/^((\\[([^\\]]+)\\])|([^=^["\']+))=(.+)$/', $var, $m)) {
350 42
                    if (!$context['flags']['advar'] && $m[3]) {
351 1
                        $context['error'][] = "Wrong argument name as '[$m[3]]' in $token ! You should fix your template or compile with LightnCandy::FLAG_ADVARNAME flag.";
352
                    }
353 42
                    $idx = $m[3] ? $m[3] : $m[4];
354 42
                    $var = $m[5];
355
                    // handle foo=(...)
356 42
                    if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) {
357 8
                        $ret[$idx] = static::subexpression($var, $context);
358 8
                        continue;
359
                    }
360
                }
361
            }
362
363 710
            if ($context['flags']['advar'] && !preg_match("/^(\"|\\\\')(.*)(\"|\\\\')$/", $var)) {
364
                    // foo]  Rule 1: no starting [ or [ not start from head
365 500
                if (preg_match('/^[^\\[\\.]+[\\]\\[]/', $var)
366
                    // [bar  Rule 2: no ending ] or ] not in the end
367 494
                    || preg_match('/[\\[\\]][^\\]\\.]+$/', $var)
368
                    // ]bar. Rule 3: middle ] not before .
369 492
                    || preg_match('/\\][^\\]\\[\\.]+\\./', $var)
370
                    // .foo[ Rule 4: middle [ not after .
371 500
                    || preg_match('/\\.[^\\]\\[\\.]+\\[/', preg_replace('/^(..\\/)+/', '', preg_replace('/\\[[^\\]]+\\]/', '[XXX]', $var)))
372
                ) {
373 12
                    $context['error'][] = "Wrong variable naming as '$var' in $token !";
374
                } else {
375 488
                    $name = preg_replace('/(\\[.+?\\])/', '', $var);
376
                    // Scan for invalid charactors which not be protected by [ ]
377
                    // now make ( and ) pass, later fix
378 488
                    if (preg_match('/[!"#%\'*+,;<=>{|}~]/', $name)) {
379 2
                        $context['error'][] = "Wrong variable naming as '$var' in $token ! You should wrap ! \" # % & ' * + , ; < = > { | } ~ into [ ]";
380
                    }
381
                }
382
            }
383
384 710
            $var = static::getExpression($var, $context, $idx);
385
386 710
            if (is_string($idx)) {
387 36
                $ret[$idx] = $var;
388
            } else {
389 709
                $ret[$i] = $var;
390 710
                $i++;
391
            }
392
        }
393 716
        return $ret;
394
    }
395
396
    /**
397
     * Detect quote charactors
398
     *
399
     * @param string $string the string to be detect the quote charactors
400
     *
401
     * @return array<string,integer>|null Expected ending string when quote charactor be detected
402
     */
403 512
    protected static function detectQuote($string) {
404
        // begin with '(' without ending ')'
405 512
        if (preg_match('/^\([^\)]*$/', $string)) {
406 33
            return array(')', 1);
407
        }
408
409
        // begin with '"' without ending '"'
410 512
        if (preg_match('/^"[^"]*$/', $string)) {
411 10
            return array('"', 0);
412
        }
413
414
        // begin with \' without ending '
415 512
        if (preg_match('/^\\\\\'[^\']*$/', $string)) {
416 7
            return array('\'', 0);
417
        }
418
419
        // '="' exists without ending '"'
420 512
        if (preg_match('/^[^"]*="[^"]*$/', $string)) {
421 3
            return array('"', 0);
422
        }
423
424
        // '[' exists without ending ']'
425 512
        if (preg_match('/^([^"\'].+)?\\[[^\\]]*$/', $string)) {
426 9
            return array(']', 0);
427
        }
428
429
        // =\' exists without ending '
430 506
        if (preg_match('/^[^\']*=\\\\\'[^\']*$/', $string)) {
431 2
            return array('\'', 0);
432
        }
433
434
        // continue to next match when =( exists without ending )
435 506
        if (preg_match('/.+(\(+)[^\)]*$/', $string, $m)) {
436 6
            return array(')', strlen($m[1]));
437
        }
438 506
    }
439
440
    /**
441
     * Analyze a token string and return parsed result.
442
     *
443
     * @param string $token preg_match results
444
     * @param array<string,array|string|integer> $context current compile context
445
     *
446
     * @return array<boolean|integer|array> Return parsed result
447
     *
448
     * @expect array('foo', 'bar') when input 'foo bar', array('flags' => array('advar' => 1))
449
     * @expect array('foo', "'bar'") when input "foo 'bar'", array('flags' => array('advar' => 1))
450
     * @expect array('[fo o]', '"bar"') when input '[fo o] "bar"', array('flags' => array('advar' => 1))
451
     * @expect array('fo=123', 'bar="45', '6"') when input 'fo=123 bar="45 6"', array('flags' => array('advar' => 0))
452
     * @expect array('fo=123', 'bar="45 6"') when input 'fo=123 bar="45 6"', array('flags' => array('advar' => 1))
453
     * @expect array('[fo', 'o]=123') when input '[fo o]=123', array('flags' => array('advar' => 0))
454
     * @expect array('[fo o]=123') when input '[fo o]=123', array('flags' => array('advar' => 1))
455
     * @expect array('[fo o]=123', 'bar="456"') when input '[fo o]=123 bar="456"', array('flags' => array('advar' => 1))
456
     * @expect array('[fo o]="1 2 3"') when input '[fo o]="1 2 3"', array('flags' => array('advar' => 1))
457
     * @expect array('foo', 'a=(foo a=(foo a="ok"))') when input 'foo a=(foo a=(foo a="ok"))', array('flags' => array('advar' => 1))
458
     */
459 716
    protected static function analyze($token, &$context) {
460 716
        $count = preg_match_all('/(\s*)([^\s]+)/', $token, $matchedall);
461
        // Parse arguments and deal with "..." or [...] or (...) or \'...\' or |...|
462 716
        if (($count > 0) && $context['flags']['advar']) {
463 513
            $vars = array();
464 513
            $prev = '';
465 513
            $expect = 0;
466 513
            $quote = 0;
467 513
            $stack = 0;
468
469 513
            foreach ($matchedall[2] as $index => $t) {
470 513
                $detected = static::detectQuote($t);
471
472 513
                if ($expect === ')') {
473 39
                    if ($detected && ($detected[0] !== ')')) {
474 6
                        $quote = $detected[0];
475
                    }
476 39
                    if (substr($t, -1, 1) === $quote) {
477 3
                        $quote = 0;
478
                    }
479
                }
480
481
                // continue from previous match when expect something
482 513
                if ($expect) {
483 59
                    $prev .= "{$matchedall[1][$index]}$t";
484 59
                    if (($quote === 0) && ($stack > 0) && preg_match('/(.+=)*(\\(+)/', $t, $m)) {
485 12
                        $stack += strlen($m[2]);
486
                    }
487
                    // end an argument when end with expected charactor
488 59
                    if (substr($t, -1, 1) === $expect) {
489 59
                        if ($stack > 0) {
490 48
                            preg_match('/(\\)+)$/', $t, $matchedq);
491 48
                            $stack -= isset($matchedq[0]) ? strlen($matchedq[0]) : 1;
492 48
                            if ($stack > 0) {
493 4
                                continue;
494
                            }
495 48
                            if ($stack < 0) {
496 1
                                $context['error'][] = "Unexcepted ')' in expression '$token' !!";
497 1
                                $expect = 0;
498 1
                                break;
499
                            }
500
                        }
501 58
                        $vars[] = $prev;
502 58
                        $prev = '';
503 58
                        $expect = 0;
504 58
                        continue;
505 31
                    } else if (($expect == ']') && (strpos($t, $expect) !== false)) {
506 1
                        $t = $prev;
507 1
                        $detected = static::detectQuote($t);
508 1
                        $expect = 0;
509
                    } else {
510 31
                        continue;
511
                    }
512
                }
513
514
515 513
                if ($detected) {
516 56
                    $prev = $t;
517 56
                    $expect = $detected[0];
518 56
                    $stack = $detected[1];
519 56
                    continue;
520
                }
521
522
                // continue to next match when 'as' without ending '|'
523 503
                if (($t === 'as') && (count($vars) > 0)) {
524 9
                    $prev = '';
525 9
                    $expect = '|';
526 9
                    $stack=1;
527 9
                    continue;
528
                }
529
530 503
                $vars[] = $t;
531
            }
532
533 513
            if ($expect) {
534 6
                $context['error'][] = "Error in '$token': expect '$expect' but the token ended!!";
535
            }
536
537 513
            return $vars;
538
        }
539 227
        return ($count > 0) ? $matchedall[2] : explode(' ', $token);
540
    }
541
}
542
543