summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Weipert <git@mail.dweipert.de>2025-02-06 13:59:50 +0100
committerDaniel Weipert <git@mail.dweipert.de>2025-02-06 13:59:50 +0100
commit54b4040a8e46c4104e228264fa57b44d17e245c9 (patch)
tree938f077f1343ee018476bd1cf68924f9181143f1
parent7667162a20ebdedac14de88260df018b961548d4 (diff)
all current tests passing
-rw-r--r--src/Interpreter/Interpreter.php226
-rw-r--r--src/Lexer/Lexer.php15
-rw-r--r--src/Parser/Parser.php24
-rw-r--r--src/standard.php18
-rw-r--r--test/const-array-complex.mnml2
-rw-r--r--test/const-array.mnml4
-rw-r--r--test/const-function-if.mnml6
-rw-r--r--test/const-function.mnml2
-rw-r--r--test/if.mnml2
-rw-r--r--test/parentheses.mnml7
-rw-r--r--test/test.mnml4
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