13.1
change xkkunxwktzyrpzmkmxwvqmnnqmsosmrs
commit cb35d415fd535c80dde0ad1e00841ba071efe2d5
author Alpha Chen <alpha@kejadlen.dev>
date
parent qvqpront
diff --git a/ruby/lib/lox/ast_printer.rb b/ruby/lib/lox/ast_printer.rb
index 8a89523..2a55dab 100644
--- a/ruby/lib/lox/ast_printer.rb
+++ b/ruby/lib/lox/ast_printer.rb
@@ -50,7 +50,8 @@ module Lox
     end
 
     def visit_class(stmt)
-      parenthesize("class #{stmt.name.lexeme}", *stmt.methods)
+      exprs = [stmt.superclass, *stmt.methods].compact
+      parenthesize("class #{stmt.name.lexeme}", *exprs)
     end
 
     def visit_function(stmt)
diff --git a/ruby/lib/lox/interpreter.rb b/ruby/lib/lox/interpreter.rb
index 95c8737..50939ab 100644
--- a/ruby/lib/lox/interpreter.rb
+++ b/ruby/lib/lox/interpreter.rb
@@ -45,13 +45,21 @@ module Lox
     end
 
     def visit_class(stmt)
+      superclass = if stmt.superclass
+                     superclass = evaluate(stmt.superclass)
+                     raise RuntimeError.new(stmt.superclass.name, "Superclass must be a class.") unless superclass.is_a?(LoxClass)
+                     superclass
+                   else
+                     nil
+                   end
+
       @env.define(stmt.name.lexeme, nil)
 
       methods = stmt.methods.to_h {|method|
         [method.name.lexeme, Function.new(method, @env, method.name.lexeme == "init")]
       }
 
-      klass = LoxClass.new(stmt.name.lexeme, methods)
+      klass = LoxClass.new(stmt.name.lexeme, superclass, methods)
       @env.assign(stmt.name, klass)
       nil
     end
diff --git a/ruby/lib/lox/lox_class.rb b/ruby/lib/lox/lox_class.rb
index 017ea86..274adc8 100644
--- a/ruby/lib/lox/lox_class.rb
+++ b/ruby/lib/lox/lox_class.rb
@@ -5,8 +5,8 @@ module Lox
 
     attr_reader :name
 
-    def initialize(name, methods)
-      @name, @methods = name, methods
+    def initialize(name, superclass, methods)
+      @name, @superclass, @methods = name, superclass, methods
     end
 
     def find_method(name) = @methods[name]
diff --git a/ruby/lib/lox/parser.rb b/ruby/lib/lox/parser.rb
index 10e42b0..a68b6d5 100644
--- a/ruby/lib/lox/parser.rb
+++ b/ruby/lib/lox/parser.rb
@@ -33,6 +33,14 @@ module Lox
 
     def class_decl
       name = consume!(:IDENTIFIER, "Expect class name.")
+
+      superclass = if match?(:LESS)
+                     consume!(:IDENTIFIER, "Expect superclass name.")
+                     Expr::Variable.new(prev)
+                   else
+                     nil
+                   end
+
       consume!(:LEFT_BRACE, "Expect '{' before class body.")
 
       methods = []
@@ -42,7 +50,7 @@ module Lox
 
       consume!(:RIGHT_BRACE, "Expect '}' after class body.")
 
-      Stmt::Class.new(name, methods)
+      Stmt::Class.new(name, superclass, methods)
     end
 
     def var_declaration
diff --git a/ruby/lib/lox/resolver.rb b/ruby/lib/lox/resolver.rb
index 25e14c6..b1c3f07 100644
--- a/ruby/lib/lox/resolver.rb
+++ b/ruby/lib/lox/resolver.rb
@@ -28,6 +28,12 @@ module Lox
     def visit_class(stmt)
       with_current_class(:CLASS) do
         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
@@ -36,8 +42,6 @@ module Lox
             decl = method.name.lexeme == "init" ? :INIT : :METHOD
             resolve_function(method, decl)
           end
-
-          define(stmt.name)
         end
       end
 
diff --git a/ruby/lib/lox/stmt.rb b/ruby/lib/lox/stmt.rb
index 23d31a6..41a69b3 100644
--- a/ruby/lib/lox/stmt.rb
+++ b/ruby/lib/lox/stmt.rb
@@ -13,7 +13,7 @@ module Lox
     end
 
     stmt :Block, :stmts
-    stmt :Class, :name, :methods
+    stmt :Class, :name, :superclass, :methods
     stmt :Expr, :expr
     stmt :Function, :name, :params, :body
     stmt :If, :cond, :then, :else
diff --git a/ruby/test/lox/test_interpreter.rb b/ruby/test/lox/test_interpreter.rb
index a3f5073..682afc9 100644
--- a/ruby/test/lox/test_interpreter.rb
+++ b/ruby/test/lox/test_interpreter.rb
@@ -451,6 +451,25 @@ class TestInterpreter < Lox::Test
     SRC
   end
 
+  def test_inheritance
+    assert_interpreted "", <<~SRC
+      class Doughnut {}
+      class BostonCream < Doughnut {}
+    SRC
+
+    assert_raises Lox::ResolverError do
+      interpret("class Oops < Oops {}")
+    end
+
+    assert_raises Lox::RuntimeError do
+      interpret(<<~SRC)
+        var NotAClass = "I am totally not a class";
+
+        class Subclass < NotAClass {} // ?!
+      SRC
+    end
+  end
+
   private
 
   def assert_interpreted(expected, src)