commit 4d6b3bd349b85713847dd2812564cf0cdab697cb
parent fbc500e66a03bed8c5210fe009ab02a7235ca276
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Sat, 8 Oct 2022 01:41:29 +0200
Functions added
Diffstat:
14 files changed, 291 insertions(+), 64 deletions(-)
diff --git a/count.lox b/count.lox
@@ -0,0 +1,16 @@
+fun makeCounter() {
+ var i = 0;
+ fun count() {
+ i = i + 1;
+ print i;
+ }
+
+ return count;
+}
+
+var counter = makeCounter();
+counter(); // "1".
+counter(); // "2".
+counter(); // "2".
+counter(); // "2".
+counter(); // "2".
diff --git a/fib.lox b/fib.lox
@@ -0,0 +1,8 @@
+fun fib(n) {
+ if (n <= 1) return n;
+ return fib(n - 2) + fib(n - 1);
+}
+
+for (var i = 0; i < 20; i = i + 1) {
+ print fib(i);
+}
diff --git a/scopebug.lox b/scopebug.lox
@@ -0,0 +1,10 @@
+var a = "global";
+{
+ fun showA() {
+ print a;
+ }
+
+ showA();
+ var a = "block";
+ showA();
+}
diff --git a/source/app.d b/source/app.d
@@ -8,7 +8,7 @@ import std.string;
import std.conv;
import keywords;
import tokentype;
-import expr;
+import ast;
import parser;
import error;
import interpreter;
diff --git a/source/ast.d b/source/ast.d
@@ -0,0 +1,29 @@
+import astgen;
+import token;
+import std.variant;
+
+static immutable string[][] expressions = [
+ ["Ternary", "Expr left", "TokenI operator", "Expr middle", "Expr right"],
+ ["Binary", "Expr left", "TokenI operator", "Expr right"],
+ ["Grouping", "Expr expression"],
+ ["Literal", "Variant value"],
+ ["Unary", "TokenI operator", "Expr right"],
+ ["Variable", "TokenI name"],
+ ["Assign", "TokenI name", "Expr value"],
+ ["Logical", "Expr left", "TokenI operator", "Expr right"],
+ ["Call", "Expr callee", "TokenI paren", "Expr[] arguments"],
+ ["Function", "TokenI[] params", "Stmt[] body"],
+];
+
+static immutable string[][] statements = [
+ ["Print", "Expr expression"],
+ ["Expression", "Expr expression"],
+ ["Var", "TokenI name", "Expr initializer"],
+ ["Block", "Stmt[] statements"],
+ ["If", "Expr condition", "Stmt thenBranch", "Stmt elseBranch"],
+ ["While", "Expr condition", "Stmt bod"],
+ ["Break", "TokenI keyword"],
+ ["Return", "TokenI keyword", "Expr value"],
+];
+
+mixin(GenAst!("Stmt", statements) ~ GenAst!("Expr", expressions));
diff --git a/source/astgen.d b/source/astgen.d
@@ -4,21 +4,19 @@ import std.algorithm.iteration;
import std.uni;
import std.algorithm.iteration;
-template GenVisitee(immutable string basename, immutable string[][] names) {
- const char[] GenVisitee = format("interface %s { void accept(Visitor visitor); }", basename) ~
+template GenAst(immutable string basename, immutable string[][] names) {
+ const char[] GenAst = format("interface %sVisitor{%s;}", basename,
+ names.map!(s => format("void visit(%s %s)", s[0], "_" ~ toLower(s[0]))).join(";"))
+ ~ format("interface %s { void accept(%sVisitor visitor); }", basename, basename) ~
names.map!(name => format(
- "class %s:%s{%sthis(%s){%s}void accept(Visitor visitor){visitor.visit(this);}}",
+ "class %s:%s{%sthis(%s){%s}void accept(%sVisitor 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(";") ~ (name.length > 1 ? ";" : ""),
+ basename
)).join();
}
-
-template GenVisitor(immutable string[][] names) {
- const char[] GenVisitor = format("interface Visitor{%s;}",
- names.map!(s => format("void visit(%s %s)", s[0], "_" ~ toLower(s[0]))).join(";"));
-}
diff --git a/source/astprinter.d b/source/astprinter.d
@@ -1,9 +1,9 @@
import std.conv;
import std.algorithm.iteration;
import std.array;
-import expr;
+import ast;
-class AstPrinter : Visitor {
+class AstPrinter : ExprVisitor {
string printed;
string print(Expr expr) {
@@ -35,9 +35,15 @@ class AstPrinter : Visitor {
if (literal.value == null) printed = "nil";
printed = literal.value.toString();
}
+ void visit(Function fun) {
+ printed = "(fun (" ~ fun.params.map!(p => p.lexeme).join(" ") ~ ") {...})";
+ }
void visit(Unary unary) {
printed = parenthesize(unary.operator.lexeme, unary.right);
}
+ void visit(Call call) {
+ printed = parenthesize("call", call.callee ~ call.arguments);
+ }
private string parenthesize(string name, Expr[] exprs ...) {
return "(" ~ name ~ " " ~ exprs.map!(e => print(e)).join(" ") ~ ")";
}
diff --git a/source/callable.d b/source/callable.d
@@ -0,0 +1,7 @@
+import interpreter;
+import std.variant;
+
+interface Callable {
+ Variant call(Interpreter interpreter, Variant[] arguments);
+ ulong arity();
+}
diff --git a/source/expr.d b/source/expr.d
@@ -1,16 +0,0 @@
-import astgen;
-import token;
-import std.variant;
-
-static immutable string[][] expressions = [
- ["Ternary", "Expr left", "TokenI operator", "Expr middle", "Expr right"],
- ["Binary", "Expr left", "TokenI operator", "Expr right"],
- ["Grouping", "Expr expression"],
- ["Literal", "Variant value"],
- ["Unary", "TokenI operator", "Expr right"],
- ["Variable", "TokenI name"],
- ["Assign", "TokenI name", "Expr value"],
- ["Logical", "Expr left", "TokenI operator", "Expr right"],
-];
-
-mixin(GenVisitor!(expressions) ~ GenVisitee!("Expr", expressions));
diff --git a/source/fun.d b/source/fun.d
@@ -0,0 +1,37 @@
+import callable;
+import ast;
+import interpreter;
+import environment;
+import std.variant;
+
+class Fun : Callable {
+ private Function fun;
+ private Environment closure;
+
+ this(Function fun, Environment closure) {
+ this.closure = closure;
+ this.fun = fun;
+ }
+
+ Variant call(Interpreter interpreter, Variant[] arguments) {
+ Environment environment = new Environment(closure);
+ for(int i = 0; i < fun.params.length; i++) {
+ environment.define(fun.params[i].lexeme, arguments[i]);
+ }
+
+ try {
+ interpreter.executeBlock(fun.body, environment);
+ } catch (Interpreter.ReturnCalled ret) {
+ return ret.value;
+ }
+ return Variant(null);
+ }
+
+ ulong arity() {
+ return fun.params.length;
+ }
+
+ void toString(scope void delegate(const(char)[]) sink) const {
+ sink("<fun>");
+ }
+}
diff --git a/source/interpreter.d b/source/interpreter.d
@@ -1,29 +1,50 @@
-import expr;
-import stmt;
import std.variant;
+import std.format;
+import std.algorithm;
+import std.stdio;
+import std.container;
+import std.datetime.systime;
+import ast;
import tokentype;
import token;
-import std.format;
import error;
import app;
-import std.algorithm;
import astprinter;
-import std.stdio;
-import std.container;
import environment;
+import callable;
+import fun;
-class Interpreter : stmt.Visitor, expr.Visitor {
+class Interpreter : StmtVisitor, ExprVisitor {
Variant value;
Environment environment;
+ Environment globals;
- private class BreakCalled : Exception {
+ class BreakCalled : Exception {
this() {
super("", "", 0);
}
}
+ class ReturnCalled : Exception {
+ Variant value;
+ this(Variant value) {
+ this.value = value;
+ super("", "", 0);
+ }
+ }
+
this() {
- environment = new Environment();
+ globals = new Environment();
+ environment = globals;
+
+ globals.define("clock", Variant(new class Callable {
+ ulong arity() {
+ return 0;
+ }
+ Variant call(Interpreter _, Variant[] __) {
+ return Variant(stdTimeToUnixTime(Clock.currStdTime()));
+ }
+ }));
}
string interpret(Array!Stmt statements) {
@@ -84,6 +105,10 @@ class Interpreter : stmt.Visitor, expr.Visitor {
throw new BreakCalled();
}
+ void visit(Return ret) {
+ throw new ReturnCalled(ret.value is null ? Variant(null) : evaluate(ret.value));
+ }
+
void visit(While stmt) {
while (isTruthy(evaluate(stmt.condition))) {
try {
@@ -106,6 +131,29 @@ class Interpreter : stmt.Visitor, expr.Visitor {
}
}
+ void visit(Function expr) {
+ value = new Fun(expr, environment);
+ }
+
+ void visit(Call expr) {
+ Variant callee = evaluate(expr.callee);
+ Variant[] arguments = [];
+
+ foreach(arg; expr.arguments) {
+ arguments ~= evaluate(arg);
+ }
+
+ if (!callee.convertsTo!(Callable)) {
+ throw new RuntimeError(expr.paren, "Expression result is not callable");
+ }
+ Callable fun = callee.get!(Callable);
+ if (arguments.length != fun.arity()) {
+ throw new RuntimeError(expr.paren,
+ format("Expected %s arguments but got %s.", fun.arity(), arguments.length));
+ }
+ value = fun.call(this, arguments);
+ }
+
void visit(Assign expr) {
Variant variant = evaluate(expr.value);
environment.assign(expr.name, variant);
diff --git a/source/parser.d b/source/parser.d
@@ -2,22 +2,27 @@ import token;
import tokentype;
import std.container;
import std.variant;
-import expr;
-import stmt;
+import ast;
import app;
/*
program → statement* EOF ;
-declaration → varDecl
+declaration → funDecl
+ | varDecl
| statement ;
varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
statement → exprStmt
| forStmt
| ifStmt
| printStmt
+ | returnStmt
| whileStmt
| breakStmt
| block ;
+returnStmt → "return" expression? ";" ;
+funDecl → "fun" IDENTIFIER function ;
+function → "(" parameters? ")" block ;
+parameters → IDENTIFIER ( "," IDENTIFIER )* ;
forStmt → "for" "(" ( varDecl | exprStmt | ";" )
expression? ";"
expression? ")" statement ;
@@ -39,9 +44,12 @@ comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
term → factor ( ( "-" | "+" ) factor )* ;
factor → unary ( ( "/" | "*" ) unary )* ;
unary → ( "!" | "-" | "ast" ) unary
- | primary ;
+ | call ;
+call → funExpr ( "(" arguments? ")" )* ;
+funExpr → "fun" function ;
primary → NUMBER | STRING | "true" | "false" | "nil"
| "(" expression ")" | IDENTIFIER ;
+arguments → expression ( "," expression )* ;
*/
class Parser {
@@ -53,7 +61,8 @@ class Parser {
private Array!TokenI tokens;
private int current = 0;
- private bool breakAllowed = false;
+ private int breakAllowed = 0;
+ private int returnAllowed = 0;
this(Array!TokenI tokens) {
this.tokens = tokens;
@@ -71,13 +80,37 @@ class Parser {
try {
if (match(TokenType.VAR))
return varDeclaration();
- else
+ if (check(TokenType.FUN) && peekNext().type == TokenType.IDENTIFIER) {
+ advance();
+ return statement!(Var)(advance(), fun());
+ } else {
return matchStatement();
+ }
} catch (ParseError err) {
synchronize();
return null;
}
}
+
+ private Expr fun() {
+ consume(TokenType.LEFT_PAREN, "Expect '(' at function declaration.");
+ TokenI[] parameters = [];
+ if (!check(TokenType.RIGHT_PAREN)) {
+ do {
+ if (parameters.length >= 255) {
+ error(peek(), "Can't have more than 255 parameters.");
+ }
+
+ parameters ~= consume(TokenType.IDENTIFIER, "Expect parameter name.");
+ } while (match(TokenType.COMMA));
+ }
+ consume(TokenType.RIGHT_PAREN, "Expect ')' after parameters.");
+ consume(TokenType.LEFT_BRACE, "Expect '{' before function body.");
+ returnAllowed++;
+ Stmt[] bod = block();
+ returnAllowed--;
+ return new Function(parameters, bod);
+ }
private Stmt varDeclaration() {
TokenI name = consume(TokenType.IDENTIFIER, "Expect variable name.");
@@ -95,6 +128,8 @@ class Parser {
return statement!(Print)(expression());
if (match(TokenType.WHILE))
return whileStatement();
+ if (match(TokenType.RETURN))
+ return returnStatement();
if (match(TokenType.LEFT_BRACE))
return statement!(Block)(block());
if (match(TokenType.BREAK))
@@ -104,12 +139,25 @@ class Parser {
private Stmt breakStatement() {
if (breakAllowed) {
- return statement!(Break)();
+ return statement!(Break)(previous());
} else {
throw error(previous(), "Break not allowed here.");
}
}
+ private Stmt returnStatement() {
+ TokenI keyword = previous();
+ if (returnAllowed) {
+ Expr value = null;
+ if (!check(TokenType.SEMICOLON) && !check(TokenType.RIGHT_BRACE)) {
+ value = expression();
+ }
+ return statement!(Return)(keyword, value);
+ } else {
+ throw error(keyword, "Return not allowed here.");
+ }
+ }
+
private Stmt forStatement() {
consume(TokenType.LEFT_PAREN, "Expect '(' after 'for'.");
@@ -135,9 +183,9 @@ class Parser {
}
consume(TokenType.RIGHT_PAREN, "Expect ')' after for clauses.");
- breakAllowed = true;
+ breakAllowed++;
Stmt bod = matchStatement();
- breakAllowed = false;
+ breakAllowed--;
if (increment !is null) {
bod = new Block([bod, new Expression(increment)]);
@@ -173,9 +221,9 @@ class Parser {
consume(TokenType.LEFT_PAREN, "Expect '(' after 'while'.");
Expr condition = expression();
consume(TokenType.RIGHT_PAREN, "Expect ')' after condition.");
- breakAllowed = true;
+ breakAllowed++;
Stmt bod = matchStatement();
- breakAllowed = false;
+ breakAllowed--;
return statement!(While)(condition, bod);
}
@@ -269,11 +317,54 @@ class Parser {
with (TokenType) if (match(BANG, MINUS, AST)) {
TokenI operator = previous();
Expr right = primary();
+ if (operator.type == AST) {
+ if(Grouping gr = cast(Grouping)right) {
+ right = gr.expression;
+ }
+ }
return new Unary(operator, right);
}
+ return call();
+ }
+
+ private Expr call() {
+ Expr expr = funExpr();
+
+ while (true) {
+ if (match(TokenType.LEFT_PAREN)) {
+ expr = finishCall(expr);
+ } else {
+ break;
+ }
+ }
+
+ return expr;
+ }
+
+ private Expr funExpr() {
+ if (match(TokenType.FUN)) {
+ return fun();
+ }
return primary();
}
+ private Expr finishCall(Expr callee) {
+ Expr[] arguments = [];
+
+ if (!check(TokenType.RIGHT_PAREN)) {
+ do {
+ if (arguments.length >= 255) {
+ error(peek(), "Can't have more than 255 arguments.");
+ }
+ arguments ~= assignment();
+ } while (match(TokenType.COMMA));
+ }
+
+ TokenI paren = consume(TokenType.RIGHT_PAREN, "Expect ')' after arguments.");
+
+ return new Call(callee, paren, arguments);
+ }
+
private Expr primary() {
with (TokenType) {
if (match(FALSE))
diff --git a/source/stmt.d b/source/stmt.d
@@ -1,16 +0,0 @@
-import astgen;
-import token;
-import std.variant;
-import expr;
-
-static immutable string[][] statements = [
- ["Print", "Expr expression"],
- ["Expression", "Expr expression"],
- ["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/thrice.lox b/thrice.lox
@@ -0,0 +1,9 @@
+fun thrice(fn) {
+ for (var i = 1; i <= 3; i = i + 1) {
+ fn(i);
+ }
+}
+
+thrice(fun (a) {
+ print a;
+});