Add an uninstall target
[jimtcl.git] / binary.tcl
blob5e9ae3fbeadb7e96b08ac7cc45c0ba26dce20c31
1 # Implements the 'binary scan' and 'binary format' commands.
3 # (c) 2010 Steve Bennett <steveb@workware.net.au>
5 # See LICENCE in this directory for licensing.
7 package require pack
8 package require regexp
10 proc binary {cmd args} {
11 tailcall "binary $cmd" {*}$args
14 proc "binary format" {formatString args} {
15 set bitoffset 0
16 set result {}
17 foreach {conv t u n} [regexp -all -inline {([a-zA-Z@])(u)?([*0-9]*)} $formatString] {
18 if {$t in {a A}} {
19 set value [binary.nextarg args]
20 set sn [string bytelength $value]
21 if {$n ne "*"} {
22 if {$n eq ""} {
23 set n 1
25 if {$n > $sn} {
26 # Need to pad the string with spaces or nulls
27 append value [string repeat [dict get {A " " a \x00} $t] $($n - $sn)]
29 } else {
30 set n $sn
32 if {$n} {
33 set bitoffset [pack result $value -str $(8 * $n) $bitoffset]
35 } elseif {[binary.intinfo $t] ne ""} {
36 # An integer type
37 lassign [binary.intinfo $t] type endian size prefix
38 set value [binary.nextarg args]
40 if {$type ne "int"} {
41 set value [split $value {}]
43 set vn [llength $value]
44 if {$n eq "*"} {
45 set n $vn
46 } elseif {$n eq ""} {
47 set n 1
48 set value [list $value]
49 } elseif {$vn < $n} {
50 if {$type eq "int"} {
51 return -code error "number of elements in list does not match count"
52 } else {
53 # Need to pad the list with zeros
54 lappend value {*}[lrepeat $($n - $vn) 0]
56 } elseif {$vn > $n} {
57 # Need to truncate the list
58 set value [lrange $value 0 $n-1]
61 if {$endian eq "host"} {
62 set endian $($::tcl_platform(byteOrder) eq "bigEndian" ? "be" : "le")
64 foreach v $value {
65 set bitoffset [pack result $prefix$v -int$endian $size $bitoffset]
67 # Now pad out with zeros to the end of the current byte
68 if {$bitoffset % 8} {
69 set bitoffset [pack result 0 -int$endian $(8 - $bitoffset % 8) $bitoffset]
71 } elseif {$t eq "x"} {
72 if {$n eq "*"} {
73 return -code error {cannot use "*" in format string with "x"}
75 if {$n eq ""} {
76 set n 1
78 loop i 0 $n {
79 set bitoffset [pack result 0 -intbe 8 $bitoffset]
81 } elseif {$t eq "@"} {
82 if {$n eq ""} {
83 return -code error {missing count for "@" field specifier}
85 if {$n eq "*"} {
86 set bitoffset $(8 * [string bytelength $result])
87 } else {
88 # May need to pad it out
89 set max [string bytelength $result]
90 while {$n > $max} {
91 append result \x00
92 incr max
94 set bitoffset $(8 * $n)
96 } elseif {$t eq "X"} {
97 if {$n eq "*"} {
98 set bitoffset 0
99 } elseif {$n eq ""} {
100 incr bitoffset -8
101 } else {
102 incr bitoffset $($n * -8)
104 if {$bitoffset < 0} {
105 set bitoffset 0
107 } else {
108 return -code error "bad field specifier \"$t\""
111 return $result
114 proc "binary scan" {value formatString {args varName}} {
115 # Pops the next arg from the front of the list and returns it.
116 # Throws an error if no more args
117 set bitoffset 0
118 set count 0
119 foreach {conv t u n} [regexp -all -inline {([a-zA-Z@])(u)?([*0-9]*)} $formatString] {
120 set rembytes $([string bytelength $value] - $bitoffset / 8)
121 if {$t in {a A}} {
122 if {$n eq "*"} {
123 set n $rembytes
124 } elseif {$n eq ""} {
125 set n 1
127 if {$n > $rembytes} {
128 continue
131 set var [binary.nextarg varName]
133 set result [unpack $value -str $bitoffset $($n * 8)]
134 incr bitoffset $([string bytelength $result] * 8)
135 if {$t eq "A"} {
136 set result [string trimright $result]
138 } elseif {[binary.intinfo $t] ne ""} {
139 # An integer type
140 lassign [binary.intinfo $t] type endian size prefix
141 set var [binary.nextarg varName]
143 if {$n eq "*"} {
144 set n $($rembytes * 8 / $size)
145 } else {
146 if {$n eq ""} {
147 set n 1
150 if {$n * $size > $rembytes * 8} {
151 continue
154 if {$type ne "int"} {
155 set u u
157 if {$endian eq "host"} {
158 set endian $($::tcl_platform(byteOrder) eq "bigEndian" ? "be" : "le")
161 set result {}
162 loop i 0 $n {
163 set v [unpack $value -${u}int$endian $bitoffset $size]
164 if {$type eq "int"} {
165 lappend result $v
166 } else {
167 append result [lindex {0 1 2 3 4 5 6 7 8 9 a b c d e f} $v]
169 incr bitoffset $size
171 # Now skip to the end of the current byte
172 if {$bitoffset % 8} {
173 incr bitoffset $(8 - ($bitoffset % 8))
175 } elseif {$t eq "x"} {
176 # Skip bytes
177 if {$n eq "*"} {
178 set n $rembytes
179 } elseif {$n eq ""} {
180 set n 1
182 if {$n > $rembytes} {
183 set n $rembytes
185 incr bitoffset $($n * 8)
186 continue
187 } elseif {$t eq "X"} {
188 # Back up bytes
189 if {$n eq "*"} {
190 set bitoffset 0
191 continue
193 if {$n eq ""} {
194 set n 1
196 if {$n * 8 > $bitoffset} {
197 set bitoffset 0
198 continue
200 incr bitoffset -$($n * 8)
201 continue
202 } elseif {$t eq "@"} {
203 if {$n eq ""} {
204 return -code error {missing count for "@" field specifier}
206 if {$n eq "*" || $n > $rembytes + $bitoffset / 8} {
207 incr bitoffset $($rembytes * 8)
208 } elseif {$n < 0} {
209 set bitoffset 0
210 } else {
211 set bitoffset $($n * 8)
213 continue
214 } else {
215 return -code error "bad field specifier \"$t\""
217 uplevel 1 [list set $var $result]
218 incr count
220 return $count
223 # Pops the next arg from the front of the list and returns it.
224 # Throws an error if no more args
225 proc binary.nextarg {&arglist} {
226 if {[llength $arglist] == 0} {
227 return -level 2 -code error "not enough arguments for all format specifiers"
229 set arglist [lassign $arglist arg]
230 return $arg
233 proc binary.intinfo {type} {
234 set info {
235 c {int be 8}
236 s {int le 16}
237 t {int host 16}
238 S {int be 16}
239 i {int le 32}
240 I {int be 32}
241 n {int host 32}
242 w {int le 64}
243 W {int be 64}
244 m {int host 64}
245 h {hex le 4 0x}
246 H {hex be 4 0x}
247 b {bin le 1}
248 B {bin be 1}
250 if {[exists info($type)]} {
251 return $info($type)
253 return ""