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 ( 21f791...86a886 )
by Zordius
04:17
created

src/Parser.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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