diff options
author | Daniel Weipert <git@mail.dweipert.de> | 2025-02-06 13:59:50 +0100 |
---|---|---|
committer | Daniel Weipert <git@mail.dweipert.de> | 2025-02-06 13:59:50 +0100 |
commit | 54b4040a8e46c4104e228264fa57b44d17e245c9 (patch) | |
tree | 938f077f1343ee018476bd1cf68924f9181143f1 | |
parent | 7667162a20ebdedac14de88260df018b961548d4 (diff) |
all current tests passing
-rw-r--r-- | src/Interpreter/Interpreter.php | 226 | ||||
-rw-r--r-- | src/Lexer/Lexer.php | 15 | ||||
-rw-r--r-- | src/Parser/Parser.php | 24 | ||||
-rw-r--r-- | src/standard.php | 18 | ||||
-rw-r--r-- | test/const-array-complex.mnml | 2 | ||||
-rw-r--r-- | test/const-array.mnml | 4 | ||||
-rw-r--r-- | test/const-function-if.mnml | 6 | ||||
-rw-r--r-- | test/const-function.mnml | 2 | ||||
-rw-r--r-- | test/if.mnml | 2 | ||||
-rw-r--r-- | test/parentheses.mnml | 7 | ||||
-rw-r--r-- | test/test.mnml | 4 |
11 files changed, 210 insertions, 100 deletions
diff --git a/src/Interpreter/Interpreter.php b/src/Interpreter/Interpreter.php index 7d18551..e8b3c82 100644 --- a/src/Interpreter/Interpreter.php +++ b/src/Interpreter/Interpreter.php @@ -2,11 +2,14 @@ namespace Mnml\Interpreter; +use Closure; use Mnml\Lexer\Lexer; use Mnml\Lexer\Token; use Mnml\Lexer\TokenType; use Mnml\Parser\ArrayMapAccessNode; use Mnml\Parser\ArrayNode; +use Mnml\Parser\BoolNode; +use Mnml\Parser\CommentNode; use Mnml\Parser\CompareExpression; use Mnml\Parser\Condition; use Mnml\Parser\ConstVariableDeclaration; @@ -23,6 +26,7 @@ use Mnml\Parser\NumberNode; use Mnml\Parser\OperatorExpression; use Mnml\Parser\Parenthesis; use Mnml\Parser\Parser; +use Mnml\Parser\PipeExpression; use Mnml\Parser\StringNode; class Interpreter @@ -40,49 +44,28 @@ class Interpreter 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); + // 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); - - if ($currentNode instanceof FunctionCall) { - var_dump($value); - } } return $scope; } - private function evaluate(Node $currentNode, Scope $scope): mixed + private function evaluate(Node $currentNode, Scope $scope): Node|null|bool { 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); - } - + $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; + return $value; } else if ($currentNode instanceof IdentifierNode) { @@ -91,14 +74,6 @@ class Interpreter 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); } @@ -108,67 +83,76 @@ class Interpreter } else if ($currentNode instanceof OperatorExpression) { - $left = $this->evaluate($currentNode->left, $scope); - $right = $this->evaluate($currentNode->right, $scope); + $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 $left . $right; + return new ValueNode($left . $right); } - return $left + $right; + return new ValueNode($left + $right); } else if ($currentNode->operator->value == "-") { - return $left - $right; + return new ValueNode($left - $right); } else if ($currentNode->operator->value == "*") { - return $left * $right; + return new ValueNode($left * $right); } else if ($currentNode->operator->value == "**") { - return pow($left, $right); + return new ValueNode(pow($left, $right)); } else if ($currentNode->operator->value == "/") { - return $left / $right; + 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); - $right = $this->evaluate($currentNode->right, $scope); - + $left = $this->evaluate($currentNode->left, $scope)->getValue(); + $right = $this->evaluate($currentNode->right, $scope)->getValue(); + if ($currentNode->operator->value == "<") { - return $left < $right; + return new ValueNode($left < $right); } else if ($currentNode->operator->value == ">") { - return $left > $right; + return new ValueNode($left > $right); } else if ($currentNode->operator->value == "<=") { - return $left <= $right; + return new ValueNode($left <= $right); } else if ($currentNode->operator->value == ">=") { - return $left >= $right; + return new ValueNode($left >= $right); } else if ($currentNode->operator->value == "==") { - return $left == $right; + return new ValueNode($left == $right); } else if ($currentNode->operator->value == "!=") { - return $left != $right; + return new ValueNode($left != $right); } } else if ($currentNode instanceof IfNode) { foreach ($currentNode->arms as $arm) { /**@var IfArm $arm*/ - if ($this->evaluate($arm->condition, $scope)) { + $condition = true; + if ($arm->condition != null) { + $condition = $this->evaluate($arm->condition, $scope)->getValue(); + } + + if ($condition) { return $this->evaluateIfArmBody($arm, $scope); } } @@ -177,15 +161,15 @@ class Interpreter } else if ($currentNode instanceof Condition) { - $left = $this->evaluate($currentNode->left, $scope); - $right = $this->evaluate($currentNode->right, $scope); + $left = $this->evaluate($currentNode->left, $scope)->getValue(); + $right = $this->evaluate($currentNode->right, $scope)->getValue(); - if ($currentNode->operator == "and") { - return $left and $right; + if ($currentNode->operator->value == "and") { + return new ValueNode($left and $right); } - else if ($currentNode->operator == "or") { - return $left or $right; + else if ($currentNode->operator->value == "or") { + return new ValueNode($left or $right); } } @@ -196,15 +180,62 @@ class Interpreter else if ($currentNode instanceof FunctionCall) { $functionDefinition = $scope->get($currentNode->identifier); - return $this->evaluateFunction($functionDefinition, $currentNode->parameters, $scope); + if ($functionDefinition instanceof FunctionDefinition) { + return $this->evaluateFunction($functionDefinition, $currentNode->parameters, $scope); + } + else if ($functionDefinition instanceof Closure) { + return $this->evaluateClosure($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; } } @@ -215,34 +246,26 @@ class Interpreter $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; + $accessee = $arrayOrMap; + + foreach ($arrayOrMapAccess->items as $item) { + $evaluation = $this->evaluate($item, $scope)->getValue(); - foreach ($arrayOrMapAccess->items as $item) { - $evaluation = $this->evaluate($item, $scope); - - foreach ($values as $value) { - $key = $this->evaluate($value->key, $scope); + 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) { - $values = $this->evaluate($value->value, $scope); - # TODO: array inside map => create ArrayOrMapAccessItem with $left and $right + $accessee = $this->evaluate($value->value, $scope); + break; } } } - - return $values; } + + return $accessee; } /** @@ -279,6 +302,20 @@ class Interpreter echo sprintf("Function %s missing return value." . PHP_EOL); } + /** + * @param FunctionCallParameter[] $parameters + */ + private function evaluateClosure(Closure $function, array $parameters, Scope $scope): mixed + { + $evaluatedParameters = []; + foreach ($parameters as $parameter) { + /**@var FunctionCallParameter $parameter*/ + $evaluatedParameters[] = $this->evaluate($parameter->value, $scope)->getValue(); + } + + return call_user_func_array($function, $evaluatedParameters); + } + private function evaluateIfArmBody(IfArm $arm, Scope $outerScope): mixed { $scope = new Scope($outerScope); @@ -301,16 +338,19 @@ 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; @@ -327,4 +367,20 @@ class Scope return $this->outerScope->get($identifier); } + + 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; + } } diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php index bb2a809..98ac5e2 100644 --- a/src/Lexer/Lexer.php +++ b/src/Lexer/Lexer.php @@ -27,7 +27,7 @@ class Lexer "(", ")", "[", "]", "{", "}", - "$", ".", + ".", ]; $lastPosition = -1; @@ -238,6 +238,18 @@ class Lexer } } + // pipe placeholder + else if ($currentChar == "$") { + $output[] = new Token( + TokenType::PipePlaceholder, + $currentChar, + $currentChar, + $this->line, + $startColumn, + ); + $this->advance(1); + } + // single char tokens else if (in_array($currentChar, $singleCharTokens)) { $output[] = new Token( @@ -489,5 +501,6 @@ enum TokenType { case Operator; case Assign; case Pipe; + case PipePlaceholder; case EndOfFile; } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 1ce092c..a3a6839 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -294,6 +294,11 @@ class Parser $this->advance(1); } + else if ($currentToken->type == TokenType::PipePlaceholder) { + $currentExpression = new IdentifierNode($currentToken); + $this->advance(1); + } + else if ($currentToken->literal == "(") { if ($this->getNextToken(2)->literal == ":") { $currentExpression = $this->parseFunctionDefinition(); @@ -559,9 +564,9 @@ class Parser $this->advance(1); } - /*else if ($currentToken->type == TokenType::Identifier) { + else if ($currentToken->type == TokenType::Identifier) { $body[] = $this->parseFunctionCall(); - }*/ + } else { $error = sprintf("Unexpected %s at %d:%d" . PHP_EOL, $currentToken->value, $currentToken->line, $currentToken->column); @@ -833,6 +838,11 @@ class NumberNode extends Node public Token $token, public int|float $value, ) {} + + public function getValue(): int|float + { + return $this->value; + } } class StringNode extends Node @@ -840,6 +850,11 @@ class StringNode extends Node public function __construct( public Token $token, ) {} + + public function getValue(): string + { + return $this->token->value; + } } class BoolNode extends Node @@ -847,6 +862,11 @@ class BoolNode extends Node public function __construct( public Token $token, ) {} + + public function getValue(): bool + { + return filter_var($this->token->value, FILTER_VALIDATE_BOOLEAN); + } } class CommentNode extends Node diff --git a/src/standard.php b/src/standard.php new file mode 100644 index 0000000..351c9e8 --- /dev/null +++ b/src/standard.php @@ -0,0 +1,18 @@ +<?php + +use Mnml\Lexer\Lexer; +use Mnml\Parser\Parser; + +$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(); +}; + +$print = function (string $string) { + echo $string; +}; + +return compact("import", "print"); diff --git a/test/const-array-complex.mnml b/test/const-array-complex.mnml index 07e2286..00edaac 100644 --- a/test/const-array-complex.mnml +++ b/test/const-array-complex.mnml @@ -1 +1 @@ -const array: [[integer]] = [[1, 2], [3, 4], [5, 6, 7]] +const array: [[integer]] = [[1, 2], [3, 4], [5, 6, 7_000_000]] diff --git a/test/const-array.mnml b/test/const-array.mnml index e5f30b8..f79adbf 100644 --- a/test/const-array.mnml +++ b/test/const-array.mnml @@ -1,8 +1,8 @@ const array: [integer] = [1, 2, 3] -const array2: [integer] = [3, 4, 5] +const array2: [integer] = [3, [4, 8], 5] const main: function = (array3: [integer], array4: [integer]): integer { - return array4[1] + return array4[1][1] } main(array3 = array, array4 = array2) diff --git a/test/const-function-if.mnml b/test/const-function-if.mnml index 2b62f8e..7b71b8c 100644 --- a/test/const-function-if.mnml +++ b/test/const-function-if.mnml @@ -1,7 +1,7 @@ const main: function = (input: string): string { if (input == "hello") { const bye: string = "bye" - return input + bye + return input + " " + bye } return input @@ -10,7 +10,7 @@ const main: function = (input: string): string { const main2: function = (input: string): string { const bye: string = main(input = "bye") - return main(input = "hello") + return input + main(input = "hello") } -main(input = "hello") +main(input = "hello") => main2(input = $ + " ") diff --git a/test/const-function.mnml b/test/const-function.mnml index 9630dd8..5a82b68 100644 --- a/test/const-function.mnml +++ b/test/const-function.mnml @@ -5,4 +5,4 @@ const main: function = (input: string or integer, default: integer = 1): void { return hello + " " + bye } -main(input = "hey", 2) +main(input = "hey", default = 2) diff --git a/test/if.mnml b/test/if.mnml index 1d273b3..8227ad5 100644 --- a/test/if.mnml +++ b/test/if.mnml @@ -1,4 +1,4 @@ -if (1 == 2) { +if ((1 == 2) or (2 == 3)) { main1() } diff --git a/test/parentheses.mnml b/test/parentheses.mnml index d18aee2..f321700 100644 --- a/test/parentheses.mnml +++ b/test/parentheses.mnml @@ -1,6 +1,7 @@ const main: function = (input: string): string { if ((input == "hello") or input == "bye") { - const bye: string = "bye" + const bye: string = "bye" + return bye } return input @@ -13,5 +14,7 @@ const main2: function = (input: string): string { const test2: string = ((main(input = "test") => main(input = $)) + "test2") - return main(input = "hello") + bye + return input + main(input = "hello") + bye + " " + test2 } + +main(input = "test") => main2(input = $) diff --git a/test/test.mnml b/test/test.mnml index a14bdec..6ecb7d9 100644 --- a/test/test.mnml +++ b/test/test.mnml @@ -12,11 +12,11 @@ const main: function = (input: string or integer): void { return hello + " " + bye } -main()/* +main(input = "mlc")/* * mlc */ -main("pipe1!") => print($) +main(input = "pipe1!") => print($) /*var mls: string = "alphabet ende |