10.4.3
change vosswqlrmvlzuosvqxmtnozmppzokmnp
commit 780a7cb862edc61d0ad3786dc792e4150eb17b91
author Alpha Chen <alpha@kejadlen.dev>
date
parent qowmqrwy
diff --git a/ruby/Rakefile b/ruby/Rakefile
index ca9dddc..bf533d4 100644
--- a/ruby/Rakefile
+++ b/ruby/Rakefile
@@ -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
diff --git a/ruby/lib/lox/ast_printer.rb b/ruby/lib/lox/ast_printer.rb
index f7578b4..6fb8f45 100644
--- a/ruby/lib/lox/ast_printer.rb
+++ b/ruby/lib/lox/ast_printer.rb
@@ -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)
diff --git a/ruby/lib/lox/environment.rb b/ruby/lib/lox/environment.rb
index f962e90..8df68f2 100644
--- a/ruby/lib/lox/environment.rb
+++ b/ruby/lib/lox/environment.rb
@@ -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
diff --git a/ruby/lib/lox/interpreter.rb b/ruby/lib/lox/interpreter.rb
index c36e7cb..826e260 100644
--- a/ruby/lib/lox/interpreter.rb
+++ b/ruby/lib/lox/interpreter.rb
@@ -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)
diff --git a/ruby/lib/lox/resolver.rb b/ruby/lib/lox/resolver.rb
index 4a376d7..19520d6 100644
--- a/ruby/lib/lox/resolver.rb
+++ b/ruby/lib/lox/resolver.rb
@@ -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
 
diff --git a/ruby/test/lox/test_environment.rb b/ruby/test/lox/test_environment.rb
index fd5b3ee..c53e580 100644
--- a/ruby/test/lox/test_environment.rb
+++ b/ruby/test/lox/test_environment.rb
@@ -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
diff --git a/ruby/test/lox/test_interpreter.rb b/ruby/test/lox/test_interpreter.rb
index 0799786..f2cfcf3 100644
--- a/ruby/test/lox/test_interpreter.rb
+++ b/ruby/test/lox/test_interpreter.rb
@@ -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
 
diff --git a/ruby/test/lox/test_parser.rb b/ruby/test/lox/test_parser.rb
index 7aee4d2..f59cddc 100644
--- a/ruby/test/lox/test_parser.rb
+++ b/ruby/test/lox/test_parser.rb
@@ -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)
diff --git a/ruby/test/lox/test_resolver.rb b/ruby/test/lox/test_resolver.rb
new file mode 100644
index 0000000..8d50cae
--- /dev/null
+++ b/ruby/test/lox/test_resolver.rb
@@ -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