Bug fixing; Finished Parser in Chapter 6; Starting Interpreter

This commit is contained in:
Garrett Dickinson 2022-07-04 20:33:01 -05:00
parent da88ff4071
commit 5cd5dda34d
6 changed files with 219 additions and 29 deletions

View File

@ -0,0 +1,54 @@
package cobalt.lang;
// Creates an unambiguous, if ugly, string representation of AST nodes.
class AstPrinter implements Expr.Visitor<String> {
String print(Expr expr) {
return expr.accept(this);
}
@Override
public String visitBinaryExpr(Expr.Binary expr) {
return parenthesize(expr.operator.lexeme, expr.left, expr.right);
}
@Override
public String visitGroupingExpr(Expr.Grouping expr) {
return parenthesize("group", expr.expression);
}
@Override
public String visitLiteralExpr(Expr.Literal expr) {
if (expr.value == null) return "nil";
return expr.value.toString();
}
@Override
public String visitUnaryExpr(Expr.Unary expr) {
return parenthesize(expr.operator.lexeme, expr.right);
}
private String parenthesize(String name, Expr... exprs) {
StringBuilder builder = new StringBuilder();
builder.append("(").append(name);
for (Expr expr : exprs) {
builder.append(" ");
builder.append(expr.accept(this));
}
builder.append(")");
return builder.toString();
}
public static void main(String[] args) {
Expr expression = new Expr.Binary(
new Expr.Unary(
new Token(TokenType.MINUS, "-", null, 1),
new Expr.Literal(123)),
new Token(TokenType.STAR, "*", null, 1),
new Expr.Grouping(
new Expr.Literal(45.67)));
System.out.println(new AstPrinter().print(expression));
}
}

View File

@ -1,4 +1,4 @@
package cobalt; package cobalt.lang;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -23,12 +23,14 @@ public class Cobalt {
} }
} }
private static void runFile(String path) throws IOException { private static void runFile(String path) throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get(path)); byte[] bytes = Files.readAllBytes(Paths.get(path));
run(new String(bytes, Charset.defaultCharset())); run(new String(bytes, Charset.defaultCharset()));
if (hadError) System.exit(65); if (hadError) System.exit(65);
} }
private static void runPrompt() throws IOException { private static void runPrompt() throws IOException {
InputStreamReader input = new InputStreamReader(System.in); InputStreamReader input = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(input); BufferedReader reader = new BufferedReader(input);
@ -42,27 +44,37 @@ public class Cobalt {
} }
} }
private static void run(String source) {
// Wont compile since we don't use the standard Java Scanner,
// gets implemented further in the textbook
Scanner scanner = new Scanner(source);
// Same situation for Token type
List<Token> tokens = scanner.scanTokens();
for (Token token : tokens) { private static void run(String source) {
System.out.println(token); Scanner scanner = new Scanner(source);
} List<Token> tokens = scanner.scanTokens();
Parser parser = new Parser(tokens);
Expr expression = parser.parse();
if (hadError) return;
System.out.println(new AstPrinter().print(expression));
} }
static void error(int line, String message) { static void error(int line, String message) {
report(line, "", message); report(line, "", message);
} }
private static void report(int line, String where, String message) { private static void report(int line, String where, String message) {
System.err.println( System.err.println(
"[line " + line + "] Error" + where + ": " + message "[line " + line + "] Error" + where + ": " + message
); );
hadError = true; hadError = true;
}
static void error(Token token, String message) {
if (token.type == TokenType.EOF) {
report(token.line, " at end", message);
} else {
report(token.line, " at '" + token.lexeme + "'", message);
}
} }
} }

View File

@ -1,6 +1,16 @@
package cobalt.lang; package cobalt.lang;
import java.util.List;
abstract class Expr { abstract class Expr {
interface Visitor<R> {
R visitBinaryExpr(Binary expr);
R visitGroupingExpr(Grouping expr);
R visitLiteralExpr(Literal expr);
R visitUnaryExpr(Unary expr);
}
static class Binary extends Expr { static class Binary extends Expr {
Binary(Expr left, Token operator, Expr right) { Binary(Expr left, Token operator, Expr right) {
this.left = left; this.left = left;
@ -8,34 +18,55 @@ abstract class Expr {
this.right = right; this.right = right;
} }
<R> R accept(Visitor<R> visitor) {
return visitor.visitBinaryExpr(this);
}
final Expr left; final Expr left;
final Token operator; final Token operator;
final Expr right; final Expr right;
} }
static class Unary extends Expr {
Unary(Token operator, Expr right) {
this.operator = operator;
this.right = right;
}
final Token operator;
final Expr right;
}
static class Grouping extends Expr { static class Grouping extends Expr {
Grouping(Expr expression) { Grouping(Expr expression) {
this.expression = expression; this.expression = expression;
} }
<R> R accept(Visitor<R> visitor) {
return visitor.visitGroupingExpr(this);
}
final Expr expression; final Expr expression;
} }
static class Literal extends Expr { static class Literal extends Expr {
Literal(Object value) { Literal(Object value) {
this.value = value; this.value = value;
} }
<R> R accept(Visitor<R> visitor) {
return visitor.visitLiteralExpr(this);
}
final Object value; final Object value;
} }
static class Unary extends Expr {
Unary(Token operator, Expr right) {
this.operator = operator;
this.right = right;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitUnaryExpr(this);
}
final Token operator;
final Expr right;
}
abstract <R> R accept(Visitor<R> visitor);
} }

View File

@ -0,0 +1,34 @@
//
// Continue at Section 7.2.3
// Page 100
//
package cobalt.lang;
class Interpreter implements Expr.Visitor<Object> {
@Override
public Object visitLiteralExpr(Expr.Literal expr) {
return expr.value;
}
@Override
public Object visitUnaryExpr(Expr.Unary expr) {
Object right = evaluate(expr.right);
switch (expr.operator.type) {
case MINUS:
return -(double)right;
}
return null;
}
@Override
public Object visitGroupingExpr(Expr.Grouping expr) {
return evaluate(expr.expression);
}
private Object evaluate(Expr expr) {
return expr.accept(this);
}
}

View File

@ -1,16 +1,11 @@
///////////
//
// Continue at page 89, Section 6.3
//
///////////
package cobalt.lang; package cobalt.lang;
import java.util.List; import java.util.List;
import static cobalt.lang.TokenType.*; import static cobalt.lang.TokenType.*;
class Parser { class Parser {
private static class ParseError extends RuntimeException {}
private final List<Token> tokens; private final List<Token> tokens;
private int current = 0; private int current = 0;
@ -18,6 +13,16 @@ class Parser {
this.tokens = tokens; this.tokens = tokens;
} }
Expr parse() {
try {
return expression();
} catch (ParseError error) {
return null;
}
}
private Expr expression() { private Expr expression() {
return equality(); return equality();
} }
@ -38,6 +43,7 @@ class Parser {
return expr; return expr;
} }
// Parser definition for handling comparisons // Parser definition for handling comparisons
// //
// comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ; // comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
@ -80,7 +86,12 @@ class Parser {
return expr; return expr;
} }
// Parser definition for handling unary statements
//
// unary -> ( "!" | "-" ) unary
// | primary ;
//
private Expr unary() { private Expr unary() {
if (match(BANG, MINUS)) { if (match(BANG, MINUS)) {
Token operator = previous(); Token operator = previous();
@ -92,6 +103,11 @@ class Parser {
} }
// Parser definition for handling primary statements
//
// primary -> NUMBER | STRING | "true" | "false" | "nil"
// | "(" expression ")" ;
//
private Expr primary() { private Expr primary() {
if (match(FALSE)) return new Expr.Literal(false); if (match(FALSE)) return new Expr.Literal(false);
if (match(TRUE)) return new Expr.Literal(true); if (match(TRUE)) return new Expr.Literal(true);
@ -106,6 +122,8 @@ class Parser {
consume(RIGHT_PAREN, "Expect ')' after expression."); consume(RIGHT_PAREN, "Expect ')' after expression.");
return new Expr.Grouping(expr); return new Expr.Grouping(expr);
} }
throw error(peek(), "Expected expression.");
} }
@ -123,6 +141,14 @@ class Parser {
} }
// Consume function
private Token consume(TokenType type, String message) {
if (check(type)) return advance();
throw error(peek(), message);
}
// Returns true if the current token is of the given type, false if otherwise // Returns true if the current token is of the given type, false if otherwise
private boolean check(TokenType type) { private boolean check(TokenType type) {
if (isAtEnd()) return false; if (isAtEnd()) return false;
@ -153,7 +179,37 @@ class Parser {
private Token previous() { private Token previous() {
return tokens.get(current - 1); return tokens.get(current - 1);
} }
// Throw a new parse error based on the incorrect token
private ParseError error(Token token, String message) {
Cobalt.error(token, message);
return new ParseError();
}
// Once finding a parse error, discard the rest of the tokens in
// a statement in order to prevent cascading errors.
private void synchronize() {
advance();
while (!isAtEnd()) {
if (previous().type == SEMICOLON) return;
switch (peek().type) {
case CLASS:
case FOR:
case FUNC:
case IF:
case PRINT:
case RETURN:
case VAR:
case WHILE:
return;
}
advance();
}
}
} }

View File

@ -18,7 +18,8 @@ class Scanner {
static { static {
keywords = new HashMap<>(); keywords = new HashMap<>();
keywords.put("and", AND); keywords.put("and", AND);
keywords.put("class", ELSE); keywords.put("class", CLASS);
keywords.put("else", ELSE);
keywords.put("false", FALSE); keywords.put("false", FALSE);
keywords.put("for", FOR); keywords.put("for", FOR);
keywords.put("func", FUNC); keywords.put("func", FUNC);
@ -142,6 +143,8 @@ class Scanner {
while (isDigit(peek())) advance(); while (isDigit(peek())) advance();
} }
addToken(NUMBER, Double.parseDouble(source.substring(start, current)));
} }