Class Parser
Recursive descent parser for QScript.
class Parser;
The parser converts a stream of tokens from the lexer into an Abstract Syntax Tree (AST) following the QScript grammar. It provides detailed error messages with line and column information for syntax errors.
Constructors
| Name | Description |
|---|---|
this
(tokens)
|
Creates a new parser for the given tokens. |
Fields
| Name | Type | Description |
|---|---|---|
_current
|
ulong | |
_errorMessage
|
string | |
_tokens
|
Token[] |
Properties
| Name | Type | Description |
|---|---|---|
errorMessage[get]
|
string | Gets the error message if parsing failed. |
Methods
| Name | Description |
|---|---|
parse
()
|
Parses the token stream into an AST. |
_advance
()
|
|
_check
(type)
|
|
_consume
(type, errorMsg)
|
|
_error
(message)
|
|
_isAtEnd
()
|
|
_match
(types)
|
|
_parseBlock
()
|
|
_parseComparison
()
|
|
_parseEquality
()
|
|
_parseExpression
()
|
|
_parseFactor
()
|
|
_parseForStmt
()
|
|
_parseFunctionCall
(identifier)
|
|
_parseFunctionDef
()
|
|
_parseIfStmt
()
|
|
_parseLogicAnd
()
|
|
_parseLogicOr
()
|
|
_parsePrimary
()
|
|
_parseReturnStmt
()
|
|
_parseStatement
()
|
|
_parseTerm
()
|
|
_parseUnary
()
|
|
_parseVarDecl
()
|
|
_parseWhileStmt
()
|
|
_peek
()
|
|
_previous
()
|
Example
Test parsing of simple expressions.
auto lexer = new Lexer("42;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
assert(program .statements .length == 1, "Should have one statement");
Example
Test parsing of binary expressions.
auto lexer = new Lexer("1 + 2;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
assert(program .statements .length == 1, "Should have one statement");
auto exprStmt = cast(ExpressionStatement) program .statements[0];
assert(exprStmt !is null, "Should be expression statement");
auto binExpr = cast(BinaryExpression) exprStmt .expression;
assert(binExpr !is null, "Should be binary expression");
assert(binExpr .operator == TokenType .PLUS, "Should be addition");
Example
Test parsing of variable declarations.
auto lexer = new Lexer("var x = 10;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
assert(program .statements .length == 1, "Should have one statement");
auto varDecl = cast(VarDeclStatement) program .statements[0];
assert(varDecl !is null, "Should be variable declaration");
assert(varDecl .name == "x", "Variable name should be 'x'");
assert(varDecl .initializer !is null, "Should have initializer");
Example
Test parsing of variable declaration without initializer.
auto lexer = new Lexer("var y;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto varDecl = cast(VarDeclStatement) program .statements[0];
assert(varDecl !is null, "Should be variable declaration");
assert(varDecl .name == "y", "Variable name should be 'y'");
assert(varDecl .initializer is null, "Should not have initializer");
Example
Test parsing of assignments.
auto lexer = new Lexer("x = 5;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto assignment = cast(AssignmentStatement) program .statements[0];
assert(assignment !is null, "Should be assignment statement");
assert(assignment .name == "x", "Variable name should be 'x'");
Example
Test parsing of function definitions.
auto lexer = new Lexer("func add(a, b) { return a + b; }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto funcDef = cast(FunctionDefStatement) program .statements[0];
assert(funcDef !is null, "Should be function definition");
assert(funcDef .name == "add", "Function name should be 'add'");
assert(funcDef .parameters .length == 2, "Should have 2 parameters");
assert(funcDef .parameters[0] == "a", "First parameter should be 'a'");
assert(funcDef .parameters[1] == "b", "Second parameter should be 'b'");
Example
Test parsing of function calls.
auto lexer = new Lexer("print(x);");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto exprStmt = cast(ExpressionStatement) program .statements[0];
assert(exprStmt !is null, "Should be expression statement");
auto funcCall = cast(FunctionCallExpression) exprStmt .expression;
assert(funcCall !is null, "Should be function call");
assert(funcCall .name == "print", "Function name should be 'print'");
assert(funcCall .arguments .length == 1, "Should have 1 argument");
Example
Test parsing of if statements.
auto lexer = new Lexer("if (x > 0) { var y = 1; }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto ifStmt = cast(IfStatement) program .statements[0];
assert(ifStmt !is null, "Should be if statement");
assert(ifStmt .condition !is null, "Should have condition");
assert(ifStmt .thenBranch !is null, "Should have then branch");
assert(ifStmt .elseBranch is null, "Should not have else branch");
Example
Test parsing of if-else statements.
auto lexer = new Lexer("if (x > 0) { var y = 1; } else { var y = 0; }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto ifStmt = cast(IfStatement) program .statements[0];
assert(ifStmt !is null, "Should be if statement");
assert(ifStmt .elseBranch !is null, "Should have else branch");
Example
Test parsing of while loops.
auto lexer = new Lexer("while (x < 10) { x = x + 1; }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto whileStmt = cast(WhileStatement) program .statements[0];
assert(whileStmt !is null, "Should be while statement");
assert(whileStmt .condition !is null, "Should have condition");
assert(whileStmt .body !is null, "Should have body");
Example
Test parsing of for loops.
auto lexer = new Lexer("for (var i = 0; i < 10; i = i + 1) { print(i); }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto forStmt = cast(ForStatement) program .statements[0];
assert(forStmt !is null, "Should be for statement");
assert(forStmt .initializer !is null, "Should have initializer");
assert(forStmt .condition !is null, "Should have condition");
assert(forStmt .increment !is null, "Should have increment");
assert(forStmt .body !is null, "Should have body");
Example
Test parsing of return statements.
auto lexer = new Lexer("func test() { return 42; }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto funcDef = cast(FunctionDefStatement) program .statements[0];
assert(funcDef !is null, "Should be function definition");
auto returnStmt = cast(ReturnStatement) funcDef .body .statements[0];
assert(returnStmt !is null, "Should be return statement");
assert(returnStmt .value !is null, "Should have return value");
Example
Test parsing of empty return.
auto lexer = new Lexer("func test() { return; }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto funcDef = cast(FunctionDefStatement) program .statements[0];
auto returnStmt = cast(ReturnStatement) funcDef .body .statements[0];
assert(returnStmt !is null, "Should be return statement");
assert(returnStmt .value is null, "Should not have return value");
Example
Test parsing of operator precedence.
auto lexer = new Lexer("1 + 2 * 3;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto exprStmt = cast(ExpressionStatement) program .statements[0];
auto binExpr = cast(BinaryExpression) exprStmt .expression;
assert(binExpr !is null, "Should be binary expression");
assert(binExpr .operator == TokenType .PLUS, "Top operator should be +");
auto rightExpr = cast(BinaryExpression) binExpr .right;
assert(rightExpr !is null, "Right side should be binary expression");
assert(rightExpr .operator == TokenType .STAR, "Should parse * with higher precedence");
Example
Test parsing of unary expressions.
auto lexer = new Lexer("-x;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto exprStmt = cast(ExpressionStatement) program .statements[0];
auto unaryExpr = cast(UnaryExpression) exprStmt .expression;
assert(unaryExpr !is null, "Should be unary expression");
assert(unaryExpr .operator == TokenType .MINUS, "Should be negation");
Example
Test parsing of grouped expressions.
auto lexer = new Lexer("(1 + 2) * 3;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto exprStmt = cast(ExpressionStatement) program .statements[0];
auto binExpr = cast(BinaryExpression) exprStmt .expression;
assert(binExpr !is null, "Should be binary expression");
assert(binExpr .operator == TokenType .STAR, "Top operator should be *");
Example
Test error on missing semicolon.
auto lexer = new Lexer("var x = 5");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program is null, "Should fail to parse");
assert(parser .errorMessage .length > 0, "Should have error message");
Example
Test error on unclosed parenthesis.
auto lexer = new Lexer("(1 + 2;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program is null, "Should fail to parse");
assert(parser .errorMessage .length > 0, "Should have error message");
Example
Test error on unexpected token.
auto lexer = new Lexer("var = 5;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program is null, "Should fail to parse");
Example
Test parsing multiple statements.
auto lexer = new Lexer("var x = 1; var y = 2; x = x + y;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
assert(program .statements .length == 3, "Should have 3 statements");
Example
Test parsing of nested blocks.
auto lexer = new Lexer("{ var x = 1; { var y = 2; } }");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto block = cast(BlockStatement) program .statements[0];
assert(block !is null, "Should be block statement");
assert(block .statements .length == 2, "Should have 2 statements");
Example
Test parsing of empty program.
auto lexer = new Lexer("");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
assert(program .statements .length == 0, "Should have no statements");
Example
Test parsing of logical operators.
auto lexer = new Lexer("true && false || true;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
auto exprStmt = cast(ExpressionStatement) program .statements[0];
auto binExpr = cast(BinaryExpression) exprStmt .expression;
assert(binExpr !is null, "Should be binary expression");
assert(binExpr .operator == TokenType .OR_OR, "Top operator should be ||");
Example
Test parsing of comparison chains.
auto lexer = new Lexer("x < y == z > w;");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
Example
Test parsing empty statement.
auto lexer = new Lexer(";");
assert(lexer .tokenize(), "Should tokenize successfully");
auto parser = new Parser(lexer .tokens);
auto program = parser .parse();
assert(program !is null, "Should parse successfully");
assert(program .statements .length == 1, "Should have one statement");