nodes = $nodes; } public function compile(array $parameters): Scope { $scope = new Scope(); // add standard library $standardLibrary = include dirname(__DIR__) . "/standard.php"; foreach ($standardLibrary as $identifier => $function) { $scope->addExternal($identifier, $function); } foreach ($this->nodes as $currentNode) { $value = $this->evaluate($currentNode, $scope); } return $scope; } private function evaluate(Node $currentNode, Scope $scope): Node|null|bool { if ($currentNode instanceof ConstVariableDeclaration) { $identifier = $currentNode->identifier; $value = new ValueNode(null); /*if ($scope->has($identifier)) { echo sprintf("%s already declared in current scope." . PHP_EOL, $identifier->token->value); }*/ # TODO: problems with recursive functions. clear scope somehow? #else { if (! is_null($currentNode->expression)) { $value = $this->evaluate($currentNode->expression, $scope); } $scope->add($identifier, $value); #} return $value; } if ($currentNode instanceof VariableAssignment) { $identifier = $currentNode->identifier; $value = new ValueNode(null); if ($scope->has($identifier)) { $curentValue = $scope->get($identifier)->getValue(); if (is_null($curentValue)) { $value = $this->evaluate($currentNode->expression, $scope); $scope->set($identifier, $value); } else { echo sprintf("%s already assigned in current scope." . PHP_EOL, $identifier->token->value); } } else { echo sprintf("%s is not declared in current scope." . PHP_EOL, $identifier->token->value); } return $value; } else if ($currentNode instanceof IdentifierNode) { $variable = $scope->get($currentNode); return $this->evaluate($variable, $scope); } 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)->getValue(); $right = $this->evaluate($currentNode->right, $scope)->getValue(); if ($currentNode->operator->value == "+") { if (is_string($left) and is_string($right)) { return new ValueNode($left . $right); } return new ValueNode($left + $right); } else if ($currentNode->operator->value == "-") { return new ValueNode($left - $right); } else if ($currentNode->operator->value == "*") { return new ValueNode($left * $right); } else if ($currentNode->operator->value == "**") { return new ValueNode(pow($left, $right)); } else if ($currentNode->operator->value == "/") { return new ValueNode($left / $right); } else if ($currentNode->operator->value == "%") { return new ValueNode($left % $right); } } else if ($currentNode instanceof CompareExpression) { $left = $this->evaluate($currentNode->left, $scope)->getValue(); $right = $this->evaluate($currentNode->right, $scope)->getValue(); if ($currentNode->operator->value == "<") { return new ValueNode($left < $right); } else if ($currentNode->operator->value == ">") { return new ValueNode($left > $right); } else if ($currentNode->operator->value == "<=") { return new ValueNode($left <= $right); } else if ($currentNode->operator->value == ">=") { return new ValueNode($left >= $right); } else if ($currentNode->operator->value == "==") { return new ValueNode($left == $right); } else if ($currentNode->operator->value == "!=") { return new ValueNode($left != $right); } } else if ($currentNode instanceof IfNode) { foreach ($currentNode->arms as $arm) { /**@var IfArm $arm*/ $condition = true; if ($arm->condition != null) { $condition = $this->evaluate($arm->condition, $scope)->getValue(); } if ($condition) { return $this->evaluateIfArmBody($arm, $scope); } } return null; } else if ($currentNode instanceof Condition) { $left = $this->evaluate($currentNode->left, $scope)->getValue(); $right = $this->evaluate($currentNode->right, $scope)->getValue(); if ($currentNode->operator->value == "and") { return new ValueNode($left and $right); } else if ($currentNode->operator->value == "or") { return new ValueNode($left or $right); } } else if ($currentNode instanceof FunctionDefinition) { return $currentNode; } else if ($currentNode instanceof FunctionCall) { $functionDefinition = $scope->get($currentNode->identifier); if ($functionDefinition instanceof FunctionDefinition) { return $this->evaluateFunction($currentNode->identifier, $functionDefinition, $currentNode->parameters, $scope); } else if ($functionDefinition instanceof Closure) { return $this->evaluateClosure($currentNode->identifier, $functionDefinition, $currentNode->parameters, $scope); } return $currentNode; } else if ($currentNode instanceof PipeExpression) { $left = $this->evaluate($currentNode->left, $scope); $pipeScope = new Scope($scope); $pipeScope->add(new IdentifierNode(new Token(TokenType::Identifier, "$", "$", 0, 0)), $left); $right = $this->evaluate($currentNode->right, $pipeScope); return $right; } else if ($currentNode instanceof NumberNode) { return $currentNode; } else if ($currentNode instanceof StringNode) { return $currentNode; } else if ($currentNode instanceof BoolNode) { return $currentNode; } else if ($currentNode instanceof ArrayNode) { return $currentNode; } else if ($currentNode instanceof MapNode) { return $currentNode; } else if ($currentNode instanceof ValueNode) { return $currentNode; } else if ($currentNode instanceof CommentNode) { return $currentNode; } else if (is_callable($currentNode)) { return true; } else { echo sprintf("Compiler Error: Unhandled Node type %s." . PHP_EOL, get_class($currentNode)); return false; } } private function evaluateArrayOrMapAccess(ArrayMapAccessNode $arrayOrMapAccess, Scope $scope): Node { $arrayOrMap = $arrayOrMapAccess->arrayOrMap; if ($arrayOrMap instanceof IdentifierNode) { $arrayOrMap = $this->evaluate($arrayOrMap, $scope); } $accessee = $arrayOrMap; foreach ($arrayOrMapAccess->items as $item) { $evaluation = $this->evaluate($item, $scope)->getValue(); if ($accessee instanceof ArrayNode) { $accessee = $accessee->values[$evaluation]; } else if ($accessee instanceof MapNode) { foreach ($accessee->values as $value) { $key = $this->evaluate($value->key, $scope)->getValue(); if ($key == $evaluation) { $accessee = $this->evaluate($value->value, $scope); break; } } } else if ($accessee instanceof ValueNode) { $accessee = new ValueNode($accessee->getValue()[$evaluation]); } } return $accessee; } /** * @param FunctionCallParameter[] $parameters */ private function evaluateFunction(IdentifierNode $identifier, FunctionDefinition $function, array $parameters, Scope $outerScope): mixed { $scope = new Scope($outerScope); foreach ($parameters as $parameter) { /**@var FunctionCallParameter $parameter*/ $scope->add($parameter->identifier, $this->evaluate($parameter->value, $outerScope)); } 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, $identifier->token->value); } /** * @param FunctionCallParameter[] $parameters */ private function evaluateClosure(IdentifierNode $identifier, Closure $function, array $parameters, Scope $scope): mixed { $evaluatedParameters = []; foreach ($parameters as $parameter) { /**@var FunctionCallParameter $parameter*/ $evaluatedParameters[] = $this->evaluate($parameter->value, $scope)->getValue(); } return new ValueNode(call_user_func_array($function, $evaluatedParameters)); } 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])) { $value = $this->values[$key]; if ($value instanceof IdentifierNode and $value->token->value == $key) {} else { 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); } public function set(IdentifierNode $identifier, mixed $value): void { $key = $identifier->token->value; if (isset($this->values[$key])) { $this->values[$key] = $value; } else if (isset($this->outerScope)){ $this->outerScope->set($identifier, $value); } else { echo sprintf("Can't set nonexistent variable %s." . PHP_EOL, $identifier->token->value); } } public function has(IdentifierNode $identifier): bool { $has = isset($this->values[$identifier->token->value]); if ($has) { return $has; } if (isset($this->outerScope)) { return $this->outerScope->has($identifier); } return false; } public function addExternal(string $identifier, mixed $value): void { $this->values[$identifier] = $value; } } class ValueNode extends Node { public function __construct(private mixed $value) {} public function getValue(): mixed { return $this->value; } }