diff --git a/app/Engine/Cfnpp/Expression/Expression.php b/app/Engine/Cfnpp/Expression/Expression.php index dbd64e5..d381967 100644 --- a/app/Engine/Cfnpp/Expression/Expression.php +++ b/app/Engine/Cfnpp/Expression/Expression.php @@ -4,19 +4,47 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Expression that can be evaluated. + * + * @author Adam Pippin + */ 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 + */ protected $variables; + /** + * Create a new expression. + * + * @param Token[] $tokens + */ public function __construct(array $tokens) { $this->tokens = $tokens; } + /** + * Evaluate the tokens contained in this expression. + * + * @param array $variables variables that can be referenced + * @return mixed + */ public function evaluate(array $variables = []) { $this->variables = $variables; @@ -26,6 +54,7 @@ class Expression while (sizeof($tokens)) { $token = array_shift($tokens); + assert(isset($token)); if ($token instanceof TokenNumericLiteral) { @@ -49,25 +78,45 @@ class Expression } } - if (sizeof($this->stack) != 1) + if (sizeof($this->stack) == 1) { - throw new \Exception('Expression did not evaluate down to a single value'); + return end($this->stack); } - return $this->stack[0]; + throw new \Exception('Expression did not evaluate down to a single value'); } - protected function evaluateTokenNumericLiteral(TokenNumericLiteral $token) + /** + * Evaluate and mutate the stack given a numeric literal. + * + * @param TokenNumericLiteral $token + * @return void + */ + protected function evaluateTokenNumericLiteral(TokenNumericLiteral $token): void { $this->push($token->getValue()); } - protected function evaluateTokenStringLiteral(TokenStringLiteral $token) + /** + * Evaluate and mutate the stack given a string literal. + * + * @param TokenStringLiteral $token + * @return void + */ + protected function evaluateTokenStringLiteral(TokenStringLiteral $token): void { $this->push($token->getValue()); } - protected function evaluateTokenOperator(TokenOperator $token) + /** + * 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()) { @@ -102,6 +151,12 @@ class Expression } } + /** + * Evaluate and mutate the stack given a variable reference. + * + * @param TokenVariable $token + * @return void + */ protected function evaluateTokenVariable(TokenVariable $token) { $name = $token->getName(); @@ -112,12 +167,28 @@ class Expression $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]; } - protected function push($value) + /** + * Push an item onto the end of the stack. + * + * @param mixed $value + * @return void + */ + protected function push($value): void { array_push($this->stack, $value); } diff --git a/app/Engine/Cfnpp/Expression/Parser.php b/app/Engine/Cfnpp/Expression/Parser.php index f483438..19cec7c 100644 --- a/app/Engine/Cfnpp/Expression/Parser.php +++ b/app/Engine/Cfnpp/Expression/Parser.php @@ -4,8 +4,18 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Parse a string into a series of expression tokens. + * + * @author Adam Pippin + */ class Parser { + /** + * List of class names of tokens this parser will parse. + * + * @var string[] + */ public const TOKEN_TYPES = [ TokenNumericLiteral::class, TokenOperator::class, @@ -13,10 +23,16 @@ class Parser TokenVariable::class ]; + /** + * Parse a string into an expression. + * + * @param string $value + * @return Expression + */ public function parse(string $value): Expression { $original_value = $value; - $value = $value.' '; + $value .= ' '; $tokens = []; while (strlen($value) > 0) { diff --git a/app/Engine/Cfnpp/Expression/Token.php b/app/Engine/Cfnpp/Expression/Token.php index e5f4c1e..75eb65d 100644 --- a/app/Engine/Cfnpp/Expression/Token.php +++ b/app/Engine/Cfnpp/Expression/Token.php @@ -4,6 +4,11 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Token representing a distinct part of an expression. + * + * @author Adam Pippin + */ abstract class Token { /** diff --git a/app/Engine/Cfnpp/Expression/TokenNumericLiteral.php b/app/Engine/Cfnpp/Expression/TokenNumericLiteral.php index 1a4127d..1aec30c 100644 --- a/app/Engine/Cfnpp/Expression/TokenNumericLiteral.php +++ b/app/Engine/Cfnpp/Expression/TokenNumericLiteral.php @@ -4,25 +4,58 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Token representing a numeric (integer or float) literal. + * + * @author Adam Pippin + */ class TokenNumericLiteral extends Token { + /** + * Numeric value this token represents. + * @var int|float + */ protected $value; + /** + * Create a new numeric literal. + * + * @param int|float $value + */ public function __construct($value) { $this->value = $value; } + /** + * Get the value this token represents. + * + * @return int|float + */ public function getValue() { return $this->value; } + /** + * Determine whether a numeric token can be parsed from a stream. + * + * @param string $stream + * @return bool + */ public static function isToken(string $stream): bool { return is_numeric($stream[0]); } + /** + * Parse a numeric token from a stream. + * + * Returns token, and modifies stream to remove all consumed characters. + * + * @param string $stream + * @return Token + */ public static function getToken(string &$stream): Token { $buffer = ''; @@ -40,6 +73,15 @@ class TokenNumericLiteral extends Token $stream = substr($stream, strlen($buffer)); + if (stristr($buffer, '.')) + { + $buffer = (float)$buffer; + } + else + { + $buffer = (int)$buffer; + } + return new TokenNumericLiteral($buffer); } } diff --git a/app/Engine/Cfnpp/Expression/TokenOperator.php b/app/Engine/Cfnpp/Expression/TokenOperator.php index ca2c02e..cffc8e4 100644 --- a/app/Engine/Cfnpp/Expression/TokenOperator.php +++ b/app/Engine/Cfnpp/Expression/TokenOperator.php @@ -4,8 +4,17 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Token representing a comparison operator. + * + * @author Adam Pippin + */ class TokenOperator extends Token { + /** + * List of valid operators this can parse. + * @var string[] + */ public const OPERATORS = [ 'eq', 'gt', @@ -17,18 +26,38 @@ class TokenOperator extends Token 'or' ]; + /** + * Operator this token represents. + * @var string + */ protected $operator; + /** + * Create a new operator token. + * + * @param string $operator + */ public function __construct($operator) { $this->operator = $operator; } - public function getOperator() + /** + * Get the operator this token represents. + * + * @return string + */ + public function getOperator(): string { return $this->operator; } + /** + * Determine whether a operator token can be parsed from a stream. + * + * @param string $stream + * @return bool + */ public static function isToken(string $stream): bool { foreach (static::OPERATORS as $operator) @@ -42,6 +71,14 @@ class TokenOperator extends Token return false; } + /** + * Parse an operator token from a stream. + * + * Returns token, and modifies stream to remove all consumed characters + * + * @param string $stream + * @return Token + */ public static function getToken(string &$stream): Token { foreach (static::OPERATORS as $operator) @@ -54,5 +91,6 @@ class TokenOperator extends Token return new TokenOperator($operator); } } + throw new \Exception('Could not parse operator token!'); } } diff --git a/app/Engine/Cfnpp/Expression/TokenStringLiteral.php b/app/Engine/Cfnpp/Expression/TokenStringLiteral.php index 30bec17..685caaa 100644 --- a/app/Engine/Cfnpp/Expression/TokenStringLiteral.php +++ b/app/Engine/Cfnpp/Expression/TokenStringLiteral.php @@ -4,25 +4,58 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Token representing a string literal. + * + * @author Adam Pippin + */ class TokenStringLiteral extends Token { + /** + * Value of the string literal. + * @var string + */ protected $value; - public function __construct($value) + /** + * Create a new string literal token. + * + * @param string $value + */ + public function __construct(string $value) { $this->value = $value; } - public function getValue() + /** + * Get the value of the string literal. + * + * @return string + */ + public function getValue(): string { return $this->value; } + /** + * Determine whether a string token can be parsed from a stream. + * + * @param string $stream + * @return bool + */ public static function isToken(string $stream): bool { return $stream[0] == '"'; } + /** + * Parse a string literal token from a stream. + * + * Returns token, and modifies stream to remove all consumed characters + * + * @param string $stream + * @return Token + */ public static function getToken(string &$stream): Token { $buffer = ''; diff --git a/app/Engine/Cfnpp/Expression/TokenVariable.php b/app/Engine/Cfnpp/Expression/TokenVariable.php index ea8cc1f..ed78f29 100644 --- a/app/Engine/Cfnpp/Expression/TokenVariable.php +++ b/app/Engine/Cfnpp/Expression/TokenVariable.php @@ -4,25 +4,58 @@ declare(strict_types=1); namespace App\Engine\Cfnpp\Expression; +/** + * Expression token referencing a variable. + * + * @author Adam Pippin + */ class TokenVariable extends Token { + /** + * Name of the variable this token references. + * @var string + */ protected $name; - public function __construct($name) + /** + * Create a new variable token. + * + * @param string $name + */ + public function __construct(string $name) { $this->name = $name; } - public function getName() + /** + * Get the name of the variable this token references. + * + * @return string + */ + public function getName(): string { return $this->name; } + /** + * Determine whether a variable name token can be parsed from a stream. + * + * @param string $stream + * @return bool + */ public static function isToken(string $stream): bool { - return preg_match('/^[A-Za-z]$/', $stream[0]); + return (bool)preg_match('/^[A-Za-z]$/', $stream[0]); } + /** + * Parse a variable token from a stream. + * + * Returns token, and modifies stream to remove all consumed characters. + * + * @param string $stream + * @return Token + */ public static function getToken(string &$stream): Token { $buffer = ''; diff --git a/app/Engine/Cfnpp/Functions.php b/app/Engine/Cfnpp/Functions.php index 8854726..51672d9 100644 --- a/app/Engine/Cfnpp/Functions.php +++ b/app/Engine/Cfnpp/Functions.php @@ -128,7 +128,12 @@ class Functions $nodes = $function->getChildren(); - $condition = $nodes[0]; // expects NodeValue + $condition = $nodes[0]; + + if (!($condition instanceof NodeValue)) + { + throw new \Exception('!if requires scalar condition'); + } $if_true = $nodes[1]; $if_false = sizeof($nodes) == 3 ? $nodes[2] : null;