2121use DevTheorem \HandlebarsParser \Ast \StringLiteral ;
2222use DevTheorem \HandlebarsParser \Ast \SubExpression ;
2323use DevTheorem \HandlebarsParser \Ast \UndefinedLiteral ;
24+ use DevTheorem \HandlebarsParser \ParserFactory ;
2425
2526/**
2627 * @internal
@@ -76,7 +77,7 @@ private function compileProgram(Program $program, bool $withSp = false): string
7677 private function accept (Node $ node ): string
7778 {
7879 return match (true ) {
79- $ node instanceof BlockStatement && $ node ->type === 'DecoratorBlock ' => $ this ->DecoratorBlock (),
80+ $ node instanceof BlockStatement && $ node ->type === 'DecoratorBlock ' => $ this ->DecoratorBlock ($ node ),
8081 $ node instanceof BlockStatement => $ this ->BlockStatement ($ node ),
8182 $ node instanceof PartialStatement => $ this ->PartialStatement ($ node ),
8283 $ node instanceof PartialBlockStatement => $ this ->PartialBlockStatement ($ node ),
@@ -268,9 +269,23 @@ private function compileBlockHelper(BlockStatement $block, string $helperName):
268269 return "'. " . $ this ->getFuncName ('hbbch ' , "\$cx, ' $ helperName', $ params, \$in, false, function( \$cx, \$in) {return $ body;} $ else " ) . ").' " ;
269270 }
270271
271- private function DecoratorBlock (): string
272+ private function DecoratorBlock (BlockStatement $ block ): string
272273 {
273- return '' ; // todo: throw?
274+ $ helperName = $ this ->getSimpleHelperName ($ block ->path );
275+
276+ if ($ helperName !== 'inline ' || count ($ block ->params ) !== 1 || !$ block ->params [0 ] instanceof StringLiteral) {
277+ return '' ;
278+ }
279+
280+ $ partialName = $ block ->params [0 ]->value ;
281+ $ body = $ block ->program ? $ this ->compileProgram ($ block ->program , true ) : "'' " ;
282+
283+ // Register at compile time so {{> partialName}} can find it
284+ $ func = "function ( \$cx, \$in, \$sp) { {$ this ->context ->fStart }$ body {$ this ->context ->fEnd }} " ;
285+ $ this ->context ->usedPartial [$ partialName ] = '' ;
286+ $ this ->context ->partialCode [$ partialName ] = Expression::quoteString ($ partialName ) . " => $ func " ;
287+
288+ return "'. " . $ this ->getFuncName ('in ' , "\$cx, ' " . addcslashes ($ partialName , "' \\" ) . "', function( \$cx, \$in, \$sp) {return $ body;} " ) . ").' " ;
274289 }
275290
276291 private function Decorator (Decorator $ decorator ): string
@@ -284,8 +299,10 @@ private function PartialStatement(PartialStatement $statement): string
284299
285300 if ($ name instanceof PathExpression) {
286301 $ p = "' " . addcslashes ($ name ->original , "' \\" ) . "' " ;
302+ $ this ->resolveAndCompilePartial ($ name ->original );
287303 } elseif ($ name instanceof SubExpression) {
288304 $ p = $ this ->SubExpression ($ name );
305+ $ this ->context ->usedDynPartial ++;
289306 } else {
290307 $ p = $ this ->compileExpression ($ name );
291308 }
@@ -303,14 +320,37 @@ private function PartialBlockStatement(PartialBlockStatement $statement): string
303320 $ pid = $ this ->partialBlockId ;
304321
305322 $ name = $ statement ->name ;
323+ $ body = $ this ->compileProgram ($ statement ->program , true );
324+ $ found = false ;
306325
307326 if ($ name instanceof PathExpression) {
308327 $ p = "' " . addcslashes ($ name ->original , "' \\" ) . "' " ;
328+ $ partialName = $ name ->original ;
329+
330+ if (!isset ($ this ->context ->usedPartial [$ partialName ])
331+ && !str_starts_with ($ partialName , '@partial-block ' )
332+ ) {
333+ $ resolveName = $ partialName ;
334+ $ cnt = Partial::resolve ($ this ->context , $ resolveName );
335+ if ($ cnt !== null ) {
336+ $ this ->context ->usedPartial [$ resolveName ] = $ cnt ;
337+ $ this ->compilePartialTemplate ($ resolveName , $ cnt );
338+ $ found = true ;
339+ }
340+ } else {
341+ $ found = isset ($ this ->context ->usedPartial [$ partialName ]);
342+ }
343+
344+ if (!$ found ) {
345+ // Register fallback body as the partial
346+ $ func = "function ( \$cx, \$in, \$sp) { {$ this ->context ->fStart }$ body {$ this ->context ->fEnd }} " ;
347+ $ this ->context ->usedPartial [$ partialName ] = '' ;
348+ $ this ->context ->partialCode [$ partialName ] = Expression::quoteString ($ partialName ) . " => $ func " ;
349+ }
309350 } else {
310351 $ p = $ this ->compileExpression ($ name );
311352 }
312353
313- $ body = $ this ->compileProgram ($ statement ->program , true );
314354 $ vars = $ this ->compilePartialParams ($ statement ->params , $ statement ->hash );
315355 $ sp = "( \$sp ?? '') . '' " ;
316356
@@ -454,8 +494,8 @@ private function PathExpression(PathExpression $expression): string
454494 if ($ p !== '' && $ depth === 0 ) {
455495 $ checks [] = "isset( $ base$ p) " ;
456496 }
457- $ baseP = "$ base$ p " ;
458- $ checks [] = $ baseP === '$in ' ? '$inary ' : "is_array( $ base$ p) " ;
497+ $ baseP = "$ base$ p " ;
498+ $ checks [] = $ baseP === '$in ' ? '$inary ' : "is_array( $ base$ p) " ;
459499
460500 $ cond = implode (' && ' , $ checks );
461501 if (count ($ checks ) > 1 ) {
@@ -506,6 +546,77 @@ private function Hash(Hash $hash): string
506546 return implode (', ' , $ pairs );
507547 }
508548
549+ // ── Partials ─────────────────────────────────────────────────────
550+
551+ private function resolveAndCompilePartial (string $ name ): void
552+ {
553+ if (isset ($ this ->context ->usedPartial [$ name ])) {
554+ return ;
555+ }
556+
557+ // @partial-block is resolved at runtime via LR::in()/LR::p()
558+ if (str_starts_with ($ name , '@partial-block ' )) {
559+ return ;
560+ }
561+
562+ $ cnt = Partial::resolve ($ this ->context , $ name );
563+
564+ if ($ cnt !== null ) {
565+ $ this ->context ->usedPartial [$ name ] = $ cnt ;
566+ $ this ->compilePartialTemplate ($ name , $ cnt );
567+ return ;
568+ }
569+
570+ $ this ->context ->error [] = "The partial $ name could not be found " ;
571+ }
572+
573+ private function compilePartialTemplate (string $ name , string $ template ): void
574+ {
575+ if (isset ($ this ->context ->partialCode [$ name ])) {
576+ return ;
577+ }
578+
579+ // Prevent infinite recursion
580+ if (end ($ this ->context ->partialStack ) === $ name && str_starts_with ($ name , '@partial-block ' )) {
581+ return ;
582+ }
583+
584+ $ tmpContext = clone $ this ->context ;
585+ $ tmpContext ->inlinePartial = [];
586+ $ tmpContext ->partialBlock = [];
587+ $ tmpContext ->partialStack [] = $ name ;
588+
589+ $ program = (new ParserFactory ())->create ()->parse ($ template );
590+ $ code = (new NewCompiler ())->compile ($ program , $ tmpContext );
591+ $ this ->context ->merge ($ tmpContext );
592+
593+ if (!$ this ->context ->options ->preventIndent ) {
594+ $ code = preg_replace ('/^/m ' , "' {$ this ->context ->separator }\$sp {$ this ->context ->separator }' " , $ code );
595+ // remove extra spaces before partial
596+ $ code = preg_replace ('/^ \'\\. \\$sp \\. \'( \'\\.LR::p \\()/m ' , '$1 ' , $ code , 1 );
597+ // remove extra spaces before section
598+ $ code = preg_replace ('/^ \'\\. \\$sp \\. \'( \'\\.LR::sec \\()/m ' , '$1 ' , $ code , 1 );
599+ // remove extra spaces before blank lines
600+ $ code = preg_replace ('/^ \'\\. \\$sp \\. \'( \';} \\))/m ' , '$1 ' , $ code , 1 );
601+ // add spaces after partial
602+ $ code = preg_replace ('/^( \'\\.LR::p \\(.+ \\) \\.)( \'.+)/m ' , '$1\$sp.$2 ' , $ code , 1 );
603+ }
604+
605+ $ func = "function ( \$cx, \$in, \$sp) { {$ this ->context ->fStart }' $ code' {$ this ->context ->fEnd }} " ;
606+ $ this ->context ->partialCode [$ name ] = Expression::quoteString ($ name ) . " => $ func " ;
607+ }
608+
609+ public function handleDynamicPartials (): void
610+ {
611+ if ($ this ->context ->usedDynPartial === 0 ) {
612+ return ;
613+ }
614+
615+ foreach ($ this ->context ->partials as $ name => $ code ) {
616+ $ this ->resolveAndCompilePartial ($ name );
617+ }
618+ }
619+
509620 // ── Helpers ──────────────────────────────────────────────────────
510621
511622 /**
@@ -535,7 +646,7 @@ private function compileParams(array $params, ?Hash $hash, ?array $blockParams =
535646 private function compilePartialParams (array $ params , ?Hash $ hash ): string
536647 {
537648 if (!$ params ) {
538- $ contextVar = $ this ->context ->options ->explicitPartialContext ? 'null ' : '' ;
649+ $ contextVar = $ this ->context ->options ->explicitPartialContext ? 'null ' : '$in ' ;
539650 } else {
540651 $ contextVar = $ this ->compileExpression ($ params [0 ]);
541652 }
0 commit comments