From 2d4d9b725c9df433a0497e53962076ca0869ad74 Mon Sep 17 00:00:00 2001 From: Adam Pippin Date: Fri, 12 Feb 2021 16:35:54 -0800 Subject: [PATCH] Refactor/rewrite compilation to be less like spaghetti --- app/Dom/Node.php | 85 ++++++++++---- app/Dom/NodeFunction.php | 2 +- app/Dom/NodeFunctionValue.php | 2 +- app/Dom/NodeValue.php | 2 +- app/Engine/Cfnpp.php | 209 +++++++++++++++++++--------------- 5 files changed, 180 insertions(+), 120 deletions(-) diff --git a/app/Dom/Node.php b/app/Dom/Node.php index b7a16ea..3ae4759 100644 --- a/app/Dom/Node.php +++ b/app/Dom/Node.php @@ -26,10 +26,10 @@ class Node implements \ArrayAccess, \Iterator /** * Create a new DOM node. * - * @param Node $parent the parent this node is nested under + * @param ?Node $parent the parent this node is nested under * @param ?string $name the name of the node, if one exists */ - public function __construct(Node $parent, ?string $name) + public function __construct(?Node $parent, ?string $name) { $this->parent = $parent; $this->children = []; @@ -98,6 +98,16 @@ class Node implements \ArrayAccess, \Iterator return sizeof($this->children) > 0; } + /** + * How many children this node has. + * + * @return int + */ + public function countChildren(): int + { + return sizeof($this->children); + } + /** * Fetch this node's children. * @@ -108,6 +118,17 @@ class Node implements \ArrayAccess, \Iterator return $this->children; } + /** + * Replace all of this node's children. + * + * @param Node[] $nodes + * @return void + */ + public function setChildren(array $nodes): void + { + $this->children = $nodes; + } + /** * Attempt to find a child node by name. * @@ -126,6 +147,35 @@ class Node implements \ArrayAccess, \Iterator return null; } + public function getChildByPath(string $path): ?Node + { + $path = explode('.', $path); + return $this->findChild($path, $this); + } + + protected function findChild(array $path, Node $current): ?Node + { + $next = array_shift($path); + if (is_numeric($next)) + { + $next = $current[$next]; + } + else + { + $next = $current->getChildByName($next); + } + if (empty($path)) + { + return $next; + } + if (!isset($next)) + { + return null; + } + + return $this->findChild($path, $next); + } + /** * Add a node to this node's children. * @@ -157,6 +207,10 @@ class Node implements \ArrayAccess, \Iterator public function isMap(): bool { + if ($this->isFunctionParent()) + { + return false; + } foreach ($this->children as $child) { if (!$child->hasName()) @@ -179,33 +233,14 @@ class Node implements \ArrayAccess, \Iterator return true; } - public function getChildByPath(string $path): ?Node + public function isFunctionParent(): bool { - $path = explode('.', $path); - return $this->findChild($path, $this); + return sizeof($this->children) == 1 && $this->children[0] instanceof NodeFunction; } - protected function findChild(array $path, Node $current): ?Node + public function remove(): void { - $next = array_shift($path); - if (is_numeric($next)) - { - $next = $current[$next]; - } - else - { - $next = $current->getChildByName($next); - } - if (empty($path)) - { - return $next; - } - if (!isset($next)) - { - return null; - } - - return $this->findChild($path, $next); + $this->getParent()->removeChild($this); } // ArrayAccess diff --git a/app/Dom/NodeFunction.php b/app/Dom/NodeFunction.php index ac4360d..df4611e 100644 --- a/app/Dom/NodeFunction.php +++ b/app/Dom/NodeFunction.php @@ -11,7 +11,7 @@ namespace App\Dom; */ class NodeFunction extends Node { - public function __construct(Node $parent, ?string $name) + public function __construct(?Node $parent, ?string $name) { parent::__construct($parent, $name); } diff --git a/app/Dom/NodeFunctionValue.php b/app/Dom/NodeFunctionValue.php index 2307210..d64bb8f 100644 --- a/app/Dom/NodeFunctionValue.php +++ b/app/Dom/NodeFunctionValue.php @@ -21,7 +21,7 @@ class NodeFunctionValue extends NodeFunction * @param ?string $name * @param scalar $value */ - public function __construct(Node $parent, ?string $name, $value) + public function __construct(?Node $parent, ?string $name, $value) { parent::__construct($parent, $name); $this->value = $value; diff --git a/app/Dom/NodeValue.php b/app/Dom/NodeValue.php index 2020ef2..3ef824c 100644 --- a/app/Dom/NodeValue.php +++ b/app/Dom/NodeValue.php @@ -21,7 +21,7 @@ class NodeValue extends Node * @param ?string $name * @param scalar $value */ - public function __construct(Node $parent, ?string $name, $value) + public function __construct(?Node $parent, ?string $name, $value) { parent::__construct($parent, $name); $this->value = $value; diff --git a/app/Engine/Cfnpp.php b/app/Engine/Cfnpp.php index 9fc0f83..13a5676 100644 --- a/app/Engine/Cfnpp.php +++ b/app/Engine/Cfnpp.php @@ -18,17 +18,22 @@ class Cfnpp implements ICompile /** @var array */ protected $functions; + /** @var array */ + protected $merge_functions; + public function __construct() { + $this->document = new Document(); $this->functions = []; - $this->registerFunction('Replace', static function(Node $orig_p, Node $tgt_p, Node $t) { - if ($t instanceof NodeFunctionValue) - { - return $t->getValue(); - } + $this->registerMergeFunction('Replace', static function(Node $orig_p, Node $tgt_p, Node $t) { + // todo: nodefunctionvalue - return $t->getChildren(); + $repl = new Node(null, $tgt_p->hasName() ? $tgt_p->getName() : null); + $repl->setChildren($t->getChildren()); + return $repl; + }); + $this->registerFunction('Unset', static function(Node $node, Node $func) { }); } @@ -37,132 +42,152 @@ class Cfnpp implements ICompile $this->functions[$name] = $callback; } + public function registerMergeFunction(string $name, callable $callback): void + { + $this->merge_functions[$name] = $callback; + } + public function setDocument(Document $document): void { $this->document = $document; } + public function getDocument(): Document + { + return $this->document; + } + public function compile(Document $document, IOptions $options): void { - $this->compileNodes($this->document, $document); + $this->runMergeFunctions($this->document, $document); + $this->merge($this->document, $document); + $this->runFunctions($this->document); } - protected function compileNodes(Node $original, Node $target): void + protected function merge(Node $original, Node $target): void { - if ($target instanceof NodeValue) + if ($original->isArray() && $target->isArray()) { - if ($original instanceof NodeValue) - { - $original->setValue($target->getValue()); - } - else + foreach ($target as $child) { - $parent = $original->getParent(); - $parent->removeChild($original); - $parent->addChild($target); + $original->addChild($child); + $child->setParent($original); } } - elseif ($target instanceof NodeFunction) - { - $this->resolveFunction($original->getParent(), $target->getParent(), $target); - } - elseif ($target instanceof Node) + elseif ($original->isMap() && $target->isMap()) { - // if they're both maps, add/overwrite by name - if ($target->isMap() && $original->isMap()) + foreach ($target as $child) { - foreach ($target as $target_child) + $orig_child = $original->getChildByName($child->getName()); + // If the key doesn't exist on the source, just copy over and we're done + if (!isset($orig_child)) { - $orig_child = $original->getChildByName($target_child->getName()); - if ($target_child instanceof NodeFunction) - { - if (!isset($orig_child)) - { - $original->addChild($orig_child = new Node($original, $target->getName())); - } - $this->compileNodes($orig_child, $target_child); - } - else - { - if (!isset($orig_child)) - { - $original->addChild($target_child); - } - else - { - $this->compileNodes($orig_child, $target_child); - } - } + $original->addChild($child); + $child->setParent($original); + continue; } - } - // if they're both arrays, append - elseif ($target->isArray() && $original->isArray()) - { - foreach ($target as $child) + + // If this is a map or array, we need to descend into it + if ($child->isMap() || $child->isArray()) + { + $this->merge($orig_child, $child); + } + // Otherwise just replace it (nodefunction, nodevalue, whatever) + else { + $original->removeChild($orig_child); $original->addChild($child); + $child->setParent($original); } } - // if there's a mismatch, replace - else - { - $parent = $original->getParent(); - $parent->removeChild($original); - $parent->addChild($target); - } + } + else + { + // If it's anything else, overwrite + $original->remove(); + $original->getParent()->addChild($target); + $target->setParent($original); } } - protected function resolveFunction(Node $original_parent, Node $target_parent, Node $target): void + protected function runMergeFunctions(Node $original, Node $target): void { - // Execute nodefunction - $function = $target->getName(); - - if (!isset($this->functions[$function])) + if ($target->isFunctionParent() && isset($this->merge_functions[$target[0]->getName()])) { - throw new \Exception('Unrecognized function: '.$function); + $function_node = $target[0]; + $result = $this->runMergeFunction($original, $target, $function_node); + + $original->remove(); + $target->remove(); + + if (isset($result)) + { + $original_node = $result; + $original_node->setParent($original->getParent()); + $original->getParent()->addChild($original_node); + $original = $original_node; + + $target_node = clone $result; + $target_node->setParent($target->getParent()); + $target->getParent()->addChild($target_node); + $target = $target_node; + } } - $result = $this->functions[$function]($original_parent, $target_parent, $target); + foreach ($target as $node) + { + if ($node->hasName()) + { + $orig_child = $original->getChildByName($node->getName()); + if (!isset($orig_child)) + { + $orig_child = new Node($original, $node->getName()); + $original->addChild($orig_child); + } + + $this->runMergeFunctions($orig_child, $node); + } + } - if (is_scalar($result)) + if (isset($result)) { - $original_grandparent = $original_parent->getParent(); - $original_grandparent->removeChild($original_parent); - $node = new NodeValue($original_grandparent, $target_parent->getName(), $result); - $original_grandparent->addChild($node); - // Remove processed function - $target_parent->getParent()->removeChild($target_parent); + $target->remove(); } - elseif (is_array($result)) + } + + protected function runFunctions(Node $node): void + { + if ($node->isFunctionParent() && isset($this->functions[$node[0]->getName()])) { - // Update original _and_ target so we can reprocess sensibly - $original_grandparent = $original_parent->getParent(); - $original_grandparent->removeChild($original_parent); - $original_parent = new Node($original_grandparent, $original_parent->getName()); - $original_grandparent->addChild($original_parent); - foreach ($result as $node) + $function_node = $node[0]; + $result = $this->runFunction($node, $function_node); + + $node->remove(); + if (isset($result)) { - $original_parent->addChild($node); - $node->setParent($original_parent->getParent()); + $node->getParent()->addChild($result); + $result->setParent($node->getParent()); + $node = $result; } - - $target_grandparent = $target_parent->getParent(); - $target_grandparent->removeChild($target_parent); - $target_parent = new Node($target_grandparent, $target_parent->getName()); - $target_grandparent->addChild($target_parent); - foreach ($result as $node) + else { - $node = clone $node; - $target_parent->addChild($node); - $node->setParent($target_parent->getParent()); + return; } - $this->compileNodes($original_parent, $target_parent); + } + + foreach ($node as $child) + { + $this->runFunctions($child); } } - public function getDocument(): Document + protected function runFunction(Node $node, NodeFunction $function): ?Node { - return $this->document; + return $this->functions[$function->getName()]($node, $function); + } + + protected function runMergeFunction(Node $original, Node $target, NodeFunction $function): ?Node + { + return $this->merge_functions[$function->getName()]($original, $target, $function); } }