1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13: 14: 15: 16: 17: 18:
19:
20: namespace LightnCandy;
21:
22: use \LightnCandy\Validator;
23: use \LightnCandy\Token;
24: use \LightnCandy\Expression;
25: use \LightnCandy\Parser;
26:
27: 28: 29:
30: class Compiler extends Validator
31: {
32: public static $lastParsed;
33:
34: 35: 36: 37: 38: 39: 40: 41:
42: public static function compileTemplate(&$context, $template)
43: {
44: array_unshift($context['parsed'], array());
45: Validator::verify($context, $template);
46: static::$lastParsed = $context['parsed'];
47:
48: if (count($context['error'])) {
49: return;
50: }
51:
52: Parser::setDelimiter($context);
53:
54: $context['compile'] = true;
55:
56:
57: Partial::handleDynamic($context);
58:
59:
60: $code = '';
61: foreach ($context['parsed'][0] as $info) {
62: if (is_array($info)) {
63: $context['tokens']['current']++;
64: $code .= "'" . static::compileToken($context, $info) . "'";
65: } else {
66: $code .= $info;
67: }
68: }
69:
70: array_shift($context['parsed']);
71:
72: return $code;
73: }
74:
75: 76: 77: 78: 79: 80: 81: 82:
83: public static function composePHPRender($context, $code)
84: {
85: $flagJStrue = Expression::boolString($context['flags']['jstrue']);
86: $flagJSObj = Expression::boolString($context['flags']['jsobj']);
87: $flagJSLen = Expression::boolString($context['flags']['jslen']);
88: $flagSPVar = Expression::boolString($context['flags']['spvar']);
89: $flagProp = Expression::boolString($context['flags']['prop']);
90: $flagMethod = Expression::boolString($context['flags']['method']);
91: $flagLambda = Expression::boolString($context['flags']['lambda']);
92: $flagMustlok = Expression::boolString($context['flags']['mustlok']);
93: $flagMustlam = Expression::boolString($context['flags']['mustlam']);
94: $flagMustsec = Expression::boolString($context['flags']['mustsec']);
95: $flagEcho = Expression::boolString($context['flags']['echo']);
96: $flagPartNC = Expression::boolString($context['flags']['partnc']);
97: $flagKnownHlp = Expression::boolString($context['flags']['knohlp']);
98:
99: $constants = Exporter::constants($context);
100: $helpers = Exporter::helpers($context);
101: $partials = implode(",\n", $context['partialCode']);
102: $debug = Runtime::DEBUG_ERROR_LOG;
103: $use = $context['flags']['standalone'] ? Exporter::runtime($context) : "use {$context['runtime']} as {$context['runtimealias']};";
104: $stringObject = $context['flags']['method'] || $context['flags']['prop'] ? Exporter::stringobject($context) : '';
105: $safeString = (($context['usedFeature']['enc'] > 0) && ($context['flags']['standalone'] === 0)) ? "use {$context['safestring']} as SafeString;" : '';
106: $exportSafeString = (($context['usedFeature']['enc'] > 0) && ($context['flags']['standalone'] >0)) ? Exporter::safestring($context) : '';
107:
108: return <<<VAREND
109: $stringObject{$safeString}{$use}{$exportSafeString}return function (\$in = null, \$options = null) {
110: \$helpers = $helpers;
111: \$partials = array($partials);
112: \$cx = array(
113: 'flags' => array(
114: 'jstrue' => $flagJStrue,
115: 'jsobj' => $flagJSObj,
116: 'jslen' => $flagJSLen,
117: 'spvar' => $flagSPVar,
118: 'prop' => $flagProp,
119: 'method' => $flagMethod,
120: 'lambda' => $flagLambda,
121: 'mustlok' => $flagMustlok,
122: 'mustlam' => $flagMustlam,
123: 'mustsec' => $flagMustsec,
124: 'echo' => $flagEcho,
125: 'partnc' => $flagPartNC,
126: 'knohlp' => $flagKnownHlp,
127: 'debug' => isset(\$options['debug']) ? \$options['debug'] : $debug,
128: ),
129: 'constants' => $constants,
130: 'helpers' => isset(\$options['helpers']) ? array_merge(\$helpers, \$options['helpers']) : \$helpers,
131: 'partials' => isset(\$options['partials']) ? array_merge(\$partials, \$options['partials']) : \$partials,
132: 'scopes' => array(),
133: 'sp_vars' => isset(\$options['data']) ? array_merge(array('root' => \$in), \$options['data']) : array('root' => \$in),
134: 'blparam' => array(),
135: 'partialid' => 0,
136: 'runtime' => '{$context['runtime']}',
137: );
138: {$context['renderex']}
139: {$context['ops']['array_check']}
140: {$context['ops']['op_start']}'$code'{$context['ops']['op_end']}
141: };
142: VAREND
143: ;
144: }
145:
146: /**
147: * Get function name for standalone or none standalone template.
148: *
149: * @param array<string,array|string|integer> $context Current context of compiler progress.
150: * @param string $name base function name
151: * @param string $tag original handlabars tag for debug
152: *
153: * @return string compiled Function name
154: *
155: * @expect 'LR::test(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 'LR'), 'test', ''
156: * @expect 'LL::test2(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 'LL'), 'test2', ''
157: * @expect "lala_abctest3(" when input array('flags' => array('standalone' => 1, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 0, 'funcprefix' => 'lala_abc'), 'test3', ''
158: * @expect 'RR::debug(\'abc\', \'test\', ' when input array('flags' => array('standalone' => 0, 'debug' => 1), 'runtime' => 'Runtime', 'runtimealias' => 'RR', 'funcprefix' => 'haha456'), 'test', 'abc'
159: */
160: protected static function getFuncName(&$context, $name, $tag)
161: {
162: static::addUsageCount($context, 'runtime', $name);
163:
164: if ($context['flags']['debug'] && ($name != 'miss')) {
165: $dbg = "'$tag', '$name', ";
166: $name = 'debug';
167: static::addUsageCount($context, 'runtime', 'debug');
168: } else {
169: $dbg = '';
170: }
171:
172: return $context['flags']['standalone'] ? "{$context['funcprefix']}$name($dbg" : "{$context['runtimealias']}::$name($dbg";
173: }
174:
175: /**
176: * Get string presentation of variables
177: *
178: * @param array<string,array|string|integer> $context current compile context
179: * @param array<array> $vn variable name array.
180: * @param array<string>|null $blockParams block param list
181: *
182: * @return array<string|array> variable names
183: *
184: * @expect array('array(array($in),array())', array('this')) when input array('flags'=>array('spvar'=>true)), array(null)
185: * @expect array('array(array($in,$in),array())', array('this', 'this')) when input array('flags'=>array('spvar'=>true)), array(null, null)
186: * @expect array('array(array(),array(\'a\'=>$in))', array('this')) when input array('flags'=>array('spvar'=>true)), array('a' => null)
187: */
188: protected static function getVariableNames(&$context, $vn, $blockParams = null)
189: {
190: $vars = array(array(), array());
191: $exps = array();
192: foreach ($vn as $i => $v) {
193: $V = static::getVariableNameOrSubExpression($context, $v);
194: if (is_string($i)) {
195: $vars[1][] = "'$i'=>{$V[0]}";
196: } else {
197: $vars[0][] = $V[0];
198: }
199: $exps[] = $V[1];
200: }
201: $bp = $blockParams ? (',array(' . Expression::listString($blockParams) . ')') : '';
202: return array('array(array(' . implode(',', $vars[0]) . '),array(' . implode(',', $vars[1]) . ")$bp)", $exps);
203: }
204:
205: /**
206: * Get string presentation of a sub expression
207: *
208: * @param array<string,array|string|integer> $context current compile context
209: * @param array<boolean|integer|string|array> $vars parsed arguments list
210: *
211: * @return array<string> code representing passed expression
212: */
213: public static function compileSubExpression(&$context, $vars)
214: {
215: $ret = static::customHelper($context, $vars, true, true, true);
216:
217: if (($ret === null) && $context['flags']['lambda']) {
218: $ret = static::compileVariable($context, $vars, true, true);
219: }
220:
221: return array($ret ? $ret : '', 'FIXME: $subExpression');
222: }
223:
224: /**
225: * Get string presentation of a subexpression or a variable
226: *
227: * @param array<array|string|integer> $context current compile context
228: * @param array<array|string|integer> $var variable parsed path
229: *
230: * @return array<string> variable names
231: */
232: protected static function getVariableNameOrSubExpression(&$context, $var)
233: {
234: return Parser::isSubExp($var) ? static::compileSubExpression($context, $var[1]) : static::getVariableName($context, $var);
235: }
236:
237: /**
238: * Get string presentation of a variable
239: *
240: * @param array<array|string|integer> $var variable parsed path
241: * @param array<array|string|integer> $context current compile context
242: * @param array<string>|null $lookup extra lookup string as valid PHP variable name
243: *
244: * @return array<string> variable names
245: *
246: * @expect array('$in', 'this') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(null)
247: * @expect array('(($inary && isset($in[\'true\'])) ? $in[\'true\'] : null)', '[true]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('true')
248: * @expect array('(($inary && isset($in[\'false\'])) ? $in[\'false\'] : null)', '[false]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('false')
249: * @expect array('true', 'true') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'true')
250: * @expect array('false', 'false') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'false')
251: * @expect array('(($inary && isset($in[\'2\'])) ? $in[\'2\'] : null)', '[2]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('2')
252: * @expect array('2', '2') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0)), array(-1, '2')
253: * @expect array('(($inary && isset($in[\'@index\'])) ? $in[\'@index\'] : null)', '[@index]') when input array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@index')
254: * @expect array("(isset(\$cx['sp_vars']['index']) ? \$cx['sp_vars']['index'] : null)", '@[index]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@index')
255: * @expect array("(isset(\$cx['sp_vars']['key']) ? \$cx['sp_vars']['key'] : null)", '@[key]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@key')
256: * @expect array("(isset(\$cx['sp_vars']['first']) ? \$cx['sp_vars']['first'] : null)", '@[first]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@first')
257: * @expect array("(isset(\$cx['sp_vars']['last']) ? \$cx['sp_vars']['last'] : null)", '@[last]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@last')
258: * @expect array('(($inary && isset($in[\'"a"\'])) ? $in[\'"a"\'] : null)', '["a"]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('"a"')
259: * @expect array('"a"', '"a"') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, '"a"')
260: * @expect array('(($inary && isset($in[\'a\'])) ? $in[\'a\'] : null)', '[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('a')
261: * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1]) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1]) && isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'] : null)', '../[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(1,'a')
262: * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3]) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3]) && isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'] : null)', '../../../[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(3,'a')
263: * @expect array('(($inary && isset($in[\'id\'])) ? $in[\'id\'] : null)', 'this.[id]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(null, 'id')
264: * @expect array('LR::v($cx, $in, isset($in) ? $in : null, array(\'id\'))', 'this.[id]') when input array('flags'=>array('prop'=>true,'spvar'=>true,'debug'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0,'standalone'=>0), 'runtime' => 'Runtime', 'runtimealias' => 'LR'), array(null, 'id')
265: */
266: protected static function getVariableName(&$context, $var, $lookup = null, $args = null)
267: {
268: if (isset($var[0]) && ($var[0] === Parser::LITERAL)) {
269: if ($var[1] === "undefined") {
270: $var[1] = "null";
271: }
272: return array($var[1], preg_replace('/\'(.*)\'/', '$1', $var[1]));
273: }
274:
275: list($levels, $spvar, $var) = Expression::analyze($context, $var);
276: $exp = Expression::toString($levels, $spvar, $var);
277: $base = $spvar ? "\$cx['sp_vars']" : '$in';
278:
279: // change base when trace to parent
280: if ($levels > 0) {
281: if ($spvar) {
282: $base .= str_repeat("['_parent']", $levels);
283: } else {
284: $base = "\$cx['scopes'][count(\$cx['scopes'])-$levels]";
285: }
286: }
287:
288: if ((empty($var) || (count($var) == 0) || (($var[0] === null) && (count($var) == 1))) && ($lookup === null)) {
289: return array($base, $exp);
290: }
291:
292: if ((count($var) > 0) && ($var[0] === null)) {
293: array_shift($var);
294: }
295:
296: // To support recursive context lookup, instance properties + methods and lambdas
297: // the only way is using slower rendering time variable resolver.
298: if ($context['flags']['prop'] || $context['flags']['method'] || $context['flags']['mustlok'] || $context['flags']['mustlam'] || $context['flags']['lambda']) {
299: $L = Expression::listString($var);
300: $L = ($L === '') ? array() : array($L);
301: if ($lookup) {
302: $L[] = $lookup[0];
303: }
304: $A = $args ? ",$args[0]" : '';
305: $E = $args ? ' ' . implode(' ', $args[1]) : '';
306: return array(static::getFuncName($context, 'v', $exp) . "\$cx, \$in, isset($base) ? $base : null, array(" . implode($L, ',') . ")$A)", $lookup ? "lookup $exp $lookup[1]" : "$exp$E");
307: }
308:
309: $n = Expression::arrayString($var);
310: $k = array_pop($var);
311: $L = $lookup ? "[{$lookup[0]}]" : '';
312: $p = $lookup ? $n : (count($var) ? Expression::arrayString($var) : '');
313:
314: $checks = array();
315: if ($levels > 0) {
316: $checks[] = "isset($base)";
317: }
318: if (!$spvar) {
319: if (($levels === 0) && $p) {
320: $checks[] = "isset($base$p)";
321: }
322: $checks[] = ("$base$p" == '$in') ? '$inary' : "is_array($base$p)";
323: }
324: $checks[] = "isset($base$n$L)";
325: $check = ((count($checks) > 1) ? '(' : '') . implode(' && ', $checks) . ((count($checks) > 1) ? ')' : '');
326:
327: $lenStart = '';
328: $lenEnd = '';
329:
330: if ($context['flags']['jslen']) {
331: if (($lookup === null) && ($k === 'length')) {
332: array_pop($checks);
333: $lenStart = '(' . ((count($checks) > 1) ? '(' : '') . implode(' && ', $checks) . ((count($checks) > 1) ? ')' : '') . " ? count($base" . Expression::arrayString($var) . ') : ';
334: $lenEnd = ')';
335: }
336: }
337:
338: return array("($check ? $base$n$L : $lenStart" . ($context['flags']['debug'] ? (static::getFuncName($context, 'miss', '') . "\$cx, '$exp')") : 'null') . ")$lenEnd", $lookup ? "lookup $exp $lookup[1]" : $exp);
339: }
340:
341: /**
342: * Return compiled PHP code for a handlebars token
343: *
344: * @param array<string,array|string|integer> $context current compile context
345: * @param array<string,array|boolean> $info parsed information
346: *
347: * @return string Return compiled code segment for the token
348: */
349: protected static function compileToken(&$context, $info)
350: {
351: list($raw, $vars, $token, $indent) = $info;
352:
353: $context['tokens']['partialind'] = $indent;
354: $context['currentToken'] = $token;
355:
356: if ($ret = static::operator($token[Token::POS_OP], $context, $vars)) {
357: return $ret;
358: }
359:
360: if (isset($vars[0][0])) {
361: if ($ret = static::customHelper($context, $vars, $raw, true)) {
362: return static::compileOutput($context, $ret, 'FIXME: helper', $raw, false);
363: }
364: if ($context['flags']['else'] && ($vars[0][0] === 'else')) {
365: return static::doElse($context, $vars);
366: }
367: if ($vars[0][0] === 'lookup') {
368: return static::compileLookup($context, $vars, $raw);
369: }
370: if ($vars[0][0] === 'log') {
371: return static::compileLog($context, $vars, $raw);
372: }
373: }
374:
375: return static::compileVariable($context, $vars, $raw, false);
376: }
377:
378: /**
379: * handle partial
380: *
381: * @param array<string,array|string|integer> $context current compile context
382: * @param array<boolean|integer|string|array> $vars parsed arguments list
383: *
384: * @return string Return compiled code segment for the partial
385: */
386: public static function partial(&$context, $vars)
387: {
388: Parser::getBlockParams($vars);
389: $pid = Parser::getPartialBlock($vars);
390: $p = array_shift($vars);
391: if ($context['flags']['runpart']) {
392: if (!isset($vars[0])) {
393: $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array();
394: }
395: $v = static::getVariableNames($context, $vars);
396: $tag = ">$p[0] " .implode(' ', $v[1]);
397: if (Parser::isSubExp($p)) {
398: list($p) = static::compileSubExpression($context, $p[1]);
399: } else {
400: $p = "'$p[0]'";
401: }
402: $sp = $context['tokens']['partialind'] ? ", '{$context['tokens']['partialind']}'" : '';
403: return $context['ops']['seperator'] . static::getFuncName($context, 'p', $tag) . "\$cx, $p, $v[0],$pid$sp){$context['ops']['seperator']}";
404: }
405: return isset($context['usedPartial'][$p[0]]) ? "{$context['ops']['seperator']}'" . Partial::compileStatic($context, $p[0]) . "'{$context['ops']['seperator']}" : $context['ops']['seperator'];
406: }
407:
408: /**
409: * handle inline partial
410: *
411: * @param array<string,array|string|integer> $context current compile context
412: * @param array<boolean|integer|string|array> $vars parsed arguments list
413: *
414: * @return string Return compiled code segment for the partial
415: */
416: public static function inline(&$context, $vars)
417: {
418: Parser::getBlockParams($vars);
419: list($code) = array_shift($vars);
420: $p = array_shift($vars);
421: if (!isset($vars[0])) {
422: $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array();
423: }
424: $v = static::getVariableNames($context, $vars);
425: $tag = ">*inline $p[0]" .implode(' ', $v[1]);
426: return $context['ops']['seperator'] . static::getFuncName($context, 'in', $tag) . "\$cx, '{$p[0]}', $code){$context['ops']['seperator']}";
427: }
428:
429: /**
430: * Return compiled PHP code for a handlebars inverted section begin token
431: *
432: * @param array<string,array|string|integer> $context current compile context
433: * @param array<boolean|integer|string|array> $vars parsed arguments list
434: *
435: * @return string Return compiled code segment for the token
436: */
437: protected static function invertedSection(&$context, $vars)
438: {
439: $v = static::getVariableName($context, $vars[0]);
440: return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'isec', '^' . $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}";
441: }
442:
443: /**
444: * Return compiled PHP code for a handlebars block custom helper begin token
445: *
446: * @param array<string,array|string|integer> $context current compile context
447: * @param array<boolean|integer|string|array> $vars parsed arguments list
448: * @param boolean $inverted the logic will be inverted
449: *
450: * @return string Return compiled code segment for the token
451: */
452: protected static function blockCustomHelper(&$context, $vars, $inverted = false)
453: {
454: $bp = Parser::getBlockParams($vars);
455: $ch = array_shift($vars);
456: $inverted = $inverted ? 'true' : 'false';
457: static::addUsageCount($context, 'helpers', $ch[0]);
458: $v = static::getVariableNames($context, $vars, $bp);
459:
460: return $context['ops']['seperator'] . static::getFuncName($context, 'hbbch', ($inverted ? '^' : '#') . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, \$in, $inverted, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}";
461: }
462:
463: /**
464: * Return compiled PHP code for a handlebars block end token
465: *
466: * @param array<string,array|string|integer> $context current compile context
467: * @param array<boolean|integer|string|array> $vars parsed arguments list
468: * @param string|null $matchop should also match to this operator
469: *
470: * @return string Return compiled code segment for the token
471: */
472: protected static function blockEnd(&$context, &$vars, $matchop = null)
473: {
474: $pop = $context['stack'][count($context['stack']) - 1];
475:
476: switch (isset($context['helpers'][$context['currentToken'][Token::POS_INNERTAG]]) ? 'skip' : $context['currentToken'][Token::POS_INNERTAG]) {
477: case 'if':
478: case 'unless':
479: if ($pop === ':') {
480: array_pop($context['stack']);
481: return "{$context['ops']['cnd_end']}";
482: }
483: if (!$context['flags']['nohbh']) {
484: return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
485: }
486: break;
487: case 'with':
488: if (!$context['flags']['nohbh']) {
489: return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
490: }
491: }
492:
493: if ($pop === ':') {
494: array_pop($context['stack']);
495: return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
496: }
497:
498: switch ($pop) {
499: case '#':
500: return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
501: case '^':
502: return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
503: }
504: }
505:
506: /**
507: * Return compiled PHP code for a handlebars block begin token
508: *
509: * @param array<string,array|string|integer> $context current compile context
510: * @param array<boolean|integer|string|array> $vars parsed arguments list
511: *
512: * @return string Return compiled code segment for the token
513: */
514: protected static function blockBegin(&$context, $vars)
515: {
516: $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array());
517: if (!$context['flags']['nohbh']) {
518: switch (isset($vars[0][0]) ? $vars[0][0] : null) {
519: case 'if':
520: $includeZero = (isset($vars['includeZero'][1]) && $vars['includeZero'][1]) ? 'true' : 'false';
521: return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]}, {$includeZero})){$context['ops']['cnd_then']}";
522: case 'unless':
523: return "{$context['ops']['cnd_start']}(!" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]}, false)){$context['ops']['cnd_then']}";
524: case 'each':
525: return static::section($context, $vars, true);
526: case 'with':
527: if ($r = static::with($context, $vars)) {
528: return $r;
529: }
530: }
531: }
532:
533: return static::section($context, $vars);
534: }
535:
536: /**
537: * compile {{#foo}} token
538: *
539: * @param array<string,array|string|integer> $context current compile context
540: * @param array<boolean|integer|string|array> $vars parsed arguments list
541: * @param boolean $isEach the section is #each
542: *
543: * @return string|null Return compiled code segment for the token
544: */
545: protected static function section(&$context, $vars, $isEach = false)
546: {
547: $bs = 'null';
548: $be = '';
549: if ($isEach) {
550: $bp = Parser::getBlockParams($vars);
551: $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null';
552: $be = $bp ? (' as |' . implode($bp, ' ') . '|') : '';
553: array_shift($vars);
554: }
555: if ($context['flags']['lambda'] && !$isEach) {
556: $V = array_shift($vars);
557: $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array('')));
558: } else {
559: $v = static::getVariableNameOrSubExpression($context, $vars[0]);
560: }
561: $each = $isEach ? 'true' : 'false';
562: return $context['ops']['seperator'] . static::getFuncName($context, 'sec', ($isEach ? 'each ' : '') . $v[1] . $be) . "\$cx, {$v[0]}, $bs, \$in, $each, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}";
563: }
564:
565: /**
566: * compile {{with}} token
567: *
568: * @param array<string,array|string|integer> $context current compile context
569: * @param array<boolean|integer|string|array> $vars parsed arguments list
570: *
571: * @return string|null Return compiled code segment for the token
572: */
573: protected static function with(&$context, $vars)
574: {
575: $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array());
576: $bp = Parser::getBlockParams($vars);
577: $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null';
578: $be = $bp ? " as |$bp[0]|" : '';
579: return $context['ops']['seperator'] . static::getFuncName($context, 'wi', 'with ' . $v[1] . $be) . "\$cx, {$v[0]}, $bs, \$in, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}";
580: }
581:
582: /**
583: * Return compiled PHP code for a handlebars custom helper token
584: *
585: * @param array<string,array|string|integer> $context current compile context
586: * @param array<boolean|integer|string|array> $vars parsed arguments list
587: * @param boolean $raw is this {{{ token or not
588: * @param boolean $nosep true to compile without seperator
589: * @param boolean $subExp true when compile for subexpression
590: *
591: * @return string|null Return compiled code segment for the token when the token is custom helper
592: */
593: protected static function customHelper(&$context, $vars, $raw, $nosep, $subExp = false)
594: {
595: if (count($vars[0]) > 1) {
596: return;
597: }
598:
599: if (!isset($context['helpers'][$vars[0][0]])) {
600: if ($subExp) {
601: if ($vars[0][0] == 'lookup') {
602: return static::compileLookup($context, $vars, $raw, true);
603: }
604: }
605: return;
606: }
607:
608: $fn = $raw ? 'raw' : $context['ops']['enc'];
609: $ch = array_shift($vars);
610: $v = static::getVariableNames($context, $vars);
611: static::addUsageCount($context, 'helpers', $ch[0]);
612: $sep = $nosep ? '' : $context['ops']['seperator'];
613:
614: return $sep . static::getFuncName($context, 'hbch', "$ch[0] " . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, '$fn', \$in)$sep";
615: }
616:
617: /**
618: * Return compiled PHP code for a handlebars else token
619: *
620: * @param array<string,array|string|integer> $context current compile context
621: * @param array<boolean|integer|string|array> $vars parsed arguments list
622: *
623: * @return string Return compiled code segment for the token when the token is else
624: */
625: protected static function doElse(&$context, $vars)
626: {
627: $v = $context['stack'][count($context['stack']) - 2];
628:
629: if ((($v === '[if]') && !isset($context['helpers']['if'])) ||
630: (($v === '[unless]') && !isset($context['helpers']['unless']))) {
631: $context['stack'][] = ':';
632: return "{$context['ops']['cnd_else']}";
633: }
634:
635: return "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}";
636: }
637:
638: /**
639: * Return compiled PHP code for a handlebars log token
640: *
641: * @param array<string,array|string|integer> $context current compile context
642: * @param array<boolean|integer|string|array> $vars parsed arguments list
643: * @param boolean $raw is this {{{ token or not
644: *
645: * @return string Return compiled code segment for the token
646: */
647: protected static function compileLog(&$context, &$vars, $raw)
648: {
649: array_shift($vars);
650: $v = static::getVariableNames($context, $vars);
651: return $context['ops']['seperator'] . static::getFuncName($context, 'lo', $v[1]) . "\$cx, {$v[0]}){$context['ops']['seperator']}";
652: }
653:
654: /**
655: * Return compiled PHP code for a handlebars lookup token
656: *
657: * @param array<string,array|string|integer> $context current compile context
658: * @param array<boolean|integer|string|array> $vars parsed arguments list
659: * @param boolean $raw is this {{{ token or not
660: * @param boolean $nosep true to compile without seperator
661: *
662: * @return string Return compiled code segment for the token
663: */
664: protected static function compileLookup(&$context, &$vars, $raw, $nosep = false)
665: {
666: $v2 = static::getVariableName($context, $vars[2]);
667: $v = static::getVariableName($context, $vars[1], $v2);
668: $sep = $nosep ? '' : $context['ops']['seperator'];
669: $ex = $nosep ? ', 1' : '';
670:
671: if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug']) {
672: return $sep . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $v[1]) . "\$cx, {$v[0]}$ex){$sep}";
673: } else {
674: return $raw ? "{$sep}$v[0]{$sep}" : "{$sep}htmlspecialchars((string){$v[0]}, ENT_QUOTES, 'UTF-8'){$sep}";
675: }
676: }
677:
678: /**
679: * Return compiled PHP code for template output
680: *
681: * @param array<string,array|string|integer> $context current compile context
682: * @param string $variable PHP code for the variable
683: * @param string $expression normalized handlebars expression
684: * @param boolean $raw is this {{{ token or not
685: * @param boolean $nosep true to compile without seperator
686: *
687: * @return string Return compiled code segment for the token
688: */
689: protected static function compileOutput(&$context, $variable, $expression, $raw, $nosep)
690: {
691: $sep = $nosep ? '' : $context['ops']['seperator'];
692: if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug'] || $nosep) {
693: return $sep . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $expression) . "\$cx, $variable)$sep";
694: } else {
695: return $raw ? "$sep$variable{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlspecialchars((string)$variable, ENT_QUOTES, 'UTF-8')$sep";
696: }
697: }
698:
699: /**
700: * Return compiled PHP code for a handlebars variable token
701: *
702: * @param array<string,array|string|integer> $context current compile context
703: * @param array<boolean|integer|string|array> $vars parsed arguments list
704: * @param boolean $raw is this {{{ token or not
705: * @param boolean $nosep true to compile without seperator
706: *
707: * @return string Return compiled code segment for the token
708: */
709: protected static function compileVariable(&$context, &$vars, $raw, $nosep)
710: {
711: if ($context['flags']['lambda']) {
712: $V = array_shift($vars);
713: $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array('')));
714: } else {
715: $v = static::getVariableName($context, $vars[0]);
716: }
717: return static::compileOutput($context, $v[0], $v[1], $raw, $nosep);
718: }
719:
720: /**
721: * Add usage count to context
722: *
723: * @param array<string,array|string|integer> $context current context
724: * @param string $category category name, can be one of: 'var', 'helpers', 'runtime'
725: * @param string $name used name
726: * @param integer $count increment
727: *
728: * @expect 1 when input array('usedCount' => array('test' => array())), 'test', 'testname'
729: * @expect 3 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname'
730: * @expect 5 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3
731: */
732: protected static function addUsageCount(&$context, $category, $name, $count = 1)
733: {
734: if (!isset($context['usedCount'][$category][$name])) {
735: $context['usedCount'][$category][$name] = 0;
736: }
737: return ($context['usedCount'][$category][$name] += $count);
738: }
739: }
740: