diff options
Diffstat (limited to 'src/Interpreter/Interpreter.php')
-rw-r--r-- | src/Interpreter/Interpreter.php | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/src/Interpreter/Interpreter.php b/src/Interpreter/Interpreter.php new file mode 100644 index 0000000..7d18551 --- /dev/null +++ b/src/Interpreter/Interpreter.php @@ -0,0 +1,330 @@ +<?php + +namespace Mnml\Interpreter; + +use Mnml\Lexer\Lexer; +use Mnml\Lexer\Token; +use Mnml\Lexer\TokenType; +use Mnml\Parser\ArrayMapAccessNode; +use Mnml\Parser\ArrayNode; +use Mnml\Parser\CompareExpression; +use Mnml\Parser\Condition; +use Mnml\Parser\ConstVariableDeclaration; +use Mnml\Parser\FunctionCall; +use Mnml\Parser\FunctionCallParameter; +use Mnml\Parser\FunctionDefinition; +use Mnml\Parser\FunctionReturn; +use Mnml\Parser\IdentifierNode; +use Mnml\Parser\IfArm; +use Mnml\Parser\IfNode; +use Mnml\Parser\MapNode; +use Mnml\Parser\Node; +use Mnml\Parser\NumberNode; +use Mnml\Parser\OperatorExpression; +use Mnml\Parser\Parenthesis; +use Mnml\Parser\Parser; +use Mnml\Parser\StringNode; + +class Interpreter +{ + private array $nodes; + + /** + * @param Node[] $nodes + */ + public function __construct(array $nodes) + { + $this->nodes = $nodes; + } + + public function compile(array $parameters): Scope + { + $scope = new Scope(); + + $import = function (string $library) { + $input = file_get_contents(dirname(__FILE__) . "/" . $library); + $lexer = new Lexer($input); + $tokens = $lexer->lex(); + $parser = new Parser($tokens); + $nodes = $parser->parse(); + }; + $scope->add(new IdentifierNode(new Token(TokenType::Identifier, "import", "import", 0, 0)), $import); + + foreach ($this->nodes as $currentNode) { + $value = $this->evaluate($currentNode, $scope); + + if ($currentNode instanceof FunctionCall) { + var_dump($value); + } + } + + return $scope; + } + + private function evaluate(Node $currentNode, Scope $scope): mixed + { + if ($currentNode instanceof ConstVariableDeclaration) { + $identifier = $currentNode->identifier; + + if ($currentNode->expression instanceof NumberNode or $currentNode->expression instanceof StringNode) { + $value = $currentNode->expression; + } else { + $value = $this->evaluate($currentNode->expression, $scope); + } + + $scope->add($identifier, $value); + + return true; + } + + else if ($currentNode instanceof NumberNode) { + return $currentNode->value; + } + + else if ($currentNode instanceof StringNode) { + return $currentNode->token->value; + } + + else if ($currentNode instanceof IdentifierNode) { + $variable = $scope->get($currentNode); + + return $this->evaluate($variable, $scope); + } + + else if ($currentNode instanceof ArrayNode) { + return $currentNode; + } + + else if ($currentNode instanceof MapNode) { + return $currentNode; + } + + else if ($currentNode instanceof ArrayMapAccessNode) { + return $this->evaluateArrayOrMapAccess($currentNode, $scope); + } + + else if ($currentNode instanceof Parenthesis) { + return $this->evaluate($currentNode->content, $scope); + } + + else if ($currentNode instanceof OperatorExpression) { + $left = $this->evaluate($currentNode->left, $scope); + $right = $this->evaluate($currentNode->right, $scope); + + if ($currentNode->operator->value == "+") { + if (is_string($left) and is_string($right)) { + return $left . $right; + } + + return $left + $right; + } + + else if ($currentNode->operator->value == "-") { + return $left - $right; + } + + else if ($currentNode->operator->value == "*") { + return $left * $right; + } + + else if ($currentNode->operator->value == "**") { + return pow($left, $right); + } + + else if ($currentNode->operator->value == "/") { + return $left / $right; + } + } + + else if ($currentNode instanceof CompareExpression) { + $left = $this->evaluate($currentNode->left, $scope); + $right = $this->evaluate($currentNode->right, $scope); + + if ($currentNode->operator->value == "<") { + return $left < $right; + } + + else if ($currentNode->operator->value == ">") { + return $left > $right; + } + + else if ($currentNode->operator->value == "<=") { + return $left <= $right; + } + + else if ($currentNode->operator->value == ">=") { + return $left >= $right; + } + + else if ($currentNode->operator->value == "==") { + return $left == $right; + } + + else if ($currentNode->operator->value == "!=") { + return $left != $right; + } + } + + else if ($currentNode instanceof IfNode) { + foreach ($currentNode->arms as $arm) { + /**@var IfArm $arm*/ + if ($this->evaluate($arm->condition, $scope)) { + return $this->evaluateIfArmBody($arm, $scope); + } + } + + return null; + } + + else if ($currentNode instanceof Condition) { + $left = $this->evaluate($currentNode->left, $scope); + $right = $this->evaluate($currentNode->right, $scope); + + if ($currentNode->operator == "and") { + return $left and $right; + } + + else if ($currentNode->operator == "or") { + return $left or $right; + } + } + + else if ($currentNode instanceof FunctionDefinition) { + return $currentNode; + } + + else if ($currentNode instanceof FunctionCall) { + $functionDefinition = $scope->get($currentNode->identifier); + + return $this->evaluateFunction($functionDefinition, $currentNode->parameters, $scope); + } + + else if (is_callable($currentNode)) { + return true; + } + + else { + echo sprintf("Compiler Error: Unhandled Node type %s." . PHP_EOL, get_class($currentNode)); + } + } + + private function evaluateArrayOrMapAccess(ArrayMapAccessNode $arrayOrMapAccess, Scope $scope): Node + { + $arrayOrMap = $arrayOrMapAccess->arrayOrMap; + if ($arrayOrMap instanceof IdentifierNode) { + $arrayOrMap = $this->evaluate($arrayOrMap, $scope); + } + + if ($arrayOrMap instanceof ArrayNode) { + $value = $arrayOrMap->values; + + foreach ($arrayOrMapAccess->items as $item) { + $evaluation = $this->evaluate($item, $scope); + $value = $value[$evaluation]; + } + + return $value; + } + + else if ($arrayOrMap instanceof MapNode) { + $values = $arrayOrMap->values; + + foreach ($arrayOrMapAccess->items as $item) { + $evaluation = $this->evaluate($item, $scope); + + foreach ($values as $value) { + $key = $this->evaluate($value->key, $scope); + if ($key == $evaluation) { + $values = $this->evaluate($value->value, $scope); + # TODO: array inside map => create ArrayOrMapAccessItem with $left and $right + } + } + } + + return $values; + } + } + + /** + * @param FunctionCallParameter[] $parameters + */ + private function evaluateFunction(FunctionDefinition $function, array $parameters, Scope $outerScope): mixed + { + $scope = new Scope($outerScope); + + foreach ($parameters as $parameter) { + /**@var FunctionCallParameter $parameter*/ + $scope->add($parameter->identifier, $parameter->value); + } + + foreach ($function->body as $currentNode) { + if ($currentNode instanceof FunctionReturn) { + return $this->evaluate($currentNode->returnValue, $scope); + } + + else if ($currentNode instanceof IfNode) { + $returnValue = $this->evaluate($currentNode, $scope); + + // return if "if" had a return inside + if ($returnValue) { + return $returnValue; + } + } + + else { + $this->evaluate($currentNode, $scope); + } + } + + echo sprintf("Function %s missing return value." . PHP_EOL); + } + + private function evaluateIfArmBody(IfArm $arm, Scope $outerScope): mixed + { + $scope = new Scope($outerScope); + + foreach ($arm->body as $currentNode) { + if ($currentNode instanceof FunctionReturn) { + return $this->evaluate($currentNode->returnValue, $scope); + } + + else { + $this->evaluate($currentNode, $scope); + } + } + + return null; + } +} + +class Scope +{ + private ?Scope $outerScope; + public array $values; + public function __construct(?Scope $outerScope = null) + { + $this->outerScope = $outerScope; + } + public function add(IdentifierNode $identifier, mixed $value): void + { + $key = $identifier->token->value; + + $this->values[$key] = $value; + } + public function get(IdentifierNode $identifier): mixed + { + $key = $identifier->token->value; + + if (isset($this->values[$key])) { + return $this->values[$key]; + } + + if (! isset($this->outerScope)) { + echo sprintf("Undefined variable \"%s\" at %d:%d" . PHP_EOL, $key, $identifier->token->line, $identifier->token->column); + + return null; + } + + return $this->outerScope->get($identifier); + } +} |