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 ( 67b915...bbe422 )
by Zordius
09:57 queued 33s
created

Validator::pushToken()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 19
cts 19
cp 1
rs 8.8977
c 0
b 0
f 0
cc 6
nc 6
nop 2
crap 6
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 Validator
15
 *
16
 * @package    LightnCandy
17
 * @author     Zordius <[email protected]>
18
 */
19
20
namespace LightnCandy;
21
22
use \LightnCandy\Token;
23
use \LightnCandy\Parser;
24
use \LightnCandy\Partial;
25
use \LightnCandy\Expression;
26
use \LightnCandy\SafeString;
27
28
/**
29
 * LightnCandy Validator
30
 */
31
class Validator
32
{
33
    /**
34
     * Verify template
35
     *
36
     * @param array<string,array|string|integer> $context Current context
37
     * @param string $template handlebars template
38
     */
39 794
    public static function verify(&$context, $template)
40
    {
41 794
        $template = SafeString::stripExtendedComments($template);
42 794
        $context['level'] = 0;
43 794
        Parser::setDelimiter($context);
44
45 794
        while (preg_match($context['tokens']['search'], $template, $matches)) {
46
            // Skip a token when it is slash escaped
47 783
            if ($context['flags']['slash'] && ($matches[Token::POS_LSPACE] === '') && preg_match('/^(.*?)(\\\\+)$/s', $matches[Token::POS_LOTHER], $escmatch)) {
48 4
                if (strlen($escmatch[2]) % 4) {
49 2
                    static::pushToken($context, substr($matches[Token::POS_LOTHER], 0, -2) . $context['tokens']['startchar']);
50 2
                    $matches[Token::POS_BEGINTAG] = substr($matches[Token::POS_BEGINTAG], 1);
51 2
                    $template = implode('', array_slice($matches, Token::POS_BEGINTAG));
52 2
                    continue;
53
                } else {
54 2
                    $matches[Token::POS_LOTHER] = $escmatch[1] . str_repeat('\\', strlen($escmatch[2]) / 2);
55
                }
56
            }
57 781
            $context['tokens']['count']++;
58 781
            $V = static::token($matches, $context);
59 781
            static::pushLeft($context);
60 781
            if ($V) {
61 742
                if (is_array($V)) {
62 735
                    array_push($V, $matches, $context['tokens']['partialind']);
63
                }
64 742
                static::pushToken($context, $V);
65
            }
66 781
            $template = "{$matches[Token::POS_RSPACE]}{$matches[Token::POS_ROTHER]}";
67
        }
68 794
        static::pushToken($context, $template);
69
70 794
        if ($context['level'] > 0) {
71 8
            array_pop($context['stack']);
72 8
            array_pop($context['stack']);
73 8
            $token = array_pop($context['stack']);
74 8
            $context['error'][] = 'Unclosed token ' . ($context['rawblock'] ? "{{{{{$token}}}}}" : ($context['partialblock'] ? "{{#>{$token}}}" : "{{#{$token}}}")) . ' !!';
75
        }
76 794
    }
77
78
    /**
79
     * push left string of current token and clear it
80
     *
81
     * @param array<string,array|string|integer> $context Current context
82
     */
83 781
    protected static function pushLeft(&$context)
84
    {
85 781
        $L = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
86 781
        static::pushToken($context, $L);
87 781
        $context['currentToken'][Token::POS_LOTHER] = $context['currentToken'][Token::POS_LSPACE] = '';
88 781
    }
89
90
    /**
91
     * push a string into the partial stacks
92
     *
93
     * @param array<string,array|string|integer> $context Current context
94
     * @param string $append a string to be appended int partial stacks
95
     */
96 783
    protected static function pushPartial(&$context, $append)
97
    {
98
        $appender = function (&$p) use ($append) {
99 21
            $p .= $append;
100 783
        };
101 783
        array_walk($context['inlinepartial'], $appender);
102 783
        array_walk($context['partialblock'], $appender);
103 783
    }
104
105
    /**
106
     * push a token into the stack when it is not empty string
107
     *
108
     * @param array<string,array|string|integer> $context Current context
109
     * @param string|array $token a parsed token or a string
110
     */
111 794
    protected static function pushToken(&$context, $token)
112
    {
113 794
        if ($token === '') {
114 654
            return;
115
        }
116 783
        if (is_string($token)) {
117 529
            static::pushPartial($context, $token);
118 529
            $append = $token;
0 ignored issues
show
Unused Code introduced by
$append is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
119 529
            if (is_string(end($context['parsed'][0]))) {
120 33
                $context['parsed'][0][key($context['parsed'][0])] .= $token;
121 529
                return;
122
            }
123
        } else {
124 735
            static::pushPartial($context, Token::toString($context['currentToken']));
125 735
            switch ($context['currentToken'][Token::POS_OP]) {
126 735
            case '#*':
127 16
                array_unshift($context['inlinepartial'], '');
128 16
                break;
129 732
            case '#>':
130 28
                array_unshift($context['partialblock'], '');
131 28
                break;
132
            }
133
        }
134 783
        $context['parsed'][0][] = $token;
135 783
    }
136
137
    /**
138
     * push current token into the section stack
139
     *
140
     * @param array<string,array|string|integer> $context Current context
141
     * @param string $operation operation string
142
     * @param array<boolean|integer|string|array> $vars parsed arguments list
143
     */
144 394
    protected static function pushStack(&$context, $operation, $vars)
145
    {
146 394
        list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
147 394
        $context['stack'][] = $context['currentToken'][Token::POS_INNERTAG];
148 394
        $context['stack'][] = Expression::toString($levels, $spvar, $var);
149 394
        $context['stack'][] = $operation;
150 394
        $context['level']++;
151 394
    }
152
153
    /**
154
     * Verify delimiters and operators
155
     *
156
     * @param string[] $token detected handlebars {{ }} token
157
     * @param array<string,array|string|integer> $context current compile context
158
     *
159
     * @return boolean|null Return true when invalid
160
     *
161
     * @expect null when input array_fill(0, 11, ''), array()
162
     * @expect null when input array(0, 0, 0, 0, 0, '{{', '#', '...', '}}'), array()
163
     * @expect true when input array(0, 0, 0, 0, 0, '{', '#', '...', '}'), array()
164
     */
165 782
    protected static function delimiter($token, &$context)
166
    {
167
        // {{ }}} or {{{ }} are invalid
168 782
        if (strlen($token[Token::POS_BEGINRAW]) !== strlen($token[Token::POS_ENDRAW])) {
169 6
            $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' or ' . Token::toString($token, array(Token::POS_BEGINRAW => '{', Token::POS_ENDRAW => '}')) . '?';
170 6
            return true;
171
        }
172
        // {{{# }}} or {{{! }}} or {{{/ }}} or {{{^ }}} are invalid.
173 776
        if ((strlen($token[Token::POS_BEGINRAW]) == 1) && $token[Token::POS_OP] && ($token[Token::POS_OP] !== '&')) {
174 5
            $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' ?';
175 5
            return true;
176
        }
177 772
    }
178
179
    /**
180
     * Verify operators
181
     *
182
     * @param string $operator the operator string
183
     * @param array<string,array|string|integer> $context current compile context
184
     * @param array<boolean|integer|string|array> $vars parsed arguments list
185
     *
186
     * @return boolean|integer|null Return true when invalid or detected
187
     *
188
     * @expect null when input '', array(), array()
189
     * @expect 2 when input '^', array('usedFeature' => array('isec' => 1), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'elselvl' => array(), 'flags' => array('spvar' => 0), 'elsechain' => false, 'helperresolver' => 0), array(array('foo'))
190
     * @expect true when input '/', array('stack' => array('[with]', '#'), 'level' => 1, 'currentToken' => array(0,0,0,0,0,0,0,'with'), 'flags' => array('nohbh' => 0)), array(array())
191
     * @expect 4 when input '#', array('usedFeature' => array('sec' => 3), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('x'))
192
     * @expect 5 when input '#', array('usedFeature' => array('if' => 4), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('if'))
193
     * @expect 6 when input '#', array('usedFeature' => array('with' => 5), 'level' => 0, 'flags' => array('nohbh' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('with'))
194
     * @expect 7 when input '#', array('usedFeature' => array('each' => 6), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('each'))
195
     * @expect 8 when input '#', array('usedFeature' => array('unless' => 7), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('unless'))
196
     * @expect 9 when input '#', array('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 8), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array()), array(array('abc'))
197
     * @expect 11 when input '#', array('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 10), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array()), array(array('abc'))
198
     * @expect true when input '>', array('partialresolver' => false, 'usedFeature' => array('partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elsechain' => false, 'elselvl' => array()), array('test')
199
     */
200 743
    protected static function operator($operator, &$context, &$vars)
201
    {
202 743
        switch ($operator) {
203 743
            case '#*':
204 16
                if (!$context['compile']) {
205 16
                    $context['stack'][] = count($context['parsed'][0]) + ($context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE] === '' ? 0 : 1);
206 16
                    static::pushStack($context, '#*', $vars);
207
                }
208 16
                return static::inline($context, $vars);
209
210 740
            case '#>':
211 28
                if (!$context['compile']) {
212 28
                    $context['stack'][] = count($context['parsed'][0]) + ($context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE] === '' ? 0 : 1);
213 28
                    $vars[Parser::PARTIALBLOCK] = ++$context['usedFeature']['pblock'];
214 28
                    static::pushStack($context, '#>', $vars);
215
                }
216
                // no break
217 732
            case '>':
218 119
                return static::partial($context, $vars);
219
220 684
            case '^':
221 64
                if (!isset($vars[0][0])) {
222 24
                    if (!$context['flags']['else']) {
223 1
                        $context['error'][] = 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag';
224 1
                        return;
225
                    } else {
226 23
                        return static::doElse($context, $vars);
227
                    }
228
                }
229
230 40
                static::doElseChain($context);
231
232 40
                if (static::isBlockHelper($context, $vars)) {
233 1
                    static::pushStack($context, '#', $vars);
234 1
                    return static::blockCustomHelper($context, $vars, true);
235
                }
236
237 39
                static::pushStack($context, '^', $vars);
238 39
                return static::invertedSection($context, $vars);
239
240 684
            case '/':
241 361
                $r = static::blockEnd($context, $vars);
242 361
                if ($r !== Token::POS_BACKFILL) {
243 361
                    array_pop($context['stack']);
244 361
                    array_pop($context['stack']);
245 361
                    array_pop($context['stack']);
246
                }
247 361
                return $r;
248
249 659
            case '#':
250 337
                static::doElseChain($context);
251 337
                static::pushStack($context, '#', $vars);
252
253 337
                if (static::isBlockHelper($context, $vars)) {
254 64
                    return static::blockCustomHelper($context, $vars);
255
                }
256
257 282
                return static::blockBegin($context, $vars);
258
        }
259 555
    }
260
261
    /**
262
     * validate inline partial begin token
263
     *
264
     * @param array<string,array|string|integer> $context current compile context
265
     * @param array<boolean|integer|string|array> $vars parsed arguments list
266
     *
267
     * @return boolean|null Return true when inline partial ends
268
     */
269 742
    protected static function inlinePartial(&$context, $vars)
270
    {
271 742
        $ended = false;
272 742
        if ($context['currentToken'][Token::POS_OP] === '/') {
273 387
            if (static::blockEnd($context, $vars, '#*') !== null) {
274 16
                $context['usedFeature']['inlpartial']++;
275 16
                $tmpl = array_shift($context['inlinepartial']) . $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
276 16
                $c = $context['stack'][count($context['stack']) - 4];
277 16
                $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
278 16
                $P = &$context['parsed'][0][$c];
279 16
                if (isset($P[1][1][0])) {
280 15
                    $context['usedPartial'][$P[1][1][0]] = $tmpl;
281 15
                    $P[1][0][0] = Partial::compileDynamic($context, $P[1][1][0]);
282
                }
283 16
                $ended = true;
284
            }
285
        }
286 742
        return $ended;
287
    }
288
289
    /**
290
     * validate partial block token
291
     *
292
     * @param array<string,array|string|integer> $context current compile context
293
     * @param array<boolean|integer|string|array> $vars parsed arguments list
294
     *
295
     * @return boolean|null Return true when partial block ends
296
     */
297 742
    protected static function partialBlock(&$context, $vars)
298
    {
299 742
        $ended = false;
300 742
        if ($context['currentToken'][Token::POS_OP] === '/') {
301 387
            if (static::blockEnd($context, $vars, '#>') !== null) {
302 27
                $c = $context['stack'][count($context['stack']) - 4];
303 27
                $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
304 27
                $found = Partial::resolve($context, $vars[0][0]) !== null;
305 27
                $v = $found ? "@partial-block{$context['parsed'][0][$c][1][Parser::PARTIALBLOCK]}" : "{$vars[0][0]}";
306 27
                if (count($context['partialblock']) == 1) {
307 27
                    $tmpl = $context['partialblock'][0] . $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
308 27
                    if ($found) {
309 19
                        $context['partials'][$v] = $tmpl;
310
                    }
311 27
                    $context['usedPartial'][$v] = $tmpl;
312 27
                    Partial::compileDynamic($context, $v);
313 27
                    if ($found) {
314 19
                        Partial::read($context, $vars[0][0]);
315
                    }
316
                }
317 27
                array_shift($context['partialblock']);
318 27
                $ended = true;
319
            }
320
        }
321 742
        return $ended;
322
    }
323
324
    /**
325
     * handle else chain
326
     *
327
     * @param array<string,array|string|integer> $context current compile context
328
     */
329 366
    protected static function doElseChain(&$context)
330
    {
331 366
        if ($context['elsechain']) {
332 14
            $context['elsechain'] = false;
333
        } else {
334 366
            array_unshift($context['elselvl'], array());
335
        }
336 366
    }
337
338
    /**
339
     * validate block begin token
340
     *
341
     * @param array<string,array|string|integer> $context current compile context
342
     * @param array<boolean|integer|string|array> $vars parsed arguments list
343
     *
344
     * @return boolean Return true always
345
     */
346 281
    protected static function blockBegin(&$context, $vars)
347
    {
348 281
        switch ((isset($vars[0][0]) && is_string($vars[0][0])) ? $vars[0][0] : null) {
349 281
            case 'with':
350 36
                return static::with($context, $vars);
351 252
            case 'each':
352 59
                return static::section($context, $vars, true);
353 204
            case 'unless':
354 7
                return static::unless($context, $vars);
355 198
            case 'if':
356 80
                return static::doIf($context, $vars);
357
            default:
358 125
                return static::section($context, $vars);
359
        }
360
    }
361
362
    /**
363
     * validate builtin helpers
364
     *
365
     * @param array<string,array|string|integer> $context current compile context
366
     * @param array<boolean|integer|string|array> $vars parsed arguments list
367
     */
368 165
    protected static function builtin(&$context, $vars)
369
    {
370 165
        if ($context['flags']['nohbh']) {
371 8
            if (isset($vars[1][0])) {
372 8
                $context['error'][] = "Do not support {{#{$vars[0][0]} var}} because you compile with LightnCandy::FLAG_NOHBHELPERS flag";
373
            }
374
        } else {
375 157
            if (count($vars) < 2) {
376 5
                $context['error'][] = "No argument after {{#{$vars[0][0]}}} !";
377
            }
378
        }
379 165
        $context['usedFeature'][$vars[0][0]]++;
380 165
    }
381
382
    /**
383
     * validate section token
384
     *
385
     * @param array<string,array|string|integer> $context current compile context
386
     * @param array<boolean|integer|string|array> $vars parsed arguments list
387
     * @param boolean $isEach the section is #each
388
     *
389
     * @return boolean Return true always
390
     */
391 184
    protected static function section(&$context, $vars, $isEach = false)
392
    {
393 184
        if ($isEach) {
394 59
            static::builtin($context, $vars);
395
        } else {
396 125
            if ((count($vars) > 1) && !$context['flags']['lambda']) {
397 1
                $context['error'][] = "Custom helper not found: {$vars[0][0]} in " . Token::toString($context['currentToken']) . ' !';
398
            }
399 125
            $context['usedFeature']['sec']++;
400
        }
401 184
        return true;
402
    }
403
404
    /**
405
     * validate with token
406
     *
407
     * @param array<string,array|string|integer> $context current compile context
408
     * @param array<boolean|integer|string|array> $vars parsed arguments list
409
     *
410
     * @return boolean Return true always
411
     */
412 36
    protected static function with(&$context, $vars)
413
    {
414 36
        static::builtin($context, $vars);
415 36
        return true;
416
    }
417
418
    /**
419
     * validate unless token
420
     *
421
     * @param array<string,array|string|integer> $context current compile context
422
     * @param array<boolean|integer|string|array> $vars parsed arguments list
423
     *
424
     * @return boolean Return true always
425
     */
426 7
    protected static function unless(&$context, $vars)
427
    {
428 7
        static::builtin($context, $vars);
429 7
        return true;
430
    }
431
432
    /**
433
     * validate if token
434
     *
435
     * @param array<string,array|string|integer> $context current compile context
436
     * @param array<boolean|integer|string|array> $vars parsed arguments list
437
     *
438
     * @return boolean Return true always
439
     */
440 80
    protected static function doIf(&$context, $vars)
441
    {
442 80
        static::builtin($context, $vars);
443 80
        return true;
444
    }
445
446
    /**
447
     * validate block custom helper token
448
     *
449
     * @param array<string,array|string|integer> $context current compile context
450
     * @param array<boolean|integer|string|array> $vars parsed arguments list
451
     * @param boolean $inverted the logic will be inverted
452
     *
453
     * @return integer|null Return number of used custom helpers
454
     */
455 64
    protected static function blockCustomHelper(&$context, $vars, $inverted = false)
456
    {
457 64
        if (is_string($vars[0][0])) {
458 64
            if (static::resolveHelper($context, $vars)) {
459 64
                return ++$context['usedFeature']['helper'];
460
            }
461
        }
462
    }
463
464
    /**
465
     * validate inverted section
466
     *
467
     * @param array<string,array|string|integer> $context current compile context
468
     * @param array<boolean|integer|string|array> $vars parsed arguments list
469
     *
470
     * @return integer Return number of inverted sections
471
     */
472 38
    protected static function invertedSection(&$context, $vars)
473
    {
474 38
        return ++$context['usedFeature']['isec'];
475
    }
476
477
    /**
478
     * Return compiled PHP code for a handlebars block end token
479
     *
480
     * @param array<string,array|string|integer> $context current compile context
481
     * @param array<boolean|integer|string|array> $vars parsed arguments list
482
     * @param string|null $match should also match to this operator
483
     *
484
     * @return boolean|integer Return true when required block ended, or Token::POS_BACKFILL when backfill happened.
485
     */
486 387
    protected static function blockEnd(&$context, &$vars, $match = null)
487
    {
488 387
        $c = count($context['stack']) - 2;
489 387
        $pop = ($c >= 0) ? $context['stack'][$c + 1] : '';
490 387
        if (($match !== null) && ($match !== $pop)) {
491 387
            return;
492
        }
493
        // if we didn't match our $pop, we didn't actually do a level, so only subtract a level here
494 387
        $context['level']--;
495 387
        $pop2 = ($c >= 0) ? $context['stack'][$c]: '';
496 387
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
497 387
            case 'with':
498 40
                if (!$context['flags']['nohbh']) {
499 38
                    if ($pop2 !== '[with]') {
500 1
                        $context['error'][] = 'Unexpect token: {{/with}} !';
501 1
                        return;
502
                    }
503
                }
504 39
                return true;
505
        }
506
507 365
        switch ($pop) {
508 365
            case '#':
509 78
            case '^':
510 330
                $elsechain = array_shift($context['elselvl']);
511 330
                if (isset($elsechain[0])) {
512
                    // we need to repeat a level due to else chains: {{else if}}
513 14
                    $context['level']++;
514 14
                    $context['currentToken'][Token::POS_RSPACE] = $context['currentToken'][Token::POS_BACKFILL] = '{{/' . implode('}}{{/', $elsechain) . '}}' . Token::toString($context['currentToken']) . $context['currentToken'][Token::POS_RSPACE];
515 14
                    return Token::POS_BACKFILL;
516
                }
517
                // no break
518 40
            case '#>':
519 17
            case '#*':
520 364
                list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
521 364
                $v = Expression::toString($levels, $spvar, $var);
522 364
                if ($pop2 !== $v) {
523 2
                    $context['error'][] = 'Unexpect token ' . Token::toString($context['currentToken']) . " ! Previous token {{{$pop}$pop2}} is not closed";
524 2
                    return;
525
                }
526 363
                return true;
527
            default:
528 1
                $context['error'][] = 'Unexpect token: ' . Token::toString($context['currentToken']) . ' !';
529 1
                return;
530
        }
531
    }
532
533
    /**
534
     * handle delimiter change
535
     *
536
     * @param array<string,array|string|integer> $context current compile context
537
     *
538
     * @return boolean|null Return true when delimiter changed
539
     */
540 771
    protected static function isDelimiter(&$context)
541
    {
542 771
        if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $context['currentToken'][Token::POS_INNERTAG], $matched)) {
543 15
            $context['usedFeature']['delimiter']++;
544 15
            Parser::setDelimiter($context, $matched[1], $matched[2]);
545 15
            return true;
546
        }
547 762
    }
548
549
    /**
550
     * handle raw block
551
     *
552
     * @param string[] $token detected handlebars {{ }} token
553
     * @param array<string,array|string|integer> $context current compile context
554
     *
555
     * @return boolean|null Return true when in rawblock mode
556
     */
557 781
    protected static function rawblock(&$token, &$context)
558
    {
559 781
        $inner = $token[Token::POS_INNERTAG];
560 781
        trim($inner);
561
562
        // skip parse when inside raw block
563 781
        if ($context['rawblock'] && !(($token[Token::POS_BEGINRAW] === '{{') && ($token[Token::POS_OP] === '/') && ($context['rawblock'] === $inner))) {
564 6
            return true;
565
        }
566
567 781
        $token[Token::POS_INNERTAG] = $inner;
568
569
        // Handle raw block
570 781
        if ($token[Token::POS_BEGINRAW] === '{{') {
571 9
            if ($token[Token::POS_ENDRAW] !== '}}') {
572 1
                $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_ENDRAW => '}}')) . ' ?';
573
            }
574 9
            if ($context['rawblock']) {
575 6
                Parser::setDelimiter($context);
576 6
                $context['rawblock'] = false;
577
            } else {
578 9
                if ($token[Token::POS_OP]) {
579 1
                    $context['error'][] = "Wrong raw block begin with " . Token::toString($token) . ' ! Remove "' . $token[Token::POS_OP] . '" to fix this issue.';
580
                }
581 9
                $context['rawblock'] = $token[Token::POS_INNERTAG];
582 9
                Parser::setDelimiter($context);
583 9
                $token[Token::POS_OP] = '#';
584
            }
585 9
            $token[Token::POS_ENDRAW] = '}}';
586
        }
587 781
    }
588
589
    /**
590
     * handle comment
591
     *
592
     * @param string[] $token detected handlebars {{ }} token
593
     * @param array<string,array|string|integer> $context current compile context
594
     *
595
     * @return boolean|null Return true when is comment
596
     */
597 762
    protected static function comment(&$token, &$context)
598
    {
599 762
        if ($token[Token::POS_OP] === '!') {
600 26
            $context['usedFeature']['comment']++;
601 26
            return true;
602
        }
603 742
    }
604
605
    /**
606
     * Collect handlebars usage information, detect template error.
607
     *
608
     * @param string[] $token detected handlebars {{ }} token
609
     * @param array<string,array|string|integer> $context current compile context
610
     *
611
     * @return string|array<string,array|string|integer>|null $token string when rawblock; array when valid token require to be compiled, null when skip the token.
612
     */
613 781
    protected static function token(&$token, &$context)
614
    {
615 781
        $context['currentToken'] = &$token;
616
617 781
        if (static::rawblock($token, $context)) {
618 6
            return Token::toString($token);
619
        }
620
621 781
        if (static::delimiter($token, $context)) {
622 10
            return;
623
        }
624
625 771
        if (static::isDelimiter($context)) {
626 15
            static::spacing($token, $context);
627 15
            return;
628
        }
629
630 762
        if (static::comment($token, $context)) {
631 26
            static::spacing($token, $context);
632 26
            return;
633
        }
634
635 742
        list($raw, $vars) = Parser::parse($token, $context);
636
637
        // Handle spacing (standalone tags, partial indent)
638 742
        static::spacing($token, $context, (($token[Token::POS_OP] === '') || ($token[Token::POS_OP] === '&')) && (!$context['flags']['else'] || !isset($vars[0][0]) || ($vars[0][0] !== 'else')) || ($context['flags']['nostd'] > 0));
639
640 742
        $inlinepartial = static::inlinePartial($context, $vars);
641 742
        $partialblock = static::partialBlock($context, $vars);
642
643 742
        if ($partialblock || $inlinepartial) {
644 39
            $context['stack'] = array_slice($context['stack'], 0, -4);
645 39
            static::pushPartial($context, $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE] . Token::toString($context['currentToken']));
646 39
            $context['currentToken'][Token::POS_LOTHER] = '';
647 39
            $context['currentToken'][Token::POS_LSPACE] = '';
648 39
            return;
649
        }
650
651 742
        if (static::operator($token[Token::POS_OP], $context, $vars)) {
652 452
            return isset($token[Token::POS_BACKFILL]) ? null : array($raw, $vars);
653
        }
654
655 559
        if (count($vars) == 0) {
656 6
            return $context['error'][] = 'Wrong variable naming in ' . Token::toString($token);
657
        }
658
659 553
        if (!isset($vars[0])) {
660 1
            return $context['error'][] = 'Do not support name=value in ' . Token::toString($token) . ', you should use it after a custom helper.';
661
        }
662
663 552
        $context['usedFeature'][$raw ? 'raw' : 'enc']++;
664
665 552
        foreach ($vars as $var) {
666 552
            if (!isset($var[0]) || ($var[0] === 0)) {
667 77
                if ($context['level'] == 0) {
668 26
                    $context['usedFeature']['rootthis']++;
669
                }
670 77
                $context['usedFeature']['this']++;
671
            }
672
        }
673
674 552
        if (!isset($vars[0][0])) {
675 57
            return array($raw, $vars);
676
        }
677
678 518
        if (($vars[0][0] === 'else') && $context['flags']['else']) {
679 39
            static::doElse($context, $vars);
680 39
            return array($raw, $vars);
681
        }
682
683 491
        if (!static::helper($context, $vars)) {
684 373
            static::lookup($context, $vars);
685 373
            static::log($context, $vars);
686
        }
687
688 491
        return array($raw, $vars);
689
    }
690
691
    /**
692
     * Return 1 or larger number when else token detected
693
     *
694
     * @param array<string,array|string|integer> $context current compile context
695
     * @param array<boolean|integer|string|array> $vars parsed arguments list
696
     *
697
     * @return integer Return 1 or larger number when else token detected
698
     */
699 61
    protected static function doElse(&$context, $vars)
700
    {
701 61
        if ($context['level'] == 0) {
702 1
            $context['error'][] = '{{else}} only valid in if, unless, each, and #section context';
703
        }
704
705 61
        if (isset($vars[1][0])) {
706 14
            $token = $context['currentToken'];
707 14
            $context['currentToken'][Token::POS_INNERTAG] = 'else';
708 14
            $context['currentToken'][Token::POS_RSPACE] = "{{#{$vars[1][0]} " . preg_replace('/^\\s*else\\s+' . $vars[1][0] . '\\s*/', '', $token[Token::POS_INNERTAG]) . '}}' . $context['currentToken'][Token::POS_RSPACE];
709 14
            array_unshift($context['elselvl'][0], $vars[1][0]);
710 14
            $context['elsechain'] = true;
711
        }
712
713 61
        return ++$context['usedFeature']['else'];
714
    }
715
716
    /**
717
     * Return true when this is {{log ...}}
718
     *
719
     * @param array<string,array|string|integer> $context current compile context
720
     * @param array<boolean|integer|string|array> $vars parsed arguments list
721
     *
722
     * @return boolean|null Return true when it is custom helper
723
     */
724 373
    public static function log(&$context, $vars)
725
    {
726 373
        if (isset($vars[0][0]) && ($vars[0][0] === 'log')) {
727 3
            if (!$context['flags']['nohbh']) {
728 3
                if (count($vars) < 2) {
729 1
                    $context['error'][] = "No argument after {{log}} !";
730
                }
731 3
                $context['usedFeature']['log']++;
732 3
                return true;
733
            }
734
        }
735 370
    }
736
737
    /**
738
     * Return true when this is {{lookup ...}}
739
     *
740
     * @param array<string,array|string|integer> $context current compile context
741
     * @param array<boolean|integer|string|array> $vars parsed arguments list
742
     *
743
     * @return boolean|null Return true when it is custom helper
744
     */
745 373
    public static function lookup(&$context, $vars)
746
    {
747 373
        if (isset($vars[0][0]) && ($vars[0][0] === 'lookup')) {
748 7
            if (!$context['flags']['nohbh']) {
749 7
                if (count($vars) < 2) {
750 1
                    $context['error'][] = "No argument after {{lookup}} !";
751 6
                } elseif (count($vars) < 3) {
752 1
                    $context['error'][] = "{{lookup}} requires 2 arguments !";
753
                }
754 7
                $context['usedFeature']['lookup']++;
755 7
                return true;
756
            }
757
        }
758 366
    }
759
760
    /**
761
     * Return true when the name is listed in helper table
762
     *
763
     * @param array<string,array|string|integer> $context current compile context
764
     * @param array<boolean|integer|string|array> $vars parsed arguments list
765
     * @param boolean $checkSubexp true when check for subexpression
766
     *
767
     * @return boolean Return true when it is custom helper
768
     */
769 499
    public static function helper(&$context, $vars, $checkSubexp = false)
770
    {
771 499
        if (static::resolveHelper($context, $vars)) {
772 133
            $context['usedFeature']['helper']++;
773 133
            return true;
774
        }
775
776 376
        if ($checkSubexp) {
777 5
            switch ($vars[0][0]) {
778 5
                case 'if':
779 5
                case 'unless':
780 5
                case 'with':
781 5
                case 'each':
782 5
                case 'lookup':
783 3
                    return $context['flags']['nohbh'] ? false : true;
784
            }
785
        }
786
787 375
        return false;
788
    }
789
790
    /**
791
     * use helperresolver to resolve helper, return true when helper founded
792
     *
793
     * @param array<string,array|string|integer> $context Current context of compiler progress.
794
     * @param array<boolean|integer|string|array> $vars parsed arguments list
795
     *
796
     * @return boolean $found helper exists or not
797
     */
798 668
    public static function resolveHelper(&$context, &$vars)
799
    {
800 668
        if (count($vars[0]) !== 1) {
801 100
            return false;
802
        }
803 610
        if (isset($context['helpers'][$vars[0][0]])) {
804 185
            return true;
805
        }
806
807 475
        if ($context['helperresolver']) {
808 3
            $helper = $context['helperresolver']($context, $vars[0][0]);
809 3
            if ($helper) {
810 3
                $context['helpers'][$vars[0][0]] = $helper;
811 3
                return true;
812
            }
813
        }
814
815 472
        return false;
816
    }
817
818
    /**
819
     * detect for block custom helper
820
     *
821
     * @param array<string,array|string|integer> $context current compile context
822
     * @param array<boolean|integer|string|array> $vars parsed arguments list
823
     *
824
     * @return boolean|null Return true when this token is block custom helper
825
     */
826 366
    protected static function isBlockHelper($context, $vars)
827
    {
828 366
        if (!isset($vars[0][0])) {
829 4
            return;
830
        }
831
832 363
        if (!static::resolveHelper($context, $vars)) {
833 307
            return;
834
        }
835
836 64
        return true;
837
    }
838
839
    /**
840
     * validate inline partial
841
     *
842
     * @param array<string,array|string|integer> $context current compile context
843
     * @param array<boolean|integer|string|array> $vars parsed arguments list
844
     *
845
     * @return boolean Return true always
846
     */
847 16
    protected static function inline(&$context, $vars)
848
    {
849 16
        if (!$context['flags']['runpart']) {
850 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
851
        }
852 16
        if (!isset($vars[0][0]) || ($vars[0][0] !== 'inline')) {
853 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, now we only support {{#*inline \"partialName\"}}template...{{/inline}}";
854
        }
855 16
        if (!isset($vars[1][0])) {
856 1
            $context['error'][] = "Error in {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}: inline require 1 argument for partial name!";
857
        }
858 16
        return true;
859
    }
860
861
    /**
862
     * validate partial
863
     *
864
     * @param array<string,array|string|integer> $context current compile context
865
     * @param array<boolean|integer|string|array> $vars parsed arguments list
866
     *
867
     * @return integer|boolean Return 1 or larger number for runtime partial, return true for other case
868
     */
869 118
    protected static function partial(&$context, $vars)
870
    {
871 118
        if (Parser::isSubExp($vars[0])) {
872 7
            if ($context['flags']['runpart']) {
873 6
                return $context['usedFeature']['dynpartial']++;
874
            } else {
875 1
                $context['error'][] = "You use dynamic partial name as '{$vars[0][2]}', this only works with option FLAG_RUNTIMEPARTIAL enabled";
876 1
                return true;
877
            }
878
        } else {
879 111
            if ($context['currentToken'][Token::POS_OP] !== '#>') {
880 100
                Partial::read($context, $vars[0][0]);
881
            }
882
        }
883 111
        if (!$context['flags']['runpart']) {
884 12
            $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0;
885 12
            if ($named || (count($vars) > 1)) {
886 1
                $context['error'][] = "Do not support {{>{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
887
            }
888
        }
889
890 111
        return true;
891
    }
892
893
    /**
894
     * Modify $token when spacing rules matched.
895
     *
896
     * @param array<string> $token detected handlebars {{ }} token
897
     * @param array<string,array|string|integer> $context current compile context
898
     * @param boolean $nost do not do stand alone logic
899
     *
900
     * @return string|null Return compiled code segment for the token
901
     */
902 771
    protected static function spacing(&$token, &$context, $nost = false)
903
    {
904
        // left line change detection
905 771
        $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[Token::POS_LSPACE], $lmatch);
906 771
        $ind = $lsp ? $lmatch[3] : $token[Token::POS_LSPACE];
907
        // right line change detection
908 771
        $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[Token::POS_RSPACE], $rmatch);
909 771
        $st = true;
910
        // setup ahead flag
911 771
        $ahead = $context['tokens']['ahead'];
912 771
        $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[Token::POS_RSPACE] . $token[Token::POS_ROTHER]);
913
        // reset partial indent
914 771
        $context['tokens']['partialind'] = '';
915
        // same tags in the same line , not standalone
916 771
        if (!$lsp && $ahead) {
917 382
            $st = false;
918
        }
919 771
        if ($nost) {
920 533
            $st = false;
921
        }
922
        // not standalone because other things in the same line ahead
923 771
        if ($token[Token::POS_LOTHER] && !$token[Token::POS_LSPACE]) {
924 238
            $st = false;
925
        }
926
        // not standalone because other things in the same line behind
927 771
        if ($token[Token::POS_ROTHER] && !$token[Token::POS_RSPACE]) {
928 381
            $st = false;
929
        }
930 771
        if ($st && (
931 174
            ($lsp && $rsp) // both side cr
932 146
                || ($rsp && !$token[Token::POS_LOTHER]) // first line without left
933 771
                || ($lsp && !$token[Token::POS_ROTHER]) // final line
934
            )) {
935
            // handle partial
936 71
            if ($token[Token::POS_OP] === '>') {
937 17
                if (!$context['flags']['noind']) {
938 12
                    $context['tokens']['partialind'] = $token[Token::POS_LSPACECTL] ? '' : $ind;
939 17
                    $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
940
                }
941
            } else {
942 60
                $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
943
            }
944 71
            $token[Token::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : '';
945
        }
946
947
        // Handle space control.
948 771
        if ($token[Token::POS_LSPACECTL]) {
949 32
            $token[Token::POS_LSPACE] = '';
950
        }
951 771
        if ($token[Token::POS_RSPACECTL]) {
952 35
            $token[Token::POS_RSPACE] = '';
953
        }
954 771
    }
955
}
956