From e749367886e9e5ac342c8e6c704044b6cc18205e Mon Sep 17 00:00:00 2001 From: nazrin Date: Sun, 1 Jun 2025 19:53:00 +0000 Subject: [PATCH] Statements and State 8 --- src/jlox/environment.d | 31 +++++++++++++++ src/jlox/expr.d | 82 ++++++++++++++++++++-------------------- src/jlox/interpreter.d | 52 ++++++++++++++++++++++--- src/jlox/main.d | 8 ++-- src/jlox/parser.d | 86 +++++++++++++++++++++++++++++++++--------- src/jlox/stmt.d | 46 ++++++++++++++++++++++ src/jlox/token.d | 14 ++++++- 7 files changed, 250 insertions(+), 69 deletions(-) create mode 100644 src/jlox/environment.d create mode 100644 src/jlox/stmt.d diff --git a/src/jlox/environment.d b/src/jlox/environment.d new file mode 100644 index 0000000..8cd3c38 --- /dev/null +++ b/src/jlox/environment.d @@ -0,0 +1,31 @@ +module jlox.environment; + +import jlox.token; +import jlox.interpreter; +import jlox.util; + +class Environment{ + Environment enclosing; + private TValue[string] values; + + mixin defaultCtor; + + void define(string name, TValue value){ + values[name] = value; + } + TValue get(Token name){ + if(TValue* value = name.lexeme in values) + return *value; + if(enclosing) + return enclosing.get(name); + throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'."); + } + void assign(Token name, TValue value){ + if(name.lexeme in values) + return values[name.lexeme] = value; + if(enclosing) + return enclosing.assign(name, value); + throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'."); + } +} + diff --git a/src/jlox/expr.d b/src/jlox/expr.d index a13ef2e..79ed79f 100644 --- a/src/jlox/expr.d +++ b/src/jlox/expr.d @@ -12,10 +12,12 @@ import jlox.util; abstract class Expr{ interface Visitor(R){ + R visit(Assign expr); R visit(Binary expr); R visit(Grouping expr); R visit(Literal expr); R visit(Unary expr); + R visit(Variable expr); } private alias rTypes = AliasSeq!(string, TValue); static foreach(T; rTypes) @@ -26,63 +28,61 @@ abstract class Expr{ override T accept(Visitor!T visitor) => visitor.visit(this); } - static class Binary : Expr{ + static class Assign : typeof(this){ + Token name; + Expr value; + mixin defCtorAndAccept; + } + static class Binary : typeof(this){ Expr left; Token operator; Expr right; mixin defCtorAndAccept; } - static class Grouping : Expr{ + static class Grouping : typeof(this){ Expr expression; mixin defCtorAndAccept; } - static class Literal : Expr{ + static class Literal : typeof(this){ TValue value; mixin defCtorAndAccept; } - static class Unary : Expr{ + static class Unary : typeof(this){ Token operator; Expr right; mixin defCtorAndAccept; } -} - -class AstPrinter : Expr.Visitor!string{ - string print(Expr expr){ - return expr.accept(this); - } - string parenthesize(Args...)(string name, Args args){ - string s = "(" ~ name; - static foreach(expr; args){ - s ~= " "; - s ~= expr.accept(this); - } - return s ~ ")"; - } - string visit(Expr.Binary expr){ - return parenthesize(expr.operator.lexeme, expr.left, expr.right); - } - string visit(Expr.Grouping expr){ - return parenthesize("group", expr.expression); - } - string visit(Expr.Literal expr){ - if(expr.value.isNil) - return "nil"; - return expr.value.to!string; - } - string visit(Expr.Unary expr){ - return parenthesize(expr.operator.lexeme, expr.right); + static class Variable : typeof(this){ + Token name; + mixin defCtorAndAccept; } } -unittest{ - auto expression = new Expr.Binary( - new Expr.Unary( - new Token(TokenType.MINUS, "-", TValue.nil(0), 1), - new Expr.Literal(123)), - new Token(TokenType.STAR, "*", TValue.nil(0), 1), - new Expr.Grouping( - new Expr.Literal(45.67))); - assert(new AstPrinter().print(expression)); -} +/* class AstPrinter : Expr.Visitor!string{ */ +/* string print(Expr expr){ */ +/* return expr.accept(this); */ +/* } */ +/* string parenthesize(Args...)(string name, Args args){ */ +/* string s = "(" ~ name; */ +/* static foreach(expr; args){ */ +/* s ~= " "; */ +/* s ~= expr.accept(this); */ +/* } */ +/* return s ~ ")"; */ +/* } */ +/* string visit(Expr.Binary expr){ */ +/* return parenthesize(expr.operator.lexeme, expr.left, expr.right); */ +/* } */ +/* string visit(Expr.Grouping expr){ */ +/* return parenthesize("group", expr.expression); */ +/* } */ +/* string visit(Expr.Literal expr){ */ +/* if(expr.value.isNil) */ +/* return "nil"; */ +/* return expr.value.to!string; */ +/* } */ +/* string visit(Expr.Unary expr){ */ +/* return parenthesize(expr.operator.lexeme, expr.right); */ +/* } */ +/* } */ diff --git a/src/jlox/interpreter.d b/src/jlox/interpreter.d index f957116..a207492 100644 --- a/src/jlox/interpreter.d +++ b/src/jlox/interpreter.d @@ -8,10 +8,12 @@ import std.functional : ctEval; import taggedalgebraic; import jlox.expr; +import jlox.stmt; import jlox.token; import jlox.tokentype; import jlox.token : TValue; import jlox.main; +import jlox.environment; class RuntimeError : Exception{ const Token token; @@ -21,15 +23,29 @@ class RuntimeError : Exception{ } } -class Interpreter : Expr.Visitor!TValue{ - void interpret(Expr expression) { +class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue { + private Environment environment = new Environment(); + void interpret(Stmt[] statements){ try { - TValue value = evaluate(expression); - writeln(value); + foreach(statement; statements) + execute(statement); } catch(RuntimeError error){ Lox.runtimeError(error); } } + private void execute(Stmt stmt){ + stmt.accept(this); + } + private void executeBlock(Stmt[] statements, Environment environment){ + Environment previous = this.environment; + try { + this.environment = environment; + foreach(Stmt statement; statements) + execute(statement); + } finally { + this.environment = previous; + } + } private TValue evaluate(Expr expr){ return expr.accept(this); } @@ -51,13 +67,29 @@ class Interpreter : Expr.Visitor!TValue{ return; throw new RuntimeError(operator, "Operand must be a number."); } + + + void visit(Stmt.Block stmt){ + executeBlock(stmt.statements, new Environment(environment)); + } + void visit(Stmt.Expression stmt){ + evaluate(stmt.expression); + } + void visit(Stmt.Print stmt){ + TValue value = evaluate(stmt.expression); + writeln(tvalueToString(value)); + } + void visit(Stmt.Var stmt){ + environment.define(stmt.name.lexeme, stmt.initialiser is null ? TValue.nil(0) : evaluate(stmt.initialiser)); + } + TValue visit(Expr.Literal expr){ return expr.value; } - TValue visit(Expr.Grouping expr) { + TValue visit(Expr.Grouping expr){ return evaluate(expr.expression); } - TValue visit(Expr.Unary expr) { + TValue visit(Expr.Unary expr){ TValue right = evaluate(expr.right); switch(expr.operator.type){ case TokenType.MINUS: @@ -104,5 +136,13 @@ class Interpreter : Expr.Visitor!TValue{ assert(0); } } + TValue visit(Expr.Variable expr){ + return environment.get(expr.name); + } + TValue visit(Expr.Assign expr){ + TValue value = evaluate(expr.value); + environment.assign(expr.name, value); + return value; + } } diff --git a/src/jlox/main.d b/src/jlox/main.d index c15fb8b..3bf99fe 100644 --- a/src/jlox/main.d +++ b/src/jlox/main.d @@ -11,9 +11,9 @@ import jlox.token; import jlox.tokentype; import jlox.scanner; import jlox.parser; -import jlox.expr; import jlox.interpreter; - +import jlox.expr; +import jlox.stmt; static class Lox{ private static Interpreter interpreter; @@ -45,13 +45,13 @@ static class Lox{ Token[] tokens = scanner.scanTokens(); Parser parser = new Parser(tokens); - Expr expression = parser.parse(); + Stmt[] statements = parser.parse(); if(hadError) return; /* writeln((new AstPrinter).print(expression)); */ - interpreter.interpret(expression); + interpreter.interpret(statements); } static void runFile(string path){ string bytes = readText(path); diff --git a/src/jlox/parser.d b/src/jlox/parser.d index ad1410a..ce52c04 100644 --- a/src/jlox/parser.d +++ b/src/jlox/parser.d @@ -7,21 +7,21 @@ import jlox.tokentype; import jlox.util; import jlox.expr; import jlox.main; +import jlox.stmt; class Parser{ private Token[] tokens; private int current = 0; mixin defaultCtor; - Expr parse(){ - try { - return expression(); - } catch (ParseError error) { - return null; - } + Stmt[] parse(){ + Stmt[] statements; + while(!isAtEnd) + statements ~= declaration(); + return statements; } - private bool isAtEnd() => peek().type == EOF; + private bool isAtEnd() => peek().type == TokenType.EOF; private Token peek() => tokens[current]; private Token previous() => tokens[current-1]; private bool check(TokenType type){ @@ -30,7 +30,7 @@ class Parser{ return peek().type == type; } private Token advance(){ - if(!isAtEnd()) + if(!isAtEnd) current++; return previous(); } @@ -59,7 +59,7 @@ class Parser{ } private void synchronize(){ advance(); - with(TokenType) while(!isAtEnd()){ + with(TokenType) while(!isAtEnd){ if(previous().type == SEMICOLON) return; switch(peek().type){ @@ -73,15 +73,49 @@ class Parser{ case RETURN: return; default: - assert(0); + /* assert(0); */ } advance(); } } - + private Stmt printStatement(){ + Expr value = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after value."); + return new Stmt.Print(value); + } + private Stmt expressionStatement(){ + Expr expr = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after expression."); + return new Stmt.Expression(expr); + } + private Stmt statement(){ + if(match(TokenType.LEFT_BRACE)) + return new Stmt.Block(block()); + if(match(TokenType.PRINT)) + return printStatement(); + return expressionStatement(); + } + private Stmt[] block(){ + Stmt[] statements; + while(!check(TokenType.RIGHT_BRACE) && !isAtEnd) + statements ~= declaration(); + consume(TokenType.RIGHT_BRACE, "Expect '}' after block."); + return statements; + } private Expr expression(){ - return equality(); + return assignment(); + } + private Expr assignment(){ + Expr expr = equality(); + if(match(TokenType.EQUAL)){ + Token equals = previous(); + Expr value = assignment(); + if(auto v = cast(Expr.Variable)expr) + return new Expr.Assign(v.name, value); + error(equals, "Invalid assignment target."); + } + return expr; } private Expr equality(){ Expr expr = comparison(); @@ -128,12 +162,14 @@ class Parser{ return primary(); } private Expr primary(){ + if(match(TokenType.IDENTIFIER)) + return new Expr.Variable(previous()); if(match(TokenType.FALSE)) - return new Expr.Literal(false); + return new Expr.Literal(TValue.bln(false)); if(match(TokenType.TRUE)) - return new Expr.Literal(true); + return new Expr.Literal(TValue.bln(true)); if(match(TokenType.NIL)) - return new Expr.Literal(null); + return new Expr.Literal(TValue.nil(0)); if(match(TokenType.NUMBER, TokenType.STRING)) return new Expr.Literal(previous().literal); if(match(TokenType.LEFT_PAREN)){ @@ -142,7 +178,23 @@ class Parser{ return new Expr.Grouping(expr); } throw error(peek(), "Expect expression."); - assert(0); } - + private Stmt varDeclaration(){ + Token name = consume(TokenType.IDENTIFIER, "Expect variable name."); + Expr initializer = null; + if(match(TokenType.EQUAL)) + initializer = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); + return new Stmt.Var(name, initializer); + } + private Stmt declaration(){ + try { + if(match(TokenType.VAR)) + return varDeclaration(); + return statement(); + } catch(ParseError error){ + synchronize(); + return null; + } + } } diff --git a/src/jlox/stmt.d b/src/jlox/stmt.d new file mode 100644 index 0000000..ecd7c9c --- /dev/null +++ b/src/jlox/stmt.d @@ -0,0 +1,46 @@ +module jlox.stmt; + +import std.conv; +import std.stdio; +import std.meta : AliasSeq; + +import jlox.token; +import jlox.tokentype; +import jlox.util; +import jlox.expr; + +abstract class Stmt{ + interface Visitor(R){ + R visit(Block expr); + R visit(Expression expr); + R visit(Print expr); + R visit(Var expr); + } + private alias rTypes = AliasSeq!(void); + static foreach(T; rTypes) + abstract T accept(Visitor!T visitor); + private template defCtorAndAccept(){ + mixin defaultCtor; + static foreach(T; rTypes) + override T accept(Visitor!T visitor) => visitor.visit(this); + } + + static class Block : typeof(this){ + Stmt[] statements; + mixin defCtorAndAccept; + } + static class Expression : typeof(this){ + Expr expression; + mixin defCtorAndAccept; + } + static class Print : typeof(this){ + Expr expression; + mixin defCtorAndAccept; + } + static class Var : typeof(this){ + Token name; + Expr initialiser; + mixin defCtorAndAccept; + } +} + diff --git a/src/jlox/token.d b/src/jlox/token.d index 92eb377..83418a8 100644 --- a/src/jlox/token.d +++ b/src/jlox/token.d @@ -13,8 +13,20 @@ private struct Value{ bool nil = false; } alias TValue = TaggedUnion!Value; +string tvalueToString(TValue val){ + final switch(val.kind){ + case TValue.Kind.str: + return val.strValue; + case TValue.Kind.dbl: + return val.dblValue.to!string; + case TValue.Kind.bln: + return val.blnValue ? "true" : "false"; + case TValue.Kind.nil: + return "nil"; + } +} -class Token { +class Token{ TokenType type; string lexeme; TValue literal;