Module: Zippo::BinaryStructure::CodeGen
- Defined in:
- lib/zippo/binary_structure/meta.rb
Overview
Profiling shows most of our time is spent iterating over various fields so let’s unroll those loops in advance.
Class Method Summary collapse
- .call_helper(receiver, meth, fields) ⇒ Object
- .define_converter_for(klass, meth, other) ⇒ Object
- .define_defaults_method_for(klass) ⇒ Object
-
.define_helper(klass, meth, fields) ⇒ Object
Defines a method on the specified class that sets a bunch of fields at once.
- .define_pack_method_for(klass) ⇒ Object
-
.define_unpack_method_for(klass) ⇒ Object
XXX - should write a spec for the “multiple helpers” implementation, none of the current binary structures would make use of it, as they all have a bunch of fixed fields, then the variable fields at the end.
- .fields_as_args(fields) ⇒ Object
Class Method Details
.call_helper(receiver, meth, fields) ⇒ Object
23 24 25 |
# File 'lib/zippo/binary_structure/meta.rb', line 23 def call_helper(receiver, meth, fields) "#{receiver}.#{meth}(#{fields_as_args(fields)})" end |
.define_converter_for(klass, meth, other) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/zippo/binary_structure/meta.rb', line 75 def define_converter_for(klass, meth, other) # serialize the klass reference "other" # we can't just use to_s, since that won't work if the class is anonymous class_ref = "ObjectSpace._id2ref(#{other.object_id})" common_fields = klass.common_fields_with(other) helper_name = "initialize_from_#{object_id}" define_helper(other, helper_name, common_fields) default_fields = other.structure.fields.select do |field| field.[:default] || field.[:signature] end.reject do |field| common_fields.include? field.name end define_helper(other, "other_fields", default_fields.map(&:name)) default_values = default_fields.map do |f| f.[:signature] || f.[:default] end klass.class_eval """ def #{meth} obj = #{class_ref}.new obj.other_fields(#{default_values.map(&:inspect).join(', ')}) #{call_helper("obj", helper_name, common_fields)} obj end """ end |
.define_defaults_method_for(klass) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/zippo/binary_structure/meta.rb', line 27 def define_defaults_method_for(klass) buf = [] buf << "def defaults" klass.structure.fields.each do |field| buf << %(@#{field.name} = #{field.[:default].inspect}) if field.[:default] buf << %(@#{field.name} = #{field.[:signature].inspect}) if field.[:signature] end buf << "self" buf << "end" klass.class_eval buf.join("\n") end |
.define_helper(klass, meth, fields) ⇒ Object
Defines a method on the specified class that sets a bunch of fields at once.
9 10 11 12 13 14 15 16 17 |
# File 'lib/zippo/binary_structure/meta.rb', line 9 def define_helper(klass, meth, fields) buf = [] buf << "def #{meth}(#{0.upto(fields.size - 1).map { |x|"a#{x}" }.join(',')})" fields.each_with_index do |field, i| buf << "@#{field} = a#{i}" end buf << "end" klass.class_eval buf.join("\n") end |
.define_pack_method_for(klass) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/zippo/binary_structure/meta.rb', line 107 def define_pack_method_for(klass) fields = klass.structure.fields.map(&:name) packing_string = klass.structure.fields.map(&:pack).join('') helper_method = """ def fields_for_packing [#{fields_as_args(fields)}] end """ klass.structure.owner_class.class_eval helper_method klass.class_eval do define_method :pack do |obj| @io << obj.fields_for_packing.pack(packing_string) end end end |
.define_unpack_method_for(klass) ⇒ Object
XXX - should write a spec for the “multiple helpers” implementation, none of the current binary structures would make use of it, as they all have a bunch of fixed fields, then the variable fields at the end. a test should def self.define_unpack_method
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/zippo/binary_structure/meta.rb', line 44 def define_unpack_method_for(klass) buf = "def unpack\n" buf << "obj = self.class.structure.owner_class.new\n" helper_num = -1 # keep track of the number of helper methods we've created field_buf = [] # iterate over the fields, gathering up the fixed fields in a # group. once a variable field is hit, unpack the current group of # fixed fields, then use that to read any variable fields. repeat. klass.structure.fields.each do |field| if field.[:size] # unpack fixed group unless field_buf.empty? s = field_buf.map(&:width).reduce(&:+) buf << %{arr = @io.read(#{s}).unpack("#{field_buf.map(&:pack).join('')}")\n} helper_name = "binary_structure_unpack_helper_#{helper_num += 1}" define_helper(klass.structure.owner_class, helper_name, field_buf.map(&:name)) buf << "obj.#{helper_name}(*arr)\n" end # unpack variable-length field buf << %{obj.instance_variable_set :@#{field.name}, @io.read(obj.#{field.[:size]})\n} field_buf = [] else field_buf << field end end buf << "obj\n" buf << "end\n" klass.class_eval(buf) end |
.fields_as_args(fields) ⇒ Object
19 20 21 |
# File 'lib/zippo/binary_structure/meta.rb', line 19 def fields_as_args(fields) fields.map { |f| "@#{f}" }.join(", ") end |