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

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

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

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

@ -30,7 +30,11 @@ module Lox
def execute(stmt) = stmt.accept(self)
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
def visit_block(stmt)
@ -125,8 +129,8 @@ module Lox
end
def lookup_var(name, expr)
if @locals.has_key?(expr)
distance = @locals.fetch(expr)
if @locals.has_key?(expr.object_id)
distance = @locals.fetch(expr.object_id)
@env.get_at(distance, name.lexeme)
else
@globals.get(name)
@ -136,8 +140,8 @@ module Lox
def visit_assign(expr)
value = evaluate(expr.value)
if @locals.has_key?(expr)
distance = @locals.get(expr)
if @locals.has_key?(expr.object_id)
distance = @locals.fetch(expr.object_id)
@env.assign_at(distance, expr.name, value)
else
@globals.assign(expr.name, value)

@ -24,7 +24,6 @@ module Lox
end
def visit_expr(stmt) = resolve(stmt.expr)
end
def visit_function(stmt)
declare(stmt.name)
@ -35,12 +34,14 @@ module Lox
end
def visit_if(stmt)
resolve(stmt.condition, stmt.then)
resolve(stmt.cond, stmt.then)
resolve(stmt.else) if stmt.else
end
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)
declare(stmt.name)
@ -49,7 +50,7 @@ module Lox
nil
end
def visit_while(stmt) = resolve(stmt.condition, stmt.body)
def visit_while(stmt) = resolve(stmt.cond, stmt.body)
def visit_variable(expr)
if !@scopes.empty? && @scopes.last[expr.name.lexeme] == false
@ -75,16 +76,18 @@ module Lox
private
def with_block
@scopes.unshift({})
def with_scope
@scopes.push({})
yield
@scopes.shift
@scopes.pop
end
def declare(name)
scope = @scopes.last
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
end
@ -92,11 +95,11 @@ module Lox
scope = @scopes.last
return if scope.nil?
scopes[name.lexeme] = true
scope[name.lexeme] = true
end
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
scope, depth = scope_and_depth
@ -109,7 +112,7 @@ module Lox
declare(param)
define(param)
end
resolve(fn.body)
resolve(*fn.body)
end
end

@ -56,4 +56,14 @@ class TestEnvironment < Lox::Test
assert_equal "foo", @env.get_at(0, "name")
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

@ -2,12 +2,12 @@ require_relative "../test_helper"
require "lox/interpreter"
require "lox/parser"
require "lox/resolver"
require "lox/scanner"
class TestInterpreter < Lox::Test
def setup
@scanner = Lox::Scanner.new
@interpreter = Lox::Interpreter.new
end
# def test_literal
@ -234,9 +234,25 @@ class TestInterpreter < Lox::Test
sayHi("Dear", "Reader");
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
0
1
@ -278,6 +294,18 @@ class TestInterpreter < Lox::Test
SRC
end
def test_resolver
assert_interpreted <<~EXPECTED.chomp, <<~SRC
1
EXPECTED
var foo = 1;
{
print foo;
var foo = 2;
}
SRC
end
private
def assert_interpreted(expected, src)
@ -303,7 +331,12 @@ class TestInterpreter < Lox::Test
def interpret(src)
with_stdout {
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

@ -86,6 +86,12 @@ class TestParser < Lox::Test
assert_parsed "(return (var foo))", :statement, "return foo;"
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
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