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 ( 24d0ed...428a72 )
by Zordius
03:29
created

Parser::getLiteral()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

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