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