Compare commits

...

7 Commits

Author SHA1 Message Date
Adam Pippin 326e856a2b Cleanup 3 years ago
Adam Pippin 0265db1946 Rework expression parser to not solve on creation; re-add dependency solving for variables in expressions in variables block 3 years ago
Adam Pippin 6f90b5d222 Bugfix: Fix processing order for functions -- deepest nodes first 3 years ago
Adam Pippin 8dd7b8d1f0 Re-add !expr function 3 years ago
Adam Pippin 4294c5b007 Add concat/select operators 3 years ago
Adam Pippin 6ddf1531af Add boolean literals to expression parser 3 years ago
Adam Pippin 706242074d Allow !replace to accept a scalar 3 years ago
  1. 31
      app/Cfnpp/Compiler.php
  2. 42
      app/Cfnpp/Expression/Expression.php
  3. 75
      app/Cfnpp/Expression/Token/BooleanLiteral.php
  4. 36
      app/Cfnpp/Expression/Token/OperatorBinary.php
  5. 44
      app/Cfnpp/Functions.php

31
app/Cfnpp/Compiler.php

@ -123,15 +123,6 @@ class Compiler implements \App\Engine\ICompile
$this->pass_2($document, $options);
return $this->document;
// Process each passed document
/*
foreach ($documents as $next_document)
{
$this->runMergeFunctions($document, $next_document);
$this->merge($document, $next_document);
$this->runFunctions($document);
}
*/
}
/**
@ -225,7 +216,7 @@ class Compiler implements \App\Engine\ICompile
foreach ($parameters_node as $parameter_node)
{
$nodes[$parameter_node->getName()] = $parameter_node;
$graph->add($parameter_node->getName(), $this->pass_1_getDependencies($parameter_node));
$graph->add($parameter_node->getName(), $this->pass_1_getDependencies($parameter_node, $options));
}
}
@ -238,7 +229,7 @@ class Compiler implements \App\Engine\ICompile
throw new \Exception('Variables and parameters cannot share the same name.');
}
$nodes[$variable_node->getName()] = $variable_node;
$graph->add($variable_node->getName(), $this->pass_1_getDependencies($variable_node));
$graph->add($variable_node->getName(), $this->pass_1_getDependencies($variable_node, $options));
}
}
@ -277,7 +268,7 @@ class Compiler implements \App\Engine\ICompile
* @param Node $node
* @return string[]
*/
protected function pass_1_getDependencies(Node $node): array
protected function pass_1_getDependencies(Node $node, IOptions $options): array
{
$stack = [$node];
@ -302,14 +293,12 @@ class Compiler implements \App\Engine\ICompile
{
$variables[] = $node->getValue();
}
/*
// TODO: Reimplement
elseif ($node instanceof NodeFunctionValue &&
$node->getName() == 'expr')
{
$expression = new \App\Cfnpp\Expression\Expression($node->getValue());
$variables = array_merge($variables, $expression->getReferencedVariables());
}*/
}
}
return $variables;
@ -454,6 +443,12 @@ class Compiler implements \App\Engine\ICompile
*/
protected function runFunctions(Node $node): void
{
$children = $node->getChildren();
foreach ($children as $child)
{
$this->runFunctions($child);
}
if ($node->isFunctionParent() && isset($this->functions[$node[0]->getName()]))
{
$function_node = $node[0];
@ -471,12 +466,6 @@ class Compiler implements \App\Engine\ICompile
return;
}
}
$children = $node->getChildren();
foreach ($children as $child)
{
$this->runFunctions($child);
}
}
/**

42
app/Cfnpp/Expression/Expression.php

@ -20,6 +20,7 @@ class Expression
* @var string[]
*/
public const TOKEN_TYPES = [
Token\BooleanLiteral::class,
Token\NumericLiteral::class,
Token\OperatorUnary::class,
Token\OperatorBinary::class,
@ -45,14 +46,22 @@ class 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)
{
$tokens = static::tokenize($expression);
$nodes = static::parse($tokens);
$this->nodes = static::solve($nodes, $options->getVariables(), array_keys($options->getParameters()));
$this->options = $options;
$this->nodes = static::parse($tokens);
}
/**
* Solve this expression down to the minimal set of nodes we can
*
* @param \App\Engine\IOptions $options
* @return void
*/
public function solve(\App\Engine\IOptions $options): void
{
$this->nodes = static::_solve($this->nodes, $options->getVariables(), array_keys($options->getParameters()));
}
/**
@ -127,6 +136,27 @@ class Expression
return static::cloudformation($this->nodes);
}
/**
* Examine the expression to determine which variables are referenced so we
* can figure out the dependencies between them
*
* @return string[] names of variables referenced
*/
public function getReferencedVariables(): array
{
$variables = [];
foreach ($this->nodes as $node)
{
$node->walk(function(GraphNode $node) use (&$variables): void {
if ($node->getValue() instanceof Token\Variable)
{
$variables[] = $node->getValue()->getName();
}
});
}
return array_values(array_unique($variables));
}
/**
* Convert an expression string into a series of tokens.
*
@ -202,7 +232,7 @@ class Expression
* @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();
foreach ($nodes as $node)

75
app/Cfnpp/Expression/Token/BooleanLiteral.php

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\Token;
use App\Cfnpp\Expression\TokenLiteral;
/**
* A boolean literal (true/false).
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class BooleanLiteral extends TokenLiteral
{
/**
* Value of this literal.
* @var bool
*/
protected $value;
/**
* New boolean literal.
*
* @param bool $value
*/
public function __construct(bool $value)
{
$this->value = $value;
}
/**
* Get the value of this literal.
*
* @return bool
*/
public function getValue(): bool
{
return $this->value;
}
public static function isToken(string $stream): bool
{
return
(strlen($stream) >= 4 && strtolower(substr($stream, 0, 4)) == 'true') ||
(strlen($stream) >= 5 && strtolower(substr($stream, 0, 5)) == 'false');
}
public static function getToken(string &$stream): Token
{
if (strlen($stream) >= 4 && strtolower(substr($stream, 0, 4)) == 'true')
{
$stream = substr($stream, 4);
return new BooleanLiteral(true);
}
elseif (strlen($stream) >= 5 && strtolower(substr($stream, 0, 5)) == 'false')
{
$stream = substr($stream, 5);
return new BooleanLiteral(false);
}
throw new \Exception('Unparseable boolean');
}
/**
* Get the value of this token.
*
* @param ?\App\Util\GraphNode[] $arguments
* @return \App\Util\GraphNode|Token|scalar|null
*/
public function execute(?array $arguments = null)
{
return $this->getValue();
}
}

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

@ -22,7 +22,9 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
public const OPERATORS = [
'and',
'or',
'eq'
'eq',
'concat',
'select'
];
/**
@ -147,8 +149,32 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
return null;
case 'concat':
if (is_scalar($value1) && is_scalar($value2))
{
return $value2.$value1;
}
elseif ($value1 instanceof Parameter && is_scalar($value2) && empty($value2))
{
return $value1;
}
elseif ($value2 instanceof Parameter && is_scalar($value1) && empty($value1))
{
return $value2;
}
return null;
case 'select':
if (is_scalar($value1) && is_array($value2))
{
return $value2[$value1];
}
return null;
default:
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator());
throw new \Exception('Missing implementation for binary operator: '.$this->getOperator());
}
}
@ -174,8 +200,12 @@ class OperatorBinary extends TokenBinary implements ICloudformationNative
return ['Fn::Or' => [$value1, $value2]];
case 'eq':
return ['Fn::Equals' => [$value1, $value2]];
case 'concat':
return ['Fn::Join' => ['', [$value2, $value1]]];
case 'select':
return ['Fn::Select' => [$value1, $value2]];
default:
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator());
throw new \Exception('Operator cannot be applied to parameters: '.$this->getOperator());
}
}
}

44
app/Cfnpp/Functions.php

@ -83,7 +83,10 @@ class Functions
*/
public function mf_replace(Node $original, Node $target, NodeFunction $function): ?Node
{
// TODO: Deal with nodefunctionvalue
if ($function instanceof NodeFunctionValue)
{
return new NodeValue(null, $target->hasName() ? $target->getName() : null, $function->getValue());
}
$replacement = new Node(null, $target->hasName() ? $target->getName() : null);
$replacement->setChildren($function->getChildren());
@ -163,7 +166,8 @@ class Functions
$if_true = $nodes[1];
$if_false = sizeof($nodes) == 3 ? $nodes[2] : null;
$expression = new \App\Cfnpp\Expression\Expression($condition->getValue(), $this->options);
$expression = new \App\Cfnpp\Expression\Expression($condition->getValue());
$expression->solve($this->options);
// We need a single resulting node as a final value either as a scalar or
// if we want to convert to cloudformation.
@ -212,23 +216,39 @@ class Functions
* @param NodeFunction $function
* @return ?Node
*/
/*
* TODO: Reimplement
public function f_expr(Node $node, NodeFunction $function): ?Node
{
if (!($function instanceof NodeFunctionValue))
{
throw new \Exception('!if requires scalar argument');
throw new \Exception('!expr requires scalar argument');
}
$parser = new \App\Cfnpp\Expression\Parser();
$expression = $parser->parse($function->getValue());
$result = $expression->evaluate($this->options->getVariables());
$expression = new \App\Cfnpp\Expression\Expression($function->getValue());
$expression->solve($this->options);
$result = Node::fromPhp($result);
$result->setName($node->hasName() ? $node->getName() : null);
if ($expression->isComplete())
{
// If we computed a final value/set of values, we can just insert those
// directly.
if ($expression->count() == 1)
{
$solution_node = Node::fromPhp($expression->getValue());
}
else
{
$solution_node = Node::fromPhp($expression->toArray());
}
}
else
{
// Otherwise let's convert it to cfn intrinsics
$solution_node = Node::fromPhp($expression->toCloudformation());
}
return $result;
if ($node->hasName())
{
$solution_node->setName($node->getName());
}
return $solution_node;
}
*/
}

Loading…
Cancel
Save