359 lines
9.8 KiB
D
359 lines
9.8 KiB
D
module jlox.parser;
|
|
|
|
import std.stdio;
|
|
|
|
import jlox.token;
|
|
import jlox.tokentype;
|
|
import common.util;
|
|
import jlox.expr;
|
|
import jlox.main;
|
|
import jlox.stmt;
|
|
import jlox.loxfunction;
|
|
|
|
class Parser{
|
|
private Token[] tokens;
|
|
private int current = 0;
|
|
|
|
mixin defaultCtor;
|
|
Stmt[] parse(){
|
|
Stmt[] statements;
|
|
while(!isAtEnd)
|
|
statements ~= declaration();
|
|
return statements;
|
|
}
|
|
|
|
private bool isAtEnd() => peek().type == TokenType.EOF;
|
|
private Token peek() => tokens[current];
|
|
private Token previous() => tokens[current-1];
|
|
private bool check(TokenType type){
|
|
if(isAtEnd)
|
|
return false;
|
|
return peek().type == type;
|
|
}
|
|
private Token advance(){
|
|
if(!isAtEnd)
|
|
current++;
|
|
return previous();
|
|
}
|
|
private bool match(TokenType[] types...){
|
|
foreach(TokenType type; types){
|
|
if(check(type)){
|
|
advance();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
private Token consume(TokenType type, string message){
|
|
if(check(type))
|
|
return advance();
|
|
throw error(peek(), message);
|
|
}
|
|
private static class ParseError : Exception {
|
|
this(string s){
|
|
super(s);
|
|
}
|
|
}
|
|
private ParseError error(Token token, string message){
|
|
Lox.error(token, message);
|
|
return new ParseError("hello");
|
|
}
|
|
private void synchronise(){
|
|
advance();
|
|
with(TokenType) while(!isAtEnd){
|
|
if(previous().type == SEMICOLON)
|
|
return;
|
|
switch(peek().type){
|
|
case CLASS:
|
|
case FUN:
|
|
case VAR:
|
|
case FOR:
|
|
case IF:
|
|
case WHILE:
|
|
case PRINT:
|
|
case RETURN:
|
|
return;
|
|
default:
|
|
/* assert(0); */
|
|
}
|
|
advance();
|
|
}
|
|
}
|
|
|
|
private Stmt printStatement(){
|
|
version(LoxPrintMultiple){
|
|
Expr[] values;
|
|
if(!check(TokenType.SEMICOLON)) do {
|
|
values ~= expression();
|
|
} while(match(TokenType.COMMA));
|
|
consume(TokenType.SEMICOLON, "Expect ';' after values.");
|
|
return new Stmt.Print(values);
|
|
} else {
|
|
Expr value = expression();
|
|
consume(TokenType.SEMICOLON, "Expect ';' after value.");
|
|
return new Stmt.Print(value);
|
|
}
|
|
}
|
|
private Stmt returnStatement(){
|
|
Token keyword = previous();
|
|
Expr value;
|
|
if(!check(TokenType.SEMICOLON))
|
|
value = expression();
|
|
consume(TokenType.SEMICOLON, "Expect ';' after return value.");
|
|
return new Stmt.Return(keyword, value);
|
|
}
|
|
private Stmt expressionStatement(){
|
|
Expr expr = expression();
|
|
consume(TokenType.SEMICOLON, "Expect ';' after expression.");
|
|
return new Stmt.Expression(expr);
|
|
}
|
|
private Stmt.Function fun(string kind){
|
|
Token name = consume(TokenType.IDENTIFIER, "Expect " ~ kind ~ " name.");
|
|
consume(TokenType.LEFT_PAREN, "Expect '(' after " ~ kind ~ " name.");
|
|
Token[] parameters;
|
|
if(!check(TokenType.RIGHT_PAREN)){
|
|
do{
|
|
if(parameters.length >= 255)
|
|
error(peek(), "Can't have more than 255 parameters.");
|
|
parameters ~= consume(TokenType.IDENTIFIER, "Expect parameter name.");
|
|
} while(match(TokenType.COMMA));
|
|
}
|
|
consume(TokenType.RIGHT_PAREN, "Expect ')' after parameters.");
|
|
consume(TokenType.LEFT_BRACE, "Expect '{' before " ~ kind ~ " body.");
|
|
Stmt[] body = block();
|
|
return new Stmt.Function(name, parameters, body);
|
|
}
|
|
private Stmt ifStatement(){
|
|
consume(TokenType.LEFT_PAREN, "Expect '(' after 'if'.");
|
|
Expr condition = expression();
|
|
consume(TokenType.RIGHT_PAREN, "Expect ')' after if condition.");
|
|
Stmt thenBranch = statement();
|
|
Stmt elseBranch = null;
|
|
if(match(TokenType.ELSE))
|
|
elseBranch = statement();
|
|
return new Stmt.If(condition, thenBranch, elseBranch);
|
|
}
|
|
private Stmt forStatement(){
|
|
consume(TokenType.LEFT_PAREN, "Expect '(' after 'for'.");
|
|
|
|
Stmt initialiser;
|
|
if(match(TokenType.SEMICOLON))
|
|
initialiser = null;
|
|
else if(match(TokenType.VAR))
|
|
initialiser = varDeclaration();
|
|
else
|
|
initialiser = expressionStatement();
|
|
|
|
Expr condition = check(TokenType.SEMICOLON) ? new Expr.Literal(new LoxBool(true)) : expression();
|
|
consume(TokenType.SEMICOLON, "Expect ';' after loop condition.");
|
|
|
|
Expr increment;
|
|
if(!check(TokenType.RIGHT_PAREN))
|
|
increment = expression();
|
|
consume(TokenType.RIGHT_PAREN, "Expect ')' after for clauses.");
|
|
|
|
Stmt body = statement();
|
|
if(increment !is null)
|
|
body = new Stmt.Block([ body, new Stmt.Expression(increment) ]);
|
|
body = new Stmt.While(condition, body);
|
|
if(initialiser !is null)
|
|
body = new Stmt.Block([ initialiser, body ]);
|
|
return body;
|
|
}
|
|
private Stmt whileStatement(){
|
|
consume(TokenType.LEFT_PAREN, "Expect '(' after 'while'.");
|
|
Expr condition = expression();
|
|
consume(TokenType.RIGHT_PAREN, "Expect ')' after condition.");
|
|
Stmt body = statement();
|
|
return new Stmt.While(condition, body);
|
|
}
|
|
private Stmt statement(){
|
|
if(match(TokenType.IF))
|
|
return ifStatement();
|
|
if(match(TokenType.FOR))
|
|
return forStatement();
|
|
if(match(TokenType.WHILE))
|
|
return whileStatement();
|
|
if(match(TokenType.PRINT))
|
|
return printStatement();
|
|
if(match(TokenType.RETURN))
|
|
return returnStatement();
|
|
if(match(TokenType.LEFT_BRACE))
|
|
return new Stmt.Block(block());
|
|
return expressionStatement();
|
|
}
|
|
private Stmt[] block(){
|
|
Stmt[] statements;
|
|
while(!check(TokenType.RIGHT_BRACE) && !isAtEnd)
|
|
statements ~= declaration();
|
|
consume(TokenType.RIGHT_BRACE, "Expect '}' after block.");
|
|
return statements;
|
|
}
|
|
private Expr expression(){
|
|
return assignment();
|
|
}
|
|
private Expr assignment(){
|
|
Expr expr = or();
|
|
if(match(TokenType.EQUAL)){
|
|
Token equals = previous();
|
|
Expr value = assignment();
|
|
if(auto v = cast(Expr.Variable)expr){
|
|
return new Expr.Assign(v.name, value);
|
|
} else if(auto get = cast(Expr.Get)expr){
|
|
return new Expr.Set(get.object, get.name, value);
|
|
}
|
|
error(equals, "Invalid assignment target.");
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr or(){
|
|
Expr expr = and();
|
|
while(match(TokenType.OR)){
|
|
Token operator = previous();
|
|
Expr right = and();
|
|
expr = new Expr.Logical(expr, operator, right);
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr and(){
|
|
Expr expr = equality();
|
|
while(match(TokenType.AND)){
|
|
Token operator = previous();
|
|
Expr right = equality();
|
|
expr = new Expr.Logical(expr, operator, right);
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr equality(){
|
|
Expr expr = comparison();
|
|
while(match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL)){
|
|
Token operator = previous();
|
|
Expr right = comparison();
|
|
expr = new Expr.Binary(expr, operator, right);
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr comparison(){
|
|
Expr expr = term();
|
|
while(match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)){
|
|
Token operator = previous();
|
|
Expr right = term();
|
|
expr = new Expr.Binary(expr, operator, right);
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr term(){
|
|
Expr expr = factor();
|
|
while(match(TokenType.MINUS, TokenType.PLUS)){
|
|
Token operator = previous();
|
|
Expr right = factor();
|
|
expr = new Expr.Binary(expr, operator, right);
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr factor(){
|
|
Expr expr = unary();
|
|
while(match(TokenType.SLASH, TokenType.STAR)){
|
|
Token operator = previous();
|
|
Expr right = unary();
|
|
expr = new Expr.Binary(expr, operator, right);
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr unary(){
|
|
if(match(TokenType.BANG, TokenType.MINUS)){
|
|
Token operator = previous();
|
|
Expr right = unary();
|
|
return new Expr.Unary(operator, right);
|
|
}
|
|
return call();
|
|
}
|
|
private Expr call(){
|
|
Expr finishCall(Expr callee){
|
|
Expr[] arguments;
|
|
if(!check(TokenType.RIGHT_PAREN)){
|
|
do {
|
|
if(arguments.length >= 255)
|
|
error(peek(), "Can't have more than 255 arguments.");
|
|
arguments ~= expression();
|
|
} while(match(TokenType.COMMA));
|
|
}
|
|
Token paren = consume(TokenType.RIGHT_PAREN, "Expect ')' after arguments.");
|
|
return new Expr.Call(callee, paren, arguments);
|
|
}
|
|
Expr expr = primary();
|
|
while(true){
|
|
if(match(TokenType.LEFT_PAREN)){
|
|
expr = finishCall(expr);
|
|
} else if(match(TokenType.DOT)){
|
|
Token name = consume(TokenType.IDENTIFIER, "Expect property name after '.'.");
|
|
expr = new Expr.Get(expr, name);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
private Expr primary(){
|
|
if(match(TokenType.IDENTIFIER))
|
|
return new Expr.Variable(previous());
|
|
if(match(TokenType.FALSE))
|
|
return new Expr.Literal(new LoxBool(false));
|
|
if(match(TokenType.TRUE))
|
|
return new Expr.Literal(new LoxBool(true));
|
|
if(match(TokenType.NIL))
|
|
return new Expr.Literal(new LoxNil());
|
|
if(match(TokenType.NUMBER, TokenType.STRING))
|
|
return new Expr.Literal(previous().literal);
|
|
if(match(TokenType.THIS))
|
|
return new Expr.This(previous());
|
|
if(match(TokenType.LEFT_PAREN)){
|
|
Expr expr = expression();
|
|
consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
|
|
return new Expr.Grouping(expr);
|
|
}
|
|
if(match(TokenType.SUPER)){
|
|
Token keyword = previous();
|
|
consume(TokenType.DOT, "Expect '.' after 'super'.");
|
|
Token method = consume(TokenType.IDENTIFIER, "Expect superclass method name.");
|
|
return new Expr.Super(keyword, method);
|
|
}
|
|
throw error(peek(), "Expect expression.");
|
|
}
|
|
private Stmt varDeclaration(){
|
|
Token name = consume(TokenType.IDENTIFIER, "Expect variable name.");
|
|
Expr initialiser = null;
|
|
if(match(TokenType.EQUAL))
|
|
initialiser = expression();
|
|
consume(TokenType.SEMICOLON, "Expect ';' after variable declaration.");
|
|
return new Stmt.Var(name, initialiser);
|
|
}
|
|
private Stmt classDeclaration() {
|
|
Token name = consume(TokenType.IDENTIFIER, "Expect class name.");
|
|
Expr.Variable superclass;
|
|
if(match(TokenType.LESS)){
|
|
consume(TokenType.IDENTIFIER, "Expect superlcass name.");
|
|
superclass = new Expr.Variable(previous);
|
|
}
|
|
consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
|
|
Stmt.Function[] methods;
|
|
while(!check(TokenType.RIGHT_BRACE) && !isAtEnd)
|
|
methods ~= fun("method");
|
|
consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
|
|
return new Stmt.Class(name, superclass, methods);
|
|
}
|
|
private Stmt declaration(){
|
|
try {
|
|
if(match(TokenType.VAR))
|
|
return varDeclaration();
|
|
if(match(TokenType.FUN))
|
|
return fun("function");
|
|
if(match(TokenType.CLASS))
|
|
return classDeclaration();
|
|
return statement();
|
|
} catch(ParseError error){
|
|
synchronise();
|
|
return null;
|
|
}
|
|
}
|
|
}
|