Resolving and Binding 11

This commit is contained in:
nazrin 2025-06-02 16:04:04 +00:00
parent a1acefab0e
commit 52a7b73a9e
14 changed files with 263 additions and 22 deletions

View file

@ -6,3 +6,4 @@ template defaultCtor(){
this.tupleof[i] = a;
}
}

View file

@ -20,6 +20,15 @@ class Environment{
return enclosing.get(name);
throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'.");
}
Environment ancestor(uint distance){
Environment environment = this;
for(uint i = 0; i < distance; i++)
environment = environment.enclosing;
return environment;
}
TValue getAt(uint distance, string name){
return ancestor(distance).values[name];
}
void assign(Token name, TValue value){
if(name.lexeme in values)
return values[name.lexeme] = value;
@ -27,5 +36,8 @@ class Environment{
return enclosing.assign(name, value);
throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'.");
}
void assignAt(uint distance, Token name, TValue value){
ancestor(distance).values[name.lexeme] = value;
}
}

View file

@ -21,7 +21,7 @@ abstract class Expr{
R visit(Unary expr);
R visit(Variable expr);
}
private alias rTypes = AliasSeq!(string, TValue);
private alias rTypes = AliasSeq!(string, TValue, void);
static foreach(T; rTypes)
abstract T accept(Visitor!T visitor);
private template defCtorAndAccept(){

View file

@ -97,7 +97,10 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
return;
throw new RuntimeError(operator, "Operand must be a number.");
}
private uint[Expr] locals;
void resolve(Expr expr, uint depth){
locals[expr] = depth;
}
void visit(Stmt.Block stmt){
executeBlock(stmt.statements, new Environment(environment));
@ -212,11 +215,20 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
return func.call(this, arguments.array);
}
TValue visit(Expr.Variable expr){
return environment.get(expr.name);
return lookUpVariable(expr.name, expr);
}
private TValue lookUpVariable(Token name, Expr expr){
if(const distance = expr in locals)
return environment.getAt(*distance, name.lexeme);
else
return globals.get(name);
}
TValue visit(Expr.Assign expr){
TValue value = evaluate(expr.value);
environment.assign(expr.name, value);
if(const distance = expr in locals)
environment.assignAt(*distance, expr.name, value);
else
globals.assign(expr.name, value);
return value;
}
}

View file

@ -14,6 +14,11 @@ import jlox.parser;
import jlox.interpreter;
import jlox.expr;
import jlox.stmt;
import jlox.resolver;
enum RetVal{
success = 0, other = 1, runtime = 2
}
private class MainException : Exception{
this(){
@ -52,7 +57,11 @@ static class Lox{
Parser parser = new Parser(tokens);
Stmt[] statements = parser.parse();
if(hadError)
return;
Resolver resolver = new Resolver(interpreter);
resolver.resolve(statements);
if(hadError)
return;
@ -85,7 +94,7 @@ int main(string[] argv){
try{
if(args.arg("path") && args.option("command")){
stderr.writeln("Cant have both path and --cmd");
return 3;
return RetVal.other;
}
if(auto cmd = args.option("command"))
Lox.run(cmd);
@ -94,8 +103,8 @@ int main(string[] argv){
else
Lox.runPrompt();
} catch(MainException rte){
return Lox.hadError ? 1 : 2;
return Lox.hadError ? RetVal.other : RetVal.runtime;
}
return 0;
return RetVal.success;
}

143
src/jlox/resolver.d Normal file
View file

@ -0,0 +1,143 @@
module jlox.resolver;
import std.container.slist;
import std.stdio;
import std.range : enumerate;
import jlox.main;
import jlox.token;
import jlox.stmt;
import jlox.expr;
import jlox.interpreter;
import common.util;
private enum FunctionType { NONE, FUNCTION }
class Resolver : Stmt.Visitor!void, Expr.Visitor!void {
private Interpreter interpreter;
this(Interpreter interpreter){
this.interpreter = interpreter;
}
private FunctionType currentFunction = FunctionType.NONE;
private SList!(bool[string]) scopes;
private void resolve(Stmt stmt) => stmt.accept(this);
private void resolve(Expr expr) => expr.accept(this);
void resolve(Stmt[] statements){
foreach(statement; statements)
resolve(statement);
}
private void resolveLocal(Expr expr, Token name){
foreach(i, scp; scopes[].enumerate){
if(name.lexeme in scp){
interpreter.resolve(expr, cast(uint)i);
return;
}
}
}
private void resolveFunction(Stmt.Function func, FunctionType type){
FunctionType enclosingFunction = currentFunction;
currentFunction = type;
scope(exit)
currentFunction = enclosingFunction;
beginScope();
foreach(param; func.params){
declare(param);
define(param);
}
resolve(func.body);
endScope();
}
private void endScope() => scopes.removeFront();
private void beginScope(){
bool[string] s;
scopes.insertFront(s);
}
private void declare(Token name){
if(scopes.empty)
return;
bool[string] scp = scopes.front;
if(name.lexeme in scp)
Lox.error(name, "Already a variable with this name in this scope.");
scp[name.lexeme] = false;
}
private void define(Token name){
if(scopes.empty)
return;
scopes.front[name.lexeme] = true;
}
void visit(Stmt.Block stmt){
beginScope();
resolve(stmt.statements);
endScope();
}
void visit(Stmt.Var stmt){
declare(stmt.name);
if(stmt.initialiser !is null)
resolve(stmt.initialiser);
define(stmt.name);
}
void visit(Stmt.Function stmt){
declare(stmt.name);
define(stmt.name);
resolveFunction(stmt, FunctionType.FUNCTION);
}
void visit(Expr.Variable expr){
if(!scopes.empty && scopes.front.get(expr.name.lexeme, true) == false)
Lox.error(expr.name, "Can't read local variable in its own initializer.");
resolveLocal(expr, expr.name);
}
void visit(Expr.Assign expr){
resolve(expr.value);
resolveLocal(expr, expr.name);
}
void visit(Stmt.Expression stmt){
resolve(stmt.expression);
}
void visit(Stmt.If stmt){
resolve(stmt.condition);
resolve(stmt.thenBranch);
if(stmt.elseBranch !is null)
resolve(stmt.elseBranch);
}
void visit(Stmt.Print stmt){
version(LoxPrintMultiple){
foreach(expr; stmt.expressions)
resolve(expr);
} else {
resolve(stmt.expression);
}
}
void visit(Stmt.Return stmt){
if(currentFunction == FunctionType.NONE)
Lox.error(stmt.keyword, "Can't return from top-level code.");
if(stmt.value !is null)
resolve(stmt.value);
}
void visit(Stmt.While stmt){
resolve(stmt.condition);
resolve(stmt.body);
}
void visit(Expr.Binary expr){
resolve(expr.left);
resolve(expr.right);
}
void visit(Expr.Call expr){
resolve(expr.callee);
foreach(argument; expr.arguments)
resolve(argument);
}
void visit(Expr.Grouping expr){
resolve(expr.expression);
}
void visit(Expr.Literal expr){}
void visit(Expr.Logical expr){
resolve(expr.left);
resolve(expr.right);
}
void visit(Expr.Unary expr){
resolve(expr.right);
}
}

View file

@ -1,23 +1,49 @@
#!/bin/env rdmd
import std.process;
import std.concurrency;
import std.conv;
import std.string, std.format;
import std.algorithm, std.range;
void main(){
string fib(uint n){
string r = "";
double a = 0;
double temp;
for(double b = 1; a <= n; b = temp + b){
r ~= a.to!string ~ "\n";
temp = a;
a = b;
}
return r;
}
assert([ "./lox", "test/closure.lox" ].execute.output == "1\n2\n");
assert([ "./lox", "test/fib_for.lox" ].execute.output == fib(6765));
assert([ "./lox", "test/fib_recursive.lox" ].execute.output == fib(34));
assert([ "./lox", "test/fib_closure.lox" ].execute.output == fib(34));
auto scheduler = new ThreadScheduler();
scheduler.spawn((){ "./test/closure.lox".run.output.match("1\n2\n"); });
scheduler.spawn((){ "./test/scope.lox".run.output.match("global first first second first ".replace(' ', '\n').repeat(2).join("\n")); });
scheduler.spawn((){ "./test/fib_for.lox".run.output.match(fib(6765)); });
scheduler.spawn((){ "./test/fib_recursive.lox".run.output.match(fib(34)); });
scheduler.spawn((){ "./test/fib_closure.lox".run.output.match(fib(34)); });
scheduler.spawn((){ "./test/err/already_defined.lox".shouldFail(RetVal.other, "Already a variable with this name"); });
scheduler.spawn((){ "./test/err/undefined_var.lox".shouldFail(RetVal.runtime, "Undefined variable"); });
scheduler.spawn((){ "./test/err/self_ref_vardecl.lox".shouldFail(RetVal.runtime, "Undefined variable"); });
scheduler.spawn((){ "./test/err/invalid_syntax.lox".shouldFail(RetVal.other); });
scheduler.spawn((){ "./test/err/global_scope_return.lox".shouldFail(RetVal.other, "Can't return from top-level code"); });
}
enum RetVal{
success = 0, other = 1, runtime = 2
}
string fib(uint n){
string r = "";
double a = 0;
double temp;
for(double b = 1; a <= n; b = temp + b){
r ~= a.to!string ~ "\n";
temp = a;
a = b;
}
return r;
}
auto run(string file) => [ "./lox", file ].execute;
void match(string res, string correct){
assert(res == correct, "Match failed\n-- Got --\n%s\n-- Expected --\n%s".format(res, correct));
}
void shouldFail(string file, int code = 1, string msg = null){
auto c = file.run;
assert(c.status == code, "Expected %s to fail with code %d but got %d".format(file, code, c.status));
assert(!msg || c.output.toLower.indexOf(msg.toLower) >= 0, "ShouldFail %s failed\n-- Got --\n%s\n-- Expected --\n%s".format(file, c.output, msg));
assert(c.output.indexOf("_Dmain") == -1, "ShouldFail %s got D exception\n%s".format(file, c.output));
}

View file

@ -0,0 +1,6 @@
fun bad(){
var a = "first";
var a = "second";
}

View file

@ -0,0 +1,3 @@
return ":)";

View file

@ -0,0 +1,3 @@
foo

View file

@ -0,0 +1,3 @@
var x = x + 1;

View file

@ -0,0 +1,3 @@
print hello;

19
test/scope.lox Normal file
View file

@ -0,0 +1,19 @@
var a = "global";
fun f(){
print a;
var a = "first";
print a;
{
print a;
var a = "second";
print a;
}
print a;
}
f();
print "";
f();

1
test/test.lox Normal file
View file

@ -0,0 +1 @@