diff --git a/lib/poison/bootstrap/compiler/compiler.rb b/lib/poison/bootstrap/compiler/compiler.rb index bc40039..ee02f2b 100644 --- a/lib/poison/bootstrap/compiler/compiler.rb +++ b/lib/poison/bootstrap/compiler/compiler.rb @@ -91,64 +91,31 @@ def imaginary(node) end # Creates a Table object. - # - # A table is a container with index-access or key-access - # - # foo = ("bar", "baz") # a list - # foo at(0) # => "bar" - # - # foo = (bar = "baz") # a dict - # foo at("bar") # => "baz" - # - # From _why's Potion, it's unclear what the behaviour should - # be for a table created with a mix of assign and values - # - # foo = (bar = "baz", "bat") - # foo at(0) # => nil - # foo at('bar') # => "baz" - # foo at(1) # => nil - # - # So what we do for Poison is this: if we find any of the - # table expressions to be an assignment, then the table is - # backed by a ruby Hash, otherwise its backed by a ruby List. - # def table(node) - if node.entries.any? { |e| e.kind_of?(Syntax::Assign) } - # A hash, all entries not being an assign, get a value of nil - - g.push_cpath_top - g.find_const :Hash - g.push node.entries.size - g.send :new_from_literal, 1 - - node.entries.each do |entry| - g.dup - if entry.kind_of? Syntax::Assign - # if key is a single name, we use its string value as key - if entry.name.expressions.size == 1 && - entry.name.expressions.first.kind_of?(Syntax::Message) - g.push_literal entry.name.expressions.first.name - else - entry.name.visit self - end - entry.value.visit self + g.push_cpath_top + g.find_const :Table + g.send :new, 0 + + node.entries.each_with_index do |entry, index| + g.dup + g.push_literal index + + if entry.kind_of?(Syntax::Assign) + if entry.name.kind_of?(Syntax::Expression) && + entry.name.expressions.first.kind_of?(Syntax::Message) + g.push_literal entry.name.expressions.first.name.to_sym else - entry.visit self - g.push_nil + entry.name.visit self end - g.send :[]=, 2 - g.pop + entry.value.visit self + + g.send :put_key, 3 + else + entry.visit self + g.send :put_val, 2 end - else - # A list - node.entries.each { |entry| entry.visit self } - g.make_array node.entries.size + g.pop end - - g.push_cpath_top - g.find_const :Table - g.swap - g.send :new, 1 end end end diff --git a/lib/poison/bootstrap/library/table.rb b/lib/poison/bootstrap/library/table.rb index 1c9a1c6..dd2d4a9 100644 --- a/lib/poison/bootstrap/library/table.rb +++ b/lib/poison/bootstrap/library/table.rb @@ -1,20 +1,58 @@ -# A Table object is created using Poison table literals: +# Poison Table is inspired by Lua's but not necessarily having the +# same exact behaviour. +# +# A table can be used as a list of objects or as a map of key-values +# +# table objects are created using Poison table literals: # # Creates a "list" like object: -# (foo, bar) +# table = ("foo", "bar") +# table at(0) #=> "foo" +# table at(1) #=> "bar" # # Creates a "dictionary" like object: -# (foo, bar) +# table = (foo = "bar") +# table at(0) #=> "bar" +# table foo #=> "bar" +# # class Table - def initialize(collection) - @collection = collection + Slot = Struct.new(:index, :key, :value) + + def initialize + @ary = Array.new + @map = Hash.new + end + + def delete_at(index) + slot = @ary[index] + @map.delete @ary[index].key if slot && slot.key + @ary.delete_at index + end + + def put_val(index, value) + delete_at index + @ary[index] = Slot.new(index, nil, value) + value + end + + def key_accessor(key) + metaclass.send :define_method, "pn:#{key}", lambda { at(key) } + end + + def put_key(index, key, value) + delete_at index + @map[key] = @ary[index] = Slot.new(index, key, value) + key_accessor key if key.kind_of?(Symbol) + value end - def pn_at(key) - @collection[key] + def at(key) + return @map[key].value if @map.key?(key) + @ary[key].value if @ary[key] end + alias_method :pn_at, :at poison_methods diff --git a/spec/library/table_spec.rb b/spec/library/table_spec.rb index caea658..3832f2f 100644 --- a/spec/library/table_spec.rb +++ b/spec/library/table_spec.rb @@ -11,3 +11,43 @@ table.should be_kind_of(Table) end end + +describe "Table#at" do + it "can access values by index" do + table = Poison::CodeLoader.execute "(9, 8, 7)" + table.poison(:at, 0).should == 9 + table.poison(:at, 1).should == 8 + table.poison(:at, 2).should == 7 + end + + it "can access values by key" do + table = Poison::CodeLoader.execute "(a = 1, b = 2, c = 3)" + table.poison(:at, :b).should == 2 + end + + it "can access value by named key or index" do + table = Poison::CodeLoader.execute "(9, b = 2, 7)" + table.poison(:at, 0).should == 9 + table.poison(:at, :b).should == 2 + table.poison(:at, 1).should == 2 + table.poison(:at, 2).should == 7 + end + + it "can access value by expression key or index" do + table = Poison::CodeLoader.execute "('bar' = 90, 'foo' = 2)" + table.poison(:at, 1).should == 2 + table.poison(:at, "foo").should == 2 + end +end + +describe "Table accessor" do + it "is generated for an assign having a name as left hand side" do + table = Poison::CodeLoader.execute "(9, foo = 42, 'bar' = 7)" + table.poison(:foo).should == 42 + end + + it "is not generated for an assign having an expression as left hand side" do + table = Poison::CodeLoader.execute "(9, foo = 42, 'bar' = 7)" + table.should_not respond_to("pn:bar") + end +end