Compare commits
1 commit
master
...
chapter-9-
Author | SHA1 | Date | |
---|---|---|---|
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitBreakStmt(Stmt.Break statement) {
|
||||
return "break";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPrintStmt(Stmt.Print statement) {
|
||||
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
|
||||
RuntimeError.java
|
||||
Environment.java
|
||||
Break.java
|
||||
ENTRY_POINT com/craftinginterpreters/lox/Lox)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 '(':
|
||||
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 ' ':
|
||||
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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(" }");
|
||||
|
|
Loading…
Reference in a new issue