diff options
-rw-r--r-- | src/Lexer/Lexer.php | 4 | ||||
-rw-r--r-- | src/Parser/Parser.php | 439 | ||||
-rw-r--r-- | test/const-function-if.mnml | 9 | ||||
-rw-r--r-- | test/const-function.mnml | 4 | ||||
-rw-r--r-- | test/if.mnml | 11 | ||||
-rw-r--r-- | test/test.mnml | 8 |
6 files changed, 425 insertions, 50 deletions
diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php index fba9623..bb2a809 100644 --- a/src/Lexer/Lexer.php +++ b/src/Lexer/Lexer.php @@ -475,8 +475,8 @@ class Token { public TokenType $type, public string $literal, public string $value, - public string $line, - public string $column, + public int $line, + public int $column, ) {} } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 7d780b0..41c8a14 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -25,16 +25,48 @@ class Parser public function parse(): array { + $lastPosition = -1; while ($this->position < count($this->tokens) - 1) { + $lastPosition = $this->position; $currentToken = $this->getCurrentToken(); + $currentStatement = null; if ($currentToken->literal == "const") { - $this->nodes[] = $this->parseConst(); + $currentStatement = $this->parseConst(); + } + + else if ($currentToken->literal == "if") { + $currentStatement = $this->parseIf(); + } + + else if ($currentToken->type == TokenType::Comment) { + $currentStatement = new CommentNode($currentToken); + $this->advance(1); } else { - $this->position += 1; + $currentStatement = $this->parseFunctionCall(); } + + $nextToken = $this->getCurrentToken(); + + if ($nextToken->literal == "=>") { + $this->advance(1); + + $currentStatement = new PipeExpression($currentStatement, $this->parseExpression(shouldBeFunction: true)); + } + + // unknown token + if ($this->position == $lastPosition) { + $error = sprintf("Unknown token %s at position %d,%d" . PHP_EOL, $currentToken, $currentToken->line, $currentToken->column); + $this->errors[] = $error; + + $this->advance(1); + + echo $error; + } + + $this->nodes[] = $currentStatement; } return $this->nodes; @@ -52,20 +84,20 @@ class Parser foreach ($properties as $propertyKey => $propertyValue) { if ($propertyValue instanceof Node) { - echo sprintf(str_repeat("> ", $level) . "%s: %s\n", $propertyKey, (new \ReflectionClass($propertyValue))->getShortName()); + echo sprintf(str_repeat("> ", $level) . "%s: %s" . PHP_EOL, $propertyKey, (new \ReflectionClass($propertyValue))->getShortName()); $this->printNodeRecursive($propertyValue, $level + 1); } else if (is_array($propertyValue)) { $length = count($propertyValue); - echo sprintf(str_repeat("> ", $level) . "%s[%d]:\n", $propertyKey, $length); + echo sprintf(str_repeat("> ", $level) . "%s[%d]:" . PHP_EOL, $propertyKey, $length); if ($length == 0) { - echo str_repeat("> ", $level + 1) . "None"; + echo str_repeat("> ", $level + 1) . "_empty_" . PHP_EOL; } else { foreach ($propertyValue as $idx => $item) { - echo sprintf(str_repeat("> ", $level + 1) . "[%d]%s\n", $idx + 1, (new \ReflectionClass($item))->getShortName()); + echo sprintf(str_repeat("> ", $level + 1) . "[%d]%s" . PHP_EOL, $idx + 1, (new \ReflectionClass($item))->getShortName()); $this->printNodeRecursive($item, $level + 2); } } @@ -74,12 +106,12 @@ class Parser else { if (! empty($propertyValue)) { if ($propertyValue instanceof Token) { - echo sprintf(str_repeat("> ", $level) . "%s: %s - %d:%d\n", $propertyKey, $propertyValue->literal, $propertyValue->line, $propertyValue->column); + echo sprintf(str_repeat("> ", $level) . "%s: %s - %d:%d" . PHP_EOL, $propertyKey, $propertyValue->literal, $propertyValue->line, $propertyValue->column); } else { - echo sprintf(str_repeat("> ", $level) . "%s: %s\n", $propertyKey, $propertyValue); + echo sprintf(str_repeat("> ", $level) . "%s: %s" . PHP_EOL, $propertyKey, $propertyValue); } } else { - echo sprintf(str_repeat("> ", $level) . "%s: %s\n", $propertyKey, "_empty_"); + echo sprintf(str_repeat("> ", $level) . "%s: %s" . PHP_EOL, $propertyKey, "_empty_"); } } } @@ -97,32 +129,66 @@ class Parser private function advance(int $steps): void { - assert( - $this->position + $steps < count($this->tokens), - "Can't advance. Position out of bounds." - ); + $lastToken = $this->getCurrentToken(); + + if ($this->position + $steps > count($this->tokens)) { + if (! empty($this->errors)) { + exit; + } + + assert( + false, + "Parser Implementation Error: Can't advance. Position out of bounds." + ); + } $this->position += $steps; } + private function stepBack(int $steps): void + { + $this->position -= $steps; + } + + private function anticipateTokenAndSkip(array|string $tokenValues): void + { + if (is_string($tokenValues)) { + $tokenValues = [$tokenValues]; + } + + foreach ($tokenValues as $value) { + $currentToken = $this->getCurrentToken(); + + if ($currentToken->value != $value) { + $error = sprintf("Expected %s but got %s instead at %d:%d" . PHP_EOL, $value, $currentToken->value, $currentToken->line, $currentToken->column); + $this->errors[] = $error; + + echo $error; + } + + $this->advance(1); + } + } + private function parseConst(): Node { // skip const - $this->advance(1); + $this->anticipateTokenAndSkip("const"); $identifier = $this->getCurrentToken(); + $this->advance(1); // skip : - $this->advance(2); + $this->anticipateTokenAndSkip(":"); $type = $this->parseType(); // skip = - $this->advance(1); + $this->anticipateTokenAndSkip("="); $expression = $this->parseExpression( shouldBeMap: $type instanceof MapTypeDeclaration, - shouldBeFunction: $type->left->literal == "function", + shouldBeFunction: $type instanceof TypeDeclaration and $type->left->literal == "function", ); return new ConstVariableDeclaration( @@ -157,7 +223,7 @@ class Parser private function parseArrayOrMapType(): Node { // skip first [ - $this->advance(1); + $this->anticipateTokenAndSkip("["); $key = $this->parseType(); @@ -171,12 +237,12 @@ class Parser $value = $this->parseType(); // skip last ] - $this->advance(1); + $this->anticipateTokenAndSkip("]"); return new MapTypeDeclaration($key, $value); } else { // skip last ] - $this->advance(1); + $this->anticipateTokenAndSkip("]"); return new ArrayTypeDeclaration($key); } @@ -191,6 +257,11 @@ class Parser $currentExpression = $this->parseArrayOrMap($shouldBeMap); } + else if ($currentToken->type == TokenType::Identifier) { + $currentExpression = new IdentifierNode($currentToken); + $this->advance(1); + } + else if ($currentToken->type == TokenType::Number) { $currentExpression = $this->parseNumber(); } @@ -201,9 +272,16 @@ class Parser } else if ($currentToken->literal == "(") { - if ($shouldBeFunction) { + #if ($shouldBeFunction) { $currentExpression = $this->parseFunctionDefinition(); - } + #} + + # TODO: parse normal () + } + + else { + # TODO: error. unknown token + $this->advance(1); } $nextToken = $this->getCurrentToken(); @@ -214,12 +292,31 @@ class Parser return new OperatorExpression($currentExpression, $nextToken, $this->parseExpression(shouldBeMap: $shouldBeMap)); } - else if (in_array($nextToken->literal, ["<", ">", "<=", ">=", "=="])) { + else if (in_array($nextToken->literal, ["<", ">", "<=", ">=", "==", "!="])) { $this->advance(1); return new CompareExpression($currentExpression, $nextToken, $this->parseExpression(shouldBeMap: $shouldBeMap)); } + else if (in_array($nextToken->literal, ["and", "or"])) { + $this->advance(1); + + return new Condition($currentExpression, $nextToken, $this->parseExpression(shouldBeMap: $shouldBeMap)); + } + + else if ($nextToken->literal == "=>") { + $this->advance(1); + + return new PipeExpression($currentExpression, $this->parseExpression(shouldBeFunction: true)); + } + + else if ($currentToken->type == TokenType::Identifier and $nextToken->literal == "(") { + // step back to parse function call fully + $this->stepBack(1); + + return $this->parseFunctionCall(); + } + else { return $currentExpression; } @@ -328,13 +425,10 @@ class Parser private function parseFunctionDefinition(): Node { - // skip first ( - $this->advance(1); - - $parameters = $this->parseFunctionParameters(); + $parameters = $this->parseFunctionDefinitionParameters(); // skip : - $this->advance(1); + $this->anticipateTokenAndSkip(":"); $returnType = $this->parseType(); @@ -343,33 +437,216 @@ class Parser return new FunctionDefinition( $parameters, $returnType, + $body, ); } - private function parseFunctionParameters(): array + private function parseFunctionDefinitionParameters(): array { - while ($this->getCurrentToken() != ")") { - # TODO + // skip first ( + if ($this->getCurrentToken()->literal == "(") { + $this->advance(1); } + + $parameters = []; - $identifier = $this->getCurrentToken(); + while ($this->getCurrentToken()->literal != ")") { + // skip , + if ($this->getCurrentToken()->literal == ",") { + $this->advance(1); + continue; + } + + $identifier = $this->getCurrentToken(); + $this->advance(1); - // skip : - $this->advance(1); + // skip : + $this->anticipateTokenAndSkip(":"); - $type = $this->parseType(); + $type = $this->parseType(); + + // skip potential , instead default value + if ($this->getCurrentToken()->literal == ",") { + $this->advance(1); + } + + // default value + $defaultValue = null; + if ($this->getCurrentToken()->literal == "=") { + $this->advance(1); + $defaultValue = $this->parseExpression(); + } + + $parameters[] = new FunctionDefinitionParameter( + $identifier, + $type, + $defaultValue, + ); + } + + // skip last ) + $this->anticipateTokenAndSkip(")"); + + return $parameters; + } + + private function parseFunctionBody(): array + { + // skip first { + $this->anticipateTokenAndSkip("{"); + + $body = []; - return new FunctionParameter( + while ($this->getCurrentToken()->literal != "}") { + $currentToken = $this->getCurrentToken(); + if ($currentToken->literal == "const") { + $body[] = $this->parseConst(); + } + + else if ($currentToken->literal == "return") { + $body[] = $this->parseFunctionReturn(); + } + + else if ($currentToken->literal == "if") { + $body[] = $this->parseIf(); + } + + // skip comments + else if ($currentToken->type == TokenType::Comment) { + $this->advance(1); + continue; + } + + else { + $body[] = $this->parseFunctionCall(); + } + } + + // skip last } + $this->anticipateTokenAndSkip("}"); + + return $body; + } + + private function parseFunctionReturn(): Node + { + // skip return + $this->anticipateTokenAndSkip("return"); + + return new FunctionReturn($this->parseExpression()); + } + + private function parseFunctionCall(): Node + { + $identifier = $this->getCurrentToken(); + $this->advance(1); + + // skip first ( + $this->anticipateTokenAndSkip("("); + + $parameters = $this->parseFunctionCallParameters(); + + return new FunctionCall( + $identifier, + $parameters, ); } - private function parseFunctionBody(): array + private function parseFunctionCallParameters(): array + { + // skip first ( + if ($this->getCurrentToken()->literal == "(") { + $this->advance(1); + } + + $parameters = []; + + + while ($this->getCurrentToken()->literal != ")") { + // skip , + if ($this->getCurrentToken()->literal == ",") { + $this->advance(1); + continue; + } + + // if "=" then identifier is name + if ($this->getNextToken()->literal == "=") { + $identifier = $this->getCurrentToken(); + $this->advance(2); + + $value = $this->parseExpression(); + + $parameters[] = new FunctionCallParameter( + $identifier, + $value, + ); + } + + // else only value is provided + else { + $parameters[] = new FunctionCallParameter( + null, + $this->parseExpression(), + ); + } + } + + // skip last ) + $this->anticipateTokenAndSkip(")"); + + return $parameters; + } + + private function parseIf(): Node { - # TODO + $arms = []; + + // parse first arm + $arms[] = $this->parseIfArm(); + + // parse remaining arms + while ($this->getCurrentToken()->literal == "else") { + $arms[] = $this->parseIfArm(); + } + + return new IfNode($arms); } - private function parseFunctionCall(){} + private function parseIfArm(): Node + { + $isElseBlock = false; + + if ($this->getCurrentToken()->literal == "if") { + $this->advance(1); + } + else if ($this->getCurrentToken()->literal == "else") { + if ($this->getNextToken()->literal == "if") { + $this->advance(2); + } else { + $isElseBlock = true; + $this->advance(1); + } + } + + $condition = null; + if (! $isElseBlock) { + // skip first ( + $this->anticipateTokenAndSkip("("); + + $condition = $this->parseExpression(); + + // skip last ) + $this->anticipateTokenAndSkip(")"); + } + + $body = $this->parseFunctionBody(); + + return new IfArm( + $condition, + $body, + ); + } } class Node @@ -462,6 +739,22 @@ class CompareExpression extends Node ) {} } +class Condition extends Node +{ + public function __construct( + public Token|Node $left, + public Token $operator, + public Token|Node $right, + ) {} +} + +class IdentifierNode extends Node +{ + public function __construct( + public Token $token, + ) {} +} + class NumberNode extends Node { public function __construct( @@ -477,14 +770,21 @@ class StringNode extends Node ) {} } +class CommentNode extends Node +{ + public function __construct( + public Token $token, + ) {} +} + class FunctionDefinition extends Node { public function __construct( /** - * @param FunctionParameter[] $parameters + * @param FunctionDefinitionParameter[] $parameters */ public array $parameters, - public TypeDefinition $returnType, + public TypeDeclaration $returnType, /** * @param Node[] $body */ @@ -492,10 +792,63 @@ class FunctionDefinition extends Node ) {} } -class FunctionParameter extends Node +class FunctionDefinitionParameter extends Node +{ + public function __construct( + public Token $identifier, + public TypeDeclaration $type, + public ?Node $defaultValue = null, + ) {} +} + +class FunctionReturn extends Node +{ + public function __construct( + public Node|Token $returnValue, + ) {} +} + +class FunctionCall extends Node { public function __construct( public Token $identifier, - public TypeDefinition $type, + /** + * @param FunctionCallParameter[] $parameters + */ + public array $parameters, + ) {} +} + +class FunctionCallParameter extends Node +{ + public function __construct( + public ?Token $identifier, + public Node|Token $value, + ) {} +} + +class IfNode extends Node +{ + public function __construct( + /** + * @param IfArm[] $arms + */ + public array $arms, + ) {} +} + +class IfArm extends Node +{ + public function __construct( + public Condition|CompareExpression|null $condition, + public array $body, + ) {} +} + +class PipeExpression extends Node +{ + public function __construct( + public Token|Node $left, + public Token|Node $right, ) {} } diff --git a/test/const-function-if.mnml b/test/const-function-if.mnml new file mode 100644 index 0000000..48fc4fb --- /dev/null +++ b/test/const-function-if.mnml @@ -0,0 +1,9 @@ +const main: function = (input: string): void { + if (input == "hello") { + const bye: string = "bye" + } + + return input +} + +main(input = "hello") diff --git a/test/const-function.mnml b/test/const-function.mnml index 1f77a73..9630dd8 100644 --- a/test/const-function.mnml +++ b/test/const-function.mnml @@ -1,6 +1,8 @@ -const main: function = (input: string or integer): void { +const main: function = (input: string or integer, default: integer = 1): void { const hello: string = input + " world!" const bye: string = "bye!" return hello + " " + bye } + +main(input = "hey", 2) diff --git a/test/if.mnml b/test/if.mnml new file mode 100644 index 0000000..1d273b3 --- /dev/null +++ b/test/if.mnml @@ -0,0 +1,11 @@ +if (1 == 2) { + main1() +} + +else if (true != false) { + main2() +} + +else { + main3() +} diff --git a/test/test.mnml b/test/test.mnml index 9659081..a14bdec 100644 --- a/test/test.mnml +++ b/test/test.mnml @@ -1,7 +1,7 @@ const henshin: integer = 2 // comment // next comment -var ply: string = "abc" -ply = "way cooler!!" +/*var ply: string = "abc" +ply = "way cooler!!"*/ const new: integer = henshin + 5 * 10 @@ -18,9 +18,9 @@ main()/* main("pipe1!") => print($) -var mls: string = "alphabet +/*var mls: string = "alphabet ende -gelände" +gelände"*/ const array: [integer] = [1, 2, 3] const map: [string][string or integer or bool] = [ |