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

NameDescription
this (tokens) Creates a new parser for the given tokens.

Fields

NameTypeDescription
_current ulong
_errorMessage string
_tokens Token[]

Properties

NameTypeDescription
errorMessage[get] stringGets the error message if parsing failed.

Methods

NameDescription
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");