summaryrefslogtreecommitdiff
path: root/src/Parser/Parser.php
diff options
context:
space:
mode:
Diffstat (limited to 'src/Parser/Parser.php')
-rw-r--r--src/Parser/Parser.php501
1 files changed, 501 insertions, 0 deletions
diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php
new file mode 100644
index 0000000..7d780b0
--- /dev/null
+++ b/src/Parser/Parser.php
@@ -0,0 +1,501 @@
+<?php
+
+namespace Mnml\Parser;
+
+use Mnml\Lexer\Token;
+use Mnml\Lexer\TokenType;
+
+class Parser
+{
+ private array $tokens;
+ private int $position;
+ private array $errors;
+ private array $nodes;
+
+ /**
+ * @param Token[] $tokens
+ */
+ public function __construct(array $tokens)
+ {
+ $this->tokens = $tokens;
+ $this->position = 0;
+ $this->errors = [];
+ $this->nodes = [];
+ }
+
+ public function parse(): array
+ {
+ while ($this->position < count($this->tokens) - 1) {
+ $currentToken = $this->getCurrentToken();
+
+ if ($currentToken->literal == "const") {
+ $this->nodes[] = $this->parseConst();
+ }
+
+ else {
+ $this->position += 1;
+ }
+ }
+
+ return $this->nodes;
+ }
+
+ public function printTree(): void
+ {
+ $tree = new Tree($this->nodes);
+ $this->printNodeRecursive($tree, 0);
+ }
+
+ private function printNodeRecursive(Node $node, int $level = 0): void
+ {
+ $properties = get_object_vars($node);
+
+ foreach ($properties as $propertyKey => $propertyValue) {
+ if ($propertyValue instanceof Node) {
+ echo sprintf(str_repeat("> ", $level) . "%s: %s\n", $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);
+
+ if ($length == 0) {
+ echo str_repeat("> ", $level + 1) . "None";
+ } else {
+ foreach ($propertyValue as $idx => $item) {
+ echo sprintf(str_repeat("> ", $level + 1) . "[%d]%s\n", $idx + 1, (new \ReflectionClass($item))->getShortName());
+ $this->printNodeRecursive($item, $level + 2);
+ }
+ }
+ }
+
+ 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);
+ } else {
+ echo sprintf(str_repeat("> ", $level) . "%s: %s\n", $propertyKey, $propertyValue);
+ }
+ } else {
+ echo sprintf(str_repeat("> ", $level) . "%s: %s\n", $propertyKey, "_empty_");
+ }
+ }
+ }
+ }
+
+ private function getCurrentToken(): Token
+ {
+ return $this->tokens[$this->position];
+ }
+
+ private function getNextToken(): ?Token
+ {
+ return $this->tokens[$this->position + 1] ?? null;
+ }
+
+ private function advance(int $steps): void
+ {
+ assert(
+ $this->position + $steps < count($this->tokens),
+ "Can't advance. Position out of bounds."
+ );
+
+ $this->position += $steps;
+ }
+
+ private function parseConst(): Node
+ {
+ // skip const
+ $this->advance(1);
+
+ $identifier = $this->getCurrentToken();
+
+ // skip :
+ $this->advance(2);
+
+ $type = $this->parseType();
+
+ // skip =
+ $this->advance(1);
+
+ $expression = $this->parseExpression(
+ shouldBeMap: $type instanceof MapTypeDeclaration,
+ shouldBeFunction: $type->left->literal == "function",
+ );
+
+ return new ConstVariableDeclaration(
+ $identifier,
+ $type,
+ $expression,
+ );
+ }
+
+ private function parseType(): Node
+ {
+ $currentToken = $this->getCurrentToken();
+ $nextToken = $this->getNextToken();
+
+ if ($currentToken->literal == "[") {
+ return $this->parseArrayOrMapType();
+ }
+
+ else if (in_array($nextToken->literal, ["and", "or"])) {
+ $this->advance(2);
+
+ return new TypeDeclaration($currentToken, $nextToken, $this->parseType());
+ }
+
+ else {
+ $this->advance(1);
+
+ return new TypeDeclaration($currentToken);
+ }
+ }
+
+ private function parseArrayOrMapType(): Node
+ {
+ // skip first [
+ $this->advance(1);
+
+ $key = $this->parseType();
+
+ $currentToken = $this->getCurrentToken();
+ $nextToken = $this->getNextToken();
+
+ if ($currentToken->literal == "]" && $nextToken->literal == "[") {
+ // skip to first type
+ $this->advance(2);
+
+ $value = $this->parseType();
+
+ // skip last ]
+ $this->advance(1);
+
+ return new MapTypeDeclaration($key, $value);
+ } else {
+ // skip last ]
+ $this->advance(1);
+
+ return new ArrayTypeDeclaration($key);
+ }
+ }
+
+ private function parseExpression($shouldBeMap = false, $shouldBeFunction = false): Node|Token
+ {
+ $currentToken = $this->getCurrentToken();
+ $currentExpression = $currentToken;
+
+ if ($currentToken->literal == "[") {
+ $currentExpression = $this->parseArrayOrMap($shouldBeMap);
+ }
+
+ else if ($currentToken->type == TokenType::Number) {
+ $currentExpression = $this->parseNumber();
+ }
+
+ else if ($currentToken->type == TokenType::String) {
+ $currentExpression = new StringNode($currentToken);
+ $this->advance(1);
+ }
+
+ else if ($currentToken->literal == "(") {
+ if ($shouldBeFunction) {
+ $currentExpression = $this->parseFunctionDefinition();
+ }
+ }
+
+ $nextToken = $this->getCurrentToken();
+
+ if (in_array($nextToken->literal, ["+", "-", "*", "**", "/"])) {
+ $this->advance(1);
+
+ return new OperatorExpression($currentExpression, $nextToken, $this->parseExpression(shouldBeMap: $shouldBeMap));
+ }
+
+ else if (in_array($nextToken->literal, ["<", ">", "<=", ">=", "=="])) {
+ $this->advance(1);
+
+ return new CompareExpression($currentExpression, $nextToken, $this->parseExpression(shouldBeMap: $shouldBeMap));
+ }
+
+ else {
+ return $currentExpression;
+ }
+ }
+
+ private function parseArrayOrMap($shouldBeMap = false): Node
+ {
+ $values = [];
+
+ // skip first [
+ if ($this->getCurrentToken()->literal == "[") {
+ $this->advance(1);
+ }
+
+ $tokenShouldBeComma = false;
+ while ($this->getCurrentToken()->literal != "]") {
+ $currentToken = $this->getCurrentToken();
+
+ // skip ,
+ if ($tokenShouldBeComma) {
+ if ($currentToken->literal == ",") {
+ $this->advance(1);
+ $tokenShouldBeComma = false;
+ continue;
+ }
+
+ // , missing => error
+ else {
+ $error = sprintf(
+ "Expected \",\" at position %d,%d - got %s instead" . PHP_EOL,
+ $currentToken->line,
+ $currentToken->column,
+ $currentToken->literal
+ );
+ $this->errors[] = $error;
+
+ echo $error;
+
+ break;
+ }
+ }
+
+ // nested array or map
+ if ($currentToken->literal == "[") {
+ $values[] = $this->parseArrayOrMap();
+ $tokenShouldBeComma = true;
+ }
+
+ // skip comments
+ else if ($currentToken->type == TokenType::Comment) {
+ $this->advance(1);
+ continue;
+ }
+
+ else {
+ $values[] = $this->parseArrayOrMapItem();
+ $tokenShouldBeComma = true;
+ }
+ }
+
+ // skip last ]
+ $this->advance(1);
+
+ if ((count($values) > 0 and $values[0] instanceof MapItemNode) or $shouldBeMap) {
+ return new MapNode($values);
+ } else {
+ return new ArrayNode($values);
+ }
+ }
+
+ private function parseArrayOrMapItem(): Node|Token
+ {
+ $key = $this->parseExpression();
+
+ // is map item
+ if ($this->getCurrentToken()->literal == "=") {
+ $this->advance(1);
+
+ $value = $this->parseExpression();
+
+ return new MapItemNode($key, $value);
+ }
+
+ // is array item
+ else {
+ return $key;
+ }
+ }
+
+ private function parseNumber(): Node
+ {
+ $currentToken = $this->getCurrentToken();
+
+ $value = $currentToken->value;
+ if (str_contains($value, ".")) {
+ $value = floatval($value);
+ } else {
+ $value = intval($value);
+ }
+
+ // step to next token
+ $this->advance(1);
+
+ return new NumberNode($currentToken, $value);
+ }
+
+ private function parseFunctionDefinition(): Node
+ {
+ // skip first (
+ $this->advance(1);
+
+ $parameters = $this->parseFunctionParameters();
+
+ // skip :
+ $this->advance(1);
+
+ $returnType = $this->parseType();
+
+ $body = $this->parseFunctionBody();
+
+ return new FunctionDefinition(
+ $parameters,
+ $returnType,
+ );
+ }
+
+ private function parseFunctionParameters(): array
+ {
+ while ($this->getCurrentToken() != ")") {
+ # TODO
+ }
+
+ $identifier = $this->getCurrentToken();
+
+ // skip :
+ $this->advance(1);
+
+ $type = $this->parseType();
+
+ return new FunctionParameter(
+
+ );
+ }
+
+ private function parseFunctionBody(): array
+ {
+ # TODO
+ }
+
+ private function parseFunctionCall(){}
+}
+
+class Node
+{}
+
+class Tree extends Node
+{
+ /**
+ * @param Node[] $nodes
+ */
+ public function __construct(
+ public array $nodes,
+ ) {}
+}
+
+class ConstVariableDeclaration extends Node
+{
+ public function __construct(
+ public Token $identifier,
+ public Node|Token $type,
+ public Node|Token $expression,
+ ) {}
+}
+
+class TypeDeclaration extends Node
+{
+ public function __construct(
+ public Token $left,
+ public ?Token $operator = null,
+ public Token|TypeDeclaration|null $right = null,
+ ) {}
+}
+
+class ArrayTypeDeclaration extends Node
+{
+ public function __construct(
+ public TypeDeclaration|ArrayTypeDeclaration $value,
+ ) {}}
+
+class MapTypeDeclaration extends Node
+{
+ public function __construct(
+ public TypeDeclaration $key,
+ public TypeDeclaration|ArrayTypeDeclaration $value,
+ ) {}
+}
+
+class ArrayNode extends Node
+{
+ /**
+ * @param Array<Token|Node> $values
+ */
+ public function __construct(
+ public array $values,
+ ) {}
+}
+
+class MapNode extends Node
+{
+ /**
+ * @param MapItemNode[] $values
+ */
+ public function __construct(
+ public array $values,
+ ) {}
+}
+class MapItemNode extends Node
+{
+ public function __construct(
+ public Token|Node $key,
+ public Token|Node $value,
+ ) {}
+}
+
+class OperatorExpression extends Node
+{
+ public function __construct(
+ public Token|Node $left,
+ public Token $operator,
+ public Token|Node $right,
+ ) {}
+}
+
+class CompareExpression extends Node
+{
+ public function __construct(
+ public Token|Node $left,
+ public Token $operator,
+ public Token|Node $right,
+ ) {}
+}
+
+class NumberNode extends Node
+{
+ public function __construct(
+ public Token $token,
+ public int|float $value,
+ ) {}
+}
+
+class StringNode extends Node
+{
+ public function __construct(
+ public Token $token,
+ ) {}
+}
+
+class FunctionDefinition extends Node
+{
+ public function __construct(
+ /**
+ * @param FunctionParameter[] $parameters
+ */
+ public array $parameters,
+ public TypeDefinition $returnType,
+ /**
+ * @param Node[] $body
+ */
+ public array $body,
+ ) {}
+}
+
+class FunctionParameter extends Node
+{
+ public function __construct(
+ public Token $identifier,
+ public TypeDefinition $type,
+ ) {}
+}