12.4
change lqvzqoxzkwlrrkloyxzmrmznrpnvkmrx
commit df2fe3d13c82fdc48b3f0c0a26abbd069bcdd27b
author Alpha Chen <alpha@kejadlen.dev>
date
parent plqlytxs
diff --git a/ruby/lib/lox/expr.rb b/ruby/lib/lox/expr.rb
index a3f40a2..52e2a82 100644
--- a/ruby/lib/lox/expr.rb
+++ b/ruby/lib/lox/expr.rb
@@ -13,9 +13,11 @@ module Lox
     expr :Assign, :name, :value
     expr :Binary, :left, :op, :right
     expr :Call, :callee, :paren, :args
+    expr :Get, :object, :name
     expr :Grouping, :expr
     expr :Literal, :value
     expr :Logical, :left, :op, :right
+    expr :Set, :object, :name, :value
     expr :Unary, :op, :right
     expr :Variable, :name
   end
diff --git a/ruby/lib/lox/instance.rb b/ruby/lib/lox/instance.rb
index 0b74998..8b27905 100644
--- a/ruby/lib/lox/instance.rb
+++ b/ruby/lib/lox/instance.rb
@@ -1,8 +1,22 @@
+require_relative "error"
+
 module Lox
   class Instance
 
     def initialize(klass)
       @klass = klass
+
+      @fields = {}
+    end
+
+    def get(name)
+      raise RuntimeError.new(name, "Undefined property '#{name.lexeme}'.") unless @fields.has_key?(name.lexeme)
+
+      @fields.fetch(name.lexeme)
+    end
+
+    def set(name, value)
+      @fields[name.lexeme] = value
     end
 
     def to_s = "#{@klass.name} instance"
diff --git a/ruby/lib/lox/interpreter.rb b/ruby/lib/lox/interpreter.rb
index e0ad8aa..c8fe02a 100644
--- a/ruby/lib/lox/interpreter.rb
+++ b/ruby/lib/lox/interpreter.rb
@@ -121,6 +121,16 @@ module Lox
       evaluate(expr.right)
     end
 
+    def visit_set(expr)
+      object = evaluate(expr.object)
+
+      raise RuntimeError.new(expr.name, "Only instances have fields.") unless object.is_a?(Instance)
+
+      value = evaluate(expr.value)
+      object.set(expr.name, value)
+      value
+    end
+
     def visit_unary(expr)
       right = evaluate(expr.right)
 
@@ -206,6 +216,13 @@ module Lox
       func.call(self, args)
     end
 
+    def visit_get(expr)
+      object = evaluate(expr.object)
+      raise RuntimeError.new(expr.name, "Only instances have properties.") unless object.is_a?(Instance)
+
+      object.get(expr.name)
+    end
+
     private
 
     def truthy?(value) = !!value
diff --git a/ruby/lib/lox/parser.rb b/ruby/lib/lox/parser.rb
index f087ec4..29f58b1 100644
--- a/ruby/lib/lox/parser.rb
+++ b/ruby/lib/lox/parser.rb
@@ -16,7 +16,8 @@ module Lox
         statements << declaration
       end
       statements
-    rescue ParseError
+    rescue ParseError => e
+      $stderr.puts e.message
       synchronize!
     end
 
@@ -172,16 +173,19 @@ module Lox
     def assignment
       expr = or_
 
-      if match?(:EQUAL)
-        eq = prev
-        value = assignment
+      return expr unless match?(:EQUAL)
 
-        raise ParseError.new(eq, "Invalid assignment target.") unless expr.instance_of?(Expr::Variable)
+      eq = prev
+      value = assignment
 
-        return Expr::Assign.new(expr.name, value)
+      case expr
+      when Expr::Variable
+        Expr::Assign.new(expr.name, value)
+      when Expr::Get
+        Expr::Set.new(expr.object, expr.name, value)
+      else
+        raise ParseError.new(eq, "Invalid assignment target.")
       end
-
-      expr
     end
 
     def or_
@@ -272,6 +276,9 @@ module Lox
       loop do
         if match?(:LEFT_PAREN)
           expr = finish_call(expr)
+        elsif match?(:DOT)
+          name = consume!(:IDENTIFIER, "Expect property name after '.'.")
+          expr = Expr::Get.new(expr, name)
         else
           break
         end
diff --git a/ruby/lib/lox/resolver.rb b/ruby/lib/lox/resolver.rb
index 224ceb5..2ea0471 100644
--- a/ruby/lib/lox/resolver.rb
+++ b/ruby/lib/lox/resolver.rb
@@ -77,9 +77,22 @@ module Lox
 
     def visit_binary(expr) = resolve(expr.left, expr.right)
     def visit_call(expr) = resolve(expr.callee, *expr.args)
+
+    def visit_get(expr)
+      resolve(expr.object)
+      nil
+    end
+
     def visit_grouping(expr) = resolve(expr.expr)
     def visit_literal(expr) = nil
     def visit_logical(expr) = resolve(expr.left, expr.right)
+
+    def visit_set(expr)
+      resolve(expr.value)
+      resolve(expr.object)
+      nil
+    end
+
     def visit_unary(expr) = resolve(expr.right)
 
     private
diff --git a/ruby/test/lox/test_interpreter.rb b/ruby/test/lox/test_interpreter.rb
index 9abf291..ad1fd14 100644
--- a/ruby/test/lox/test_interpreter.rb
+++ b/ruby/test/lox/test_interpreter.rb
@@ -336,6 +336,15 @@ class TestInterpreter < Lox::Test
     SRC
   end
 
+  def test_set
+    assert_interpreted "bar", <<~SRC
+      class Bagel {}
+      var bagel = Bagel();
+      bagel.foo = "bar";
+      print bagel.foo;
+    SRC
+  end
+
   private
 
   def assert_interpreted(expected, src)