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();
}
@Override
public String visitBreakStmt(Stmt.Break statement) {
return "break";
}
@Override
public String visitPrintStmt(Stmt.Print statement) {
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
RuntimeError.java
Environment.java
Break.java
ENTRY_POINT com/craftinginterpreters/lox/Lox)

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ public class GenerateAst {
defineAst(outputDir, "Stmt",
Arrays.asList("Block : List<Stmt> statements", "Expression : 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 {
@ -71,8 +71,10 @@ public class GenerateAst {
// Store parameters in fields.
String[] fields = fieldList.split(", ");
for (String field : fields) {
String name = field.split(" ")[1];
writer.println(" this." + name + " = " + name + ";");
if (!field.isEmpty()) {
String name = field.split(" ")[1];
writer.println(" this." + name + " = " + name + ";");
}
}
writer.println(" }");
@ -87,7 +89,9 @@ public class GenerateAst {
// Fields
writer.println();
for (String field : fields) {
writer.println(" final " + field + ";");
if (!field.isEmpty()) {
writer.println(" final " + field + ";");
}
}
writer.println(" }");