Browse Source

Refactor/rewrite compilation to be less like spaghetti

master
Adam Pippin 3 years ago
parent
commit
2d4d9b725c
  1. 85
      app/Dom/Node.php
  2. 2
      app/Dom/NodeFunction.php
  3. 2
      app/Dom/NodeFunctionValue.php
  4. 2
      app/Dom/NodeValue.php
  5. 209
      app/Engine/Cfnpp.php

85
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

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

2
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;

2
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;

209
app/Engine/Cfnpp.php

@ -18,17 +18,22 @@ class Cfnpp implements ICompile
/** @var array<string, callable> */
protected $functions;
/** @var array<string, callable> */
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);
}
}

Loading…
Cancel
Save