autoObjectData.tcl

#

FILENAME: autoObjectData.tcl

AUTHOR: theerik@github

DESCRIPTION: AutoObject is an object-based system used to create auto-assembling objects using run-time descriptors. AutoObjectData defines many core data type classes that can be used as mixins to an AutoObject.

Data types defined in the core include:

Also defined are generic enum and bitfield classes that can be used as-is for one-off uses, or as templates or superclasses for more specialized derived classes.

  • enum8
  • bitfield8
  • bitfield16
  • bitfield32

Copyright 2015-19, Erik N. Johnson

This software is distributed under the MIT 3-clause license.

Package documentation is auto-generated with Pycco: https://pycco-docs.github.io/pycco/

Use "pycco filename" to re-generate HTML documentation in ./docs .

#

rewrite the introductory matter

#

autoObject datatype classes

All types used as AutoObject fields must support the following canonical methods:

  • get
  • set
  • toList
  • fromList
  • toString

Optional methods used primarily for GUI building are:

  • follow
  • createWidget

Data types that need a constructor-equivalent run on each new instance of a field of that type can get that by defining the following:

  • InitField

Data types that have any limitations on data that can be represented and expect to use the base-class array helper must support the following, which is called on each array element during setting operations:

  • ValueFilter

If a subclassing application needs additional methods, the base classes can be extended by defining additional methods. These will be available by using the syntax:

  *$object* *fieldName* *fieldMethodName*

All new classes should be declared in the ::AutoObject:: namespace, as below. To allow much of the base autoObject container class to work as expected, the actual data value should be held in the canonical name "DataArray($field)". If the data value is not held in DataArray($field), the base class follow method and array management are unlikely to work correctly.

#

uint8_t

The most basic field type, a single byte of unsigned data.

puts "Creating data classes in [namespace current]"

oo::class create ::AutoObject::uint8_t {
    variable DataArray

    method uint8_t::ValueFilter {field newVal} {
log::debug "uint8_t:ValueFilter $field $newVal"
        return [expr {$newVal % 256}]
    }
    method uint8_t::set {field newVal} {
log::debug "uint8_t:set $field $newVal"
        set DataArray($field) [my $field ValueFilter $newVal]
    }
    method uint8_t::get {field} {
        return $DataArray($field)
    }
    method uint8_t::toString {field} {
        return [format "%-3d" $DataArray($field)]
    }
    method uint8_t::fromList {field inList} {
log::debug "uint8_t::fromList $field $inList"
        set DataArray($field) [expr {[lindex $inList 0] % 256}]
        return [lrange $inList 1 end]
    }
    method uint8_t::toList {field} {
        return [format "%d" [expr {$DataArray($field) % 256}]]
    }
}
#

int8_t

A single byte of signed data. Since list data is effectively unsigned, perform the final 2's complement on the MSbit; the ValueFilter does this on set operations.

oo::class create ::AutoObject::int8_t {
    superclass ::AutoObject::uint8_t
    variable DataArray

    method int8_t::ValueFilter {field newVal} {
        return [expr {($newVal % 256) - (($newVal& 0x80) << 1)}]
    }
    forward int8_t::set         my uint8_t::set
    forward int8_t::get         my uint8_t::get
    forward int8_t::toList      my uint8_t::toList
    forward int8_t::toString    my uint8_t::toString
    method  int8_t::fromList {field inList} {
        set outL [my uint8_t::fromList $field $inList]
        if {$DataArray($field) & 0x80} {
            incr DataArray($field) -256
        }
        return $outL
    }
}
#

uint16_t

Two bytes of data, where the byte list is interpreted as a single unsigned number in little-endian format.

oo::class create ::AutoObject::uint16_t {
    variable DataArray

    method uint16_t::ValueFilter {field newVal} {
        return [expr {$newVal % 65536}]
    }
    method uint16_t::get {field} {
        return $DataArray($field)
    }
    method uint16_t::set {field newVal} {
        set DataArray($field) [my $field ValueFilter $newVal]
    }
    method uint16_t::toString {field} {
        return [format "%-5d" $DataArray($field)]
    }
    method uint16_t::fromList {field inList} {
        set DataArray($field) [expr {[format "0x%02x%02x" {*}[lreverse \
                                                    [lrange $inList 0 1]]]}]
        return [lrange $inList 2 end]
    }
    method uint16_t::toList {field} {
        set outL [format "%d" [expr {$DataArray($field) & 0x00FF}]]
        lappend outL [format "%d" [expr {($DataArray($field) & 0xFF00) >> 8}]]
        return $outL
    }
}
#

uint16_bt

Same as uint16_t except big-endian in byte list format.

oo::class create ::AutoObject::uint16_bt {
    superclass ::AutoObject::uint16_t
    variable DataArray

    forward uint16_bt::ValueFilter  my uint16_t::ValueFilter
    forward uint16_bt::set          my uint16_t::set
    forward uint16_bt::get          my uint16_t::get
    forward uint16_bt::toString     my uint16_t::toString
    method  uint16_bt::toList {field} {
        set outL [format "%d" [expr {($DataArray($field) & 0xFF00) >> 8}]]
        lappend outL [format "%d" [expr {$DataArray($field) & 0x00FF}]]
        return $outL
    }
    method  uint16_bt::fromList {field inList} {
        set DataArray($field) [expr {[format "0x%02x%02x" {*}[lrange $inList 0 1]]}]
        return [lrange $inList 2 end]
    }
}
#

int16_t

Same as uint16_t except the value is interpreted as signed. Since list data is effectively unsigned, perform the final 2's complement on the MSbit, and don't force the set method to an unsigned value.

oo::class create ::AutoObject::int16_t {
    superclass ::AutoObject::uint16_t
    variable DataArray

    method int16_t::ValueFilter {field newVal} {
        return [expr {($newVal % 65536) - (($newVal& 0x8000) << 1)}]
    }
    forward int16_t::set        my uint16_t::set
    forward int16_t::get        my uint16_t::get
    forward int16_t::toList     my uint16_t::toList
    forward int16_t::toString   my uint16_t::toString
    method  int16_t::fromList {field inList} {
        set outL [my uint16_t::fromList $field $inList]
        if {$DataArray($field) & 0x8000} {
            incr DataArray($field) -65536
        }
        return $outL
    }
}
#

int16_bt

Same as uint16_t except the list is interpreted as big-endian and the value is interpreted as signed. Since list data is effectively unsigned, perform the final 2's complement on the MSbit, and don't force the set method to an unsigned value.

oo::class create ::AutoObject::int16_bt {
    superclass ::AutoObject::int16_t
    mixin ::AutoObject::uint16_bt
    variable DataArray

    forward int16_bt::ValueFilter my int16_t::ValueFilter
    forward int16_bt::set         my int16_t::set
    forward int16_bt::get         my uint16_t::get
    forward int16_bt::toString    my uint16_t::toString
    forward int16_bt::toList      my uint16_bt::toList
    method  int16_bt::fromList {field inList} {
        set outL [my uint16_bt::fromList $field $inList]
        if {$DataArray($field) & 0x8000} {
            incr DataArray($field) -65536
        }
        return $outL
    }
}
#

uint32_t

Four bytes of data, where the byte list is interpreted as a single unsigned number in little-endian format.

oo::class create ::AutoObject::uint32_t {
    variable DataArray

    method uint32_t::ValueFilter {field newVal} {
        return [expr {$newVal % 4294967296}]
    }
    method uint32_t::set {field newVal} {
log::debug "uint32_t::Set  field: $field  val: $newVal"
        set DataArray($field) [my $field ValueFilter $newVal]
    }
    method uint32_t::get {field} {
        return $DataArray($field)
    }
    method uint32_t::toString {field} {
        return [format "%-10d" $DataArray($field)]
    }
    method uint32_t::fromList {field inList} {
        set DataArray($field) [expr {[format "0x%02x%02x%02x%02x" {*}[lreverse \
                                                    [lrange $inList 0 3]]]}]
        return [lrange $inList 4 end]
    }
    method uint32_t::toList {field} {
        set outL     [format "%d" [expr { $DataArray($field) & 0x000000FF}]]
        lappend outL [format "%d" [expr {($DataArray($field) & 0x0000FF00) >> 8}]]
        lappend outL [format "%d" [expr {($DataArray($field) & 0x00FF0000) >> 16}]]
        lappend outL [format "%d" [expr {($DataArray($field) & 0xFF000000) >> 24}]]
        return $outL
    }
}
#

uint32_bt

Same as uint32_t except big-endian in byte list format.

oo::class create ::AutoObject::uint32_bt {
    superclass ::AutoObject::uint32_t
    variable DataArray

    forward uint32_bt::ValueFilter  my uint32_t::ValueFilter
    forward uint32_bt::set          my uint32_t::set
    forward uint32_bt::get          my uint32_t::get
    forward uint32_bt::toString     my uint32_t::toString
    method uint32_bt::toList {field} {
        set outL     [format "%d" [expr {($DataArray($field) & 0xFF000000) >> 24}]]
        lappend outL [format "%d" [expr {($DataArray($field) & 0x00FF0000) >> 16}]]
        lappend outL [format "%d" [expr {($DataArray($field) & 0x0000FF00) >> 8}]]
        lappend outL [format "%d" [expr { $DataArray($field) & 0x000000FF}]]
        return $outL
    }
    method uint32_bt::::fromList {field inList} {
        set DataArray($field) [expr {[format "0x%02x%02x%02x%02x" {*}[lrange $inList 0 3]]}]
        return [lrange $inList 4 end]
    }
}
#

int32_t

Same as uint32_t except the value is interpreted as signed. Since list data is effectively unsigned, perform the final 2's complement on the MSbit, and don't force the set method to an unsigned value.

oo::class create ::AutoObject::int32_t {
    superclass ::AutoObject::uint32_t
    variable DataArray

    method int32_t::ValueFilter {field newVal} {
        return [expr {($newVal % 4294967296) - (($newVal& 0x80000000) << 1)}]
    }
    forward int32_t::set        my uint32_t::set
    forward int32_t::get        my uint32_t::get
    forward int32_t::toList     my uint32_t::toList
    forward int32_t::toString   my uint32_t::toString
    method int32_t::fromList {field inList} {
        my variable MyValue
        set outL [my uint32_t::fromList $field $inList]
        if {$DataArray($field) & 0x80000000} {
            incr DataArray($field) -4294967296
        }
        return $outL
    }
}
#

int32_bt

Same as uint32_t except the list is interpreted as big-endian and the value is interpreted as signed. Since list data is effectively unsigned, perform the final 2's complement on the MSbit, and don't force the set method to an unsigned value.

oo::class create ::AutoObject::int32_bt {
    superclass ::AutoObject::uint32_bt
    mixin ::AutoObject::int32_t
    variable DataArray

    forward int32_bt::ValueFilter   my int32_t::ValueFilter
    forward int32_bt::set           my uint32_t::set
    forward int32_bt::get           my uint32_t::get
    forward int32_bt::toString      my uint32_t::toString
    forward int32_bt::toList        my uint32_bt::toList
    method  int32_bt::fromList {field inList} {
        my variable MyValue
        set outL [my uint32_bt::fromList $field $inList]
        if {$DataArray($field) & 0x80000000} {
            incr DataArray($field) -4294967296
        }
        return $outL
    }
}
#

float_t

Four bytes of data interpreted as a single-precision IEEE format floating point number.

N.B. that this works only when both sides of the serial interface agree on the bit format of a single-precision floating point number. If they're the same processor or processor family it's not an issue; if it's inter-processor communication and one of the processors has a different floating-point format, much more complex manipulation is needed and a custom class should be created for that use.

oo::class create ::AutoObject::float_t {
    variable DataArray

    method float_t::ValueFilter {field newVal} {
        return [expr {$newVal + 0.0}]
    }
    method float_t::set {field newVal} {
        set DataArray($field) [my $field ValueFilter $newVal]
    }
    method float_t::get {field} {
        return $DataArray($field)
    }
    method float_t::toString {field} {
        if [catch {set outS [format %8f $DataArray($field)]}] {
            log::error "$field: $DataArray($field) is Not a Number!"
        }
        return $outS
    }
    method float_t::toList {field} {
#

Convert using the native float format. N.B. this works only when the target & host use the same float format.

        binary scan [binary format f $DataArray($field)] cu* outL
        return $outL
    }
    method float_t::fromList {field inList} {
#

Convert using the native float format. N.B. this works only when the target & host use the same float format.

        binary scan [binary format c4 [lrange $inList 0 3]] f value
        set DataArray($field) $value
        return [lrange $inList 4 end]
    }
}
#

time_t

Time is stored internally as a Unix time_t - a 32-bit count of seconds since the Unix epoch (Jan 1, 1970). The set method will accept an integer directly, or if passed a non-integer will attempt to parse it using clock scan. If that fails, it allows clock scan to throw the error. Since it's stored as a 32-bit int, most methods act as the uint32_t superclass.

The Constructor can be passed a default time format string to be used when stringifying; we also add a method to set the default string later. toString accepts a format string; if one is not provided it uses the default string set earlier.

oo::class create ::AutoObject::time_t {
    superclass ::AutoObject::uint32_t
    variable DataArray FieldInfo

    method  time_t::InitField {field args} {
        ;# default value for a new time object is "now".
        set DataArray($field) [clock seconds]
        if {$args ne ""} {
            set fmtS $args
        } else {
            set  "%B %d, %Y at %H:%M:%S"
        }
        dict set FieldInfo $field MyFormatStr $fmtS
    }
    method time_t::ValueFilter {field newVal} {
        if {![string is integer -strict $newVal]} {
            return [clock scan $newVal]
        } else {
            return $newVal
        }
    }
    method time_t::set {field newVal} {
        set DataArray($field) [my $field ValueFilter $newVal]
    }
    forward time_t::get       my uint32_t::get
    forward time_t::toList    my uint32_bt::toList
    forward time_t::fromList  my uint32_bt::fromList
#

time_t.toString

Note that for the time_t class, toString has an additional optional argument of a format string to be used for this particular call.

    method time_t::toString {field args} {
        if {$args ne ""} {
            set fmtStr $args
        } else {
            set fmtStr [dict get $FieldInfo $field MyFormatStr]
        }
        return [clock format $DataArray($field) -format $fmtStr]
    }
#

time_t.setFormat newFormatString

Note that the time_t class has an additional method: setFormat. This allows the user of the class to change the default format used whenever an object of this class is stringified with the toString method. Also note that toString has an additional optional argument of a format string to be used for an individual call.

    method time_t::setFormat {field newFmtStr} {
        dict set FieldInfo $field MyFormatStr $newFmtStr
    }
}
#

string_t

The string_t autoData type is constrained by the length of its field. It will truncate any attempt to set it to longer to the first N characters that will fit into that field length. If the input is shorter, the string will be filled to its field length with NULLs in the line format.

string_t expects its string-format data to be ASCII or UTF-8, one byte per character. To store/transfer unicode data, use type unicode_t.

oo::class create ::AutoObject::string_t {
    variable DataArray FieldInfo

    method string_t::ValueFilter {field newVal} {
        dict with $FieldInfo $field {
            set count [expr {($arrcnt == 0) ? $size : ($size / $arrcnt)}]
        }
        set DataArray($field) [string range $newVal 0 ${count}-1 ]
    }
    method string_t::get {field} { return $DataArray($field) }
    method string_t::set {field newVal} {
        set DataArray($field) [my $field ValueFilter $newVal]
    }
    method string_t::toString {field} { return "\"$DataArray($field)\"" }
    method string_t::toList {field} {
        set maxLen [dict get $FieldInfo $field size]
        binary scan $DataArray($field) cu* dataL
        if {[llength $dataL] < $maxLen} {
            lappend dataL {*}[lrepeat [expr {$maxLen - [llength $dataL]}] 0]
        }
        return $dataL
    }
    method string_t::fromList {field inList} {
        set maxLen [dict get $FieldInfo $field size]
        if {$maxLen < [llength $inList]} {
            set dataL [lrange $inList 0 $maxLen-1]
        } else {
            set dataL $inList
        }
        set DataArray($field) [string trimright [binary format c* $dataL] '\0']
        return [lrange $inList $maxLen end]
    }
}
#

unicode_t

N.B. This class expects the length of the field IN CHARACTERS to be on average 2 bytes each, so it divides the number of bytes by 2 and limits its character length based on that.

The unicode_t autoData type is constrained by the length of its field. It will truncate any attempt to set it to longer to the first N characters that will fit into that field length. If the input is shorter, the string will be null terminated and filled with NULLs in the line format.

oo::class create ::AutoObject::unicode_t {
    superclass ::AutoObject::string_t
    variable DataArray FieldInfo

    method unicode_t::ValueFilter {field newVal} {
        dict with $FieldInfo $field {
            set count [expr {(($arrcnt == 0) ? $size : ($size / $arrcnt)) / 2}]
        }
        set DataArray($field) [string range $newVal 0 ${count}-1 ]
    }
    forward unicode_t::get       my string_t::get
    forward unicode_t::set       my string_t::set
    forward unicode_t::toString  my string_t::toString
    method  unicode_t::toList {field} {
        dict with $FieldInfo $field {
            set maxLen [expr {($arrcnt == 0) ? $size : ($size / $arrcnt)}]
        }
        binary scan [encoding convertto unicode $DataArray($field)] cu* dataL
        if {[llength $dataL] < $maxLen} {
            lappend dataL {*}[lrepeat [expr {$maxLen - [llength $dataL]}] 0]
        } elseif {[llength $dataL] > $maxLen} {
            set dataL [lrange $dataL 0 $maxLen]
        }
        return $dataL
    }
    method unicode_t::fromList {field inList} {
        set maxLen [dict get $FieldInfo $field size]
        if {$maxLen < [llength $inList]} {
            set dataL [lrange $inList 0 [expr {$maxLen - 1}]]
        } else {
            set dataL $inList
        }
        set DataArray($field) [string trimright [encoding convertfrom unicode \
                                       [binary format c* $dataL]] '\0']
        return [lrange $inList $maxLen end]
    }
}
#

enum_mix

This class is a mixin for classes usually based on the uint8_t class (or uint16_t or uint32_t if you need large fields for some reason). The host class is expected to provide most of the features, including any of the length-specific code; the mixin only provides an override for the set and toString methods.

N.B. that the enum_mix class is a bit of code clevverness that expects to be initialized before use. The base class only provides the mechanisms; initialization provides the lookup table mapping the symbol to the enum value. Without providing the definition of the enum, it's not very useful.

To initialize, immediately after mixing it into the base class, invoke the setEnumDef proc with the classname and a list consisting of name/value pairs. Objects of the ensuing class will then accept the symbolic names of the enum as inputs to set, and will print via toString the symbolic names.

N.B. that the setEnumDef proc should be invoked on the class immediately after class definition, not on the resulting objects. The extra data is stored in the class object proper, not in the objects created by the class.

oo::class create ::AutoObject::enum_mix { variable defArray

method set {newVal} { my variable MyValue set ns [info object namespace [info object class [self object]]] upvar ${ns}::defArray dA if {[info exists dA($newVal)]} { set MyValue $dA($newVal) } else { ;# Sometimes input comes in 0x## hex format. Expr it to decimal. catch {set expVal [expr $newVal]} if {[info exists dA(val-$expVal)]} { set MyValue $expVal } else { log::warn "Tried to set enum [self] ([info object class [self object]]) to unknown symbol $expVal" set MyValue $expVal } } } method toString {} { my variable MyValue set ns [info object namespace [info object class [self object]]] upvar ${ns}::defArray dA if {[info exists dA(val-$MyValue)]} { return $dA(val-$MyValue) } else { return $MyValue } } # Important note: if using the GUI elements of the system, any enum_mix # field should specify the autoCombobox widget as field 6 of the # defining list. method createWidget {args} { my variable MyWidget # N.B. that the autoCombobox widget method will be called first in # the method chain, and that we only get here by the "next" call. if {[winfo class $MyWidget] ne "TCombobox"} { puts "I ([self]) am a [winfo class $MyWidget]" return } set ns [info object namespace [info object class [self object]]] upvar ${ns}::defArray dA set nL [array names dA "val-*"] foreach n $nL {lappend enumL $dA($n)} $MyWidget configure -values $enumL bind $MyWidget <> [list [self] widgetUpdate] $MyWidget set [my toString] } method widgetUpdate {} { my variable MyWidget my set [$MyWidget get] } }

N.B. that the setEnumDef proc should be invoked on the class immediately after class definition, not on the resulting objects. The extra data is stored in the class object proper, not in the objects created by the class. proc ::AutoObject::setEnumDef {classname defL} { namespace upvar [info object namespace $classname] defArray dA array set dA $defL foreach {key val} [array get dA] { set dA(val-$val) $key } }

#

bitfield_mix

N.B. This class is a mixin for classes based on the uint8_t class (or uint16_t or uint32_t if you need large fields for some reason). The host class is expected to provide most of the features, including any of the length-specific code; the mixin only provides an override for the set, get and toString methods.

N.B. the bitfield_mix class is a bit of code clevverness that expects to be initialized before use. The base class only provides the mechanisms; initialization provides the lookup table mapping the fields to the bits. Without providing the definition of the enum, it's not very useful.

To initialize, immediately after mixing it into the base class, invoke the setBitfieldDef proc with the classname and a list consisting of field definition lists, where each field definition list is the name, number of bits, and (optional) enum value of the bit combinations (in order 0 -> max).

N.B. that the setBitfieldDef proc should be invoked on the class immediately after class definition, not on the resulting objects. The extra data is stored in the class object proper, not in the objects created by the class.

oo::class create ::AutoObject::bitfield_mix { variable ns

method get {args} { my variable MyValue ns if {![info exists ns]} { set ns [info object namespace [info object class [self object]]] } upvar ${ns}::defArray dA if {[llength $args] == 0} { return $MyValue } else { set outL {} foreach field $args { set val [expr ($MyValue & $dA($field,mask) ) \ >> $dA($field,shift)] lappend outL $val } return $outL } } method set {args} { my variable MyValue ns if {![info exists ns]} { set ns [info object namespace [info object class [self object]]] } upvar ${ns}::defArray dA set len [llength $args] # If there's only one value, interpret it as setting the entire value if {$len == 1} { set MyValue $args # If setting individual fields, must be a list of field/value pairs } elseif {$len % 2 == 0} { foreach {key val} $args { # If there is an enum and the input is a known symbol, # use its value if {[info exists dA($key,enumL)] && \ ($val in $dA($key,enumL))} { set val [lsearch $dA($key,enumL) $val] } # Shift up the field value, mask & combine with the rest. set MyValue [expr {($MyValue & ~$dA($key,mask)) | \ (($val << $dA($key,shift)) & \ $dA($key,mask))}] } } else { error "Odd number of items in key/value list: \ [llength $args] items in $args" } } method toString {} { my variable MyValue if {![info exists ns]} { set ns [info object namespace [info object class [self object]]] } upvar ${ns}::defArray dA set outS "" set newline "" # We print field names indented up to the data side of the format, # so compute where that is. set nameSize [expr [string length [lindex $dA(nameL) 0]] + 44] set outS [format "%s Decoded to:\n" [next]] foreach name $dA(nameL) { set val [expr ($MyValue & $dA($name,mask)) \ >> $dA($name,shift)] # If there's an enum, print the symbol if it exists. # Otherwise, print it in binary. if {[info exists dA($name,enumL)] && \ $val < [llength $dA($name,enumL)]} { set valS [lindex $dA($name,enumL) $val] } else { set size $dA($name,size) set valS [format "b'%0${size}b" $val] } append outS [format "%${nameSize}s - %s\n" $name $valS] } # trim to display multiline output properly in autoObject output set outS [string trimright $outS "\n"] return $outS } ;# @@@ TODO %%% ;# Special widget not yet defined; commented out until working. ;# # N.B. that the base widget method will be called first in the ;# # method chain, and that we only get here by the "next" call. ;# method createWidget {args} { ;# my variable MyWidget ;# set ns [info object namespace [info object class [self object]]] ;# upvar ${ns}::defArray dA ;# # Get rid of the old widget, replacing by the collection of ;# # per-bitfield widgets ;# set wname $MyWidget ;# destroy $MyWidget ;# ;# foreach name $dA(nameL) { ;# } ;# ;# } method widgetUpdate {} { my variable MyWidget my set [$MyWidget get] } }

#

N.B. that the setBitfieldDef proc should be invoked on the class immediately after class definition, not on the resulting objects. The extra data is stored in the class object proper, not in the objects created by the class. proc ::AutoObject::setBitfieldDef {classname defL} { set [info object namespace $classname]::ns [info object namespace $classname] namespace upvar [info object namespace $classname] defArray dA set offset 0 foreach bf $defL { set name [lindex $bf 0] lappend nameL $name set size [lindex $bf 1] set dA($name,size) $size if {[llength $bf] > 2} { set dA($name,enumL) [lindex $bf 2] } set maskS [string repeat "1" $size] append maskS [string repeat "0" $offset] scan $maskS "%b" mask set dA($name,shift) $offset set dA($name,mask) $mask set offset [expr {$offset + $size}] } set dA(nameL) $nameL }