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