DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit 4d6b3bd349b85713847dd2812564cf0cdab697cb
parent fbc500e66a03bed8c5210fe009ab02a7235ca276
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Sat,  8 Oct 2022 01:41:29 +0200

Functions added

Diffstat:
Acount.lox | 16++++++++++++++++
Afib.lox | 8++++++++
Ascopebug.lox | 10++++++++++
Msource/app.d | 2+-
Asource/ast.d | 29+++++++++++++++++++++++++++++
Msource/astgen.d | 16+++++++---------
Msource/astprinter.d | 10++++++++--
Asource/callable.d | 7+++++++
Dsource/expr.d | 16----------------
Asource/fun.d | 37+++++++++++++++++++++++++++++++++++++
Msource/interpreter.d | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msource/parser.d | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Dsource/stmt.d | 16----------------
Athrice.lox | 9+++++++++
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; +});