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 ( a810fc...c9811d )
by Zordius
02:09
created

Parser::getExpression()   F

Complexity

Conditions 24
Paths 868

Size

Total Lines 82
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 24

Importance

Changes 6
Bugs 1 Features 1
Metric Value
c 6
b 1
f 1
dl 0
loc 82
ccs 43
cts 43
cp 1
rs 2.439
cc 24
eloc 45
nc 868
nop 3
crap 24

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 204
    public static function getBlockParams(&$vars) {
63 204
        if (isset($vars[static::BLOCKPARAM])) {
64 8
            $list = $vars[static::BLOCKPARAM];
65 8
            unset($vars[static::BLOCKPARAM]);
66 8
            return $list;
67
        }
68 199
    }
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 108
    protected static function getLiteral($name, $asis, $quote = false) {
81 108
        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 673
    protected static function getExpression($v, &$context, $pos) {
111 673
        $asis = ($pos === 0);
112
113
        // handle number
114 673
        if (is_numeric($v)) {
115 29
            return static::getLiteral(strval(1 * $v), $asis);
116
        }
117
118
        // handle double quoted string
119 668
        if (preg_match('/^"(.*)"$/', $v, $matched)) {
120 59
            return static::getLiteral(preg_replace('/([^\\\\])\\\\\\\\"/', '$1"', preg_replace('/^\\\\\\\\"/', '"', $matched[1])), $asis, true);
121
        }
122
123
        // handle single quoted string
124 662
        if (preg_match('/^\\\\\'(.*)\\\\\'$/', $v, $matched)) {
125 21
            return static::getLiteral($matched[1], $asis, true);
126
        }
127
128
        // handle boolean, null and undefined
129 659
        if (preg_match('/^(true|false|null|undefined)$/', $v)) {
130 29
            return static::getLiteral($v, $asis);
131
        }
132
133 654
        $ret = array();
134 654
        $levels = 0;
135
136
        // handle ..
137 654
        if ($v === '..') {
138 7
            $v = '../';
139
        }
140
141
        // Trace to parent for ../ N times
142 654
        $v = preg_replace_callback('/\\.\\.\\//', function() use (&$levels) {
143 47
            $levels++;
144 47
            return '';
145 654
        }, trim($v));
146
147
        // remove ./ in path
148 654
        $v = preg_replace('/\\.\\//', '', $v, -1, $scoped);
149
150 654
        $strp = (($pos !== 0) && $context['flags']['strpar']);
151 654
        if ($levels && !$strp) {
152 44
            $ret[] = $levels;
153 44
            if (!$context['flags']['parent']) {
154 4
                $context['error'][] = 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag';
155
            }
156 44
            $context['usedFeature']['parent'] ++;
157
        }
158
159 654
        if ($context['flags']['advar'] && preg_match('/\\]/', $v)) {
160 30
            preg_match_all(static::VARNAME_SEARCH, $v, $matchedall);
161
        } else {
162 630
            preg_match_all('/([^\\.\\/]+)/', $v, $matchedall);
163
        }
164
165 654
        if ($v !== '.') {
166 648
            $vv = implode('.', $matchedall[1]);
167 648
            if (strlen($v) !== strlen($vv)) {
168 8
                $context['error'][] = "Unexpected charactor in '$v' ! (should it be '$vv' ?)";
169
            }
170
        }
171
172 654
        foreach ($matchedall[1] as $m) {
173 647
            if ($context['flags']['advar'] && substr($m, 0, 1) === '[') {
174 24
                $ret[] = substr($m, 1, -1);
175 641
            } else if ((!$context['flags']['this'] || ($m !== 'this')) && ($m !== '.')) {
176 639
                $ret[] = $m;
177
            } else {
178 647
                $scoped++;
179
            }
180
        }
181
182 654
        if ($strp) {
183 10
            return array(static::LITERAL, "'" . implode('.', $ret) . "'");
184
        }
185
186 654
        if (($scoped > 0) && ($levels === 0) && (count($ret) > 0)) {
187 9
            array_unshift($ret, 0);
188
        }
189
190 654
        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
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...
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 679
    public static function parse(&$token, &$context) {
232 679
        $vars = static::analyze($token[static::POS_INNERTAG], $context);
233 679
        if ($token[static::POS_OP] === '>') {
234 91
            $fn = static::getPartialName($vars);
235 652
        } else if ($token[static::POS_OP] === '#*') {
236 13
            $fn = static::getPartialName($vars, 1);
237
        }
238
239 679
        $avars = static::advancedVariable($vars, $context, static::toString($token));
240
241 679
        if (isset($fn) && ($fn !== null)) {
242 88
            if ($token[static::POS_OP] === '>') {
243 86
                $avars[0] = $fn;
244 12
            } else if ($token[static::POS_OP] === '#*') {
245 12
                $avars[1] = $fn;
246
            }
247
        }
248
249 679
        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
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...
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 94
    public static function getPartialName(&$vars, $pos = 0) {
268 94
        if (!isset($vars[$pos])) {
269 2
            return;
270
        }
271 93
        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
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...
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 44
    public static function subexpression($expression, &$context) {
285 44
        $context['usedFeature']['subexp']++;
286 44
        $vars = static::analyze(substr($expression, 1, -1), $context);
287 44
        $avars = static::advancedVariable($vars, $context, $expression);
288 44
        if (isset($avars[0][0]) && !$context['flags']['exhlp']) {
289 21
            if (!Validator::helper($context, $avars[0][0])) {
290 2
                $context['error'][] = "Can not find custom helper function defination {$avars[0][0]}() !";
291
            }
292
        }
293 44
        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 386
    public static function isSubExp($var) {
311 386
        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 679
    protected static function advancedVariable($vars, &$context, $token) {
332 679
        $ret = array();
333 679
        $i = 0;
334 679
        foreach ($vars as $idx => $var) {
335
            // handle (...)
336 673
            if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) {
337 37
                $ret[$i] = static::subexpression($var, $context);
338 37
                $i++;
339 37
                continue;
340
            }
341
342
            // handle |...|
343 673
            if (preg_match(SafeString::IS_BLOCKPARAM_SEARCH, $var, $matched)) {
344 8
                $ret[static::BLOCKPARAM] = explode(' ', $matched[1]);
345 8
                continue;
346
            }
347
348 673
            if ($context['flags']['namev']) {
349 458
                if (preg_match('/^((\\[([^\\]]+)\\])|([^=^["\']+))=(.+)$/', $var, $m)) {
350 41
                    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 41
                    $idx = $m[3] ? $m[3] : $m[4];
354 41
                    $var = $m[5];
355
                    // handle foo=(...)
356 41
                    if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) {
357 7
                        $ret[$idx] = static::subexpression($var, $context);
358 7
                        continue;
359
                    }
360
                }
361
            }
362
363 673
            if ($context['flags']['advar'] && !preg_match("/^(\"|\\\\')(.*)(\"|\\\\')$/", $var)) {
364
                    // foo]  Rule 1: no starting [ or [ not start from head
365 473
                if (preg_match('/^[^\\[\\.]+[\\]\\[]/', $var)
366
                    // [bar  Rule 2: no ending ] or ] not in the end
367 467
                    || preg_match('/[\\[\\]][^\\]\\.]+$/', $var)
368
                    // ]bar. Rule 3: middle ] not before .
369 465
                    || preg_match('/\\][^\\]\\[\\.]+\\./', $var)
370
                    // .foo[ Rule 4: middle [ not after .
371 473
                    || preg_match('/\\.[^\\]\\[\\.]+\\[/', preg_replace('/^(..\\/)+/', '', preg_replace('/\\[[^\\]]+\\]/', '[XXX]', $var)))
372
                ) {
373 12
                    $context['error'][] = "Wrong variable naming as '$var' in $token !";
374
                } else {
375 461
                    $name = preg_replace('/(\\[.+?\\])/', '', $var);
376
                    // Scan for invalid charactors which not be protected by [ ]
377
                    // now make ( and ) pass, later fix
378 461
                    if (preg_match('/[!"#%\'*+,;<=>{|}~]/', $name)) {
379 2
                        $context['error'][] = "Wrong variable naming as '$var' in $token ! You should wrap ! \" # % & ' * + , ; < = > { | } ~ into [ ]";
380
                    }
381
                }
382
            }
383
384 673
            $var = static::getExpression($var, $context, $idx);
385
386 673
            if (is_string($idx)) {
387 35
                $ret[$idx] = $var;
388
            } else {
389 672
                $ret[$i] = $var;
390 673
                $i++;
391
            }
392
        }
393 679
        return $ret;
394
    }
395
396
    /**
397
     * Analyze a token string and return parsed result.
398
     *
399
     * @param string $token preg_match results
400
     * @param array<string,array|string|integer> $context current compile context
401
     *
402
     * @return array<boolean|integer|array> Return parsed result
403
     *
404
     * @expect array('foo', 'bar') when input 'foo bar', array('flags' => array('advar' => 1))
405
     * @expect array('foo', "'bar'") when input "foo 'bar'", array('flags' => array('advar' => 1))
406
     * @expect array('[fo o]', '"bar"') when input '[fo o] "bar"', array('flags' => array('advar' => 1))
407
     * @expect array('fo=123', 'bar="45', '6"') when input 'fo=123 bar="45 6"', array('flags' => array('advar' => 0))
408
     * @expect array('fo=123', 'bar="45 6"') when input 'fo=123 bar="45 6"', array('flags' => array('advar' => 1))
409
     * @expect array('[fo', 'o]=123') when input '[fo o]=123', array('flags' => array('advar' => 0))
410
     * @expect array('[fo o]=123') when input '[fo o]=123', array('flags' => array('advar' => 1))
411
     * @expect array('[fo o]=123', 'bar="456"') when input '[fo o]=123 bar="456"', array('flags' => array('advar' => 1))
412
     */
413 679
    protected static function analyze($token, &$context) {
414 679
        $count = preg_match_all('/(\s*)([^\s]+)/', $token, $matchedall);
415
        // Parse arguments and deal with "..." or [...] or (...) or \'...\' or |...|
416 679
        if (($count > 0) && $context['flags']['advar']) {
417 486
            $vars = array();
418 486
            $prev = '';
419 486
            $expect = 0;
420 486
            $stack = 0;
421
422 486
            foreach ($matchedall[2] as $index => $t) {
423
                // continue from previous match when expect something
424 486
                if ($expect) {
425 54
                    $prev .= "{$matchedall[1][$index]}$t";
426 54
                    if (($stack > 0) && (substr($t, 0, 1) === '(')) {
427 10
                        $stack++;
428
                    }
429
                    // end an argument when end with expected charactor
430 54
                    if (substr($t, -1, 1) === $expect || (($expect == ']') && (strpos($t, $expect) !== false))) {
431 54
                        if ($stack > 0) {
432 42
                            preg_match('/(\\)+)$/', $t, $matchedq);
433 42
                            $stack -= isset($matchedq[0]) ? strlen($matchedq[0]) : 1;
434 42
                            if ($stack > 0) {
435 4
                                continue;
436
                            }
437 42
                            if ($stack < 0) {
438 1
                                $context['error'][] = "Unexcepted ')' in expression '$token' !!";
439 1
                                $expect = 0;
440 1
                                break;
441
                            }
442
                        }
443 53
                        $vars[] = $prev;
444 53
                        $prev = '';
445 53
                        $expect = 0;
446
                    }
447 54
                    continue;
448
                }
449
450
                // continue to next match when begin with '(' without ending ')'
451 486
                if (preg_match('/^\([^\)]*$/', $t)) {
452 30
                    $prev = $t;
453 30
                    $expect = ')';
454 30
                    $stack=1;
455 30
                    continue;
456
                }
457
458
                // continue to next match when begin with '"' without ending '"'
459 486
                if (preg_match('/^"[^"]*$/', $t)) {
460 7
                    $prev = $t;
461 7
                    $expect = '"';
462 7
                    continue;
463
                }
464
465
                // continue to next match when begin with \' without ending '
466 485
                if (preg_match('/^\\\\\'[^\']*$/', $t)) {
467 6
                    $prev = $t;
468 6
                    $expect = '\'';
469 6
                    continue;
470
                }
471
472
                // continue to next match when '="' exists without ending '"'
473 484
                if (preg_match('/^[^"]*="[^"]*$/', $t)) {
474 3
                    $prev = $t;
475 3
                    $expect = '"';
476 3
                    continue;
477
                }
478
479
                // continue to next match when '[' exists without ending ']'
480 484
                if (preg_match('/^([^"\'].+)?\\[[^\\]]*$/', $t)) {
481 10
                    $prev = $t;
482 10
                    $expect = ']';
483 10
                    continue;
484
                }
485
486
                // continue to next match when =\' exists without ending '
487 476
                if (preg_match('/^[^\']*=\\\\\'[^\']*$/', $t)) {
488 1
                    $prev = $t;
489 1
                    $expect = '\'';
490 1
                    continue;
491
                }
492
493
                // continue to next match when =( exists without ending )
494 476
                if (preg_match('/.+\([^\)]*$/', $t)) {
495 5
                    $prev = $t;
496 5
                    $expect = ')';
497 5
                    $stack=1;
498 5
                    continue;
499
                }
500
501
                // continue to next match when 'as' without ending '|'
502 476
                if (($t === 'as') && (count($vars) > 0)) {
503 8
                    $prev = '';
504 8
                    $expect = '|';
505 8
                    $stack=1;
506 8
                    continue;
507
                }
508
509 476
                $vars[] = $t;
510
            }
511
512 486
            if ($expect) {
513 6
                $context['error'][] = "Error in '$token': expect '$expect' but the token ended!!";
514
            }
515
516 486
            return $vars;
517
        }
518 217
        return ($count > 0) ? $matchedall[2] : explode(' ', $token);
519
    }
520
}
521
522