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)