diff --git a/ruby/lib/lox/environment.rb b/ruby/lib/lox/environment.rb index b568f69..15cf9cc 100644 --- a/ruby/lib/lox/environment.rb +++ b/ruby/lib/lox/environment.rb @@ -2,7 +2,8 @@ require_relative "error" module Lox class Environment - def initialize + def initialize(enclosing = nil) + @enclosing = enclosing @values = {} end @@ -13,13 +14,27 @@ module Lox def get(token) name = token.lexeme - @values.fetch(name) { raise RuntimeError.new(token, "Undefined variable '#{name}'.") } + @values.fetch(name) { + raise RuntimeError.new(token, "Undefined variable '#{name}'.") if @enclosing.nil? + + @enclosing.get(token) + } end def assign(name, value) - raise RuntimeError.new(name, "Undefined variable '#{name.lexeme}'.") unless @values.has_key?(name.lexeme) + lexeme = name.lexeme + + if @values.has_key?(lexeme) + @values[lexeme] = value + return + end + + unless @enclosing.nil? + @enclosing.assign(name, value) + return + end - @values[name.lexeme] = value + raise RuntimeError.new(name, "Undefined variable '#{lexeme}'.") end end end diff --git a/ruby/test/lox/test_environment.rb b/ruby/test/lox/test_environment.rb index 8079a17..1838a3d 100644 --- a/ruby/test/lox/test_environment.rb +++ b/ruby/test/lox/test_environment.rb @@ -5,6 +5,8 @@ require "lox/error" require "lox/token" class TestEnvironment < Lox::Test + NAME_TOKEN = Lox::Token.new(:IDENTIFIER, "name", "name", 0) + def setup @env = Lox::Environment.new end @@ -12,12 +14,34 @@ class TestEnvironment < Lox::Test def test_define @env.define("name", "value") - assert_equal "value", @env.get(Lox::Token.new(:IDENTIFIER, "name", "name", 0)) + assert_equal "value", @env.get(NAME_TOKEN) end def test_get assert_raises Lox::RuntimeError, "Undefined variable name 'name'." do - @env.get(Lox::Token.new(:IDENTIFIER, "name", "name", 0)) + @env.get(NAME_TOKEN) end end + + def test_enclosing_define + @env.define("name", "value") + enclosed = Lox::Environment.new(@env) + assert_equal "value", enclosed.get(NAME_TOKEN) + + enclosed.define("name", "foo") + assert_equal "foo", enclosed.get(NAME_TOKEN) + end + + def test_enclosing_assign + @env.define("name", "foo") + enclosed = Lox::Environment.new(@env) + enclosed.assign(NAME_TOKEN, "bar") + assert_equal "bar", enclosed.get(NAME_TOKEN) + assert_equal "bar", @env.get(NAME_TOKEN) + + enclosed.define("name", "baz") + enclosed.assign(NAME_TOKEN, "qux") + assert_equal "qux", enclosed.get(NAME_TOKEN) + assert_equal "bar", @env.get(NAME_TOKEN) + end end