[fields] implements a simple minded packing and unpacking of arrays and lists into [[[binary]]] strings, given a field spec. The idea is that, while [binary] gives one facility with binary strings, and lets one implement binary packed data structures, it is unwieldy and error-prone - mainly because the format of each field and its name are not associated by the [binary] command. This bit of code allows one to come up with a simple-minded record specification which associates fields' names and specification. ---- # binary fields packing and unpacking # a field specification looks like this: # fieldname {type ?count? ?endian?} ... namespace eval fields { variable endianness "" variable cache } proc fields::2form {spec array {endian ""}} { variable cache variable endianness if {$endian == ""} { set endian $endianness } if {[info exists cache($endian,$array,$spec)]} { return $cache($endian,$array,$spec) } set form "" set vars {} foreach {name qual} $spec { foreach {type count fendian extra} $qual break set t [string index $type 0] if {$fendian == ""} { set fendian [string tolower [string index $endian 0]] } else { set fendian [string tolower [string index $fendian 0]] } # special forms skip n, back n, jump n if {$name == "skip" && [string is integer $type]} { set count $type set type "x" } elseif {$name == "back" && [string is integer $type]} { set count $type set type "X" } elseif {$name == "jump" && [string is integer $type]} { set count $type set type "@" } if {$fendian == "h" || $fendian == "b"} { set ty [string toupper $t] } elseif {$fendian == "l"} { set ty [string tolower $t] } switch [string tolower $t] { a { # ascii - char string of $count # Ascii - pad with " " } b { # bits - low2high # Bits - high2low } c { # char - 8 bit integer values set ty [string tolower $t] } h { # hex low2high # Hex high2low } i { # integer - 32bits low2high # Integer - 32bits high2low } s { # short - 16bits low2high # Short - 16bits high2low } w { # wide-integer - 64bits low2high # Wide-integer - 64bits high2low } f { # float set ty $t ;# don't play with endianness } d { # double set ty $t ;# don't play with endianness } @ { # skip to absolute location set name "" } x { # x - move relative forward # X - move relative back set ty $t ;# don't play with endianness set name "" } } if {$name != ""} { append outvars "$array\($name\) " append invars "\$$array\($name\) " } append form $ty$count } set cache($endian,$array,$spec) [list $form $outvars $invars] return $cache($endian,$array,$spec) } # pack the fields contained in array into a binary string according to spec proc ::fields::pack {spec array {endian ""}} { upvar $array Record foreach {form out in} [::fields::2form $spec Record $endian] break #puts stderr "pack: binary format $form $in" return [eval binary format [list $form] {expand}$in] } # pack the fields from $packed contained into array according to spec proc ::fields::unpack {packed spec array {endian ""}} { upvar $array Record foreach {form out in} [::fields::2form $spec Record $endian] break #puts stderr "unpack: binary scan $form $out" return [binary scan $packed [list $form] {expand}$out] } # binary scan the fields from $packed according to spec proc ::fields::scan {spec packed {endian ""}} { ::fields::unpack $packed $spec Record $endian foreach {form out in} [::fields::2form $spec Record $endian] break set result {} foreach var $out { lappend result [set $var] } return $result } # binary format the args according to spec proc ::fields::format {spec endian args} { foreach {form out in} [::fields::2form $spec Record $endian] break set result {} foreach var $out arg $args { set $var $arg } return [::fields::pack $form Record $endian] } if {$argv0 == [info script]} { puts stderr "fields test(s)" # spec is a record specifier set spec { fred integer wilma short pebbles {hex 4} barney {ascii 10} betty double } # record is an instance of a record array set record { fred 123 wilma 456 pebbles feaf barney "woo" betty 12.345 } # pack record according to spec set packed [::fields::pack $spec record bigendian] # unpack $packed into record1 according to spec ::fields::unpack $packed $spec record1 bigendian # compare record and record1 foreach {field val} [array get record1] { if {$record($field) != $val} { puts "$field: $record($field) != $val" } } }