Browse Source
Moving towards something a little more formal -- eventual goal here is that we can evaluate expressions as far as possible given everything available to us at the time and translate the rest into a cloudformation conditionmaster
Adam Pippin
3 years ago
13 changed files with 762 additions and 195 deletions
@ -0,0 +1,10 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression; |
||||
|
|
||||
|
interface ICloudformationNative |
||||
|
{ |
||||
|
public function toCloudformation(?array $arguments = null): array; |
||||
|
} |
@ -0,0 +1,62 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression\Token; |
||||
|
|
||||
|
use App\Cfnpp\Expression\Token; |
||||
|
use App\Cfnpp\Expression\TokenLiteral; |
||||
|
|
||||
|
class NumericLiteral extends TokenLiteral |
||||
|
{ |
||||
|
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)); |
||||
|
|
||||
|
if (stristr($buffer, '.')) |
||||
|
{ |
||||
|
$buffer = (float)$buffer; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
$buffer = (int)$buffer; |
||||
|
} |
||||
|
|
||||
|
return new NumericLiteral($buffer); |
||||
|
} |
||||
|
|
||||
|
public function execute(?array $arguments = null) |
||||
|
{ |
||||
|
return $this->getValue(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,146 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression\Token; |
||||
|
|
||||
|
use App\Cfnpp\Expression\Token; |
||||
|
use App\Cfnpp\Expression\TokenBinary; |
||||
|
use App\Cfnpp\Expression\ICloudformationNative; |
||||
|
|
||||
|
class OperatorBinary extends TokenBinary implements ICloudformationNative |
||||
|
{ |
||||
|
public const OPERATORS = [ |
||||
|
'and', |
||||
|
'or', |
||||
|
'eq' |
||||
|
]; |
||||
|
|
||||
|
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 OperatorBinary($operator); |
||||
|
} |
||||
|
} |
||||
|
throw new \Exception('Could not parse OperatorBinary'); |
||||
|
} |
||||
|
|
||||
|
public function execute(?array $arguments = null) |
||||
|
{ |
||||
|
$value1 = $arguments[0]->getValue(); |
||||
|
$value2 = $arguments[1]->getValue(); |
||||
|
|
||||
|
switch ($this->getOperator()) |
||||
|
{ |
||||
|
case 'and': |
||||
|
if (is_scalar($value1) && is_scalar($value2)) |
||||
|
{ |
||||
|
return $value1 && $value2; |
||||
|
} |
||||
|
elseif (is_scalar($value1)) |
||||
|
{ |
||||
|
return $value1 ? $value2 : false; |
||||
|
} |
||||
|
elseif (is_scalar($value2)) |
||||
|
{ |
||||
|
return $value2 ? $value1 : false; |
||||
|
} |
||||
|
elseif ($value1 instanceof Parameter && $value2 instanceof Parameter && $value1->getName() == $value2->getName()) |
||||
|
{ |
||||
|
return $value1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
// no break |
||||
|
case 'or': |
||||
|
if (is_scalar($value1) && is_scalar($value2)) |
||||
|
{ |
||||
|
return $value1 || $value2; |
||||
|
} |
||||
|
elseif (is_scalar($value1)) |
||||
|
{ |
||||
|
return $value1 ? true : $value1; |
||||
|
} |
||||
|
elseif (is_scalar($value2)) |
||||
|
{ |
||||
|
return $value2 ? true : $value1; |
||||
|
} |
||||
|
elseif ($value1 instanceof Parameter && $value2 instanceof Parameter && $value1->getName() == $value2->getName()) |
||||
|
{ |
||||
|
return $value1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
// no break |
||||
|
case 'eq': |
||||
|
if (is_scalar($value1) && is_scalar($value2)) |
||||
|
{ |
||||
|
return $arguments[0] == $arguments[1]; |
||||
|
} |
||||
|
elseif ($value1 instanceof Parameter && $value2 instanceof Parameter && $value1->getName() == $value2->getName()) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
// no break |
||||
|
default: |
||||
|
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function toCloudformation(?array $arguments = null): array |
||||
|
{ |
||||
|
$value1 = $arguments[0]->getValue(); |
||||
|
$value2 = $arguments[1]->getValue(); |
||||
|
|
||||
|
switch ($this->getOperator()) |
||||
|
{ |
||||
|
case 'and': |
||||
|
return ['Fn::And' => [$value1, $value2]]; |
||||
|
case 'or': |
||||
|
return ['Fn::Or' => [$value1, $value2]]; |
||||
|
case 'eq': |
||||
|
return ['Fn::Equals' => [$value1, $value2]]; |
||||
|
default: |
||||
|
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator()); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,80 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression\Token; |
||||
|
|
||||
|
use App\Cfnpp\Expression\Token; |
||||
|
use App\Cfnpp\Expression\TokenUnary; |
||||
|
use App\Cfnpp\Expression\ICloudformationNative; |
||||
|
|
||||
|
class OperatorUnary extends TokenUnary implements ICloudformationNative |
||||
|
{ |
||||
|
public const OPERATORS = [ |
||||
|
'not' |
||||
|
]; |
||||
|
|
||||
|
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 OperatorUnary($operator); |
||||
|
} |
||||
|
} |
||||
|
throw new \Exception('Could not parse OperatorUnary'); |
||||
|
} |
||||
|
|
||||
|
public function execute(?array $arguments = null) |
||||
|
{ |
||||
|
$value = $arguments[0]->getValue(); |
||||
|
switch ($this->getOperator()) |
||||
|
{ |
||||
|
case 'not': |
||||
|
return is_scalar($value) ? !$value : null; |
||||
|
default: |
||||
|
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function toCloudformation(?array $arguments = null): array |
||||
|
{ |
||||
|
$value = $arguments[0]->getValue(); |
||||
|
switch ($this->getOperator()) |
||||
|
{ |
||||
|
case 'not': |
||||
|
return ['Fn::Not' => [$value]]; |
||||
|
default: |
||||
|
throw new \Exception('Missing implementation for unary operator: '.$this->getOperator()); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression\Token; |
||||
|
|
||||
|
use App\Cfnpp\Expression\Token; |
||||
|
use App\Cfnpp\Expression\TokenLiteral; |
||||
|
use App\Cfnpp\Expression\ICloudformationNative; |
||||
|
|
||||
|
class Parameter extends TokenLiteral implements ICloudformationNative |
||||
|
{ |
||||
|
protected $name; |
||||
|
|
||||
|
public function __construct($name) |
||||
|
{ |
||||
|
$this->name = $name; |
||||
|
} |
||||
|
|
||||
|
public function getName() |
||||
|
{ |
||||
|
return $this->name; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
return (bool)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 Parameter($buffer); |
||||
|
} |
||||
|
|
||||
|
public function execute(?array $arguments = null) |
||||
|
{ |
||||
|
throw new \Exception('Unreplaced parameter'); |
||||
|
} |
||||
|
|
||||
|
public function toCloudformation(?array $arguments = null): array |
||||
|
{ |
||||
|
return ['Ref' => $this->getName()]; |
||||
|
} |
||||
|
} |
@ -0,0 +1,54 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression\Token; |
||||
|
|
||||
|
use App\Cfnpp\Expression\Token; |
||||
|
use App\Cfnpp\Expression\TokenLiteral; |
||||
|
|
||||
|
class Variable extends TokenLiteral |
||||
|
{ |
||||
|
protected $name; |
||||
|
|
||||
|
public function __construct($name) |
||||
|
{ |
||||
|
$this->name = $name; |
||||
|
} |
||||
|
|
||||
|
public function getName() |
||||
|
{ |
||||
|
return $this->name; |
||||
|
} |
||||
|
|
||||
|
public static function isToken(string $stream): bool |
||||
|
{ |
||||
|
return (bool)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 Variable($buffer); |
||||
|
} |
||||
|
|
||||
|
public function execute(?array $arguments = null) |
||||
|
{ |
||||
|
throw new \Exception('Unreplaced variable'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression; |
||||
|
|
||||
|
/** |
||||
|
* A token that takes two arguments. |
||||
|
*/ |
||||
|
abstract class TokenBinary extends Token |
||||
|
{ |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression; |
||||
|
|
||||
|
/** |
||||
|
* A token that takes no arguments. |
||||
|
*/ |
||||
|
abstract class TokenLiteral extends Token |
||||
|
{ |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Cfnpp\Expression; |
||||
|
|
||||
|
/** |
||||
|
* A token that takes one argument. |
||||
|
*/ |
||||
|
abstract class TokenUnary extends Token |
||||
|
{ |
||||
|
} |
@ -0,0 +1,114 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Util; |
||||
|
|
||||
|
class GraphNode |
||||
|
{ |
||||
|
protected $parent; |
||||
|
|
||||
|
protected $value; |
||||
|
|
||||
|
protected $children; |
||||
|
|
||||
|
public function __construct($value = null) |
||||
|
{ |
||||
|
$this->value = $value; |
||||
|
$this->children = []; |
||||
|
} |
||||
|
|
||||
|
public function __invoke() |
||||
|
{ |
||||
|
return $this->value; |
||||
|
} |
||||
|
|
||||
|
public function setValue($value): void |
||||
|
{ |
||||
|
$this->value = $value; |
||||
|
} |
||||
|
|
||||
|
public function getValue() |
||||
|
{ |
||||
|
return $this->value; |
||||
|
} |
||||
|
|
||||
|
public function setParent(GraphNode $node): void |
||||
|
{ |
||||
|
$this->parent = $node; |
||||
|
} |
||||
|
|
||||
|
public function hasParent(): bool |
||||
|
{ |
||||
|
return isset($this->parent); |
||||
|
} |
||||
|
|
||||
|
public function getParent(): GraphNode |
||||
|
{ |
||||
|
return $this->parent; |
||||
|
} |
||||
|
|
||||
|
public function countChildren(): int |
||||
|
{ |
||||
|
return sizeof($this->children); |
||||
|
} |
||||
|
|
||||
|
public function hasChildren(): bool |
||||
|
{ |
||||
|
return sizeof($this->children) > 0; |
||||
|
} |
||||
|
|
||||
|
public function getChildren(): array |
||||
|
{ |
||||
|
return $this->children; |
||||
|
} |
||||
|
|
||||
|
public function appendChild(GraphNode $child): void |
||||
|
{ |
||||
|
$this->children[] = $child; |
||||
|
$child->setParent($this); |
||||
|
} |
||||
|
|
||||
|
public function replaceChild(GraphNode $original, GraphNode $new): void |
||||
|
{ |
||||
|
for ($i = 0; $i < sizeof($this->children); $i++) |
||||
|
{ |
||||
|
if ($this->children[$i] === $original) |
||||
|
{ |
||||
|
$this->children[$i] = $new; |
||||
|
$new->setParent($this); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function clearChildren(): void |
||||
|
{ |
||||
|
$this->children = []; |
||||
|
} |
||||
|
|
||||
|
public function add($value): GraphNode |
||||
|
{ |
||||
|
$this->children[] = new GraphNode($value); |
||||
|
end($this->children)->setParent($this); |
||||
|
return end($this->children); |
||||
|
} |
||||
|
|
||||
|
public function walk(callable $callback) |
||||
|
{ |
||||
|
static::walkNodes([$this], $callback); |
||||
|
} |
||||
|
|
||||
|
protected static function walkNodes(array $nodes, callable $callback) |
||||
|
{ |
||||
|
foreach ($nodes as $node) |
||||
|
{ |
||||
|
if ($node->hasChildren()) |
||||
|
{ |
||||
|
static::walkNodes($node->getChildren(), $callback); |
||||
|
} |
||||
|
|
||||
|
$callback($node); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Util; |
||||
|
|
||||
|
class Stack |
||||
|
{ |
||||
|
protected $stack; |
||||
|
|
||||
|
public function __construct() |
||||
|
{ |
||||
|
$this->stack = []; |
||||
|
} |
||||
|
|
||||
|
public function count(): int |
||||
|
{ |
||||
|
return sizeof($this->stack); |
||||
|
} |
||||
|
|
||||
|
public function get(): array |
||||
|
{ |
||||
|
return $this->stack; |
||||
|
} |
||||
|
|
||||
|
public function pop(int $offset = 0) |
||||
|
{ |
||||
|
if (sizeof($this->stack) < $offset + 1) |
||||
|
{ |
||||
|
throw new \Exception('Stack underflow!'); |
||||
|
} |
||||
|
return array_splice($this->stack, -1 * ($offset + 1), 1)[0]; |
||||
|
} |
||||
|
|
||||
|
public function push($value): void |
||||
|
{ |
||||
|
array_push($this->stack, $value); |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue