Adam Pippin
3 years ago
7 changed files with 405 additions and 0 deletions
@ -0,0 +1,124 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
class Expression |
||||
|
{ |
||||
|
protected $tokens; |
||||
|
|
||||
|
protected $stack; |
||||
|
|
||||
|
protected $variables; |
||||
|
|
||||
|
public function __construct(array $tokens) |
||||
|
{ |
||||
|
$this->tokens = $tokens; |
||||
|
} |
||||
|
|
||||
|
public function evaluate(array $variables = []) |
||||
|
{ |
||||
|
$this->variables = $variables; |
||||
|
$this->stack = []; |
||||
|
$tokens = $this->tokens; |
||||
|
|
||||
|
while (sizeof($tokens)) |
||||
|
{ |
||||
|
$token = array_shift($tokens); |
||||
|
|
||||
|
if ($token instanceof TokenNumericLiteral) |
||||
|
{ |
||||
|
$this->evaluateTokenNumericLiteral($token); |
||||
|
} |
||||
|
elseif ($token instanceof TokenStringLiteral) |
||||
|
{ |
||||
|
$this->evaluateTokenStringLiteral($token); |
||||
|
} |
||||
|
elseif ($token instanceof TokenOperator) |
||||
|
{ |
||||
|
$this->evaluateTokenOperator($token); |
||||
|
} |
||||
|
elseif ($token instanceof TokenVariable) |
||||
|
{ |
||||
|
$this->evaluateTokenVariable($token); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new \Exception('Unhandled expression token type: '.basename(get_class($token))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (sizeof($this->stack) != 1) |
||||
|
{ |
||||
|
throw new \Exception('Expression did not evaluate down to a single value'); |
||||
|
} |
||||
|
|
||||
|
return $this->stack[0]; |
||||
|
} |
||||
|
|
||||
|
protected function evaluateTokenNumericLiteral(TokenNumericLiteral $token) |
||||
|
{ |
||||
|
$this->push($token->getValue()); |
||||
|
} |
||||
|
|
||||
|
protected function evaluateTokenStringLiteral(TokenStringLiteral $token) |
||||
|
{ |
||||
|
$this->push($token->getValue()); |
||||
|
} |
||||
|
|
||||
|
protected function evaluateTokenOperator(TokenOperator $token) |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected function evaluateTokenVariable(TokenVariable $token) |
||||
|
{ |
||||
|
$name = $token->getName(); |
||||
|
if (!isset($this->variables[$name])) |
||||
|
{ |
||||
|
throw new \Exception('Undefined variable: '.$name); |
||||
|
} |
||||
|
$this->push($this->variables[$name]); |
||||
|
} |
||||
|
|
||||
|
protected function pop(int $offset = 0) |
||||
|
{ |
||||
|
return array_splice($this->stack, -1 * ($offset + 1), 1)[0]; |
||||
|
} |
||||
|
|
||||
|
protected function push($value) |
||||
|
{ |
||||
|
array_push($this->stack, $value); |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
class Parser |
||||
|
{ |
||||
|
public const TOKEN_TYPES = [ |
||||
|
TokenNumericLiteral::class, |
||||
|
TokenOperator::class, |
||||
|
TokenStringLiteral::class, |
||||
|
TokenVariable::class |
||||
|
]; |
||||
|
|
||||
|
public function parse(string $value): Expression |
||||
|
{ |
||||
|
$original_value = $value; |
||||
|
$value = $value.' '; |
||||
|
$tokens = []; |
||||
|
while (strlen($value) > 0) |
||||
|
{ |
||||
|
foreach (static::TOKEN_TYPES as $token_class) |
||||
|
{ |
||||
|
if ($token_class::isToken($value)) |
||||
|
{ |
||||
|
$tokens[] = $token_class::getToken($value); |
||||
|
|
||||
|
if (substr($value, 0, 1) != ' ') |
||||
|
{ |
||||
|
throw new \Exception('Incompletely consumed token at offset '.(strlen($original_value) - strlen($value)).' in expression '.$original_value); |
||||
|
} |
||||
|
|
||||
|
$value = substr($value, 1); |
||||
|
continue 2; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new \Exception('Unparseable value at offset '.(strlen($original_value) - strlen($value)).' in expression '.$original_value); |
||||
|
} |
||||
|
|
||||
|
return new Expression($tokens); |
||||
|
} |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
abstract class Token |
||||
|
{ |
||||
|
/** |
||||
|
* Check whether the stream passed in contains a valid token of this |
||||
|
* type. |
||||
|
* |
||||
|
* @param string $stream |
||||
|
* @return bool |
||||
|
*/ |
||||
|
abstract public static function isToken(string $stream): bool; |
||||
|
|
||||
|
/** |
||||
|
* Consume a token from the stream. |
||||
|
* |
||||
|
* Only valid if isToken is true. |
||||
|
* |
||||
|
* @param string $stream |
||||
|
* @return Token |
||||
|
*/ |
||||
|
abstract public static function getToken(string &$stream): Token; |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
class TokenNumericLiteral extends Token |
||||
|
{ |
||||
|
protected $value; |
||||
|
|
||||
|
public function __construct($value) |
||||
|
{ |
||||
|
$this->value = $value; |
||||
|
} |
||||
|
|
||||
|
public function getValue() |
||||
|
{ |
||||
|
return $this->value; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
return is_numeric($stream[0]); |
||||
|
} |
||||
|
|
||||
|
public static function getToken(string &$stream): Token |
||||
|
{ |
||||
|
$buffer = ''; |
||||
|
for ($i = 0; $i < strlen($stream); $i++) |
||||
|
{ |
||||
|
if (preg_match('/^[0-9]$/', $stream[$i])) |
||||
|
{ |
||||
|
$buffer .= $stream[$i]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$stream = substr($stream, strlen($buffer)); |
||||
|
|
||||
|
return new TokenNumericLiteral($buffer); |
||||
|
} |
||||
|
} |
@ -0,0 +1,58 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
class TokenOperator extends Token |
||||
|
{ |
||||
|
public const OPERATORS = [ |
||||
|
'eq', |
||||
|
'gt', |
||||
|
'gte', |
||||
|
'lt', |
||||
|
'lte', |
||||
|
'neq', |
||||
|
'and', |
||||
|
'or' |
||||
|
]; |
||||
|
|
||||
|
protected $operator; |
||||
|
|
||||
|
public function __construct($operator) |
||||
|
{ |
||||
|
$this->operator = $operator; |
||||
|
} |
||||
|
|
||||
|
public function getOperator() |
||||
|
{ |
||||
|
return $this->operator; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
foreach (static::OPERATORS as $operator) |
||||
|
{ |
||||
|
if (strlen($stream) > strlen($operator) && |
||||
|
substr($stream, 0, strlen($operator)) == $operator) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static function getToken(string &$stream): Token |
||||
|
{ |
||||
|
foreach (static::OPERATORS as $operator) |
||||
|
{ |
||||
|
if (strlen($stream) > strlen($operator) && |
||||
|
substr($stream, 0, strlen($operator)) == $operator) |
||||
|
{ |
||||
|
$operator = substr($stream, 0, strlen($operator)); |
||||
|
$stream = substr($stream, strlen($operator)); |
||||
|
return new TokenOperator($operator); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
class TokenStringLiteral extends Token |
||||
|
{ |
||||
|
protected $value; |
||||
|
|
||||
|
public function __construct($value) |
||||
|
{ |
||||
|
$this->value = $value; |
||||
|
} |
||||
|
|
||||
|
public function getValue() |
||||
|
{ |
||||
|
return $this->value; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
return $stream[0] == '"'; |
||||
|
} |
||||
|
|
||||
|
public static function getToken(string &$stream): Token |
||||
|
{ |
||||
|
$buffer = ''; |
||||
|
$in_string = false; |
||||
|
$escaped = false; |
||||
|
|
||||
|
for ($i = 0; $i < strlen($stream); $i++) |
||||
|
{ |
||||
|
if ($escaped) |
||||
|
{ |
||||
|
$buffer .= $stream[$i]; |
||||
|
} |
||||
|
elseif ($stream[$i] == '"') |
||||
|
{ |
||||
|
if ($in_string) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
$in_string = true; |
||||
|
} |
||||
|
elseif ($stream[$i] == '\\') |
||||
|
{ |
||||
|
$escaped = true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
$buffer .= $stream[$i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$stream = substr($stream, $i + 1); |
||||
|
|
||||
|
return new TokenStringLiteral($buffer); |
||||
|
} |
||||
|
} |
@ -0,0 +1,46 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Engine\Cfnpp\Expression; |
||||
|
|
||||
|
class TokenVariable extends Token |
||||
|
{ |
||||
|
protected $name; |
||||
|
|
||||
|
public function __construct($name) |
||||
|
{ |
||||
|
$this->name = $name; |
||||
|
} |
||||
|
|
||||
|
public function getName() |
||||
|
{ |
||||
|
return $this->name; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
return preg_match('/^[A-Za-z]$/', $stream[0]); |
||||
|
} |
||||
|
|
||||
|
public static function getToken(string &$stream): Token |
||||
|
{ |
||||
|
$buffer = ''; |
||||
|
$buffer = $stream[0]; |
||||
|
for ($i = 1; $i < strlen($stream); $i++) |
||||
|
{ |
||||
|
if (preg_match('/^[A-Za-z0-9]$/', $stream[$i])) |
||||
|
{ |
||||
|
$buffer .= $stream[$i]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$stream = substr($stream, strlen($buffer)); |
||||
|
|
||||
|
return new TokenVariable($buffer); |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue