Skip to content

Commit d483a9b

Browse files
committed
Refactor duplicated logic to methods
1 parent 2624ba5 commit d483a9b

2 files changed

Lines changed: 67 additions & 48 deletions

File tree

src/Compiler.php

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,8 @@ private function BlockStatement(BlockStatement $block): string
170170
return $this->compileBlockHelper($block, $literalKey);
171171
}
172172

173-
$escapedKey = addcslashes($literalKey, "'\\");
174-
$miss = $this->context->options->strict
175-
? "LR::miss('" . addcslashes($literalKey, "'\\") . "')"
176-
: 'null';
173+
$escapedKey = $this->escape($literalKey);
174+
$miss = $this->missValue($literalKey);
177175
$var = "\$in['$escapedKey'] ?? $miss";
178176

179177
if ($block->program === null) {
@@ -264,8 +262,7 @@ private function compileEach(BlockStatement $block): string
264262
}
265263

266264
$var = $this->compileExpression($block->params[0]);
267-
$bp = $block->program ? $block->program->blockParams : [];
268-
$bs = $bp ? Expression::listString($bp) : 'null';
265+
[$bp, $bs] = $this->getProgramBlockParams($block->program);
269266

270267
$body = $block->program ? $this->compileProgramWithBlockParams($block->program, $bp, true) : "''";
271268
$else = $this->compileElseClause($block);
@@ -280,8 +277,7 @@ private function compileWith(BlockStatement $block): string
280277
}
281278

282279
$var = $this->compileExpression($block->params[0]);
283-
$bp = $block->program ? $block->program->blockParams : [];
284-
$bs = $bp ? Expression::listString($bp) : 'null';
280+
[$bp, $bs] = $this->getProgramBlockParams($block->program);
285281

286282
$body = $block->program ? $this->compileProgram($block->program) : "''";
287283
$else = $this->compileElseClause($block);
@@ -353,7 +349,7 @@ private function DecoratorBlock(BlockStatement $block): string
353349
// Do NOT add to partialCode - `in()` handles runtime registration, keeping inline partials block-scoped.
354350
$this->context->usedPartial[$partialName] = '';
355351

356-
return "'." . $this->getFuncName('in', "\$cx, '" . addcslashes($partialName, "'\\") . "', function(\$cx, \$in, \$sp) {return $body;}") . ").'";
352+
return "'." . $this->getFuncName('in', "\$cx, '" . $this->escape($partialName) . "', function(\$cx, \$in, \$sp) {return $body;}") . ").'";
357353
}
358354

359355
private function Decorator(Decorator $decorator): never
@@ -366,25 +362,25 @@ private function PartialStatement(PartialStatement $statement): string
366362
$name = $statement->name;
367363

368364
if ($name instanceof PathExpression) {
369-
$p = "'" . addcslashes($name->original, "'\\") . "'";
365+
$p = "'" . $this->escape($name->original) . "'";
370366
$this->resolveAndCompilePartial($name->original);
371367
} elseif ($name instanceof SubExpression) {
372368
$p = $this->SubExpression($name);
373369
$this->context->usedDynPartial++;
374370
} elseif ($name instanceof NumberLiteral) {
375371
$literalName = (string) $name->value;
376-
$p = "'" . addcslashes($literalName, "'\\") . "'";
372+
$p = "'" . $this->escape($literalName) . "'";
377373
$this->resolveAndCompilePartial($literalName);
378374
} elseif ($name instanceof StringLiteral) {
379375
$literalName = $name->value;
380-
$p = "'" . addcslashes($literalName, "'\\") . "'";
376+
$p = "'" . $this->escape($literalName) . "'";
381377
$this->resolveAndCompilePartial($literalName);
382378
} else {
383379
$p = $this->compileExpression($name);
384380
}
385381

386382
$vars = $this->compilePartialParams($statement->params, $statement->hash);
387-
$indent = addcslashes($statement->indent, "'\\");
383+
$indent = $this->escape($statement->indent);
388384

389385
// When preventIndent is set, emit the indent as literal content (like handlebars.js
390386
// appendContent opcode) and invoke the partial with an empty indent so its lines are
@@ -418,10 +414,10 @@ private function PartialBlockStatement(PartialBlockStatement $statement): string
418414

419415
if ($name instanceof PathExpression) {
420416
$partialName = $name->original;
421-
$p = "'" . addcslashes($partialName, "'\\") . "'";
417+
$p = "'" . $this->escape($partialName) . "'";
422418
} elseif ($name instanceof StringLiteral || $name instanceof NumberLiteral) {
423-
$partialName = $name instanceof StringLiteral ? $name->value : (string) $name->value;
424-
$p = "'" . addcslashes($partialName, "'\\") . "'";
419+
$partialName = $this->getLiteralKeyName($name);
420+
$p = "'" . $this->escape($partialName) . "'";
425421
} else {
426422
$p = $this->compileExpression($name);
427423
$partialName = null;
@@ -509,7 +505,7 @@ private function MustacheStatement(MustacheStatement $mustache): string
509505

510506
if ($this->resolveHelper($literalKey)) {
511507
$params = $this->compileParams($mustache->params, $mustache->hash);
512-
$escapedKey = addcslashes($literalKey, "'\\");
508+
$escapedKey = $this->escape($literalKey);
513509
$call = "LR::hbch(\$cx, '$escapedKey', $params, \$in)";
514510
return "'." . $this->getFuncName($fn, $call) . ").'";
515511
}
@@ -518,17 +514,15 @@ private function MustacheStatement(MustacheStatement $mustache): string
518514
throw new \Exception('Missing helper: "' . $literalKey . '"');
519515
}
520516

521-
$escapedKey = addcslashes($literalKey, "'\\");
522-
$miss = $this->context->options->strict
523-
? "LR::miss('" . addcslashes($literalKey, "'\\") . "')"
524-
: 'null';
517+
$escapedKey = $this->escape($literalKey);
518+
$miss = $this->missValue($literalKey);
525519
$val = "\$in['$escapedKey'] ?? $miss";
526520
return "'." . $this->getFuncName($fn, $val) . ").'";
527521
}
528522

529523
private function ContentStatement(ContentStatement $statement): string
530524
{
531-
return addcslashes($statement->value, "'\\");
525+
return $this->escape($statement->value);
532526
}
533527

534528
private function CommentStatement(CommentStatement $statement): string
@@ -552,7 +546,7 @@ private function SubExpression(SubExpression $expression): string
552546
// Registered helper
553547
if ($helperName !== null && $this->resolveHelper($helperName)) {
554548
$params = $this->compileParams($expression->params, $expression->hash);
555-
$escapedName = addcslashes($helperName, "'\\");
549+
$escapedName = $this->escape($helperName);
556550
return "LR::hbch(\$cx, '$escapedName', $params, \$in)";
557551
}
558552

@@ -584,9 +578,7 @@ private function PathExpression(PathExpression $expression): string
584578
return $base;
585579
}
586580

587-
$miss = $this->context->options->strict
588-
? "LR::miss('" . addcslashes($expression->original, "'\\") . "')"
589-
: 'null';
581+
$miss = $this->missValue($expression->original);
590582

591583
// @partial-block as variable: truthy when an active partial block exists
592584
if ($data && $depth === 0 && count($stringParts) === 1 && $stringParts[0] === 'partial-block') {
@@ -597,7 +589,7 @@ private function PathExpression(PathExpression $expression): string
597589
if (!$data && $depth === 0 && !self::scopedId($expression)) {
598590
$bpIdx = $this->lookupBlockParam($stringParts[0]);
599591
if ($bpIdx !== null) {
600-
$escapedName = addcslashes($stringParts[0], "'\\");
592+
$escapedName = $this->escape($stringParts[0]);
601593
$bpBase = "\$cx->blParam[$bpIdx]['$escapedName']";
602594
$remaining = $this->buildKeyAccess(array_slice($stringParts, 1));
603595
return "$bpBase$remaining ?? $miss";
@@ -638,7 +630,7 @@ private function PathExpression(PathExpression $expression): string
638630

639631
private function StringLiteral(StringLiteral $literal): string
640632
{
641-
return "'" . addcslashes($literal->value, "'\\") . "'";
633+
return "'" . $this->escape($literal->value) . "'";
642634
}
643635

644636
private function NumberLiteral(NumberLiteral $literal): string
@@ -700,7 +692,7 @@ private function Hash(Hash $hash): string
700692
{
701693
$pairs = [];
702694
foreach ($hash->pairs as $pair) {
703-
$key = addcslashes($pair->key, "'\\");
695+
$key = $this->escape($pair->key);
704696
$value = $this->compileExpression($pair->value);
705697
$pairs[] = "'$key'=>$value";
706698
}
@@ -905,7 +897,7 @@ private function buildKeyAccess(array $parts): string
905897
{
906898
$n = '';
907899
foreach ($parts as $part) {
908-
$n .= "['" . addcslashes($part, "'\\") . "']";
900+
$n .= "['" . $this->escape($part) . "']";
909901
}
910902
return $n;
911903
}
@@ -923,6 +915,26 @@ private function getFuncName(string $name, string $args): string
923915
return "LR::$name($args";
924916
}
925917

918+
private function escape(string $s): string
919+
{
920+
return addcslashes($s, "'\\");
921+
}
922+
923+
private function missValue(string $key): string
924+
{
925+
return $this->context->options->strict
926+
? "LR::miss('" . $this->escape($key) . "')"
927+
: 'null';
928+
}
929+
930+
/** @return array{list<string>, string} [$bp, $bs] */
931+
private function getProgramBlockParams(?Program $program): array
932+
{
933+
$bp = $program ? $program->blockParams : [];
934+
$bs = $bp ? Expression::listString($bp) : 'null';
935+
return [$bp, $bs];
936+
}
937+
926938
/**
927939
* Get includeZero value from hash.
928940
*/
@@ -982,9 +994,7 @@ private function compilePathWithLookup(PathExpression $path, string $lookupCode)
982994
$base = $this->buildBasePath($data, $depth);
983995
$n = $this->buildKeyAccess($parts);
984996

985-
$miss = $this->context->options->strict
986-
? "LR::miss('" . addcslashes($path->original, "'\\") . "')"
987-
: 'null';
997+
$miss = $this->missValue($path->original);
988998

989999
return $base . $n . "[$lookupCode] ?? $miss";
9901000
}
@@ -1006,7 +1016,7 @@ private function getWithLookup(AstExpression $itemsExpr, AstExpression $idxExpr)
10061016
$varCode = $this->compilePathWithLookup($itemsExpr, $idxCode);
10071017
} else {
10081018
$itemsCode = $this->compileExpression($itemsExpr);
1009-
$miss = $this->context->options->strict ? "LR::miss('lookup')" : 'null';
1019+
$miss = $this->missValue('lookup');
10101020
$varCode = $itemsCode . "[$idxCode] ?? $miss";
10111021
}
10121022
return $varCode;

src/Runtime.php

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public static function raw(array|string|int|bool|null $v, int $ex = 0): string|a
110110
}
111111

112112
if (is_array($v)) {
113-
if (count(array_diff_key($v, array_keys(array_keys($v)))) > 0) {
113+
if (static::isObjectArray($v)) {
114114
return '[object Object]';
115115
} else {
116116
$ret = '';
@@ -152,16 +152,16 @@ public static function sec(RuntimeContext $cx, mixed $v, ?array $bp, mixed $in,
152152
// #var, detect input type is object or not
153153
if (!$loop && $isAry) {
154154
$keys = array_keys($v);
155-
$loop = (count(array_diff_key($v, array_keys($keys))) == 0);
156-
$isObj = !$loop;
155+
$isObj = static::isObjectArray($v);
156+
$loop = !$isObj;
157157
}
158158

159159
if (($loop && $isAry) || $isTrav) {
160160
if ($each && !$isTrav) {
161161
// Detect input type is object or not when never done once
162162
if ($keys == null) {
163163
$keys = array_keys($v);
164-
$isObj = (count(array_diff_key($v, array_keys($keys))) > 0);
164+
$isObj = static::isObjectArray($v);
165165
}
166166
}
167167
$ret = [];
@@ -274,9 +274,7 @@ public static function wi(RuntimeContext $cx, mixed $v, ?array $bp, array|\stdCl
274274
if ($v === $in) {
275275
$ret = $cb($cx, $v);
276276
} else {
277-
$cx->scopes[] = $in;
278-
$ret = $cb($cx, $v);
279-
array_pop($cx->scopes);
277+
$ret = static::withScope($cx, $in, $v, $cb);
280278
}
281279

282280
$cx->partials = $savedPartials;
@@ -496,9 +494,7 @@ private static function makeBlockFn(RuntimeContext $cx, mixed $_this, ?\Closure
496494
if ($context === null || $context === $_this) {
497495
$ret = $cb($cx, $_this);
498496
} else {
499-
$cx->scopes[] = $_this;
500-
$ret = $cb($cx, $context);
501-
array_pop($cx->scopes);
497+
$ret = static::withScope($cx, $_this, $context, $cb);
502498
}
503499

504500
if (isset($data['data'])) {
@@ -518,10 +514,7 @@ private static function makeInverseFn(RuntimeContext $cx, mixed $_this, ?\Closur
518514
if ($context === null) {
519515
return $else($cx, $_this);
520516
}
521-
$cx->scopes[] = $_this;
522-
$ret = $else($cx, $context);
523-
array_pop($cx->scopes);
524-
return $ret;
517+
return static::withScope($cx, $_this, $context, $else);
525518
}
526519
: fn() => '';
527520
}
@@ -540,6 +533,22 @@ private static function applyBlockHelperMissing(RuntimeContext $cx, mixed $resul
540533
return static::sec($cx, $result, null, $_this, false, $cb ?? static fn() => '', $else);
541534
}
542535

536+
private static function withScope(RuntimeContext $cx, mixed $scope, mixed $context, \Closure $cb): string
537+
{
538+
$cx->scopes[] = $scope;
539+
$ret = $cb($cx, $context);
540+
array_pop($cx->scopes);
541+
return $ret;
542+
}
543+
544+
/**
545+
* @param array<mixed> $v
546+
*/
547+
private static function isObjectArray(array $v): bool
548+
{
549+
return count(array_diff_key($v, array_keys(array_keys($v)))) !== 0;
550+
}
551+
543552
/**
544553
* Execute custom helper with prepared options
545554
*

0 commit comments

Comments
 (0)