main
Alpha Chen 2 years ago
parent 78f3d0ee76
commit 1b3c8b6253

@ -7,12 +7,7 @@ namespace :test do
desc "Run tests on source changes" desc "Run tests on source changes"
task :watch do task :watch do
loop do loop do
sh <<~SH sh "fd .*.rb | entr -d rake test:isolated"
while true
do
fd .*.rb | entr -d rake test:isolated
done
SH
end end
end end
end end

@ -45,6 +45,10 @@ module Lox
parenthesize("return", *exprs) parenthesize("return", *exprs)
end end
def visit_while(stmt)
parenthesize("while", stmt.cond, stmt.body)
end
private private
def parenthesize(name, *exprs) def parenthesize(name, *exprs)

@ -48,7 +48,7 @@ module Lox
end end
def assign_at(distance, name, value) def assign_at(distance, name, value)
ancestor(distance).values[name] = value ancestor(distance).values[name.lexeme] = value
end end
end end
end end

@ -30,7 +30,11 @@ module Lox
def execute(stmt) = stmt.accept(self) def execute(stmt) = stmt.accept(self)
def resolve(expr, depth) def resolve(expr, depth)
@locals[expr] = depth # HACK We use the object id here since we do **NOT** want
# struct equality for getting the depth of a local since
# we can have multiple locals referencing the same variable
# in different scopes on a single line.
@locals[expr.object_id] = depth
end end
def visit_block(stmt) def visit_block(stmt)
@ -125,8 +129,8 @@ module Lox
end end
def lookup_var(name, expr) def lookup_var(name, expr)
if @locals.has_key?(expr) if @locals.has_key?(expr.object_id)
distance = @locals.fetch(expr) distance = @locals.fetch(expr.object_id)
@env.get_at(distance, name.lexeme) @env.get_at(distance, name.lexeme)
else else
@globals.get(name) @globals.get(name)
@ -136,8 +140,8 @@ module Lox
def visit_assign(expr) def visit_assign(expr)
value = evaluate(expr.value) value = evaluate(expr.value)
if @locals.has_key?(expr) if @locals.has_key?(expr.object_id)
distance = @locals.get(expr) distance = @locals.fetch(expr.object_id)
@env.assign_at(distance, expr.name, value) @env.assign_at(distance, expr.name, value)
else else
@globals.assign(expr.name, value) @globals.assign(expr.name, value)

@ -24,7 +24,6 @@ module Lox
end end
def visit_expr(stmt) = resolve(stmt.expr) def visit_expr(stmt) = resolve(stmt.expr)
end
def visit_function(stmt) def visit_function(stmt)
declare(stmt.name) declare(stmt.name)
@ -35,12 +34,14 @@ module Lox
end end
def visit_if(stmt) def visit_if(stmt)
resolve(stmt.condition, stmt.then) resolve(stmt.cond, stmt.then)
resolve(stmt.else) if stmt.else resolve(stmt.else) if stmt.else
end end
def visit_print(stmt) = resolve(stmt.expr) def visit_print(stmt) = resolve(stmt.expr)
def visit_return(stmt) = resolve(stmt.value) if stmt.value def visit_return(stmt)
resolve(stmt.value) if stmt.value
end
def visit_var(stmt) def visit_var(stmt)
declare(stmt.name) declare(stmt.name)
@ -49,7 +50,7 @@ module Lox
nil nil
end end
def visit_while(stmt) = resolve(stmt.condition, stmt.body) def visit_while(stmt) = resolve(stmt.cond, stmt.body)
def visit_variable(expr) def visit_variable(expr)
if !@scopes.empty? && @scopes.last[expr.name.lexeme] == false if !@scopes.empty? && @scopes.last[expr.name.lexeme] == false
@ -75,16 +76,18 @@ module Lox
private private
def with_block def with_scope
@scopes.unshift({}) @scopes.push({})
yield yield
@scopes.shift @scopes.pop
end end
def declare(name) def declare(name)
scope = @scopes.last scope = @scopes.last
return if scope.nil? return if scope.nil?
raise ResolverError.new(name, "Already a variable with this name in this scope") if scope.has_key?(name.lexeme)
scope[name.lexeme] = false scope[name.lexeme] = false
end end
@ -92,11 +95,11 @@ module Lox
scope = @scopes.last scope = @scopes.last
return if scope.nil? return if scope.nil?
scopes[name.lexeme] = true scope[name.lexeme] = true
end end
def resolve_local(expr, name) def resolve_local(expr, name)
scope_and_depth = @scopes.each.with_index.find {|scope, depth| scope.has_key?(name.lexeme) } scope_and_depth = @scopes.reverse.each.with_index.find {|scope, depth| scope.has_key?(name.lexeme) }
return unless scope_and_depth return unless scope_and_depth
scope, depth = scope_and_depth scope, depth = scope_and_depth
@ -109,7 +112,7 @@ module Lox
declare(param) declare(param)
define(param) define(param)
end end
resolve(fn.body) resolve(*fn.body)
end end
end end

@ -56,4 +56,14 @@ class TestEnvironment < Lox::Test
assert_equal "foo", @env.get_at(0, "name") assert_equal "foo", @env.get_at(0, "name")
end end
def test_assign_at
enclosed = Lox::Environment.new(@env)
enclosed.assign_at(0, NAME_TOKEN, "foo")
assert_equal "foo", enclosed.get_at(0, "name")
enclosed.assign_at(1, NAME_TOKEN, "bar")
assert_equal "bar", enclosed.get_at(1, "name")
end
end end

@ -2,12 +2,12 @@ require_relative "../test_helper"
require "lox/interpreter" require "lox/interpreter"
require "lox/parser" require "lox/parser"
require "lox/resolver"
require "lox/scanner" require "lox/scanner"
class TestInterpreter < Lox::Test class TestInterpreter < Lox::Test
def setup def setup
@scanner = Lox::Scanner.new @scanner = Lox::Scanner.new
@interpreter = Lox::Interpreter.new
end end
# def test_literal # def test_literal
@ -234,9 +234,25 @@ class TestInterpreter < Lox::Test
sayHi("Dear", "Reader"); sayHi("Dear", "Reader");
SRC SRC
end
def test_function assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
{
var i = 0; while (i < 3) { { print i; } i = i + 1; }
}
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
for (var i = 0; i < 3; i = i + 1) print i;
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC assert_interpreted <<~EXPECTED.chomp, <<~SRC
0 0
1 1
@ -278,6 +294,18 @@ class TestInterpreter < Lox::Test
SRC SRC
end end
def test_resolver
assert_interpreted <<~EXPECTED.chomp, <<~SRC
1
EXPECTED
var foo = 1;
{
print foo;
var foo = 2;
}
SRC
end
private private
def assert_interpreted(expected, src) def assert_interpreted(expected, src)
@ -303,7 +331,12 @@ class TestInterpreter < Lox::Test
def interpret(src) def interpret(src)
with_stdout { with_stdout {
stmts = Lox::Parser.new(@scanner.scan(src)).parse! stmts = Lox::Parser.new(@scanner.scan(src)).parse!
@interpreter.interpret(stmts) interpreter = Lox::Interpreter.new
resolver = Lox::Resolver.new(interpreter);
resolver.resolve(*stmts);
interpreter.interpret(stmts)
} }
end end

@ -86,6 +86,12 @@ class TestParser < Lox::Test
assert_parsed "(return (var foo))", :statement, "return foo;" assert_parsed "(return (var foo))", :statement, "return foo;"
end end
def test_for
assert_parsed <<~AST.chomp, :statement, "for(var i=0; i<3; i=i+1) print i;"
(block (var i 0.0) (while (< (var i) 3.0) (block (print (var i)) (assign i (+ (var i) 1.0)))))
AST
end
private private
def assert_parsed(expected, name, src) def assert_parsed(expected, name, src)

@ -0,0 +1,73 @@
require_relative "../test_helper"
require "lox/ast_printer"
require "lox/parser"
require "lox/resolver"
require "lox/scanner"
class TestResolver < Lox::Test
def setup
@ast_printer = Lox::AstPrinter.new
@scanner = Lox::Scanner.new
@interpreter = Interpreter.new
@resolver = Lox::Resolver.new(@interpreter)
end
def test_resolver
assert_resolved [
["(var j)", 0],
["(var j)", 2],
["(var j)", 1],
["(assign j (+ (var j) 1.0))", 1],
["(var k)", 0],
["(var k)", 2],
["(var k)", 1],
["(assign k (+ (var k) 1.0))", 1],
], <<~SRC
var i = 0;
while (i < 3) {
print i;
i = i + 1;
}
for(var j=0; j<3; j=j+1) {
print j;
}
{
var k = 0;
while (k < 3) {
{
print k;
}
k = k + 1;
}
}
SRC
end
private
def assert_resolved(expected, src)
stmts = Lox::Parser.new(@scanner.scan(src)).parse!
@resolver.resolve(*stmts);
assert_equal expected, @interpreter.resolves.map {|expr, depth|
[@ast_printer.print(expr), depth]
}
end
class Interpreter
attr_reader :resolves
def initialize
@resolves = []
end
def resolve(expr, depth)
resolves << [expr, depth]
end
end
end
Loading…
Cancel
Save