cloudformation-plus-plus: cfn template preprocessor
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

231 lines
4.8 KiB

<?php
declare(strict_types=1);
namespace App\Engine\Cfnpp\Expression;
/**
* Expression that can be evaluated.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class Expression
{
/**
* List of Tokens in the expression.
* @var Token[]
*/
protected $tokens;
/**
* Evaluation stack.
* @var mixed[]
*/
protected $stack;
/**
* Variables that can be referenced in the expression.
* @var array<string,mixed>
*/
protected $variables;
/**
* Create a new expression.
*
* @param Token[] $tokens
*/
public function __construct(array $tokens)
{
$this->tokens = $tokens;
}
/**
* Find all referenced variables so we can do proper ordering of variable
* block evaluate.
*
* @return string[]
*/
public function getReferencedVariables(): array
{
$variables = [];
foreach ($this->tokens as $token)
{
if ($token instanceof TokenVariable)
{
$variables[] = $token->getName();
}
}
return array_values(array_unique($variables));
}
/**
* Evaluate the tokens contained in this expression.
*
* @param array<string,mixed> $variables variables that can be referenced
* @return mixed if expression evaluated down to a single value a scalar, otherwise an array
*/
public function evaluate(array $variables = [])
{
$this->variables = $variables;
$this->stack = [];
$tokens = $this->tokens;
while (sizeof($tokens))
{
$token = array_shift($tokens);
assert(isset($token));
$token_class = get_class($token);
$token_name = substr($token_class, strrpos($token_class, '\\') + 1);
$func = 'evaluate'.$token_name;
if (method_exists($this, $func))
{
$this->{$func}($token);
}
else
{
throw new \Exception('Unhandled expression token type: '.basename(get_class($token)));
}
}
if (sizeof($this->stack) == 1)
{
return end($this->stack);
}
return $this->stack;
}
/**
* Evaluate and mutate the stack given a numeric literal.
*
* @param TokenNumericLiteral $token
* @return void
*/
protected function evaluateTokenNumericLiteral(TokenNumericLiteral $token): void
{
$this->push($token->getValue());
}
/**
* Evaluate and mutate the stack given a string literal.
*
* @param TokenStringLiteral $token
* @return void
*/
protected function evaluateTokenStringLiteral(TokenStringLiteral $token): void
{
$this->push($token->getValue());
}
/**
* Evaluate and mutate the stack given a comparison operator.
*
* $this->pop() == $this->pop() is valid.
* @suppress PhanPluginDuplicateExpressionBinaryOp
* @param TokenOperator $token
* @return void
*/
protected function evaluateTokenOperator(TokenOperator $token): void
{
switch ($token->getOperator())
{
case 'eq':
$this->push($this->pop() == $this->pop());
break;
case 'neq':
$this->push($this->pop() != $this->pop());
break;
case 'gt':
$this->push($this->pop(1) > $this->pop());
break;
case 'gte':
$this->push($this->pop(1) >= $this->pop());
break;
case 'lt':
$this->push($this->pop(1) < $this->pop());
break;
case 'lte':
$this->push($this->pop(1) <= $this->pop());
break;
case 'and':
$var1 = $this->pop();
$var2 = $this->pop();
$this->push($var1 && $var2);
break;
case 'or':
$var1 = $this->pop();
$var2 = $this->pop();
$this->push($var1 || $var2);
break;
default:
throw new \Exception('Unhandled comparison operator: '.$token->getOperator());
}
}
/**
* Evaluate and mutate the stack given a function token.
*
* @param TokenFunction $token
* @return void
*/
protected function evaluateTokenFunction(TokenFunction $token): void
{
switch ($token->getFunction())
{
case 'concat':
$this->push($this->pop(1).$this->pop());
break;
case 'concat*':
while (sizeof($this->stack) > 1)
{
$this->push($this->pop(1).$this->pop());
}
break;
default:
throw new \Exception('Unhandled function: '.$token->getFunction());
}
}
/**
* Evaluate and mutate the stack given a variable reference.
*
* @param TokenVariable $token
* @return void
*/
protected function evaluateTokenVariable(TokenVariable $token)
{
$name = $token->getName();
if (!isset($this->variables[$name]))
{
throw new \Exception('Undefined variable: '.$name);
}
$this->push($this->variables[$name]);
}
/**
* Pop an item off the stack.
*
* @param int $offset offset from the end of the stack to pop from
* @return mixed
*/
protected function pop(int $offset = 0)
{
if (sizeof($this->stack) < $offset + 1)
{
throw new \Exception('Expression stack underflow!');
}
return array_splice($this->stack, -1 * ($offset + 1), 1)[0];
}
/**
* Push an item onto the end of the stack.
*
* @param mixed $value
* @return void
*/
protected function push($value): void
{
array_push($this->stack, $value);
}
}