summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Lexer/Lexer.php4
-rw-r--r--src/Parser/Parser.php439
-rw-r--r--test/const-function-if.mnml9
-rw-r--r--test/const-function.mnml4
-rw-r--r--test/if.mnml11
-rw-r--r--test/test.mnml8
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] = [