|
|
@ -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); |
|
|
|
} |
|
|
|
} |
|
|
|