Browse Source

Cleanup + comments

master
Adam Pippin 3 years ago
parent
commit
41b26006ac
  1. 25
      app/Cfnpp/Compiler.php
  2. 135
      app/Cfnpp/Expression/Expression.php
  3. 12
      app/Cfnpp/Expression/ICloudformationNative.php
  4. 25
      app/Cfnpp/Expression/Token.php
  5. 25
      app/Cfnpp/Expression/Token/NumericLiteral.php
  6. 63
      app/Cfnpp/Expression/Token/OperatorBinary.php
  7. 41
      app/Cfnpp/Expression/Token/OperatorUnary.php
  8. 35
      app/Cfnpp/Expression/Token/Parameter.php
  9. 29
      app/Cfnpp/Expression/Token/StringLiteral.php
  10. 30
      app/Cfnpp/Expression/Token/Variable.php
  11. 10
      app/Cfnpp/Functions.php
  12. 108
      app/Util/GraphNode.php
  13. 34
      app/Util/Stack.php

25
app/Cfnpp/Compiler.php

@ -44,7 +44,7 @@ class Compiler implements \App\Engine\ICompile
/** /**
* Stores current state of the document so it can be mutated mid-pass. * Stores current state of the document so it can be mutated mid-pass.
* *
* @var Document * @var ?Document
*/ */
protected $document; protected $document;
@ -73,6 +73,15 @@ class Compiler implements \App\Engine\ICompile
$this->merge_functions[$name] = $callback; $this->merge_functions[$name] = $callback;
} }
/**
* Add a condition to the document currently being processed.
*
* The only appropriate place to call this is from the document functions
*
* @param string $name name of condition to create
* @param Node $node
* @return void
*/
public function addCondition(string $name, Node $node): void public function addCondition(string $name, Node $node): void
{ {
$conditions = $this->document->getChildByName('Conditions'); $conditions = $this->document->getChildByName('Conditions');
@ -109,12 +118,11 @@ class Compiler implements \App\Engine\ICompile
$cfnpp_functions = new Functions($this, $options); $cfnpp_functions = new Functions($this, $options);
$cfnpp_functions->register($this); $cfnpp_functions->register($this);
$this->document = $this->pass_0($documents, $options); $this->document = $document = $this->pass_0($documents, $options);
$this->pass_1($this->document, $options); $this->pass_1($document, $options);
$this->pass_2($this->document, $options); $this->pass_2($document, $options);
return $this->document; return $this->document;
// Process each passed document // Process each passed document
/* /*
foreach ($documents as $next_document) foreach ($documents as $next_document)
@ -294,13 +302,14 @@ class Compiler implements \App\Engine\ICompile
{ {
$variables[] = $node->getValue(); $variables[] = $node->getValue();
} }
/*
// TODO: Reimplement
elseif ($node instanceof NodeFunctionValue && elseif ($node instanceof NodeFunctionValue &&
$node->getName() == 'expr') $node->getName() == 'expr')
{ {
$parser = new \App\Cfnpp\Expression\Parser(); $expression = new \App\Cfnpp\Expression\Expression($node->getValue());
$expression = $parser->parse($node->getValue());
$variables = array_merge($variables, $expression->getReferencedVariables()); $variables = array_merge($variables, $expression->getReferencedVariables());
} }*/
} }
return $variables; return $variables;

135
app/Cfnpp/Expression/Expression.php

@ -7,8 +7,18 @@ namespace App\Cfnpp\Expression;
use App\Util\GraphNode; use App\Util\GraphNode;
use App\Util\Stack; use App\Util\Stack;
/**
* Represents an expression and handles tokenizing, parsing, and executing
* expressions.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class Expression class Expression
{ {
/**
* References to all Token implementations this class can handle.
* @var string[]
*/
public const TOKEN_TYPES = [ public const TOKEN_TYPES = [
Token\NumericLiteral::class, Token\NumericLiteral::class,
Token\OperatorUnary::class, Token\OperatorUnary::class,
@ -17,10 +27,26 @@ class Expression
Token\Variable::class Token\Variable::class
]; ];
/**
* Solved expression this represents.
* @var GraphNode[]
*/
protected $nodes; protected $nodes;
protected $solved = false; /**
* Referencing to our compilation state for accessing variables/parameters.
* @var \App\Engine\IOptions
*/
protected $options;
/**
* Create a new expression.
*
* Expression is tokenized, parsed, and solved on creation.
*
* @param string $expression
* @param \App\Engine\IOptions $options
*/
public function __construct(string $expression, \App\Engine\IOptions $options) public function __construct(string $expression, \App\Engine\IOptions $options)
{ {
$tokens = static::tokenize($expression); $tokens = static::tokenize($expression);
@ -29,7 +55,16 @@ class Expression
$this->options = $options; $this->options = $options;
} }
public function isComplete() /**
* Check whether this expression is 'complete'.
*
* Complete is defined as having been solved down to a single value, or array
* of single values none of which are still tokens. That is, we have a set of
* computed scalars and no unresolved var/param references.
*
* @return bool
*/
public function isComplete(): bool
{ {
// I think this works? If we resolved down to a flat set of values and // I think this works? If we resolved down to a flat set of values and
// all are scalar, then we're done? // all are scalar, then we're done?
@ -49,26 +84,55 @@ class Expression
return $complete; return $complete;
} }
public function count() /**
* Check how many values are contained in the solution.
*
* @return int
*/
public function count(): int
{ {
return sizeof($this->nodes); return sizeof($this->nodes);
} }
/**
* Assuming the solution contains only a single value, fetch it.
*
* @return mixed
*/
public function getValue() public function getValue()
{ {
return $this->nodes[0]->getValue(); return $this->nodes[0]->getValue();
} }
/**
* Fetch the solution as an array.
*
* Only valid if the solution is complete
*
* @return mixed[]
*/
public function toArray(): array public function toArray(): array
{ {
return static::unwrap($this->nodes); return static::unwrap($this->nodes);
} }
/**
* Convert an incomplete solution into a CloudFormation condition using
* CloudFormation intrinsic functions.
*
* @return mixed[]
*/
public function toCloudformation(): array public function toCloudformation(): array
{ {
return static::cloudformation($this->nodes); return static::cloudformation($this->nodes);
} }
/**
* Convert an expression string into a series of tokens.
*
* @param string $expression
* @return Token[]
*/
protected static function tokenize(string $expression): array protected static function tokenize(string $expression): array
{ {
$tokens = []; $tokens = [];
@ -95,6 +159,12 @@ class Expression
return $tokens; return $tokens;
} }
/**
* Build a tree out of a series of tokens.
*
* @param Token[] $tokens
* @return GraphNode[]
*/
protected static function parse(array $tokens): array protected static function parse(array $tokens): array
{ {
$stack = new Stack(); $stack = new Stack();
@ -115,6 +185,7 @@ class Expression
{ {
$node = new GraphNode($token); $node = new GraphNode($token);
$node->appendChild($stack->pop()); $node->appendChild($stack->pop());
// @phan-suppress-next-line PhanPluginDuplicateAdjacentStatement
$node->appendChild($stack->pop()); $node->appendChild($stack->pop());
$stack->push($node); $stack->push($node);
} }
@ -122,6 +193,15 @@ class Expression
return $stack->get(); return $stack->get();
} }
/**
* Solve a tree of tokens by inserting all variable values and wherever possible
* resolving all functions.
*
* @param GraphNode[] $nodes parsed tree
* @param array<string,mixed> $variables variable names and values
* @param string[] $parameters parameter names
* @return GraphNode[]
*/
protected static function solve(array $nodes, array $variables = [], array $parameters = []): array protected static function solve(array $nodes, array $variables = [], array $parameters = []): array
{ {
$root = new GraphNode(); $root = new GraphNode();
@ -129,21 +209,24 @@ class Expression
{ {
$root->appendChild($node); $root->appendChild($node);
} }
$nodes = static::fillVariables($root->getChildren(), $variables, $parameters); static::fillVariables($root->getChildren(), $variables, $parameters);
$nodes = static::collapse($root->getChildren()); static::collapse($root->getChildren());
return $root->getChildren(); return $root->getChildren();
} }
protected static function execute(array $nodes, array $variables = [], array $parameters = []) /**
{ * Convert a tree of of nodes into something that could be a valid CloudFormation
return static::unwrap(static::solve($nodes, $variables, $parameters)); * condition.
} *
* @param GraphNode[] $nodes
* @throws \Exception if the remaining nodes contain functions that cannot be expressed in cloudformation
* @return mixed[]
*/
protected static function cloudformation(array $nodes): array protected static function cloudformation(array $nodes): array
{ {
foreach ($nodes as $node) foreach ($nodes as $node)
{ {
$node->walk(static function($node) { $node->walk(static function(GraphNode $node): void {
if (is_scalar($node->getValue())) if (is_scalar($node->getValue()))
{ {
return; return;
@ -164,11 +247,20 @@ class Expression
return $nodes[0]->getValue(); return $nodes[0]->getValue();
} }
/**
* Fill all variable values by replacing variable tokens with their actual values.
*
* @param GraphNode[] $nodes
* @param array<string,mixed> $variables variable values
* @param string[] $parameters parameter names
* @throws \Exception if a reference is made to an undefined variable
* @return void
*/
protected static function fillVariables(array $nodes, array $variables, array $parameters): void protected static function fillVariables(array $nodes, array $variables, array $parameters): void
{ {
foreach ($nodes as $node) foreach ($nodes as $node)
{ {
$node->walk(static function($node) use ($variables, $parameters) { $node->walk(static function(GraphNode $node) use ($variables, $parameters): void {
if ($node->getValue() instanceof Token\Variable) if ($node->getValue() instanceof Token\Variable)
{ {
$var_name = $node->getValue()->getName(); $var_name = $node->getValue()->getName();
@ -189,11 +281,17 @@ class Expression
} }
} }
/**
* 'collapse' a node tree by executing nodes.
*
* @param GraphNode[] $nodes
* @return void
*/
protected static function collapse(array $nodes): void protected static function collapse(array $nodes): void
{ {
foreach ($nodes as $node) foreach ($nodes as $node)
{ {
$node->walk(static function($node) { $node->walk(static function(GraphNode $node): void {
if ($node->getValue() instanceof Token\Parameter) if ($node->getValue() instanceof Token\Parameter)
{ {
return; return;
@ -221,9 +319,18 @@ class Expression
} }
} }
/**
* Unwrap an array of nodes by returning their values.
*
* Not valid if any nodes still contain children, as the assumption is that
* those nodes are unresolved.
*
* @param GraphNode[] $nodes
* @return mixed[]
*/
protected static function unwrap(array $nodes) protected static function unwrap(array $nodes)
{ {
return array_map(static function($node) { return array_map(/** @return mixed */ static function(GraphNode $node) {
if ($node->hasChildren()) if ($node->hasChildren())
{ {
throw new \Exception('Cannot unwrap node: still has children'); throw new \Exception('Cannot unwrap node: still has children');

12
app/Cfnpp/Expression/ICloudformationNative.php

@ -4,7 +4,19 @@ declare(strict_types=1);
namespace App\Cfnpp\Expression; namespace App\Cfnpp\Expression;
/**
* Token type that supports being converted directly to a CloudFormation intrinsic.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
interface ICloudformationNative interface ICloudformationNative
{ {
/**
* Convert this token to a CloudFormation intrinsic, formatted as a simple
* PHP array.
*
* @param ?\App\Util\GraphNode[] $arguments requested token parameters
* @return mixed[]
*/
public function toCloudformation(?array $arguments = null): array; public function toCloudformation(?array $arguments = null): array;
} }

25
app/Cfnpp/Expression/Token.php

@ -5,13 +5,36 @@ declare(strict_types=1);
namespace App\Cfnpp\Expression; namespace App\Cfnpp\Expression;
/** /**
* Token parent class. * Token parsed out of an expression.
*
* @author Adam Pippin <hello@adampippin.ca>
*/ */
abstract class Token abstract class Token
{ {
/**
* Determine whether the passed stream contains an instance of this token.
*
* @param string $stream
* @return bool
*/
abstract public static function isToken(string $stream): bool; abstract public static function isToken(string $stream): bool;
/**
* Consume a token from the passed stream.
*
* This method is expected to modify the stream to remove all
* consumed characters
*
* @param string $stream
* @return Token
*/
abstract public static function getToken(string &$stream): Token; abstract public static function getToken(string &$stream): Token;
/**
* Execute the token given the requested parameters.
*
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
abstract public function execute(?array $arguments = null); abstract public function execute(?array $arguments = null);
} }

25
app/Cfnpp/Expression/Token/NumericLiteral.php

@ -7,15 +7,34 @@ namespace App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\Token; use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenLiteral; use App\Cfnpp\Expression\TokenLiteral;
/**
* A number literal.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class NumericLiteral extends TokenLiteral class NumericLiteral extends TokenLiteral
{ {
/**
* Value of this literal.
* @var int|float
*/
protected $value; protected $value;
/**
* New number literal.
*
* @param int|float $value
*/
public function __construct($value) public function __construct($value)
{ {
$this->value = $value; $this->value = $value;
} }
/**
* Get the value of this literal.
*
* @return int|float
*/
public function getValue() public function getValue()
{ {
return $this->value; return $this->value;
@ -55,6 +74,12 @@ class NumericLiteral extends TokenLiteral
return new NumericLiteral($buffer); return new NumericLiteral($buffer);
} }
/**
* Get the value of this token.
*
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null) public function execute(?array $arguments = null)
{ {
return $this->getValue(); return $this->getValue();

63
app/Cfnpp/Expression/Token/OperatorBinary.php

@ -8,22 +8,45 @@ use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenBinary; use App\Cfnpp\Expression\TokenBinary;
use App\Cfnpp\Expression\ICloudformationNative; use App\Cfnpp\Expression\ICloudformationNative;
/**
* Basic comparison operators that take two parameters.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class OperatorBinary extends TokenBinary implements ICloudformationNative class OperatorBinary extends TokenBinary implements ICloudformationNative
{ {
/**
* List of valid operators.
* @var string[]
*/
public const OPERATORS = [ public const OPERATORS = [
'and', 'and',
'or', 'or',
'eq' 'eq'
]; ];
/**
* The operator this instance represents.
* @var string
*/
protected $operator; protected $operator;
public function __construct($operator) /**
* New binary operator.
*
* @param string $operator
*/
public function __construct(string $operator)
{ {
$this->operator = $operator; $this->operator = $operator;
} }
public function getOperator() /**
* Get the operator this instance represents.
*
* @return string
*/
public function getOperator(): string
{ {
return $this->operator; return $this->operator;
} }
@ -56,6 +79,15 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
throw new \Exception('Could not parse OperatorBinary'); throw new \Exception('Could not parse OperatorBinary');
} }
/**
* Execute the operator.
*
* Suppressing accesses to arguments since this is guaranteed valid
* unless there are bugs in Expression.
* @suppress PhanTypeArraySuspiciousNullable
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null) public function execute(?array $arguments = null)
{ {
$value1 = $arguments[0]->getValue(); $value1 = $arguments[0]->getValue();
@ -80,11 +112,9 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
{ {
return $value1; return $value1;
} }
else
{
return null; return null;
}
// no break
case 'or': case 'or':
if (is_scalar($value1) && is_scalar($value2)) if (is_scalar($value1) && is_scalar($value2))
{ {
@ -102,11 +132,9 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
{ {
return $value1; return $value1;
} }
else
{
return null; return null;
}
// no break
case 'eq': case 'eq':
if (is_scalar($value1) && is_scalar($value2)) if (is_scalar($value1) && is_scalar($value2))
{ {
@ -116,16 +144,23 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
{ {
return true; return true;
} }
else
{
return null; return null;
}
// no break
default: default:
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator()); throw new \Exception('Missing implementation for unary operator: '.$this->getOperator());
} }
} }
/**
* Convert this token into a CloudFormation intrinsic.
*
* Suppressing accesses to arguments since this is guaranteed valid
* unless there are bugs in Expression.
* @suppress PhanTypeArraySuspiciousNullable
* @param ?\App\Util\GraphNode[] $arguments
* @return mixed[]
*/
public function toCloudformation(?array $arguments = null): array public function toCloudformation(?array $arguments = null): array
{ {
$value1 = $arguments[0]->getValue(); $value1 = $arguments[0]->getValue();

41
app/Cfnpp/Expression/Token/OperatorUnary.php

@ -8,19 +8,42 @@ use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenUnary; use App\Cfnpp\Expression\TokenUnary;
use App\Cfnpp\Expression\ICloudformationNative; use App\Cfnpp\Expression\ICloudformationNative;
/**
* Basic operators that take one parameter.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class OperatorUnary extends TokenUnary implements ICloudformationNative class OperatorUnary extends TokenUnary implements ICloudformationNative
{ {
/**
* List of valid operators.
* @var string[]
*/
public const OPERATORS = [ public const OPERATORS = [
'not' 'not'
]; ];
/**
* The operator this instance represents.
* @var string
*/
protected $operator; protected $operator;
/**
* New unary operator.
*
* @param string $operator
*/
public function __construct($operator) public function __construct($operator)
{ {
$this->operator = $operator; $this->operator = $operator;
} }
/**
* Get the operator this instance represents.
*
* @return string
*/
public function getOperator() public function getOperator()
{ {
return $this->operator; return $this->operator;
@ -54,6 +77,15 @@ class OperatorUnary extends TokenUnary implements ICloudformationNative
throw new \Exception('Could not parse OperatorUnary'); throw new \Exception('Could not parse OperatorUnary');
} }
/**
* Execute the operator.
*
* Suppressing accesses to arguments since this is guaranteed valid
* unless there are bugs in Expression.
* @suppress PhanTypeArraySuspiciousNullable
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null) public function execute(?array $arguments = null)
{ {
$value = $arguments[0]->getValue(); $value = $arguments[0]->getValue();
@ -66,6 +98,15 @@ class OperatorUnary extends TokenUnary implements ICloudformationNative
} }
} }
/**
* Convert this token into a CloudFormation intrinsic.
*
* Suppressing accesses to arguments since this is guaranteed valid
* unless there are bugs in Expression.
* @suppress PhanTypeArraySuspiciousNullable
* @param ?\App\Util\GraphNode[] $arguments
* @return mixed[]
*/
public function toCloudformation(?array $arguments = null): array public function toCloudformation(?array $arguments = null): array
{ {
$value = $arguments[0]->getValue(); $value = $arguments[0]->getValue();

35
app/Cfnpp/Expression/Token/Parameter.php

@ -8,16 +8,35 @@ use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenLiteral; use App\Cfnpp\Expression\TokenLiteral;
use App\Cfnpp\Expression\ICloudformationNative; use App\Cfnpp\Expression\ICloudformationNative;
/**
* Represents a reference to a CloudFormation parameter.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class Parameter extends TokenLiteral implements ICloudformationNative class Parameter extends TokenLiteral implements ICloudformationNative
{ {
/**
* Name of the referenced parameter.
* @var string
*/
protected $name; protected $name;
public function __construct($name) /**
* New parameter.
* @string $name
* @param string $name
*/
public function __construct(string $name)
{ {
$this->name = $name; $this->name = $name;
} }
public function getName() /**
* Get the name of the parameter.
*
* @return string
*/
public function getName(): string
{ {
return $this->name; return $this->name;
} }
@ -48,11 +67,23 @@ class Parameter extends TokenLiteral implements ICloudformationNative
return new Parameter($buffer); return new Parameter($buffer);
} }
/**
* Get the value of this token.
*
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null) public function execute(?array $arguments = null)
{ {
throw new \Exception('Unreplaced parameter'); throw new \Exception('Unreplaced parameter');
} }
/**
* Return the cloudformation representation of a reference to a parameter.
*
* @param ?\App\Util\GraphNode[] $arguments
* @return mixed[]
*/
public function toCloudformation(?array $arguments = null): array public function toCloudformation(?array $arguments = null): array
{ {
return ['Ref' => $this->getName()]; return ['Ref' => $this->getName()];

29
app/Cfnpp/Expression/Token/StringLiteral.php

@ -7,16 +7,35 @@ namespace App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\Token; use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenLiteral; use App\Cfnpp\Expression\TokenLiteral;
/**
* A string literal.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class StringLiteral extends TokenLiteral class StringLiteral extends TokenLiteral
{ {
/**
* Value of the string literal.
* @var string
*/
protected $value; protected $value;
public function __construct($value) /**
* New string literal.
*
* @param string $value
*/
public function __construct(string $value)
{ {
$this->value = $value; $this->value = $value;
} }
public function getValue() /**
* Get the value of this literal.
*
* @return string
*/
public function getValue(): string
{ {
return $this->value; return $this->value;
} }
@ -63,6 +82,12 @@ class StringLiteral extends TokenLiteral
return new StringLiteral($buffer); return new StringLiteral($buffer);
} }
/**
* Get the value of this token.
*
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null) public function execute(?array $arguments = null)
{ {
return $this->getValue(); return $this->getValue();

30
app/Cfnpp/Expression/Token/Variable.php

@ -7,16 +7,35 @@ namespace App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\Token; use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenLiteral; use App\Cfnpp\Expression\TokenLiteral;
/**
* Reference to a variable.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class Variable extends TokenLiteral class Variable extends TokenLiteral
{ {
/**
* Name of the variable.
* @var string
*/
protected $name; protected $name;
public function __construct($name) /**
* New variable reference.
*
* @param string $name
*/
public function __construct(string $name)
{ {
$this->name = $name; $this->name = $name;
} }
public function getName() /**
* Get the name of the variable.
*
* @return string
*/
public function getName(): string
{ {
return $this->name; return $this->name;
} }
@ -47,6 +66,13 @@ class Variable extends TokenLiteral
return new Variable($buffer); return new Variable($buffer);
} }
/**
* Not valid. Variable replacement should be handled by the expression parser.
*
* @param ?\App\Util\GraphNode[] $arguments
* @throws \Exception if called
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null) public function execute(?array $arguments = null)
{ {
throw new \Exception('Unreplaced variable'); throw new \Exception('Unreplaced variable');

10
app/Cfnpp/Functions.php

@ -119,6 +119,13 @@ class Functions
return new NodeValue(null, $node->hasName() ? $node->getName() : null, $value); return new NodeValue(null, $node->hasName() ? $node->getName() : null, $value);
} }
/**
* Create a reference to a CloudFormation parameter.
*
* @param Node $node
* @param NodeFunction $function
* @return ?Node
*/
public function f_param(Node $node, NodeFunction $function): ?Node public function f_param(Node $node, NodeFunction $function): ?Node
{ {
if (!($function instanceof NodeFunctionValue)) if (!($function instanceof NodeFunctionValue))
@ -201,6 +208,8 @@ class Functions
* @param NodeFunction $function * @param NodeFunction $function
* @return ?Node * @return ?Node
*/ */
/*
* TODO: Reimplement
public function f_expr(Node $node, NodeFunction $function): ?Node public function f_expr(Node $node, NodeFunction $function): ?Node
{ {
if (!($function instanceof NodeFunctionValue)) if (!($function instanceof NodeFunctionValue))
@ -217,4 +226,5 @@ class Functions
return $result; return $result;
} }
*/
} }

108
app/Util/GraphNode.php

@ -4,71 +4,143 @@ declare(strict_types=1);
namespace App\Util; namespace App\Util;
/**
* Represents a node in a tree.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class GraphNode class GraphNode
{ {
/**
* Parent of this node.
* @var GraphNode
*/
protected $parent; protected $parent;
/**
* Value of this node.
* @var mixed
*/
protected $value; protected $value;
/**
* Children of this node.
* @var GraphNode[]
*/
protected $children; protected $children;
/**
* Create a new graph node.
*
* @param mixed $value
*/
public function __construct($value = null) public function __construct($value = null)
{ {
$this->value = $value; $this->value = $value;
$this->children = []; $this->children = [];
} }
public function __invoke() /**
{ * Set the value of this node.
return $this->value; *
} * @param mixed $value
* @return void
*/
public function setValue($value): void public function setValue($value): void
{ {
$this->value = $value; $this->value = $value;
} }
/**
* Get the value of this node.
*
* @return mixed
*/
public function getValue() public function getValue()
{ {
return $this->value; return $this->value;
} }
/**
* Set the parent node of this node.
*
* @param GraphNode $node
* @return void
*/
public function setParent(GraphNode $node): void public function setParent(GraphNode $node): void
{ {
$this->parent = $node; $this->parent = $node;
} }
/**
* Determine whether this node has a parent.
*
* @return bool
*/
public function hasParent(): bool public function hasParent(): bool
{ {
return isset($this->parent); return isset($this->parent);
} }
/**
* Get this node's parent node.
*
* @return GraphNode
*/
public function getParent(): GraphNode public function getParent(): GraphNode
{ {
return $this->parent; return $this->parent;
} }
/**
* Count how many child nodes this node has.
*
* @return int
*/
public function countChildren(): int public function countChildren(): int
{ {
return sizeof($this->children); return sizeof($this->children);
} }
/**
* Determine whether this node has any children.
*
* @return bool
*/
public function hasChildren(): bool public function hasChildren(): bool
{ {
return sizeof($this->children) > 0; return sizeof($this->children) > 0;
} }
/**
* Fetch all of this node's children.
*
* @return GraphNode[]
*/
public function getChildren(): array public function getChildren(): array
{ {
return $this->children; return $this->children;
} }
/**
* Add a node to this child's list of children.
*
* @param GraphNode $child
* @return void
*/
public function appendChild(GraphNode $child): void public function appendChild(GraphNode $child): void
{ {
$this->children[] = $child; $this->children[] = $child;
$child->setParent($this); $child->setParent($this);
} }
/**
* Replace a child node with another node.
*
* @param GraphNode $original
* @param GraphNode $new
* @return void
*/
public function replaceChild(GraphNode $original, GraphNode $new): void public function replaceChild(GraphNode $original, GraphNode $new): void
{ {
for ($i = 0; $i < sizeof($this->children); $i++) for ($i = 0; $i < sizeof($this->children); $i++)
@ -82,11 +154,22 @@ class GraphNode
} }
} }
/**
* Remove all children of this node.
*
* @return void
*/
public function clearChildren(): void public function clearChildren(): void
{ {
$this->children = []; $this->children = [];
} }
/**
* Add a child node by value.
*
* @param mixed $value value to add as a child
* @return GraphNode the node created as a child
*/
public function add($value): GraphNode public function add($value): GraphNode
{ {
$this->children[] = new GraphNode($value); $this->children[] = new GraphNode($value);
@ -94,11 +177,26 @@ class GraphNode
return end($this->children); return end($this->children);
} }
/**
* Walk through this node and all children, calling callback on each node.
*
* Callback is called on a node's children before a node
*
* @param callable $callback callback(GraphNode $node): void
* @return void
*/
public function walk(callable $callback) public function walk(callable $callback)
{ {
static::walkNodes([$this], $callback); static::walkNodes([$this], $callback);
} }
/**
* Internal function for recursively visiting all nodes.
*
* @param GraphNode[] $nodes
* @param callable $callback
* @return void
*/
protected static function walkNodes(array $nodes, callable $callback) protected static function walkNodes(array $nodes, callable $callback)
{ {
foreach ($nodes as $node) foreach ($nodes as $node)

34
app/Util/Stack.php

@ -4,25 +4,53 @@ declare(strict_types=1);
namespace App\Util; namespace App\Util;
/**
* Basic stack data structure.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class Stack class Stack
{ {
/**
* Stack data.
* @var mixed[]
*/
protected $stack; protected $stack;
/**
* Create a new stack.
*/
public function __construct() public function __construct()
{ {
$this->stack = []; $this->stack = [];
} }
/**
* Count how many items this stack contains.
*
* @return int
*/
public function count(): int public function count(): int
{ {
return sizeof($this->stack); return sizeof($this->stack);
} }
/**
* Get all items in this stack.
*
* @return mixed[]
*/
public function get(): array public function get(): array
{ {
return $this->stack; return $this->stack;
} }
/**
* Pop an item off of this stack.
*
* @param int $offset offset from the end to pop if not the last element
* @return mixed
*/
public function pop(int $offset = 0) public function pop(int $offset = 0)
{ {
if (sizeof($this->stack) < $offset + 1) if (sizeof($this->stack) < $offset + 1)
@ -32,6 +60,12 @@ class Stack
return array_splice($this->stack, -1 * ($offset + 1), 1)[0]; return array_splice($this->stack, -1 * ($offset + 1), 1)[0];
} }
/**
* Push an item onto the end of the stack.
*
* @param mixed $value
* @return void
*/
public function push($value): void public function push($value): void
{ {
array_push($this->stack, $value); array_push($this->stack, $value);

Loading…
Cancel
Save