diff --git a/app/Engine/Cfnpp/Compiler.php b/app/Engine/Cfnpp/Compiler.php index 7920562..9dd680e 100644 --- a/app/Engine/Cfnpp/Compiler.php +++ b/app/Engine/Cfnpp/Compiler.php @@ -7,6 +7,7 @@ namespace App\Engine\Cfnpp; use App\Dom\Document; use App\Dom\Node; use App\Dom\NodeFunction; +use App\Engine\IOptions; /** * Compiler that implements the cfnpp "language" and allows you to @@ -67,18 +68,18 @@ class Compiler implements \App\Engine\ICompile * Compile a set of documents and return the result. * * @param Document[] $documents - * @param \App\Engine\IOptions $options + * @param IOptions $options * @return void */ - public function compile(array $documents, \App\Engine\IOptions $options): Document + public function compile(array $documents, IOptions $options): Document { if (!($options instanceof Options)) { - throw new \Exception('Cfnpp\Compiler requires Cfnpp\Options'); + throw new \Exception('Cfnpp\\Compiler requires Cfnpp\\Options'); } // Initialize state - $document = new Document(); + $document = null; $this->functions = []; $this->merge_functions = []; @@ -86,13 +87,80 @@ class Compiler implements \App\Engine\ICompile $cfnpp_functions = new Functions($this, $options); $cfnpp_functions->register($this); + return $this->pass_0($documents, $options); + // Process each passed document + /* foreach ($documents as $next_document) { $this->runMergeFunctions($document, $next_document); $this->merge($document, $next_document); $this->runFunctions($document); } + */ + } + + /** + * Compiler Pass 0 - Build a dependency graph, run mergeFunctions + merge + * for each as appropriate. Return the resulting document. + * + * @param Document[] $documents + * @param IOptions $options + * @return Document + */ + protected function pass_0(array $documents, IOptions $options): Document + { + + // Build dependency graph + $graph = new \App\Util\DependencyGraph(); + foreach ($documents as $doc) + { + $stack = []; + try + { + $stack = $doc->getMeta('stack'); + } + catch (\Exception $ex) + { + } + $graph->add($doc->getDocumentName(), $stack); + } + + $graph->link(); + $docs = $graph->flatten(); + + // Reorder all our documents in whatever order the dependency solver + // says we should process them in. + // O(N^2), fix this if it ever actually becomes a problem. + $ordered_documents = []; + foreach ($docs as $doc_name) + { + foreach ($documents as $document) + { + if ($document->getDocumentName() == $doc_name) + { + $ordered_documents[] = $document; + continue 2; + } + } + } + + // Run our merge + merge functions + $document = new Document(); + foreach ($documents as $next_document) + { + $this->runMergeFunctions($document, $next_document); + $this->merge($document, $next_document); + } + + // Throw some of our state into the document for posterity's sake + $prefix = $this->getShortestCommonPrefix($docs); + foreach ($docs as &$doc) + { + $doc = substr($doc, strlen($prefix)); + } + unset($doc); + $document->setChildByPath('Metadata.Stack', Node::fromPhp($docs)); return $document; } @@ -274,4 +342,34 @@ class Compiler implements \App\Engine\ICompile { return $this->merge_functions[$function->getName()]($original, $target, $function); } + + /** + * Given a list of strings, find and return the shortest common prefix of all + * strings. Can return an empty string if there's no prefix in common. + * + * @param string[] $strings + * @return string + */ + protected function getShortestCommonPrefix(array $strings): string + { + // Find longest common prefix + $shortest_string_length = array_reduce($strings, static function(int $carry, string $item) { + return min($carry, strlen($item)); + }, PHP_INT_MAX); + + for ($i = 0; $i < $shortest_string_length; $i++) + { + $c = $strings[0][$i]; + for ($x = 1; $x < sizeof($strings); $x++) + { + if ($strings[$x][$i] != $c) + { + --$i; + break 2; + } + } + } + + return $i == 0 ? '' : substr($strings[0], 0, $i); + } }