Add break statement

This commit is contained in:
Tom Willemse 2021-01-18 17:42:02 -08:00
parent 613c2388be
commit 80394e1842
8 changed files with 136 additions and 80 deletions

View file

@ -64,6 +64,11 @@ class AstPrinter implements Expr.Visitor<String>, Stmt.Visitor<String> {
return builder.toString(); return builder.toString();
} }
@Override
public String visitBreakStmt(Stmt.Break statement) {
return "break";
}
@Override @Override
public String visitPrintStmt(Stmt.Print statement) { public String visitPrintStmt(Stmt.Print statement) {
return parenthesize("print", statement.expression); return parenthesize("print", statement.expression);

View file

@ -0,0 +1,5 @@
package com.craftinginterpreters.lox;
class Break extends RuntimeException {
}

View file

@ -20,4 +20,5 @@ add_jar(Lox
Interpreter.java Interpreter.java
RuntimeError.java RuntimeError.java
Environment.java Environment.java
Break.java
ENTRY_POINT com/craftinginterpreters/lox/Lox) ENTRY_POINT com/craftinginterpreters/lox/Lox)

View file

@ -161,13 +161,22 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
@Override @Override
public Void visitWhileStmt(Stmt.While stmt) { public Void visitWhileStmt(Stmt.While stmt) {
while (isTruthy(evaluate(stmt.condition))) { try {
execute(stmt.body); while (isTruthy(evaluate(stmt.condition))) {
execute(stmt.body);
}
} catch (Break ex) {
// We continue on.
} }
return null; return null;
} }
@Override
public Void visitBreakStmt(Stmt.Break stmt) {
throw new Break();
}
@Override @Override
public Object visitAssignExpr(Expr.Assign expr) { public Object visitAssignExpr(Expr.Assign expr) {
Object value = evaluate(expr.value); Object value = evaluate(expr.value);

View file

@ -44,6 +44,8 @@ class Parser {
} }
private Stmt statement() { private Stmt statement() {
if (match(BREAK))
return breakStatement();
if (match(FOR)) if (match(FOR))
return forStatement(); return forStatement();
if (match(IF)) if (match(IF))
@ -84,13 +86,11 @@ class Parser {
Stmt body = statement(); Stmt body = statement();
if (increment != null) { if (increment != null) {
body = new Stmt.Block( body = new Stmt.Block(Arrays.asList(body, new Stmt.Expression(increment)));
Arrays.asList(
body,
new Stmt.Expression(increment)));
} }
if (condition == null) condition = new Expr.Literal(true); if (condition == null)
condition = new Expr.Literal(true);
body = new Stmt.While(condition, body); body = new Stmt.While(condition, body);
if (initializer != null) { if (initializer != null) {
@ -100,6 +100,11 @@ class Parser {
return body; return body;
} }
private Stmt breakStatement() {
consume(SEMICOLON, "Expect ';' after break statement.");
return new Stmt.Break();
}
private Stmt ifStatement() { private Stmt ifStatement() {
consume(LEFT_PAREN, "Expect '(' after 'if'."); consume(LEFT_PAREN, "Expect '(' after 'if'.");
Expr condition = expression(); Expr condition = expression();

View file

@ -33,6 +33,7 @@ class Scanner {
keywords.put("true", TRUE); keywords.put("true", TRUE);
keywords.put("var", VAR); keywords.put("var", VAR);
keywords.put("while", WHILE); keywords.put("while", WHILE);
keywords.put("break", BREAK);
} }
Scanner(String source) { Scanner(String source) {
@ -54,91 +55,118 @@ class Scanner {
char c = advance(); char c = advance();
switch (c) { switch (c) {
case '(': addToken(LEFT_PAREN); break; case '(':
case ')': addToken(RIGHT_PAREN); break; addToken(LEFT_PAREN);
case '{': addToken(LEFT_BRACE); break; break;
case '}': addToken(RIGHT_BRACE); break; case ')':
case ',': addToken(COMMA); break; addToken(RIGHT_PAREN);
case '.': addToken(DOT); break; break;
case '-': addToken(MINUS); break; case '{':
case '+': addToken(PLUS); break; addToken(LEFT_BRACE);
case ';': addToken(SEMICOLON); break; break;
case '*': addToken(STAR); break; case '}':
case '!': addToken(RIGHT_BRACE);
addToken(match('=') ? BANG_EQUAL : BANG); break;
break; case ',':
case '=': addToken(COMMA);
addToken(match('=') ? EQUAL_EQUAL : EQUAL); break;
break; case '.':
case '<': addToken(DOT);
addToken(match('=') ? LESS_EQUAL : LESS); break;
break; case '-':
case '>': addToken(MINUS);
addToken(match('=') ? GREATER_EQUAL : GREATER); break;
break; case '+':
case '/': addToken(PLUS);
if (match('/')) { break;
// A comment goes until the end of the line. case ';':
while (peek() != '\n' && !isAtEnd()) advance(); addToken(SEMICOLON);
} else { break;
addToken(SLASH); case '*':
} addToken(STAR);
break; break;
case '!':
addToken(match('=') ? BANG_EQUAL : BANG);
break;
case '=':
addToken(match('=') ? EQUAL_EQUAL : EQUAL);
break;
case '<':
addToken(match('=') ? LESS_EQUAL : LESS);
break;
case '>':
addToken(match('=') ? GREATER_EQUAL : GREATER);
break;
case '/':
if (match('/')) {
// A comment goes until the end of the line.
while (peek() != '\n' && !isAtEnd())
advance();
} else {
addToken(SLASH);
}
break;
case ' ': case ' ':
case '\r': case '\r':
case '\t': case '\t':
// Ignore whitespace. // Ignore whitespace.
break; break;
// I guess this code isn't meant to run on Mac OS 9 and before. // I guess this code isn't meant to run on Mac OS 9 and before.
case '\n': case '\n':
line++; line++;
break; break;
case '"': string(); break; case '"':
string();
break;
default: default:
if (isDigit(c)) { if (isDigit(c)) {
number(); number();
} else if (isAlpha(c)) { } else if (isAlpha(c)) {
identifier(); identifier();
} else { } else {
Lox.error(line, "Unexpected character."); Lox.error(line, "Unexpected character.");
} }
break; break;
} }
} }
private void identifier() { private void identifier() {
while (isAlphaNumeric(peek())) advance(); while (isAlphaNumeric(peek()))
advance();
String text = source.substring(start, current); String text = source.substring(start, current);
TokenType type = keywords.get(text); TokenType type = keywords.get(text);
if (type == null) type = IDENTIFIER; if (type == null)
type = IDENTIFIER;
addToken(type); addToken(type);
} }
private void number() { private void number() {
while (isDigit(peek())) advance(); while (isDigit(peek()))
advance();
// Look for a fractional part. // Look for a fractional part.
if (peek() == '.' && isDigit(peekNext())) { if (peek() == '.' && isDigit(peekNext())) {
// Consume the "." // Consume the "."
advance(); advance();
while (isDigit(peek())) advance(); while (isDigit(peek()))
advance();
} }
addToken(NUMBER, addToken(NUMBER, Double.parseDouble(source.substring(start, current)));
Double.parseDouble(source.substring(start, current)));
} }
// I guess we won't be able to include escaped characters in the string? At // I guess we won't be able to include escaped characters in the string? At
// least not escaped " characters. // least not escaped " characters.
private void string() { private void string() {
while (peek() != '"' && !isAtEnd()) { while (peek() != '"' && !isAtEnd()) {
if (peek() == '\n') line++; if (peek() == '\n')
line++;
advance(); advance();
} }
@ -156,27 +184,29 @@ class Scanner {
} }
private boolean match(char expected) { private boolean match(char expected) {
if (isAtEnd()) return false; if (isAtEnd())
if (source.charAt(current) != expected) return false; return false;
if (source.charAt(current) != expected)
return false;
current++; current++;
return true; return true;
} }
private char peek() { private char peek() {
if (isAtEnd()) return '\0'; if (isAtEnd())
return '\0';
return source.charAt(current); return source.charAt(current);
} }
private char peekNext() { private char peekNext() {
if (current + 1 >= source.length()) return '\0'; if (current + 1 >= source.length())
return '\0';
return source.charAt(current + 1); return source.charAt(current + 1);
} }
private boolean isAlpha(char c) { private boolean isAlpha(char c) {
return (c >= 'a' && c <= 'z') || return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
(c >= 'A' && c <= 'Z') ||
c == '_';
} }
private boolean isAlphaNumeric(char c) { private boolean isAlphaNumeric(char c) {

View file

@ -2,19 +2,16 @@ package com.craftinginterpreters.lox;
enum TokenType { enum TokenType {
// Single-character tokens. // Single-character tokens.
LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, COMMA, DOT, MINUS, PLUS, LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR,
SEMICOLON, SLASH, STAR,
// One or two character tokens. // One or two character tokens.
BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL, GREATER, GREATER_EQUAL, LESS, BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL,
LESS_EQUAL,
// Literals. // Literals.
IDENTIFIER, STRING, NUMBER, IDENTIFIER, STRING, NUMBER,
// Keywords. // Keywords.
AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, PRINT, RETURN, SUPER, THIS, AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, BREAK,
TRUE, VAR, WHILE,
EOF EOF
} }

View file

@ -22,7 +22,7 @@ public class GenerateAst {
defineAst(outputDir, "Stmt", defineAst(outputDir, "Stmt",
Arrays.asList("Block : List<Stmt> statements", "Expression : Expr expression", Arrays.asList("Block : List<Stmt> statements", "Expression : Expr expression",
"If : Expr condition, Stmt thenBranch, Stmt elseBranch", "Print : Expr expression", "If : Expr condition, Stmt thenBranch, Stmt elseBranch", "Print : Expr expression",
"Var : Token name, Expr initializer", "While : Expr condition, Stmt body")); "Var : Token name, Expr initializer", "While : Expr condition, Stmt body", "Break : "));
} }
private static void defineAst(String outputDir, String baseName, List<String> types) throws IOException { private static void defineAst(String outputDir, String baseName, List<String> types) throws IOException {
@ -71,8 +71,10 @@ public class GenerateAst {
// Store parameters in fields. // Store parameters in fields.
String[] fields = fieldList.split(", "); String[] fields = fieldList.split(", ");
for (String field : fields) { for (String field : fields) {
String name = field.split(" ")[1]; if (!field.isEmpty()) {
writer.println(" this." + name + " = " + name + ";"); String name = field.split(" ")[1];
writer.println(" this." + name + " = " + name + ";");
}
} }
writer.println(" }"); writer.println(" }");
@ -87,7 +89,9 @@ public class GenerateAst {
// Fields // Fields
writer.println(); writer.println();
for (String field : fields) { for (String field : fields) {
writer.println(" final " + field + ";"); if (!field.isEmpty()) {
writer.println(" final " + field + ";");
}
} }
writer.println(" }"); writer.println(" }");