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 $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, ) {} }