diff --git a/app/Engine/Cfnpp/Compiler.php b/app/Engine/Cfnpp/Compiler.php index 0fe68a7..2d5d2ca 100644 --- a/app/Engine/Cfnpp/Compiler.php +++ b/app/Engine/Cfnpp/Compiler.php @@ -7,7 +7,9 @@ namespace App\Engine\Cfnpp; use App\Dom\Document; use App\Dom\Node; use App\Dom\NodeFunction; +use App\Dom\NodeFunctionValue; use App\Engine\IOptions; +use App\Util\DependencyGraph; /** * Compiler that implements the cfnpp "language" and allows you to @@ -87,7 +89,10 @@ class Compiler implements \App\Engine\ICompile $cfnpp_functions = new Functions($this, $options); $cfnpp_functions->register($this); - return $this->pass_0($documents, $options); + $document = $this->pass_0($documents, $options); + $this->pass_1($document, $options); + + return $document; // Process each passed document /* @@ -112,7 +117,7 @@ class Compiler implements \App\Engine\ICompile { // Build dependency graph - $graph = new \App\Util\DependencyGraph(); + $graph = new DependencyGraph(); foreach ($documents as $doc) { $stack = []; @@ -146,7 +151,7 @@ class Compiler implements \App\Engine\ICompile // Run our merge + merge functions $document = new Document(); - foreach ($documents as $next_document) + foreach ($ordered_documents as $next_document) { $this->runMergeFunctions($document, $next_document); $this->merge($document, $next_document); @@ -164,6 +169,76 @@ class Compiler implements \App\Engine\ICompile return $document; } + /** + * Compiler Pass 1 - Grab all variables, build dependency graph of + * dependencies between variable values, resolve variables values. Then + * set them on $options to include them in program state. + * + * @param Document $document + * @param IOptions $options + * @return void + */ + protected function pass_1(Document $document, IOptions $options): void + { + $variables_node = $document->getChildByPath('cfnpp.variables'); + // If there are no variables, we're done here. + if (!isset($variables_node)) + { + return; + } + + $variables_raw = []; + $graph = new DependencyGraph(); + + foreach ($variables_node as $variable_node) + { + $variables_raw[$variable_node->getName()] = $variable_node; + $graph->add($variable_node->getName(), $this->pass_1_getVariableDependencies($variable_node)); + } + + $variables_ordered = $graph->solve(); + + foreach ($variables_ordered as $variable) + { + $this->runFunctions($variables_raw[$variable]); + $options->setVariable($variable, Node::toPhp($variables_node->getChildByName($variable))); + } + } + + /** + * Given a node tree, get a list of variables that are referenced in that + * tree. + * + * @todo whenever we add expressions this will have to be extended/rewritten + * @param Node $node + * @return string[] + */ + protected function pass_1_getVariableDependencies(Node $node): array + { + $stack = [$node]; + + $variables = []; + + while (sizeof($stack)) + { + $node = array_shift($stack); + + if ($node->hasChildren()) + { + $stack = array_merge($stack, $node->getChildren()); + } + + // We're really only handling one case here right now + if ($node instanceof NodeFunctionValue && + $node->getName() == 'var') + { + $variables[] = $node->getValue(); + } + } + + return $variables; + } + /** * Performs a basic recursive merge of two dom trees with target overwriting * original.