Class: Depix::Dict

Inherits:
Object
  • Object
show all
Defined in:
lib/depix/dict.rb

Overview

Base class for a struct. Could also be implemented as a module actually

Constant Summary collapse

DEF_OPTS =
{ :req => false, :desc => nil }

Class Method Summary collapse

Class Method Details

.apply!(string) ⇒ Object

Apply this structure to data in the string, returning an instance of this structure with fields completed



431
432
433
# File 'lib/depix/dict.rb', line 431

def apply!(string)
  consume!(string.unpack(pattern))
end

.array(name, mapped_to, *extras) {|a.members| ... } ⇒ Object

Define an array of values

Yields:

  • (a.members)


383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/depix/dict.rb', line 383

def array(name, mapped_to, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  
  a = ArrayField.new({:name => name}.merge(opts))
  a.members = if mapped_to.is_a?(Class) # Array of structs
    [InnerField.new(:cast => mapped_to)] * count
  else
    [Field.send("emit_#{mapped_to}")] * count
  end
  yield a.members if block_given?
  fields << a
end

.char(name, *extras) ⇒ Object

Define a char field



405
406
407
408
409
# File 'lib/depix/dict.rb', line 405

def char(name, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  fields << Field.emit_char( {:name => name, :length => count}.merge(opts) )
end

.consume!(stack_of_unpacked_values) ⇒ Object

Consume a stack of unpacked values, letting each field decide how many to consume



422
423
424
425
426
427
428
# File 'lib/depix/dict.rb', line 422

def consume!(stack_of_unpacked_values)
  new_item = new
  @fields.each do | field |
    new_item.send("#{field.name}=", field.consume!(stack_of_unpacked_values)) unless field.name.nil?
  end
  new_item
end

.fieldsObject

Get the array of fields defined in this struct



342
343
344
# File 'lib/depix/dict.rb', line 342

def fields
  @fields ||= []
end

.fillerObject

Get an opaque struct based on this one, that will consume exactly as many bytes as this structure would occupy, but discard them instead



460
461
462
# File 'lib/depix/dict.rb', line 460

def filler
  only([])
end

.inner(name, mapped_to, *extras) ⇒ Object

Define a nested struct



398
399
400
401
402
# File 'lib/depix/dict.rb', line 398

def inner(name, mapped_to, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  fields << InnerField.new({:name => name, :cast => mapped_to}.merge(opts))
end

.lengthObject

How many bytes are needed to complete this structure



417
418
419
# File 'lib/depix/dict.rb', line 417

def length
  fields.inject(0){|_, s| _ + s.length }
end

.only(*field_names) ⇒ Object

Get a class that would parse just the same, preserving only the fields passed in the array. This speeds up parsing because we only extract and conform the fields that we need



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/depix/dict.rb', line 437

def only(*field_names)
  distillate = fields.inject([]) do | m, f |
    if field_names.include?(f.name) # preserve
      m.push(f)
    else # create filler
      unless m[-1].is_a?(Filler)
        m.push(Filler.new(:length =>  f.length))
      else
        m[-1].length += f.length
      end
      m
    end
  end
  
  anon = Class.new(self)
  anon.fields.replace(distillate)
  only_items = distillate.map{|n| n.name }
  
  anon
end

.pack(instance, buffer = nil) ⇒ Object

Pack the instance of this struct



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/depix/dict.rb', line 465

def pack(instance, buffer = nil)
  
  # Preallocate a buffer just as big as me since we want everything to remain at fixed offsets
  buffer ||= ("\000" * length)
  
  # If the instance is nil return pure padding
  return buffer if instance.nil?
  
  # Now for the important stuff. For each field that we have, replace a piece at offsets in the buffer
  # with the packed results, skipping fillers
  fields.each_with_index do | f, i |
    
    # Skip blanking, we just dont touch it. TODO - test!
    next if f.is_a?(Filler)
    
    # Where should we put that value?
    offset = fields[0...i].inject(0){|_, s| _ + s.length }

    val = instance.send(f.name)

    # Validate the passed value using the format the field supports
    f.validate!(val)
    
    packed = f.pack(val)
    
    # Signal offset violation
    raise "Improper length for #{f.name} - packed #{packed.length} bytes but #{f.length} is required to fill the slot" if packed.length != f.length

    buffer[offset...(offset+f.length)] = packed
  end
  raise "Resulting buffer not the same length, expected #{length} bytes but compued #{buffer.length}" if buffer.length != length
  buffer
end

.patternObject

Get the pattern that will be used to unpack this structure and all of it’s descendants



412
413
414
# File 'lib/depix/dict.rb', line 412

def pattern
  fields.map{|f| f.pattern }.join
end

.r32(name, *extras) ⇒ Object

Define a real number



376
377
378
379
380
# File 'lib/depix/dict.rb', line 376

def r32(name, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  fields << Field.emit_r32( {:name => name}.merge(opts) )
end

.u16(name, *extras) ⇒ Object

Define a double-width unsigned integer



361
362
363
364
365
# File 'lib/depix/dict.rb', line 361

def u16(name, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  fields << Field.emit_u16( {:name => name }.merge(opts) )
end

.u32(name, *extras) ⇒ Object

Define a 4-byte unsigned integer



354
355
356
357
358
# File 'lib/depix/dict.rb', line 354

def u32(name, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  fields << Field.emit_u32( {:name => name }.merge(opts) )
end

.u8(name, *extras) ⇒ Object

Define a small unsigned integer



369
370
371
372
373
# File 'lib/depix/dict.rb', line 369

def u8(name, *extras)
  count, opts = count_and_opts_from(extras)
  attr_accessor name
  fields << Field.emit_u8( {:name => name }.merge(opts) )
end

.validate!(instance) ⇒ Object

Validate a passed instance



347
348
349
350
351
# File 'lib/depix/dict.rb', line 347

def validate!(instance)
  fields.each do | f |
    f.validate!(instance.send(f.name)) if f.name
  end
end