Add break statement
This commit is contained in:
parent
613c2388be
commit
80394e1842
8 changed files with 136 additions and 80 deletions
|
@ -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);
|
||||||
|
|
5
src/com/craftinginterpreters/lox/Break.java
Normal file
5
src/com/craftinginterpreters/lox/Break.java
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
class Break extends RuntimeException {
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 '!':
|
||||||
case ' ':
|
addToken(match('=') ? BANG_EQUAL : BANG);
|
||||||
case '\r':
|
break;
|
||||||
case '\t':
|
case '=':
|
||||||
// Ignore whitespace.
|
addToken(match('=') ? EQUAL_EQUAL : EQUAL);
|
||||||
break;
|
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.
|
// 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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(" }");
|
||||||
|
|
Loading…
Reference in a new issue