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

Instance 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



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

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

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

Define an array of values

Yields:

  • (a.members)


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

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



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

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



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

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



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

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



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

def filler
  only([])
end

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

Define a nested struct



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

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



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

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



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

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



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
498
# File 'lib/depix/dict.rb', line 466

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



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

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

.r32(name, *extras) ⇒ Object

Define a real number



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

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



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

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



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

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



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

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



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

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

Instance Method Details

#[](field) ⇒ Object



513
514
515
# File 'lib/depix/dict.rb', line 513

def [](field)
  send(field)
end

#[]=(field, value) ⇒ Object

End class methods



509
510
511
# File 'lib/depix/dict.rb', line 509

def []=(field, value)
  send("#{field}=", value)
end