@@ -170,15 +170,13 @@ 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 ) {
180178 // Inverted section: {{^"foo"}}...{{/"foo"}}
181- $ body = $ block -> inverse ? $ this ->compileProgram ($ block ->inverse ) : " '' " ;
179+ $ body = $ this ->compileProgramOrEmpty ($ block ->inverse );
182180 return "'.(( " . $ this ->getFuncName ('isec ' , $ var ) . ")) ? $ body : '').' " ;
183181 }
184182
@@ -240,7 +238,7 @@ private function compileIf(BlockStatement $block, bool $unless): string
240238 $ var = $ this ->compileExpression ($ block ->params [0 ]);
241239 $ includeZero = $ this ->getIncludeZero ($ block ->hash );
242240
243- $ then = $ block -> program ? $ this ->compileProgram ($ block ->program ) : " '' " ;
241+ $ then = $ this ->compileProgramOrEmpty ($ block ->program );
244242
245243 if ($ block ->inverse && $ block ->inverse ->chained ) {
246244 // {{else if ...}} chain — compile the inner block directly
@@ -250,7 +248,7 @@ private function compileIf(BlockStatement $block, bool $unless): string
250248 }
251249 $ else = "' " . $ elseCode . "' " ;
252250 } else {
253- $ else = $ block -> inverse ? $ this ->compileProgram ($ block ->inverse ) : " '' " ;
251+ $ else = $ this ->compileProgramOrEmpty ($ block ->inverse );
254252 }
255253
256254 $ negate = $ unless ? '! ' : '' ;
@@ -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,10 +277,9 @@ 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
286- $ body = $ block -> program ? $ this ->compileProgram ($ block ->program ) : " '' " ;
282+ $ body = $ this ->compileProgramOrEmpty ($ block ->program );
287283 $ else = $ this ->compileElseClause ($ block );
288284
289285 return "'. " . $ this ->getFuncName ('wi ' , "\$cx, LR::dv( $ var, \$in), $ bs, \$in, function( \$cx, \$in) {return $ body;} $ else " ) . ").' " ;
@@ -293,7 +289,7 @@ private function compileSection(BlockStatement $block): string
293289 {
294290 $ var = $ this ->compileExpression ($ block ->path );
295291
296- $ body = $ block -> program ? $ this ->compileProgram ($ block ->program , true ) : " '' " ;
292+ $ body = $ this ->compileProgramOrEmpty ($ block ->program , true );
297293 $ else = $ this ->compileElseClause ($ block );
298294
299295 return "'. " . $ this ->getFuncName ('sec ' , "\$cx, $ var, null, \$in, false, function( \$cx, \$in) use (& \$sp) {return $ body;} $ else " ) . ").' " ;
@@ -302,7 +298,7 @@ private function compileSection(BlockStatement $block): string
302298 private function compileInvertedSection (BlockStatement $ block ): string
303299 {
304300 $ var = $ this ->compileExpression ($ block ->path );
305- $ body = $ block -> inverse ? $ this ->compileProgram ($ block ->inverse ) : " '' " ;
301+ $ body = $ this ->compileProgramOrEmpty ($ block ->inverse );
306302
307303 return "'.(( " . $ this ->getFuncName ('isec ' , $ var ) . ")) ? $ body : '').' " ;
308304 }
@@ -321,7 +317,7 @@ private function compileBlockHelper(BlockStatement $block, string $helperName):
321317 $ params = $ this ->compileParams ($ block ->params , $ block ->hash , $ bp ?: null );
322318
323319 if ($ inverted ) {
324- $ body = $ block -> inverse ? $ this ->compileProgram ($ block ->inverse ) : " '' " ;
320+ $ body = $ this ->compileProgramOrEmpty ($ block ->inverse );
325321 return "'. " . $ this ->getFuncName ('hbbch ' , "\$cx, ' $ helperName', $ params, \$in, true, function( \$cx, \$in) {return $ body;} " ) . ").' " ;
326322 }
327323
@@ -347,13 +343,13 @@ private function DecoratorBlock(BlockStatement $block): string
347343 $ partialName = $ this ->getLiteralKeyName ($ firstArg );
348344 }
349345
350- $ body = $ block -> program ? $ this ->compileProgram ($ block ->program , true ) : " '' " ;
346+ $ body = $ this ->compileProgramOrEmpty ($ block ->program , true );
351347
352348 // Register in usedPartial so {{> partialName}} can compile without error.
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,21 @@ 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 ++;
374- } elseif ($ name instanceof NumberLiteral) {
375- $ literalName = (string ) $ name ->value ;
376- $ p = "' " . addcslashes ($ literalName , "' \\" ) . "' " ;
377- $ this ->resolveAndCompilePartial ($ literalName );
378- } elseif ($ name instanceof StringLiteral) {
379- $ literalName = $ name ->value ;
380- $ p = "' " . addcslashes ($ literalName , "' \\" ) . "' " ;
370+ } elseif ($ name instanceof NumberLiteral || $ name instanceof StringLiteral) {
371+ $ literalName = $ this ->getLiteralKeyName ($ name );
372+ $ p = "' " . $ this ->escape ($ literalName ) . "' " ;
381373 $ this ->resolveAndCompilePartial ($ literalName );
382374 } else {
383375 $ p = $ this ->compileExpression ($ name );
384376 }
385377
386378 $ vars = $ this ->compilePartialParams ($ statement ->params , $ statement ->hash );
387- $ indent = addcslashes ($ statement ->indent , " ' \\" );
379+ $ indent = $ this -> escape ($ statement ->indent );
388380
389381 // When preventIndent is set, emit the indent as literal content (like handlebars.js
390382 // appendContent opcode) and invoke the partial with an empty indent so its lines are
@@ -418,10 +410,10 @@ private function PartialBlockStatement(PartialBlockStatement $statement): string
418410
419411 if ($ name instanceof PathExpression) {
420412 $ partialName = $ name ->original ;
421- $ p = "' " . addcslashes ($ partialName, " ' \\" ) . "' " ;
413+ $ p = "' " . $ this -> escape ($ partialName ) . "' " ;
422414 } elseif ($ name instanceof StringLiteral || $ name instanceof NumberLiteral) {
423- $ partialName = $ name instanceof StringLiteral ? $ name -> value : ( string ) $ name-> value ;
424- $ p = "' " . addcslashes ($ partialName, " ' \\" ) . "' " ;
415+ $ partialName = $ this -> getLiteralKeyName ( $ name) ;
416+ $ p = "' " . $ this -> escape ($ partialName ) . "' " ;
425417 } else {
426418 $ p = $ this ->compileExpression ($ name );
427419 $ partialName = null ;
@@ -509,7 +501,7 @@ private function MustacheStatement(MustacheStatement $mustache): string
509501
510502 if ($ this ->resolveHelper ($ literalKey )) {
511503 $ params = $ this ->compileParams ($ mustache ->params , $ mustache ->hash );
512- $ escapedKey = addcslashes ($ literalKey, " ' \\" );
504+ $ escapedKey = $ this -> escape ($ literalKey );
513505 $ call = "LR::hbch( \$cx, ' $ escapedKey', $ params, \$in) " ;
514506 return "'. " . $ this ->getFuncName ($ fn , $ call ) . ").' " ;
515507 }
@@ -518,17 +510,15 @@ private function MustacheStatement(MustacheStatement $mustache): string
518510 throw new \Exception ('Missing helper: " ' . $ literalKey . '" ' );
519511 }
520512
521- $ escapedKey = addcslashes ($ literalKey , "' \\" );
522- $ miss = $ this ->context ->options ->strict
523- ? "LR::miss(' " . addcslashes ($ literalKey , "' \\" ) . "') "
524- : 'null ' ;
513+ $ escapedKey = $ this ->escape ($ literalKey );
514+ $ miss = $ this ->missValue ($ literalKey );
525515 $ val = "\$in[' $ escapedKey'] ?? $ miss " ;
526516 return "'. " . $ this ->getFuncName ($ fn , $ val ) . ").' " ;
527517 }
528518
529519 private function ContentStatement (ContentStatement $ statement ): string
530520 {
531- return addcslashes ($ statement ->value , " ' \\" );
521+ return $ this -> escape ($ statement ->value );
532522 }
533523
534524 private function CommentStatement (CommentStatement $ statement ): string
@@ -552,7 +542,7 @@ private function SubExpression(SubExpression $expression): string
552542 // Registered helper
553543 if ($ helperName !== null && $ this ->resolveHelper ($ helperName )) {
554544 $ params = $ this ->compileParams ($ expression ->params , $ expression ->hash );
555- $ escapedName = addcslashes ($ helperName, " ' \\" );
545+ $ escapedName = $ this -> escape ($ helperName );
556546 return "LR::hbch( \$cx, ' $ escapedName', $ params, \$in) " ;
557547 }
558548
@@ -577,16 +567,14 @@ private function PathExpression(PathExpression $expression): string
577567 $ base = $ this ->buildBasePath ($ data , $ depth );
578568
579569 // Filter out SubExpression parts for string-only operations
580- $ stringParts = array_values ( array_filter ( $ parts, fn ( $ p ) => is_string ( $ p )) );
570+ $ stringParts = self :: stringPartsOf ( $ parts );
581571
582572 // `this` with no parts or empty parts
583573 if (($ expression ->this_ && !$ parts ) || !$ stringParts ) {
584574 return $ base ;
585575 }
586576
587- $ miss = $ this ->context ->options ->strict
588- ? "LR::miss(' " . addcslashes ($ expression ->original , "' \\" ) . "') "
589- : 'null ' ;
577+ $ miss = $ this ->missValue ($ expression ->original );
590578
591579 // @partial-block as variable: truthy when an active partial block exists
592580 if ($ data && $ depth === 0 && count ($ stringParts ) === 1 && $ stringParts [0 ] === 'partial-block ' ) {
@@ -597,7 +585,7 @@ private function PathExpression(PathExpression $expression): string
597585 if (!$ data && $ depth === 0 && !self ::scopedId ($ expression )) {
598586 $ bpIdx = $ this ->lookupBlockParam ($ stringParts [0 ]);
599587 if ($ bpIdx !== null ) {
600- $ escapedName = addcslashes ($ stringParts [0 ], " ' \\" );
588+ $ escapedName = $ this -> escape ($ stringParts [0 ]);
601589 $ bpBase = "\$cx->blParam[ $ bpIdx][' $ escapedName'] " ;
602590 $ remaining = $ this ->buildKeyAccess (array_slice ($ stringParts , 1 ));
603591 return "$ bpBase$ remaining ?? $ miss " ;
@@ -638,7 +626,7 @@ private function PathExpression(PathExpression $expression): string
638626
639627 private function StringLiteral (StringLiteral $ literal ): string
640628 {
641- return "' " . addcslashes ($ literal ->value , " ' \\" ) . "' " ;
629+ return "' " . $ this -> escape ($ literal ->value ) . "' " ;
642630 }
643631
644632 private function NumberLiteral (NumberLiteral $ literal ): string
@@ -700,7 +688,7 @@ private function Hash(Hash $hash): string
700688 {
701689 $ pairs = [];
702690 foreach ($ hash ->pairs as $ pair ) {
703- $ key = addcslashes ($ pair ->key , " ' \\" );
691+ $ key = $ this -> escape ($ pair ->key );
704692 $ value = $ this ->compileExpression ($ pair ->value );
705693 $ pairs [] = "' $ key'=> $ value " ;
706694 }
@@ -905,7 +893,7 @@ private function buildKeyAccess(array $parts): string
905893 {
906894 $ n = '' ;
907895 foreach ($ parts as $ part ) {
908- $ n .= "[' " . addcslashes ($ part, " ' \\" ) . "'] " ;
896+ $ n .= "[' " . $ this -> escape ($ part ) . "'] " ;
909897 }
910898 return $ n ;
911899 }
@@ -923,6 +911,41 @@ private function getFuncName(string $name, string $args): string
923911 return "LR:: $ name( $ args " ;
924912 }
925913
914+ private function escape (string $ s ): string
915+ {
916+ return addcslashes ($ s , "' \\" );
917+ }
918+
919+ private function missValue (string $ key ): string
920+ {
921+ return $ this ->context ->options ->strict
922+ ? "LR::miss(' " . $ this ->escape ($ key ) . "') "
923+ : 'null ' ;
924+ }
925+
926+ /** @return array{list<string>, string} [$bp, $bs] */
927+ private function getProgramBlockParams (?Program $ program ): array
928+ {
929+ $ bp = $ program ? $ program ->blockParams : [];
930+ $ bs = $ bp ? Expression::listString ($ bp ) : 'null ' ;
931+ return [$ bp , $ bs ];
932+ }
933+
934+ private function compileProgramOrEmpty (?Program $ program , bool $ withSp = false ): string
935+ {
936+ return $ program ? $ this ->compileProgram ($ program , $ withSp ) : "'' " ;
937+ }
938+
939+ /**
940+ * Return only the string parts of a mixed parts array, re-indexed.
941+ * @param list<string|SubExpression> $parts
942+ * @return list<string>
943+ */
944+ private static function stringPartsOf (array $ parts ): array
945+ {
946+ return array_values (array_filter ($ parts , fn ($ p ) => is_string ($ p )));
947+ }
948+
926949 /**
927950 * Get includeZero value from hash.
928951 */
@@ -977,14 +1000,12 @@ private function compilePathWithLookup(PathExpression $path, string $lookupCode)
9771000 {
9781001 $ data = $ path ->data ;
9791002 $ depth = $ path ->depth ;
980- $ parts = array_values ( array_filter ( $ path ->parts , fn ( $ p ) => is_string ( $ p )) );
1003+ $ parts = self :: stringPartsOf ( $ path ->parts );
9811004
9821005 $ base = $ this ->buildBasePath ($ data , $ depth );
9831006 $ n = $ this ->buildKeyAccess ($ parts );
9841007
985- $ miss = $ this ->context ->options ->strict
986- ? "LR::miss(' " . addcslashes ($ path ->original , "' \\" ) . "') "
987- : 'null ' ;
1008+ $ miss = $ this ->missValue ($ path ->original );
9881009
9891010 return $ base . $ n . "[ $ lookupCode] ?? $ miss " ;
9901011 }
@@ -1006,7 +1027,7 @@ private function getWithLookup(AstExpression $itemsExpr, AstExpression $idxExpr)
10061027 $ varCode = $ this ->compilePathWithLookup ($ itemsExpr , $ idxCode );
10071028 } else {
10081029 $ itemsCode = $ this ->compileExpression ($ itemsExpr );
1009- $ miss = $ this ->context -> options -> strict ? " LR::miss ('lookup')" : ' null ' ;
1030+ $ miss = $ this ->missValue ('lookup ' );
10101031 $ varCode = $ itemsCode . "[ $ idxCode] ?? $ miss " ;
10111032 }
10121033 return $ varCode ;
0 commit comments