diff --git a/ruby/lib/lox/interpreter.rb b/ruby/lib/lox/interpreter.rb
index 50939ab..5b76ef4 100644
--- a/ruby/lib/lox/interpreter.rb
+++ b/ruby/lib/lox/interpreter.rb
@@ -55,11 +55,18 @@ module Lox
@env.define(stmt.name.lexeme, nil)
+ if superclass
+ @env = Environment.new(@env)
+ @env.define("super", superclass)
+ end
+
methods = stmt.methods.to_h {|method|
[method.name.lexeme, Function.new(method, @env, method.name.lexeme == "init")]
}
klass = LoxClass.new(stmt.name.lexeme, superclass, methods)
+ @env = @env.enclosing if superclass
+
@env.assign(stmt.name, klass)
nil
end
@@ -144,6 +151,16 @@ module Lox
value
end
+ def visit_super(expr)
+ distance = @locals.fetch(expr)
+ superclass = @env.get_at(distance, "super")
+ object = @env.get_at(distance-1, "this")
+
+ method = superclass.find_method(expr.method.lexeme)
+ raise RuntimeError.new(expr.method, "Undefined property '#{expr.method.lexeme}'.") if method.nil?
+ method.bind(object)
+ end
+
def visit_this(expr) = lookup_var(expr.keyword, expr)
def visit_unary(expr)
diff --git a/ruby/lib/lox/resolver.rb b/ruby/lib/lox/resolver.rb
index b1c3f07..0683335 100644
--- a/ruby/lib/lox/resolver.rb
+++ b/ruby/lib/lox/resolver.rb
@@ -30,17 +30,20 @@ module Lox
declare(stmt.name)
define(stmt.name)
- if stmt.superclass
- raise ResolverError.new(stmt.superclass.name, "A class can't inherit from itself.") if stmt.name.lexeme == stmt.superclass.name.lexeme
- resolve(stmt.superclass)
- end
-
- with_scope do
- @scopes.last["this"] = true
-
- stmt.methods.each do |method|
- decl = method.name.lexeme == "init" ? :INIT : :METHOD
- resolve_function(method, decl)
+ with_superclass_scope = if stmt.superclass
+ raise ResolverError.new(stmt.superclass.name, "A class can't inherit from itself.") if stmt.name.lexeme == stmt.superclass.name.lexeme
+ resolve(stmt.superclass)
+ ->(&block) { with_scope(super: true) { block.call } }
+ else
+ ->(&block) { block.call }
+ end
+
+ with_superclass_scope.call do
+ with_scope(this: true) do
+ stmt.methods.each do |method|
+ decl = method.name.lexeme == "init" ? :INIT : :METHOD
+ resolve_function(method, decl)
+ end
end
end
end
@@ -117,6 +120,11 @@ module Lox
nil
end
+ def visit_super(expr)
+ resolve_local(expr, expr.keyword)
+ nil
+ end
+
def visit_this(expr)
raise ResolverError.new(expr.keyword, "Can't use 'this' outside of a class.") if @current_class == :NONE
@@ -128,8 +136,8 @@ module Lox
private
- def with_scope
- @scopes.push({})
+ def with_scope(scope={})
+ @scopes.push(scope.transform_keys(&:to_s))
yield
@scopes.pop
end
diff --git a/ruby/test/lox/test_interpreter.rb b/ruby/test/lox/test_interpreter.rb
index 4c5d21d..446af71 100644
--- a/ruby/test/lox/test_interpreter.rb
+++ b/ruby/test/lox/test_interpreter.rb
@@ -485,7 +485,7 @@ class TestInterpreter < Lox::Test
end
def test_calling_superclass_methods
- assert_interpreted <<~OUT, <<~SRC
+ assert_interpreted <<~OUT.chomp, <<~SRC
Fry until golden brown.
Pipe full of custard and coat with chocolate.
OUT
@@ -504,6 +504,28 @@ class TestInterpreter < Lox::Test
BostonCream().cook();
SRC
+
+ assert_interpreted "A method", <<~SRC
+ class A {
+ method() {
+ print "A method";
+ }
+ }
+
+ class B < A {
+ method() {
+ print "B method";
+ }
+
+ test() {
+ super.method();
+ }
+ }
+
+ class C < B {}
+
+ C().test();
+ SRC
end
private