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. * 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 * @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->parent = $parent;
$this->children = []; $this->children = [];
@ -98,6 +98,16 @@ class Node implements \ArrayAccess, \Iterator
return sizeof($this->children) > 0; 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. * Fetch this node's children.
* *
@ -108,6 +118,17 @@ class Node implements \ArrayAccess, \Iterator
return $this->children; 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. * Attempt to find a child node by name.
* *
@ -126,6 +147,35 @@ class Node implements \ArrayAccess, \Iterator
return null; 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. * Add a node to this node's children.
* *
@ -157,6 +207,10 @@ class Node implements \ArrayAccess, \Iterator
public function isMap(): bool public function isMap(): bool
{ {
if ($this->isFunctionParent())
{
return false;
}
foreach ($this->children as $child) foreach ($this->children as $child)
{ {
if (!$child->hasName()) if (!$child->hasName())
@ -179,33 +233,14 @@ class Node implements \ArrayAccess, \Iterator
return true; return true;
} }
public function getChildByPath(string $path): ?Node public function isFunctionParent(): bool
{ {
$path = explode('.', $path); return sizeof($this->children) == 1 && $this->children[0] instanceof NodeFunction;
return $this->findChild($path, $this);
} }
protected function findChild(array $path, Node $current): ?Node public function remove(): void
{ {
$next = array_shift($path); $this->getParent()->removeChild($this);
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);
} }
// ArrayAccess // ArrayAccess

2
app/Dom/NodeFunction.php

@ -11,7 +11,7 @@ namespace App\Dom;
*/ */
class NodeFunction extends Node class NodeFunction extends Node
{ {
public function __construct(Node $parent, ?string $name) public function __construct(?Node $parent, ?string $name)
{ {
parent::__construct($parent, $name); parent::__construct($parent, $name);
} }

2
app/Dom/NodeFunctionValue.php

@ -21,7 +21,7 @@ class NodeFunctionValue extends NodeFunction
* @param ?string $name * @param ?string $name
* @param scalar $value * @param scalar $value
*/ */
public function __construct(Node $parent, ?string $name, $value) public function __construct(?Node $parent, ?string $name, $value)
{ {
parent::__construct($parent, $name); parent::__construct($parent, $name);
$this->value = $value; $this->value = $value;

2
app/Dom/NodeValue.php

@ -21,7 +21,7 @@ class NodeValue extends Node
* @param ?string $name * @param ?string $name
* @param scalar $value * @param scalar $value
*/ */
public function __construct(Node $parent, ?string $name, $value) public function __construct(?Node $parent, ?string $name, $value)
{ {
parent::__construct($parent, $name); parent::__construct($parent, $name);
$this->value = $value; $this->value = $value;

209
app/Engine/Cfnpp.php

@ -18,17 +18,22 @@ class Cfnpp implements ICompile
/** @var array<string, callable> */ /** @var array<string, callable> */
protected $functions; protected $functions;
/** @var array<string, callable> */
protected $merge_functions;
public function __construct() public function __construct()
{ {
$this->document = new Document();
$this->functions = []; $this->functions = [];
$this->registerFunction('Replace', static function(Node $orig_p, Node $tgt_p, Node $t) { $this->registerMergeFunction('Replace', static function(Node $orig_p, Node $tgt_p, Node $t) {
if ($t instanceof NodeFunctionValue) // todo: nodefunctionvalue
{
return $t->getValue();
}
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; $this->functions[$name] = $callback;
} }
public function registerMergeFunction(string $name, callable $callback): void
{
$this->merge_functions[$name] = $callback;
}
public function setDocument(Document $document): void public function setDocument(Document $document): void
{ {
$this->document = $document; $this->document = $document;
} }
public function getDocument(): Document
{
return $this->document;
}
public function compile(Document $document, IOptions $options): void 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) foreach ($target as $child)
{
$original->setValue($target->getValue());
}
else
{ {
$parent = $original->getParent(); $original->addChild($child);
$parent->removeChild($original); $child->setParent($original);
$parent->addChild($target);
} }
} }
elseif ($target instanceof NodeFunction) elseif ($original->isMap() && $target->isMap())
{
$this->resolveFunction($original->getParent(), $target->getParent(), $target);
}
elseif ($target instanceof Node)
{ {
// if they're both maps, add/overwrite by name foreach ($target as $child)
if ($target->isMap() && $original->isMap())
{ {
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()); $original->addChild($child);
if ($target_child instanceof NodeFunction) $child->setParent($original);
{ continue;
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);
}
}
} }
}
// if they're both arrays, append // If this is a map or array, we need to descend into it
elseif ($target->isArray() && $original->isArray()) if ($child->isMap() || $child->isArray())
{ {
foreach ($target as $child) $this->merge($orig_child, $child);
}
// Otherwise just replace it (nodefunction, nodevalue, whatever)
else
{ {
$original->removeChild($orig_child);
$original->addChild($child); $original->addChild($child);
$child->setParent($original);
} }
} }
// if there's a mismatch, replace }
else else
{ {
$parent = $original->getParent(); // If it's anything else, overwrite
$parent->removeChild($original); $original->remove();
$parent->addChild($target); $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 if ($target->isFunctionParent() && isset($this->merge_functions[$target[0]->getName()]))
$function = $target->getName();
if (!isset($this->functions[$function]))
{ {
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(); $target->remove();
$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);
} }
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 $function_node = $node[0];
$original_grandparent = $original_parent->getParent(); $result = $this->runFunction($node, $function_node);
$original_grandparent->removeChild($original_parent);
$original_parent = new Node($original_grandparent, $original_parent->getName()); $node->remove();
$original_grandparent->addChild($original_parent); if (isset($result))
foreach ($result as $node)
{ {
$original_parent->addChild($node); $node->getParent()->addChild($result);
$node->setParent($original_parent->getParent()); $result->setParent($node->getParent());
$node = $result;
} }
else
$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)
{ {
$node = clone $node; return;
$target_parent->addChild($node);
$node->setParent($target_parent->getParent());
} }
$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