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)