258 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace Picea\ControlStructure;
 | |
| 
 | |
| class BlockToken implements ControlStructure {
 | |
| 
 | |
|     public array $token = [ "arguments", "block", "endblock", "define", "slot", "endslot", "using" ];
 | |
| 
 | |
|     public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
 | |
|     {
 | |
|         static $depth = 0;
 | |
|         static $slotDefinitions = [];
 | |
| 
 | |
|         # dump($depth, $token, $arguments, $slotDefinitions);
 | |
| 
 | |
|         switch($token) {
 | |
|             case "block":
 | |
|                 $slotDefinitions[] = $this->slotDefinitions();
 | |
|                 $depth++;
 | |
| 
 | |
|                 return "<?php \$___block = \Picea\ControlStructure\BlockToken::instanciateBlock($arguments); ?>";
 | |
| 
 | |
|             case "endblock":
 | |
|                 $depth--;
 | |
|                 return "<?php echo \$___block->render(\$___class__template); unset(\$___block); ?>";
 | |
| 
 | |
|             case "arguments":
 | |
|                 $class = static::class;
 | |
| 
 | |
|                 return <<<PHP
 | |
|                     <?php 
 | |
|                         try {
 | |
|                             extract( \$this->using ?? [], \EXTR_OVERWRITE);
 | |
|                             extract( \\$class::parseArguments(function($arguments) {}, \$inlineVariables), \EXTR_OVERWRITE);
 | |
|                             unset(\$inlineVariables);
 | |
|                         } 
 | |
|                         catch(\TypeError \$ex) {
 | |
|                             throw new \Exception(
 | |
|                                 sprintf('A block awaiting arguments `%s` instead received `%s` with values `%s`', '$arguments', implode(', ', array_map('gettype', \$inlineVariables ?? [])), json_encode(\$inlineVariables))
 | |
|                             );
 | |
|                         }
 | |
|                     /*%EXCEPTION_LINE_BASE%*/?>
 | |
|                 PHP;
 | |
| 
 | |
|             case "define":
 | |
|                 list($name, $definition) = array_pad(explode(',', $arguments, 2), 2, "");
 | |
| 
 | |
|                 ( $slotDefinitions[$depth] ?? end($slotDefinitions) )->setDefinition(eval("return $name;"), $definition);
 | |
| 
 | |
|                 return <<<PHP
 | |
|                 <?php \$this->defineSlot($name, function($definition) {}); ?>
 | |
|                 PHP;
 | |
| 
 | |
|             case "slot":
 | |
|                 $def = ( $slotDefinitions[$depth] ?? end($slotDefinitions) );
 | |
| 
 | |
|                 list($name, $definition) = array_pad(explode(',', $arguments, 2), 2, "");
 | |
| 
 | |
|                 $loops = count($context->iterationStack ?? []) ? ",". implode(', ', array_filter(array_column($context->iterationStack, 'uid'), fn($e) => strpos($e, '[') === false)) : null;
 | |
| 
 | |
|                 if ($def->hasDefinitions() ) {
 | |
|                     $slotName = eval("return $name;");
 | |
|                     $def->currentSlot = $slotName;
 | |
|                     $def->setDefinitionVars($slotName, $definition);
 | |
| 
 | |
|                     $definition = $def->printDefinition($slotName);
 | |
| 
 | |
|                     if ($definition) {
 | |
|                         $definition .= ",";
 | |
|                     }
 | |
| 
 | |
|                     return <<<PHP
 | |
|                     <?php \$this->printSlot($name, function($definition array \$___using = []) use (\$picea $loops) { extract(\$___using, \EXTR_SKIP); ?>
 | |
|                     PHP;
 | |
|                 }
 | |
|                 else {
 | |
|                     if ($definition) {
 | |
|                         $definition .= ",";
 | |
|                     }
 | |
| 
 | |
|                     return <<<PHP
 | |
|                     <?php (\$___block ?? \$this)->slotIsSet($name) || (\$___block ?? \$this)->setSlot($name, function($definition array \$___using = []) use (\$picea $loops) { extract(\$___using, \EXTR_SKIP); ?>
 | |
|                     PHP;
 | |
|                 }
 | |
| 
 | |
|             case "endslot":
 | |
|                 $def = ( $slotDefinitions[$depth] ?? end($slotDefinitions) );
 | |
| 
 | |
|                 if ($def->hasDefinitions() ) {
 | |
|                     $definition = $def->getCurrentSlotDefinitionVars();
 | |
| 
 | |
|                     if ($definition) {
 | |
|                         $definition .= ",";
 | |
|                     }
 | |
| 
 | |
|                     return <<<PHP
 | |
|                     <?php })->call(\$this, $definition array_merge(get_defined_vars(), \$this->using)); ?>
 | |
|                     PHP;
 | |
|                 }
 | |
|                 else {
 | |
|                     return "<?php }); ?>";
 | |
|                 }
 | |
| 
 | |
|             case "using":
 | |
|                 return "<?php \$___block->setUsing($arguments); ?>";
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     public function inlineHtml(? object $proxy, string $viewPath, ... $variables) {
 | |
|         return $this->renderHtml($viewPath, [ 'inlineVariables' => $variables ], $proxy);
 | |
|     }
 | |
| 
 | |
|     public static function parseSlotArguments(Callable $method) : array
 | |
|     {
 | |
|         #return
 | |
|     }
 | |
| 
 | |
| 
 | |
|     public static function parseArguments(Callable $method, array $arguments) : array
 | |
|     {
 | |
|         try{
 | |
|             call_user_func_array($method, $arguments);
 | |
|         }
 | |
|         catch(\TypeError $ex) {
 | |
|             throw $ex;
 | |
|         }
 | |
| 
 | |
|         $parameters = [];
 | |
| 
 | |
|         foreach((new \ReflectionFunction($method))->getParameters() as $key => $value) {
 | |
|             if ( isset($arguments[$key]) ) {
 | |
|                 $parameters[ $value->getName() ] = $arguments[$key];
 | |
|             }
 | |
|             elseif ( $value->isDefaultValueAvailable() ) {
 | |
|                 $parameters[ $value->getName() ] = $value->getDefaultValue();
 | |
|             }
 | |
|             elseif ( $value->isVariadic() ) {
 | |
|                 $parameters[ $value->getName() ] = [];
 | |
|             }
 | |
|             else {
 | |
|                 $parameters[ $value->getName() ] = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $parameters;
 | |
|     }
 | |
| 
 | |
|     public static function slotDefinitions() {
 | |
|         return new class() {
 | |
| 
 | |
|             public array $definitions = [];
 | |
| 
 | |
|             public array $variables = [];
 | |
| 
 | |
|             public bool $rendering = false;
 | |
| 
 | |
|             public string $currentSlot;
 | |
| 
 | |
|             public function setDefinition(string $name, string $definition) : self
 | |
|             {
 | |
|                 $this->definitions[$name] = $definition;
 | |
| 
 | |
|                 return $this;
 | |
|             }
 | |
| 
 | |
|             public function setDefinitionVars(string $name, string $variables) : self
 | |
|             {
 | |
|                 $this->variables[$name] = $variables;
 | |
| 
 | |
|                 return $this;
 | |
|             }
 | |
| 
 | |
|             public function getDefinitionVars(string $name) : string
 | |
|             {
 | |
|                 return $this->variables[$name] ?? "";
 | |
|             }
 | |
| 
 | |
|             public function getCurrentSlotDefinitionVars() : string
 | |
|             {
 | |
|                 return $this->getDefinitionVars($this->currentSlot);
 | |
|             }
 | |
| 
 | |
|             public function hasDefinitions() : bool
 | |
|             {
 | |
|                 return count($this->definitions) > 0;
 | |
|             }
 | |
| 
 | |
|             public function printDefinition(string $name) : string
 | |
|             {
 | |
|                 if ( ! isset($this->definitions[$name]) ) {
 | |
|                     throw new \Exception("Slot definition for `$name` was not found. Have you defined it in your block header using something like '{% define \"$name\", ...\$arguments %}' ?");
 | |
|                 }
 | |
| 
 | |
|                 return $this->definitions[$name];
 | |
|             }
 | |
| 
 | |
|             public function render() : void
 | |
|             {
 | |
|                 $this->rendering = true;
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     public static function instanciateBlock(string $viewPath, ... $arguments)
 | |
|     {
 | |
|         return new class($viewPath, ...$arguments) {
 | |
| 
 | |
|             public bool $rendering = false;
 | |
| 
 | |
|             public string $viewPath;
 | |
| 
 | |
|             public array $using = [];
 | |
| 
 | |
|             public array $arguments = [];
 | |
| 
 | |
|             public array $slots = [];
 | |
| 
 | |
|             public array $definition = [];
 | |
| 
 | |
|             public function __construct(string $viewPath, ...$arguments) {
 | |
|                 $this->viewPath = $viewPath;
 | |
|                 $this->arguments = $arguments;
 | |
|             }
 | |
| 
 | |
|             public function render(object $classTemplate) : string
 | |
|             {
 | |
|                 $this->rendering = true;
 | |
| 
 | |
|                 return $classTemplate->picea->inlineBlock($this, $this->viewPath, ...$this->arguments);
 | |
|             }
 | |
| 
 | |
|             public function setSlot(string $name, Callable $method) : void
 | |
|             {
 | |
|                 $this->slots[$name] = $method;
 | |
|             }
 | |
| 
 | |
|             public function defineSlot(string $name, Callable $method) : void
 | |
|             {
 | |
|                 $this->definition[$name] = $method;
 | |
|             }
 | |
| 
 | |
|             public function slotIsSet(string $name) : bool
 | |
|             {
 | |
|                 return ! empty($this->slots[$name]);
 | |
|             }
 | |
| 
 | |
|             public function printSlot(string $name, Callable $default)
 | |
|             {
 | |
|                 return $this->slotIsSet($name) ? $this->slots[$name] : $default;
 | |
|             }
 | |
| 
 | |
|             public function setUsing(array $variables) {
 | |
|                 $this->using = $variables;
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| }
 |