Class: Tem::Builders::Abi
- Inherits:
-
Object
- Object
- Tem::Builders::Abi
- Defined in:
- lib/tem/builders/abi.rb
Overview
Builder class for the ABI (Abstract Binary Interface) builder.
Direct Known Subclasses
Defined Under Namespace
Modules: Impl
Instance Attribute Summary collapse
-
#target ⇒ Object
readonly
The module / class impacted by the builder.
Class Method Summary collapse
-
.define_abi(class_or_module) {|new(class_or_module)| ... } ⇒ Object
Creates a builder targeting a module / class.
Instance Method Summary collapse
-
#conditional_wrapper(name, tag_length, rules) ⇒ Object
Defines methods for handling an ‘enum’-like ABI type whose type is determined by a fixed-length tag that is prefixed to the data.
-
#fixed_length_number(name, bytes, options = {}) ⇒ Object
Defines the methods for handling a fixed-length number type in an ABI.
-
#fixed_length_string(name, bytes, options = {}) ⇒ Object
Defines the methods for handling a fixed-length string type in an ABI.
-
#initialize(target) ⇒ Abi
constructor
Creates a builder targeting a module / class.
-
#object_wrapper(name, object_class, schema, hooks = {}) ⇒ Object
Defines methods for handling a complex ABI structure wrapped into an object.
-
#packed_variable_length_numbers(name, length_type, components, options = {}) ⇒ Object
Defines the methods for handling a group of packed variable-length numbers in the ABI.
-
#variable_length_number(name, length_type, options = {}) ⇒ Object
Defines the methods for handling a variable-length number type in an ABI.
Constructor Details
#initialize(target) ⇒ Abi
Creates a builder targeting a module / class.
368 369 370 |
# File 'lib/tem/builders/abi.rb', line 368 def initialize(target) @target = target end |
Instance Attribute Details
#target ⇒ Object (readonly)
The module / class impacted by the builder.
365 366 367 |
# File 'lib/tem/builders/abi.rb', line 365 def target @target end |
Class Method Details
.define_abi(class_or_module) {|new(class_or_module)| ... } ⇒ Object
Creates a builder targeting a module / class.
The given parameter should be a class or module.
18 19 20 |
# File 'lib/tem/builders/abi.rb', line 18 def self.define_abi(class_or_module) # :yields: abi yield new(class_or_module) end |
Instance Method Details
#conditional_wrapper(name, tag_length, rules) ⇒ Object
Defines methods for handling an ‘enum’-like ABI type whose type is determined by a fixed-length tag that is prefixed to the data.
tag_length indicates the tag’s length, in bytes. The mapping between tags and lower-level ABI types is expressed as an array of rules. Each rule is a hash, and the following attributes are supported.
tag:: an array of numbers; the tag must match this array (de-serializing)
type:: the lower-level ABI type used for serialization/de-serialization
class:: a ruby Class; if present, the value must be a kind of the given
class to match the rule (serializing)
predicate:: a Proc; if present, the Proc is given the value, and must
return a true value for the value to match the rule (serializing)
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/tem/builders/abi.rb', line 326 def conditional_wrapper(name, tag_length, rules) impl = Tem::Builders::Abi::Impl defines = Proc.new do define_method :"read_#{name}" do |array, offset| tag = array[offset, tag_length] matching_rule = rules.find { |rule| rule[:tag] == tag } raise "Rules don't cover tag #{tag.inspect}" unless matching_rule self.send :"read_#{matching_rule[:type]}", array, offset + tag_length end define_method :"read_#{name}_length" do |array, offset| tag = array[offset, tag_length] matching_rule = rules.find { |rule| rule[:tag] == tag } raise "Rules don't cover tag #{tag.inspect}" unless matching_rule if self.respond_to? :"#{matching_rule[:type]}_length" tag_length + self.send(:"#{matching_rule[:type]}_length") else tag_length + self.send(:"read_#{matching_rule[:type]}_length", array, offset + tag_length) end end define_method :"to_#{name}" do |value| matching_rule = rules.find do |rule| next false if rule[:class] && !value.kind_of?(rule[:class]) next false if rule[:predicate] && !rule[:predicate].call(value) true end raise "Rules don't cover #{value.inspect}" unless matching_rule matching_rule[:tag] + self.send(:"to_#{matching_rule[:type]}", value) end define_method(:"#{name}_length") { bytes } end @target.class_eval(&defines) (class << @target; self; end).module_eval(&defines) end |
#fixed_length_number(name, bytes, options = {}) ⇒ Object
Defines the methods for handling a fixed-length number type in an ABI.
The |options| hash supports the following keys:
signed:: if false, the value type cannot hold negative numbers;
signed values are stored using 2s-complement; defaults to true
big_endian:: if true, bytes are sent over the wire in big-endian order
The following methods are defined for a type named ‘name’:
* read_name(array, offset) -> number
* to_name(number) -> array
* signed_to_name(number) -> array # takes signed inputs on unsigned types
* name_length -> number # the number of bytes in the number type
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/tem/builders/abi.rb', line 34 def fixed_length_number(name, bytes, = {}) impl = Tem::Builders::Abi::Impl signed = .fetch :signed, true big_endian = .fetch :big_endian, true defines = Proc.new do define_method :"read_#{name}" do |array, offset| impl.number_from_array array, offset, bytes, signed, big_endian end define_method :"to_#{name}" do |n| impl.check_number_range n, bytes, signed impl.number_to_array n, bytes, signed, big_endian end define_method :"signed_to_#{name}" do |n| impl.number_to_array n, bytes, signed, big_endian end define_method(:"#{name}_length") { bytes } end @target.class_eval(&defines) (class << @target; self; end).module_eval(&defines) end |
#fixed_length_string(name, bytes, options = {}) ⇒ Object
Defines the methods for handling a fixed-length string type in an ABI.
The |options| hash supports the following keys:
signed:: if false, the value type cannot hold negative numbers;
signed values are stored using 2s-complement; defaults to true
big_endian:: if true, bytes are sent over the wire in big-endian order
The following methods are defined for a type named ‘name’:
* read_name(array, offset) -> string
* to_name(string or array) -> array
* name_length -> number # the number of bytes in the string type
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/tem/builders/abi.rb', line 184 def fixed_length_string(name, bytes, = {}) impl = Tem::Builders::Abi::Impl signed = .fetch :signed, true big_endian = .fetch :big_endian, true defines = Proc.new do define_method :"read_#{name}" do |array, offset| impl.string_from_array array, offset, bytes end define_method :"to_#{name}" do |n| impl.string_to_array n, bytes end define_method(:"#{name}_length") { bytes } end @target.class_eval(&defines) (class << @target; self; end).module_eval(&defines) end |
#object_wrapper(name, object_class, schema, hooks = {}) ⇒ Object
Defines methods for handling a complex ABI structure wrapped into an object.
Objects are assumed to be of the object_class type. The objects are serialized according to a schema, which is an array of 2-element directives. The first element in a directive indicates the lower-level ABI type to be serialized, and the 2nd element indicates the mapping between the object and the higher level ABI. The mapping can be:
* a symbol - the lower level ABI output is assigned to an object property
* a hash - the keys in the lower level ABI output are assigned to the
object properties indicated by the values
* nil - the components of the lower level ABI type (which should act like
packed_variable_length_numbers types) are mapped to identically
named object properties
The following methods are defined for a type named ‘name’:
* read_name(array, offset) -> object
* read_name_length(array, offset) -> number
* to_name(object) -> array
* name_class -> Class
The following hooks (Procs in the hooks argument) are supported:
new(object_class) -> object:: called to instantiate a new object in read_;
if the hook is not present, object_class.new is used instead
read(object) -> object:: called after the object is de-serialized using
the lower-level ABI; if the hook is present, its value is returned
from the read_ method
to(object) -> object:: called before the object is serialized using the
lower-level ABI; if the hook is present, its value is used for
serialization
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/tem/builders/abi.rb', line 232 def object_wrapper(name, object_class, schema, hooks = {}) if hooks[:new] read_body = "r = #{name}_newhook(#{name}_class);" else read_body = "r = #{name}_class.new;" end to_body = "r = [];" to_body << "value = #{name}_tohook(value);" if hooks[:to] readlen_body = "old_offset = offset;" 0.upto schema.length / 2 - 1 do |i| abi_type = schema[i * 2] type_mapping = schema[i * 2 + 1] # Set up the translation table. if type_mapping.nil? type_mapping = {} components = @target.send :"#{abi_type}_components" components.each { |c| type_mapping[c] = c } end # Set up the read_ and read_name_length methods. if abi_type.kind_of? Symbol read_body << "v = read_#{abi_type}(array,offset);" else read_body << "v = #{abi_type.inspect};" end case type_mapping when Symbol read_body << "r.#{type_mapping} = v;" when Hash, nil type_mapping.each do |k, v| read_body << "r.#{v} = v[:#{k}];" end end if abi_type.kind_of? Symbol if @target.respond_to? :"#{abi_type}_length" read_body << "offset += #{@target.send :"#{abi_type}_length"};" readlen_body << "offset += #{@target.send :"#{abi_type}_length"};" elsif @target.respond_to? :"read_#{abi_type}_length" read_body << "offset += read_#{abi_type}_length(array,offset);" readlen_body << "offset += read_#{abi_type}_length(array,offset);" else raise "#{abi_type} doesn't support _length or read_#{abi_type}_length" end end # Set up the to_ method. next unless abi_type.kind_of? Symbol to_body << "r += to_#{abi_type}(" case type_mapping when Symbol to_body << "value.#{type_mapping}" when Hash to_body << type_mapping.map { |k, v| ":#{k} => value.#{v}" }.join(', ') end to_body << ");" end read_body << "r = self.#{name}_readhook(r);" if hooks[:read] read_body << "r;" to_body << "r;" readlen_body << "offset - old_offset;" define_str = "def read_#{name}(array,offset);#{read_body}end;" define_str << "def read_#{name}_length(array,offset);#{readlen_body}end;" define_str << "def to_#{name}(value);#{to_body}end;" defines = Proc.new do define_method(:"#{name}_class") { object_class } define_method(:"#{name}_newhook", &hooks[:new]) if hooks[:new] define_method(:"#{name}_readhook", &hooks[:read]) if hooks[:read] define_method(:"#{name}_tohook", &hooks[:to]) if hooks[:to] end @target.class_eval(&defines) @target.class_eval define_str (class << @target; self; end).module_eval(&defines) (class << @target; self; end).module_eval define_str end |
#packed_variable_length_numbers(name, length_type, components, options = {}) ⇒ Object
Defines the methods for handling a group of packed variable-length numbers in the ABI.
When serializing a group of variable-length numbers, it’s desirable to have the lengths of all the numbers grouped before the number data. This makes reading and writing easier & faster for embedded code. The optimization is important enough that it’s made its way into the API.
All the numbers’ lengths are assumed to be represented by the same fixed-length type, whose name is given as the length_type parameter.
The numbers are de-serialized into a hash, where each number is associated with a key. The components argument specifies the names of the keys, in the order that the numbers are serialized in.
The |options| hash supports the following keys:
signed:: if false, the value type cannot hold negative numbers;
signed values are stored using 2s-complement; defaults to true
big_endian:: if true, bytes are sent over the wire in big-endian order
The following methods are defined for a type named ‘name’:
* read_name(array, offset) -> hash
* read_name_length(array, offset) -> number
* to_name(hash) -> array
* name_components -> array
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/tem/builders/abi.rb', line 124 def packed_variable_length_numbers(name, length_type, components, = {}) impl = Tem::Builders::Abi::Impl sub_names = components.freeze signed = .fetch :signed, true big_endian = .fetch :big_endian, true length_bytes = @target.send :"#{length_type}_length" read_length_msg = :"read_#{length_type}" write_length_msg = :"to_#{length_type}" defines = Proc.new do define_method :"read_#{name}" do |array, offset| response = {} data_offset = offset + length_bytes * sub_names.length sub_names.each_with_index do |sub_name, i| length = self.send read_length_msg, array, offset + i * length_bytes response[sub_name] = impl.number_from_array array, data_offset, length, signed, big_endian data_offset += length end response end define_method :"to_#{name}" do |numbers| number_data = sub_names.map do |sub_name| impl.number_to_array numbers[sub_name], nil, signed, big_endian end length_data = number_data.map do |number| self.send write_length_msg, number.length end # Concatenate all the arrays without using flatten. lengths = length_data.inject([]) { |acc, i| acc += i } number_data.inject(lengths) { |acc, i| acc += i } end define_method :"read_#{name}_length" do |array, offset| response = sub_names.length * length_bytes 0.upto(sub_names.length - 1) do |i| response += self.send read_length_msg, array, offset + i * length_bytes end response end define_method(:"#{name}_components") { sub_names } end @target.class_eval(&defines) (class << @target; self; end).module_eval(&defines) end |
#variable_length_number(name, length_type, options = {}) ⇒ Object
Defines the methods for handling a variable-length number type in an ABI.
The length_type argument holds the name of a fixed-length number type that will be used to store the length of hte variable-length number.
The |options| hash supports the following keys:
signed:: if false, the value type cannot hold negative numbers;
signed values are stored using 2s-complement; defaults to true
big_endian:: if true, bytes are sent over the wire in big-endian order
The following methods are defined for a type named ‘name’:
* read_name(array, offset) -> number
* read_name_length(array, offset) -> number
* to_name(number) -> array
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/tem/builders/abi.rb', line 71 def variable_length_number(name, length_type, = {}) impl = Tem::Builders::Abi::Impl signed = .fetch :signed, true big_endian = .fetch :big_endian, true length_bytes = @target.send :"#{length_type}_length" read_length_msg = :"read_#{length_type}" write_length_msg = :"to_#{length_type}" defines = Proc.new do define_method :"read_#{name}" do |array, offset| length = self.send read_length_msg, array, offset impl.number_from_array array, offset + length_bytes, length, signed, big_endian end define_method :"to_#{name}" do |n| number_data = impl.number_to_array n, nil, signed, big_endian length_data = self.send write_length_msg, number_data.length length_data + number_data end define_method :"read_#{name}_length" do |array, offset| length_bytes + self.send(read_length_msg, array, offset) end end @target.class_eval(&defines) (class << @target; self; end).module_eval(&defines) end |