commit fbc500e66a03bed8c5210fe009ab02a7235ca276
parent 80437b724f906283996193ab6c834446600e502f
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Mon, 3 Oct 2022 23:30:29 +0200
Control flow finished
Diffstat:
7 files changed, 120 insertions(+), 9 deletions(-)
diff --git a/source/app.d b/source/app.d
@@ -32,6 +32,12 @@ class Lox {
static bool hadError = false;
static bool hadRuntimeError = false;
+ Interpreter interpreter;
+
+ this() {
+ interpreter = new Interpreter();
+ }
+
extern(C) static void completion(const char *buf, linenoiseCompletions *lc) {
auto bufs = fromStringz(buf);
auto lastsp = 1 + lastIndexOfAny(bufs, [
@@ -77,7 +83,6 @@ class Lox {
auto expression = parser.parse();
if(hadError) return;
- Interpreter interpreter = new Interpreter();
auto result = interpreter.interpret(expression);
if(hadError) return;
diff --git a/source/astgen.d b/source/astgen.d
@@ -7,9 +7,15 @@ import std.algorithm.iteration;
template GenVisitee(immutable string basename, immutable string[][] names) {
const char[] GenVisitee = format("interface %s { void accept(Visitor visitor); }", basename) ~
names.map!(name => format(
- "class %s:%s{%s;this(%s){%s;}void accept(Visitor visitor){visitor.visit(this);}}",
- name[0], basename, name[1..$].join(";"), name[1..$].join(","),
- name[1..$].map!(s => "this." ~ [s.split(" ")[$-1]].replicate(2).join("=")).join(";"))).join();
+ "class %s:%s{%sthis(%s){%s}void accept(Visitor visitor){visitor.visit(this);}}",
+ name[0],
+ basename,
+ name[1..$].join(";") ~ (name.length > 1 ? ";" : ""),
+ name[1..$].join(","),
+ name[1..$].map!(s =>
+ "this." ~ [s.split(" ")[$-1]].replicate(2).join("=")
+ ).join(";") ~ (name.length > 1 ? ";" : "")
+ )).join();
}
template GenVisitor(immutable string[][] names) {
diff --git a/source/interpreter.d b/source/interpreter.d
@@ -14,9 +14,15 @@ import environment;
class Interpreter : stmt.Visitor, expr.Visitor {
Variant value;
- static Environment environment;
+ Environment environment;
- static this() {
+ private class BreakCalled : Exception {
+ this() {
+ super("", "", 0);
+ }
+ }
+
+ this() {
environment = new Environment();
}
@@ -74,6 +80,19 @@ class Interpreter : stmt.Visitor, expr.Visitor {
}
}
+ void visit(Break _) {
+ throw new BreakCalled();
+ }
+
+ void visit(While stmt) {
+ while (isTruthy(evaluate(stmt.condition))) {
+ try {
+ execute(stmt.bod);
+ } catch (BreakCalled _) break;
+ }
+ value = null;
+ }
+
void executeBlock(Stmt[] statements, Environment environment) {
Environment previous = this.environment;
try {
diff --git a/source/keywords.d b/source/keywords.d
@@ -21,6 +21,7 @@ shared static this() {
"true": TRUE,
"var": VAR,
"while": WHILE,
+ "break": BREAK,
"ast": AST,
];
}
diff --git a/source/parser.d b/source/parser.d
@@ -12,9 +12,16 @@ declaration → varDecl
| statement ;
varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
statement → exprStmt
- | printStmt
+ | forStmt
| ifStmt
+ | printStmt
+ | whileStmt
+ | breakStmt
| block ;
+forStmt → "for" "(" ( varDecl | exprStmt | ";" )
+ expression? ";"
+ expression? ")" statement ;
+whileStmt → "while" "(" expression ")" statement ;
ifStmt → "if" "(" expression ")" statement
( "else" statement )? ;
block → "{" declaration* "}" ;
@@ -46,6 +53,7 @@ class Parser {
private Array!TokenI tokens;
private int current = 0;
+ private bool breakAllowed = false;
this(Array!TokenI tokens) {
this.tokens = tokens;
@@ -79,14 +87,72 @@ class Parser {
}
private Stmt matchStatement() {
+ if (match(TokenType.FOR))
+ return forStatement();
+ if (match(TokenType.IF))
+ return ifStatement();
if (match(TokenType.PRINT))
return statement!(Print)(expression());
+ if (match(TokenType.WHILE))
+ return whileStatement();
if (match(TokenType.LEFT_BRACE))
return statement!(Block)(block());
- if (match(TokenType.IF))
- return ifStatement();
+ if (match(TokenType.BREAK))
+ return breakStatement();
return statement!(Expression)(expression());
}
+
+ private Stmt breakStatement() {
+ if (breakAllowed) {
+ return statement!(Break)();
+ } else {
+ throw error(previous(), "Break not allowed here.");
+ }
+ }
+
+ private Stmt forStatement() {
+ consume(TokenType.LEFT_PAREN, "Expect '(' after 'for'.");
+
+ Stmt initializer;
+
+ if (match(TokenType.SEMICOLON)) {
+ initializer = null;
+ } else if (match(TokenType.VAR)) {
+ initializer = varDeclaration();
+ } else {
+ initializer = statement!(Expression)(expression());
+ }
+
+ Expr condition = null;
+ if (!check(TokenType.SEMICOLON)) {
+ condition = expression();
+ }
+ consume(TokenType.SEMICOLON, "Expect ';' after loop condition.");
+
+ Expr increment = null;
+ if (!check(TokenType.RIGHT_PAREN)) {
+ increment = expression();
+ }
+ consume(TokenType.RIGHT_PAREN, "Expect ')' after for clauses.");
+
+ breakAllowed = true;
+ Stmt bod = matchStatement();
+ breakAllowed = false;
+
+ if (increment !is null) {
+ bod = new Block([bod, new Expression(increment)]);
+ }
+ if (condition is null) {
+ condition = new Literal(Variant(true));
+ }
+
+ bod = new While(condition, bod);
+ if (initializer !is null) {
+ bod = new Block([initializer, bod]);
+ }
+
+ return bod;
+ }
private Stmt ifStatement() {
consume(TokenType.LEFT_PAREN, "Expect '(' after 'if'.");
@@ -103,6 +169,17 @@ class Parser {
return statement!(If)(condition, thenBranch, elseBranch);
}
+ private Stmt whileStatement() {
+ consume(TokenType.LEFT_PAREN, "Expect '(' after 'while'.");
+ Expr condition = expression();
+ consume(TokenType.RIGHT_PAREN, "Expect ')' after condition.");
+ breakAllowed = true;
+ Stmt bod = matchStatement();
+ breakAllowed = false;
+
+ return statement!(While)(condition, bod);
+ }
+
private Stmt[] block() {
Stmt[] statements;
diff --git a/source/stmt.d b/source/stmt.d
@@ -9,6 +9,8 @@ static immutable string[][] statements = [
["Var", "TokenI name", "Expr initializer"],
["Block", "Stmt[] statements"],
["If", "Expr condition", "Stmt thenBranch", "Stmt elseBranch"],
+ ["While", "Expr condition", "Stmt bod"],
+ ["Break"]
];
mixin(GenVisitor!(statements) ~ GenVisitee!("Stmt", statements));
diff --git a/source/tokentype.d b/source/tokentype.d
@@ -16,6 +16,7 @@ enum TokenType {
// Keywords.
AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR,
PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, AST,
+ BREAK,
EOF
}