Statements and State 8

This commit is contained in:
nazrin 2025-06-01 19:53:00 +00:00
parent f4338ba51f
commit e749367886
7 changed files with 250 additions and 69 deletions

31
src/jlox/environment.d Normal file
View file

@ -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 ~ "'.");
}
}

View file

@ -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); */
/* } */
/* } */

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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;
}
}
}

46
src/jlox/stmt.d Normal file
View file

@ -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;
}
}

View file

@ -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;