Resolving and Binding 11
This commit is contained in:
parent
a1acefab0e
commit
52a7b73a9e
14 changed files with 263 additions and 22 deletions
|
|
@ -6,3 +6,4 @@ template defaultCtor(){
|
|||
this.tupleof[i] = a;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
143
src/jlox/resolver.d
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
56
test/all.d
56
test/all.d
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
6
test/err/already_defined.lox
Normal file
6
test/err/already_defined.lox
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
fun bad(){
|
||||
var a = "first";
|
||||
var a = "second";
|
||||
}
|
||||
|
||||
3
test/err/global_scope_return.lox
Normal file
3
test/err/global_scope_return.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
return ":)";
|
||||
|
||||
3
test/err/invalid_syntax.lox
Normal file
3
test/err/invalid_syntax.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
foo
|
||||
|
||||
3
test/err/self_ref_vardecl.lox
Normal file
3
test/err/self_ref_vardecl.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
var x = x + 1;
|
||||
|
||||
3
test/err/undefined_var.lox
Normal file
3
test/err/undefined_var.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
print hello;
|
||||
|
||||
19
test/scope.lox
Normal file
19
test/scope.lox
Normal 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
1
test/test.lox
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
Loading…
Add table
Add a link
Reference in a new issue