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); } }