| [8597867d] | 1 | # bee.tcl -- |
|---|
| 2 | # |
|---|
| 3 | # BitTorrent Bee de- and encoder. |
|---|
| 4 | # |
|---|
| 5 | # Copyright (c) 2004 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
|---|
| 6 | # See the file license.terms. |
|---|
| 7 | |
|---|
| 8 | package require Tcl 8.4 |
|---|
| 9 | |
|---|
| 10 | namespace eval ::bee { |
|---|
| 11 | # Encoder commands |
|---|
| 12 | namespace export \ |
|---|
| 13 | encodeString encodeNumber \ |
|---|
| 14 | encodeListArgs encodeList \ |
|---|
| 15 | encodeDictArgs encodeDict |
|---|
| 16 | |
|---|
| 17 | # Decoder commands. |
|---|
| 18 | namespace export \ |
|---|
| 19 | decode \ |
|---|
| 20 | decodeChannel \ |
|---|
| 21 | decodeCancel \ |
|---|
| 22 | decodePush |
|---|
| 23 | |
|---|
| 24 | # Channel decoders, reference to state information, keyed by |
|---|
| 25 | # channel handle. |
|---|
| 26 | |
|---|
| 27 | variable bee |
|---|
| 28 | array set bee {} |
|---|
| 29 | |
|---|
| 30 | # Counter for generation of names for the state variables. |
|---|
| 31 | |
|---|
| 32 | variable count 0 |
|---|
| 33 | |
|---|
| 34 | # State information for the channel decoders. |
|---|
| 35 | |
|---|
| 36 | # stateN, with N an integer number counting from 0 on up. |
|---|
| 37 | # ...(chan) Handle of channel the decoder is for. |
|---|
| 38 | # ...(cmd) Command prefix, completion callback |
|---|
| 39 | # ...(exact) Boolean flag, set for exact processing. |
|---|
| 40 | # ...(read) Buffer for new characters to process. |
|---|
| 41 | # ...(type) Type of current value (integer, string, list, dict) |
|---|
| 42 | # ...(value) Buffer for assembling the current value. |
|---|
| 43 | # ...(pend) Stack of pending 'value' buffers, for nested |
|---|
| 44 | # containers. |
|---|
| 45 | # ...(state) Current state of the decoding state machine. |
|---|
| 46 | |
|---|
| 47 | # States of the finite automaton ... |
|---|
| 48 | # intro - One char, type of value, or 'e' as stop of container. |
|---|
| 49 | # signum - sign or digit, for integer. |
|---|
| 50 | # idigit - digit, for integer, or 'e' as stop |
|---|
| 51 | # ldigit - digit, for length of string, or : |
|---|
| 52 | # data - string data, 'get' characters. |
|---|
| 53 | # Containers via 'pend'. |
|---|
| 54 | |
|---|
| 55 | #Debugging help, nesting level |
|---|
| 56 | #variable X 0 |
|---|
| 57 | } |
|---|
| 58 | |
|---|
| 59 | |
|---|
| 60 | # ::bee::encodeString -- |
|---|
| 61 | # |
|---|
| 62 | # Encode a string to bee-format. |
|---|
| 63 | # |
|---|
| 64 | # Arguments: |
|---|
| 65 | # string The string to encode. |
|---|
| 66 | # |
|---|
| 67 | # Results: |
|---|
| 68 | # The bee-encoded form of the string. |
|---|
| 69 | |
|---|
| 70 | proc ::bee::encodeString {string} { |
|---|
| 71 | return "[string length $string]:$string" |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | # ::bee::encodeNumber -- |
|---|
| 76 | # |
|---|
| 77 | # Encode an integer number to bee-format. |
|---|
| 78 | # |
|---|
| 79 | # Arguments: |
|---|
| 80 | # num The integer number to encode. |
|---|
| 81 | # |
|---|
| 82 | # Results: |
|---|
| 83 | # The bee-encoded form of the integer number. |
|---|
| 84 | |
|---|
| 85 | proc ::bee::encodeNumber {num} { |
|---|
| 86 | if {![string is integer -strict $num]} { |
|---|
| 87 | return -code error "Expected integer number, got \"$num\"" |
|---|
| 88 | } |
|---|
| 89 | |
|---|
| 90 | # The reformatting deals with hex, octal and other tcl |
|---|
| 91 | # representation of the value. In other words we normalize the |
|---|
| 92 | # string representation of the input value. |
|---|
| 93 | |
|---|
| 94 | set num [format %d $num] |
|---|
| 95 | return "i${num}e" |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | |
|---|
| 99 | # ::bee::encodeList -- |
|---|
| 100 | # |
|---|
| 101 | # Encode a list of bee-coded values to bee-format. |
|---|
| 102 | # |
|---|
| 103 | # Arguments: |
|---|
| 104 | # list The list to encode. |
|---|
| 105 | # |
|---|
| 106 | # Results: |
|---|
| 107 | # The bee-encoded form of the list. |
|---|
| 108 | |
|---|
| 109 | proc ::bee::encodeList {list} { |
|---|
| 110 | return "l[join $list ""]e" |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | |
|---|
| 114 | # ::bee::encodeListArgs -- |
|---|
| 115 | # |
|---|
| 116 | # Encode a variable list of bee-coded values to bee-format. |
|---|
| 117 | # |
|---|
| 118 | # Arguments: |
|---|
| 119 | # args The values to encode. |
|---|
| 120 | # |
|---|
| 121 | # Results: |
|---|
| 122 | # The bee-encoded form of the list of values. |
|---|
| 123 | |
|---|
| 124 | proc ::bee::encodeListArgs {args} { |
|---|
| 125 | return [encodeList $args] |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | |
|---|
| 129 | # ::bee::encodeDict -- |
|---|
| 130 | # |
|---|
| 131 | # Encode a dictionary of keys and bee-coded values to bee-format. |
|---|
| 132 | # |
|---|
| 133 | # Arguments: |
|---|
| 134 | # dict The dictionary to encode. |
|---|
| 135 | # |
|---|
| 136 | # Results: |
|---|
| 137 | # The bee-encoded form of the dictionary. |
|---|
| 138 | |
|---|
| 139 | proc ::bee::encodeDict {dict} { |
|---|
| 140 | if {([llength $dict] % 2) == 1} { |
|---|
| 141 | return -code error "Expected even number of elements, got \"[llength $dict]\"" |
|---|
| 142 | } |
|---|
| 143 | set temp [list] |
|---|
| 144 | foreach {k v} $dict { |
|---|
| 145 | lappend temp [list $k $v] |
|---|
| 146 | } |
|---|
| 147 | set res "d" |
|---|
| 148 | foreach item [lsort -index 0 $temp] { |
|---|
| 149 | foreach {k v} $item break |
|---|
| 150 | append res [encodeString $k]$v |
|---|
| 151 | } |
|---|
| 152 | append res "e" |
|---|
| 153 | return $res |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | # ::bee::encodeDictArgs -- |
|---|
| 158 | # |
|---|
| 159 | # Encode a variable dictionary of keys and bee-coded values to bee-format. |
|---|
| 160 | # |
|---|
| 161 | # Arguments: |
|---|
| 162 | # args The keys and values to encode. |
|---|
| 163 | # |
|---|
| 164 | # Results: |
|---|
| 165 | # The bee-encoded form of the dictionary. |
|---|
| 166 | |
|---|
| 167 | proc ::bee::encodeDictArgs {args} { |
|---|
| 168 | return [encodeDict $args] |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | # ::bee::decode -- |
|---|
| 173 | # |
|---|
| 174 | # Decode a bee-encoded value and returns the embedded tcl |
|---|
| 175 | # value. For containers this recurses into the contained value. |
|---|
| 176 | # |
|---|
| 177 | # Arguments: |
|---|
| 178 | # value The string containing the bee-encoded value to decode. |
|---|
| 179 | # evar Optional. If set the name of the variable to store the |
|---|
| 180 | # index of the first character after the decoded value to. |
|---|
| 181 | # start Optional. If set the index of the first character of the |
|---|
| 182 | # value to decode. Defaults to 0, i.e. the beginning of the |
|---|
| 183 | # string. |
|---|
| 184 | # |
|---|
| 185 | # Results: |
|---|
| 186 | # The tcl value embedded in the encoded string. |
|---|
| 187 | |
|---|
| 188 | proc ::bee::decode {value {evar {}} {start 0}} { |
|---|
| 189 | #variable X |
|---|
| 190 | #puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout |
|---|
| 191 | |
|---|
| 192 | if {$evar ne ""} {upvar 1 $evar end} else {set end _} |
|---|
| 193 | |
|---|
| 194 | if {[string length $value] < ($start+2)} { |
|---|
| 195 | # This checked that the 'start' index is still in the string, |
|---|
| 196 | # and the end of the value most likely as well. Note that each |
|---|
| 197 | # encoded value consists of at least two characters (the |
|---|
| 198 | # bracketing characters for integer, list, and dict, and for |
|---|
| 199 | # string at least one digit length and the colon). |
|---|
| 200 | |
|---|
| 201 | #puts \t[string length $value]\ <\ ($start+2) |
|---|
| 202 | return -code error "String not large enough for value" |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | set type [string index $value $start] |
|---|
| 206 | |
|---|
| 207 | #puts -nonewline " $type=" ; flush stdout |
|---|
| 208 | |
|---|
| 209 | if {$type eq "i"} { |
|---|
| 210 | # Extract integer |
|---|
| 211 | #puts -nonewline integer ; flush stdout |
|---|
| 212 | |
|---|
| 213 | incr start ; # Skip over intro 'i'. |
|---|
| 214 | set end [string first e $value $start] |
|---|
| 215 | if {$end < 0} { |
|---|
| 216 | return -code error "End of integer number not found" |
|---|
| 217 | } |
|---|
| 218 | incr end -1 ; # Get last character before closing 'e'. |
|---|
| 219 | set num [string range $value $start $end] |
|---|
| 220 | if { |
|---|
| 221 | [regexp {^-0+$} $num] || |
|---|
| 222 | ![string is integer -strict $num] || |
|---|
| 223 | (([string length $num] > 1) && [string match 0* $num]) |
|---|
| 224 | } { |
|---|
| 225 | return -code error "Expected integer number, got \"$num\"" |
|---|
| 226 | } |
|---|
| 227 | incr end 2 ; # Step after closing 'e' to the beginning of |
|---|
| 228 | # ........ ; # the next bee-value behind the current one. |
|---|
| 229 | |
|---|
| 230 | #puts " ($num) @$end" |
|---|
| 231 | return $num |
|---|
| 232 | |
|---|
| 233 | } elseif {($type eq "l") || ($type eq "d")} { |
|---|
| 234 | #puts -nonewline $type\n ; flush stdout |
|---|
| 235 | |
|---|
| 236 | # Extract list or dictionary, recursively each contained |
|---|
| 237 | # element. From the perspective of the decoder this is the |
|---|
| 238 | # same, the tcl representation of both is a list, and for a |
|---|
| 239 | # dictionary keys and values are also already in the correct |
|---|
| 240 | # order. |
|---|
| 241 | |
|---|
| 242 | set result [list] |
|---|
| 243 | incr start ; # Step over intro 'e' to beginning of the first |
|---|
| 244 | # ........ ; # contained value, or behind the container (if |
|---|
| 245 | # ........ ; # empty). |
|---|
| 246 | |
|---|
| 247 | set end $start |
|---|
| 248 | #incr X |
|---|
| 249 | while {[string index $value $start] ne "e"} { |
|---|
| 250 | lappend result [decode $value end $start] |
|---|
| 251 | set start $end |
|---|
| 252 | } |
|---|
| 253 | #incr X -1 |
|---|
| 254 | incr end |
|---|
| 255 | |
|---|
| 256 | #puts "[string repeat " " $X]($result) @$end" |
|---|
| 257 | |
|---|
| 258 | if {$type eq "d" && ([llength $result] % 2 == 1)} { |
|---|
| 259 | return -code error "Dictionary has to be of even length" |
|---|
| 260 | } |
|---|
| 261 | return $result |
|---|
| 262 | |
|---|
| 263 | } elseif {[string match {[0-9]} $type]} { |
|---|
| 264 | #puts -nonewline string ; flush stdout |
|---|
| 265 | |
|---|
| 266 | # Extract string. First the length, bounded by a colon, then |
|---|
| 267 | # the appropriate number of characters. |
|---|
| 268 | |
|---|
| 269 | set end [string first : $value $start] |
|---|
| 270 | if {$end < 0} { |
|---|
| 271 | return -code error "End of string length not found" |
|---|
| 272 | } |
|---|
| 273 | incr end -1 |
|---|
| 274 | set length [string range $value $start $end] |
|---|
| 275 | incr end 2 ;# Skip to beginning of the string after the colon |
|---|
| 276 | |
|---|
| 277 | if {![string is integer -strict $length]} { |
|---|
| 278 | return -code error "Expected integer number for string length, got \"$length\"" |
|---|
| 279 | } elseif {$length < 0} { |
|---|
| 280 | # This cannot happen. To happen "-" has to be first character, |
|---|
| 281 | # and this is caught as unknown bee-type. |
|---|
| 282 | return -code error "Illegal negative string length" |
|---|
| 283 | } elseif {($end + $length) > [string length $value]} { |
|---|
| 284 | return -code error "String not large enough for value" |
|---|
| 285 | } |
|---|
| 286 | |
|---|
| 287 | #puts -nonewline \[$length\] ; flush stdout |
|---|
| 288 | if {$length > 0} { |
|---|
| 289 | set start $end |
|---|
| 290 | incr end $length |
|---|
| 291 | incr end -1 |
|---|
| 292 | set result [string range $value $start $end] |
|---|
| 293 | incr end |
|---|
| 294 | } else { |
|---|
| 295 | set result "" |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | #puts " ($result) @$end" |
|---|
| 299 | return $result |
|---|
| 300 | |
|---|
| 301 | } else { |
|---|
| 302 | return -code error "Unknown bee-type \"$type\"" |
|---|
| 303 | } |
|---|
| 304 | } |
|---|
| 305 | |
|---|
| 306 | # ::bee::decodeIndices -- |
|---|
| 307 | # |
|---|
| 308 | # Similar to 'decode', but does not return the decoded tcl values, |
|---|
| 309 | # but a structure containing the start- and end-indices for all |
|---|
| 310 | # values in the structure. |
|---|
| 311 | # |
|---|
| 312 | # Arguments: |
|---|
| 313 | # value The string containing the bee-encoded value to decode. |
|---|
| 314 | # evar Optional. If set the name of the variable to store the |
|---|
| 315 | # index of the first character after the decoded value to. |
|---|
| 316 | # start Optional. If set the index of the first character of the |
|---|
| 317 | # value to decode. Defaults to 0, i.e. the beginning of the |
|---|
| 318 | # string. |
|---|
| 319 | # |
|---|
| 320 | # Results: |
|---|
| 321 | # The structure of the value, with indices and types for all |
|---|
| 322 | # contained elements. |
|---|
| 323 | |
|---|
| 324 | proc ::bee::decodeIndices {value {evar {}} {start 0}} { |
|---|
| 325 | #variable X |
|---|
| 326 | #puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout |
|---|
| 327 | |
|---|
| 328 | if {$evar ne ""} {upvar 1 $evar end} else {set end _} |
|---|
| 329 | |
|---|
| 330 | if {[string length $value] < ($start+2)} { |
|---|
| 331 | # This checked that the 'start' index is still in the string, |
|---|
| 332 | # and the end of the value most likely as well. Note that each |
|---|
| 333 | # encoded value consists of at least two characters (the |
|---|
| 334 | # bracketing characters for integer, list, and dict, and for |
|---|
| 335 | # string at least one digit length and the colon). |
|---|
| 336 | |
|---|
| 337 | #puts \t[string length $value]\ <\ ($start+2) |
|---|
| 338 | return -code error "String not large enough for value" |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | set type [string index $value $start] |
|---|
| 342 | |
|---|
| 343 | #puts -nonewline " $type=" ; flush stdout |
|---|
| 344 | |
|---|
| 345 | if {$type eq "i"} { |
|---|
| 346 | # Extract integer |
|---|
| 347 | #puts -nonewline integer ; flush stdout |
|---|
| 348 | |
|---|
| 349 | set begin $start |
|---|
| 350 | |
|---|
| 351 | incr start ; # Skip over intro 'i'. |
|---|
| 352 | set end [string first e $value $start] |
|---|
| 353 | if {$end < 0} { |
|---|
| 354 | return -code error "End of integer number not found" |
|---|
| 355 | } |
|---|
| 356 | incr end -1 ; # Get last character before closing 'e'. |
|---|
| 357 | set num [string range $value $start $end] |
|---|
| 358 | if { |
|---|
| 359 | [regexp {^-0+$} $num] || |
|---|
| 360 | ![string is integer -strict $num] || |
|---|
| 361 | (([string length $num] > 1) && [string match 0* $num]) |
|---|
| 362 | } { |
|---|
| 363 | return -code error "Expected integer number, got \"$num\"" |
|---|
| 364 | } |
|---|
| 365 | incr end |
|---|
| 366 | set stop $end |
|---|
| 367 | incr end 1 ; # Step after closing 'e' to the beginning of |
|---|
| 368 | # ........ ; # the next bee-value behind the current one. |
|---|
| 369 | |
|---|
| 370 | #puts " ($num) @$end" |
|---|
| 371 | return [list integer $begin $stop] |
|---|
| 372 | |
|---|
| 373 | } elseif {$type eq "l"} { |
|---|
| 374 | #puts -nonewline $type\n ; flush stdout |
|---|
| 375 | |
|---|
| 376 | # Extract list, recursively each contained element. |
|---|
| 377 | |
|---|
| 378 | set result [list] |
|---|
| 379 | |
|---|
| 380 | lappend result list $start @ |
|---|
| 381 | |
|---|
| 382 | incr start ; # Step over intro 'e' to beginning of the first |
|---|
| 383 | # ........ ; # contained value, or behind the container (if |
|---|
| 384 | # ........ ; # empty). |
|---|
| 385 | |
|---|
| 386 | set end $start |
|---|
| 387 | #incr X |
|---|
| 388 | |
|---|
| 389 | set contained [list] |
|---|
| 390 | while {[string index $value $start] ne "e"} { |
|---|
| 391 | lappend contained [decodeIndices $value end $start] |
|---|
| 392 | set start $end |
|---|
| 393 | } |
|---|
| 394 | lappend result $contained |
|---|
| 395 | #incr X -1 |
|---|
| 396 | set stop $end |
|---|
| 397 | incr end |
|---|
| 398 | |
|---|
| 399 | #puts "[string repeat " " $X]($result) @$end" |
|---|
| 400 | |
|---|
| 401 | return [lreplace $result 2 2 $stop] |
|---|
| 402 | |
|---|
| 403 | } elseif {($type eq "l") || ($type eq "d")} { |
|---|
| 404 | #puts -nonewline $type\n ; flush stdout |
|---|
| 405 | |
|---|
| 406 | # Extract dictionary, recursively each contained element. |
|---|
| 407 | |
|---|
| 408 | set result [list] |
|---|
| 409 | |
|---|
| 410 | lappend result dict $start @ |
|---|
| 411 | |
|---|
| 412 | incr start ; # Step over intro 'e' to beginning of the first |
|---|
| 413 | # ........ ; # contained value, or behind the container (if |
|---|
| 414 | # ........ ; # empty). |
|---|
| 415 | |
|---|
| 416 | set end $start |
|---|
| 417 | set atkey 1 |
|---|
| 418 | #incr X |
|---|
| 419 | |
|---|
| 420 | set contained [list] |
|---|
| 421 | set val [list] |
|---|
| 422 | while {[string index $value $start] ne "e"} { |
|---|
| 423 | if {$atkey} { |
|---|
| 424 | lappend contained [decode $value {} $start] |
|---|
| 425 | lappend val [decodeIndices $value end $start] |
|---|
| 426 | set atkey 0 |
|---|
| 427 | } else { |
|---|
| 428 | lappend val [decodeIndices $value end $start] |
|---|
| 429 | lappend contained $val |
|---|
| 430 | set val [list] |
|---|
| 431 | set atkey 1 |
|---|
| 432 | } |
|---|
| 433 | set start $end |
|---|
| 434 | } |
|---|
| 435 | lappend result $contained |
|---|
| 436 | #incr X -1 |
|---|
| 437 | set stop $end |
|---|
| 438 | incr end |
|---|
| 439 | |
|---|
| 440 | #puts "[string repeat " " $X]($result) @$end" |
|---|
| 441 | |
|---|
| 442 | if {[llength $result] % 2 == 1} { |
|---|
| 443 | return -code error "Dictionary has to be of even length" |
|---|
| 444 | } |
|---|
| 445 | return [lreplace $result 2 2 $stop] |
|---|
| 446 | |
|---|
| 447 | } elseif {[string match {[0-9]} $type]} { |
|---|
| 448 | #puts -nonewline string ; flush stdout |
|---|
| 449 | |
|---|
| 450 | # Extract string. First the length, bounded by a colon, then |
|---|
| 451 | # the appropriate number of characters. |
|---|
| 452 | |
|---|
| 453 | set end [string first : $value $start] |
|---|
| 454 | if {$end < 0} { |
|---|
| 455 | return -code error "End of string length not found" |
|---|
| 456 | } |
|---|
| 457 | incr end -1 |
|---|
| 458 | set length [string range $value $start $end] |
|---|
| 459 | incr end 2 ;# Skip to beginning of the string after the colon |
|---|
| 460 | |
|---|
| 461 | if {![string is integer -strict $length]} { |
|---|
| 462 | return -code error "Expected integer number for string length, got \"$length\"" |
|---|
| 463 | } elseif {$length < 0} { |
|---|
| 464 | # This cannot happen. To happen "-" has to be first character, |
|---|
| 465 | # and this is caught as unknown bee-type. |
|---|
| 466 | return -code error "Illegal negative string length" |
|---|
| 467 | } elseif {($end + $length) > [string length $value]} { |
|---|
| 468 | return -code error "String not large enough for value" |
|---|
| 469 | } |
|---|
| 470 | |
|---|
| 471 | #puts -nonewline \[$length\] ; flush stdout |
|---|
| 472 | incr end -1 |
|---|
| 473 | if {$length > 0} { |
|---|
| 474 | incr end $length |
|---|
| 475 | set stop $end |
|---|
| 476 | } else { |
|---|
| 477 | set stop $end |
|---|
| 478 | } |
|---|
| 479 | incr end |
|---|
| 480 | |
|---|
| 481 | #puts " ($result) @$end" |
|---|
| 482 | return [list string $start $stop] |
|---|
| 483 | |
|---|
| 484 | } else { |
|---|
| 485 | return -code error "Unknown bee-type \"$type\"" |
|---|
| 486 | } |
|---|
| 487 | } |
|---|
| 488 | |
|---|
| 489 | |
|---|
| 490 | # ::bee::decodeChannel -- |
|---|
| 491 | # |
|---|
| 492 | # Attach decoder for a bee-value to a channel. See the |
|---|
| 493 | # documentation for details. |
|---|
| 494 | # |
|---|
| 495 | # Arguments: |
|---|
| 496 | # chan Channel to attach to. |
|---|
| 497 | # -command cmdprefix Completion callback. Required. |
|---|
| 498 | # -exact Keep running after completion. |
|---|
| 499 | # -prefix data Seed for decode buffer. |
|---|
| 500 | # |
|---|
| 501 | # Results: |
|---|
| 502 | # A token to use when referring to the decoder. |
|---|
| 503 | # For example when canceling it. |
|---|
| 504 | |
|---|
| 505 | proc ::bee::decodeChannel {chan args} { |
|---|
| 506 | variable bee |
|---|
| 507 | if {[info exists bee($chan)]} { |
|---|
| 508 | return -code error "bee-Decoder already active for channel" |
|---|
| 509 | } |
|---|
| 510 | |
|---|
| 511 | # Create state and token. |
|---|
| 512 | |
|---|
| 513 | variable count |
|---|
| 514 | variable [set st state$count] |
|---|
| 515 | array set $st {} |
|---|
| 516 | set bee($chan) $st |
|---|
| 517 | upvar 0 $st state |
|---|
| 518 | incr count |
|---|
| 519 | |
|---|
| 520 | # Initialize the decoder state, process the options. When |
|---|
| 521 | # encountering errors here destroy the half-baked state before |
|---|
| 522 | # throwing the message. |
|---|
| 523 | |
|---|
| 524 | set state(chan) $chan |
|---|
| 525 | array set state { |
|---|
| 526 | exact 0 |
|---|
| 527 | type ? |
|---|
| 528 | read {} |
|---|
| 529 | value {} |
|---|
| 530 | pend {} |
|---|
| 531 | state intro |
|---|
| 532 | get 1 |
|---|
| 533 | } |
|---|
| 534 | |
|---|
| 535 | while {[llength $args]} { |
|---|
| 536 | set option [lindex $args 0] |
|---|
| 537 | set args [lrange $args 1 end] |
|---|
| 538 | if {$option eq "-command"} { |
|---|
| 539 | if {![llength $args]} { |
|---|
| 540 | unset bee($chan) |
|---|
| 541 | unset state |
|---|
| 542 | return -code error "Missing value for option -command." |
|---|
| 543 | } |
|---|
| 544 | set state(cmd) [lindex $args 0] |
|---|
| 545 | set args [lrange $args 1 end] |
|---|
| 546 | |
|---|
| 547 | } elseif {$option eq "-prefix"} { |
|---|
| 548 | if {![llength $args]} { |
|---|
| 549 | unset bee($chan) |
|---|
| 550 | unset state |
|---|
| 551 | return -code error "Missing value for option -prefix." |
|---|
| 552 | } |
|---|
| 553 | set state(read) [lindex $args 0] |
|---|
| 554 | set args [lrange $args 1 end] |
|---|
| 555 | |
|---|
| 556 | } elseif {$option eq "-exact"} { |
|---|
| 557 | set state(exact) 1 |
|---|
| 558 | } else { |
|---|
| 559 | unset bee($chan) |
|---|
| 560 | unset state |
|---|
| 561 | return -code error "Illegal option \"$option\",\ |
|---|
| 562 | expected \"-command\", \"-prefix\", or \"-keep\"" |
|---|
| 563 | } |
|---|
| 564 | } |
|---|
| 565 | |
|---|
| 566 | if {![info exists state(cmd)]} { |
|---|
| 567 | unset bee($chan) |
|---|
| 568 | unset state |
|---|
| 569 | return -code error "Missing required completion callback." |
|---|
| 570 | } |
|---|
| 571 | |
|---|
| 572 | # Set up the processing of incoming data. |
|---|
| 573 | |
|---|
| 574 | fileevent $chan readable [list ::bee::Process $chan $bee($chan)] |
|---|
| 575 | |
|---|
| 576 | # Return the name of the state array as token. |
|---|
| 577 | return $bee($chan) |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | # ::bee::Parse -- |
|---|
| 581 | # |
|---|
| 582 | # Internal helper. Fileevent handler for a decoder. |
|---|
| 583 | # Parses input and handles both error and eof conditions. |
|---|
| 584 | # |
|---|
| 585 | # Arguments: |
|---|
| 586 | # token The decoder to run on its input. |
|---|
| 587 | # |
|---|
| 588 | # Results: |
|---|
| 589 | # None. |
|---|
| 590 | |
|---|
| 591 | proc ::bee::Process {chan token} { |
|---|
| 592 | if {[catch {Parse $token} msg]} { |
|---|
| 593 | # Something failed. Destroy and report. |
|---|
| 594 | Command $token error $msg |
|---|
| 595 | return |
|---|
| 596 | } |
|---|
| 597 | |
|---|
| 598 | if {[eof $chan]} { |
|---|
| 599 | # Having data waiting, either in the input queue, or in the |
|---|
| 600 | # output stack (of nested containers) is a failure. Report |
|---|
| 601 | # this instead of the eof. |
|---|
| 602 | |
|---|
| 603 | variable $token |
|---|
| 604 | upvar 0 $token state |
|---|
| 605 | |
|---|
| 606 | if { |
|---|
| 607 | [string length $state(read)] || |
|---|
| 608 | [llength $state(pend)] || |
|---|
| 609 | [string length $state(value)] || |
|---|
| 610 | ($state(state) ne "intro") |
|---|
| 611 | } { |
|---|
| 612 | Command $token error "Incomplete value at end of channel" |
|---|
| 613 | } else { |
|---|
| 614 | Command $token eof |
|---|
| 615 | } |
|---|
| 616 | } |
|---|
| 617 | return |
|---|
| 618 | } |
|---|
| 619 | |
|---|
| 620 | # ::bee::Parse -- |
|---|
| 621 | # |
|---|
| 622 | # Internal helper. Reading from the channel and parsing the input. |
|---|
| 623 | # Uses a hardwired state machine. |
|---|
| 624 | # |
|---|
| 625 | # Arguments: |
|---|
| 626 | # token The decoder to run on its input. |
|---|
| 627 | # |
|---|
| 628 | # Results: |
|---|
| 629 | # None. |
|---|
| 630 | |
|---|
| 631 | proc ::bee::Parse {token} { |
|---|
| 632 | variable $token |
|---|
| 633 | upvar 0 $token state |
|---|
| 634 | upvar 0 state(state) current |
|---|
| 635 | upvar 0 state(read) input |
|---|
| 636 | upvar 0 state(type) type |
|---|
| 637 | upvar 0 state(value) value |
|---|
| 638 | upvar 0 state(pend) pend |
|---|
| 639 | upvar 0 state(exact) exact |
|---|
| 640 | upvar 0 state(get) get |
|---|
| 641 | set chan $state(chan) |
|---|
| 642 | |
|---|
| 643 | #puts Parse/$current |
|---|
| 644 | |
|---|
| 645 | if {!$exact} { |
|---|
| 646 | # Add all waiting characters to the buffer so that we can process as |
|---|
| 647 | # much as is possible in one go. |
|---|
| 648 | append input [read $chan] |
|---|
| 649 | } else { |
|---|
| 650 | # Exact reading. Usually one character, but when in the data |
|---|
| 651 | # section for a string value we know for how many characters |
|---|
| 652 | # we are looking for. |
|---|
| 653 | |
|---|
| 654 | append input [read $chan $get] |
|---|
| 655 | } |
|---|
| 656 | |
|---|
| 657 | # We got nothing, do nothing. |
|---|
| 658 | if {![string length $input]} return |
|---|
| 659 | |
|---|
| 660 | |
|---|
| 661 | if {$current eq "data"} { |
|---|
| 662 | # String data, this can be done faster, as we read longer |
|---|
| 663 | # sequences of characters for this. |
|---|
| 664 | set l [string length $input] |
|---|
| 665 | if {$l < $get} { |
|---|
| 666 | # Not enough, wait for more. |
|---|
| 667 | append value $input |
|---|
| 668 | incr get -$l |
|---|
| 669 | return |
|---|
| 670 | } elseif {$l == $get} { |
|---|
| 671 | # Got all, exactly. Prepare state machine for next value. |
|---|
| 672 | |
|---|
| 673 | if {[Complete $token $value$input]} return |
|---|
| 674 | |
|---|
| 675 | set current intro |
|---|
| 676 | set get 1 |
|---|
| 677 | set value "" |
|---|
| 678 | set input "" |
|---|
| 679 | |
|---|
| 680 | return |
|---|
| 681 | } else { |
|---|
| 682 | # Got more than required (only for !exact). |
|---|
| 683 | |
|---|
| 684 | incr get -1 |
|---|
| 685 | if {[Complete $token $value[string range $input 0 $get]]} {return} |
|---|
| 686 | |
|---|
| 687 | incr get |
|---|
| 688 | set input [string range $input $get end] |
|---|
| 689 | set get 1 |
|---|
| 690 | set value "" |
|---|
| 691 | set current intro |
|---|
| 692 | # This now falls into the loop below. |
|---|
| 693 | } |
|---|
| 694 | } |
|---|
| 695 | |
|---|
| 696 | set where 0 |
|---|
| 697 | set n [string length $input] |
|---|
| 698 | |
|---|
| 699 | #puts Parse/$n |
|---|
| 700 | |
|---|
| 701 | while {$where < $n} { |
|---|
| 702 | # Hardwired state machine. Get current character. |
|---|
| 703 | set ch [string index $input $where] |
|---|
| 704 | |
|---|
| 705 | #puts Parse/@$where/$current/$ch/ |
|---|
| 706 | if {$current eq "intro"} { |
|---|
| 707 | # First character of a value. |
|---|
| 708 | |
|---|
| 709 | if {$ch eq "i"} { |
|---|
| 710 | # Begin reading integer. |
|---|
| 711 | set type integer |
|---|
| 712 | set current signum |
|---|
| 713 | } elseif {$ch eq "l"} { |
|---|
| 714 | # Begin a list. |
|---|
| 715 | set type list |
|---|
| 716 | lappend pend list {} |
|---|
| 717 | #set current intro |
|---|
| 718 | |
|---|
| 719 | } elseif {$ch eq "d"} { |
|---|
| 720 | # Begin a dictionary. |
|---|
| 721 | set type dict |
|---|
| 722 | lappend pend dict {} |
|---|
| 723 | #set current intro |
|---|
| 724 | |
|---|
| 725 | } elseif {$ch eq "e"} { |
|---|
| 726 | # Close a container. Throw an error if there is no |
|---|
| 727 | # container to close. |
|---|
| 728 | |
|---|
| 729 | if {![llength $pend]} { |
|---|
| 730 | return -code error "End of container outside of container." |
|---|
| 731 | } |
|---|
| 732 | |
|---|
| 733 | set v [lindex $pend end] |
|---|
| 734 | set t [lindex $pend end-1] |
|---|
| 735 | set pend [lrange $pend 0 end-2] |
|---|
| 736 | |
|---|
| 737 | if {$t eq "dict" && ([llength $v] % 2 == 1)} { |
|---|
| 738 | return -code error "Dictionary has to be of even length" |
|---|
| 739 | } |
|---|
| 740 | |
|---|
| 741 | if {[Complete $token $v]} {return} |
|---|
| 742 | set current intro |
|---|
| 743 | |
|---|
| 744 | } elseif {[string match {[0-9]} $ch]} { |
|---|
| 745 | # Begin reading a string, length section first. |
|---|
| 746 | set type string |
|---|
| 747 | set current ldigit |
|---|
| 748 | set value $ch |
|---|
| 749 | |
|---|
| 750 | } else { |
|---|
| 751 | # Unknown type. Throw error. |
|---|
| 752 | return -code error "Unknown bee-type \"$ch\"" |
|---|
| 753 | } |
|---|
| 754 | |
|---|
| 755 | # To next character. |
|---|
| 756 | incr where |
|---|
| 757 | } elseif {$current eq "signum"} { |
|---|
| 758 | # Integer number, a minus sign, or a digit. |
|---|
| 759 | if {[string match {[-0-9]} $ch]} { |
|---|
| 760 | append value $ch |
|---|
| 761 | set current idigit |
|---|
| 762 | } else { |
|---|
| 763 | return -code error "Syntax error in integer,\ |
|---|
| 764 | expected sign or digit, got \"$ch\"" |
|---|
| 765 | } |
|---|
| 766 | incr where |
|---|
| 767 | |
|---|
| 768 | } elseif {$current eq "idigit"} { |
|---|
| 769 | # Integer number, digit or closing 'e'. |
|---|
| 770 | |
|---|
| 771 | if {[string match {[-0-9]} $ch]} { |
|---|
| 772 | append value $ch |
|---|
| 773 | } elseif {$ch eq "e"} { |
|---|
| 774 | # Integer closes. Validate and report. |
|---|
| 775 | #puts validate |
|---|
| 776 | if { |
|---|
| 777 | [regexp {^-0+$} $value] || |
|---|
| 778 | ![string is integer -strict $value] || |
|---|
| 779 | (([string length $value] > 1) && [string match 0* $value]) |
|---|
| 780 | } { |
|---|
| 781 | return -code error "Expected integer number, got \"$value\"" |
|---|
| 782 | } |
|---|
| 783 | |
|---|
| 784 | if {[Complete $token $value]} {return} |
|---|
| 785 | set value "" |
|---|
| 786 | set current intro |
|---|
| 787 | } else { |
|---|
| 788 | return -code error "Syntax error in integer,\ |
|---|
| 789 | expected digit, or 'e', got \"$ch\"" |
|---|
| 790 | } |
|---|
| 791 | incr where |
|---|
| 792 | |
|---|
| 793 | } elseif {$current eq "ldigit"} { |
|---|
| 794 | # String, length section, digit, or : |
|---|
| 795 | |
|---|
| 796 | if {[string match {[-0-9]} $ch]} { |
|---|
| 797 | append value $ch |
|---|
| 798 | |
|---|
| 799 | } elseif {$ch eq ":"} { |
|---|
| 800 | # Length section closes, validate, |
|---|
| 801 | # then perform data processing. |
|---|
| 802 | |
|---|
| 803 | set num $value |
|---|
| 804 | if { |
|---|
| 805 | [regexp {^-0+$} $num] || |
|---|
| 806 | ![string is integer -strict $num] || |
|---|
| 807 | (([string length $num] > 1) && [string match 0* $num]) |
|---|
| 808 | } { |
|---|
| 809 | return -code error "Expected integer number as string length, got \"$num\"" |
|---|
| 810 | } |
|---|
| 811 | |
|---|
| 812 | set value "" |
|---|
| 813 | |
|---|
| 814 | # We may have already part of the data in |
|---|
| 815 | # memory. Process that piece before looking for more. |
|---|
| 816 | |
|---|
| 817 | incr where |
|---|
| 818 | set have [expr {$n - $where}] |
|---|
| 819 | if {$num < $have} { |
|---|
| 820 | # More than enough in the buffer. |
|---|
| 821 | |
|---|
| 822 | set end $where |
|---|
| 823 | incr end $num |
|---|
| 824 | incr end -1 |
|---|
| 825 | |
|---|
| 826 | if {[Complete $token [string range $input $where $end]]} {return} |
|---|
| 827 | |
|---|
| 828 | set where $end ;# Further processing behind the string. |
|---|
| 829 | set current intro |
|---|
| 830 | |
|---|
| 831 | } elseif {$num == $have} { |
|---|
| 832 | # Just enough. |
|---|
| 833 | |
|---|
| 834 | if {[Complete $token [string range $input $where end]]} {return} |
|---|
| 835 | |
|---|
| 836 | set where $n |
|---|
| 837 | set current intro |
|---|
| 838 | } else { |
|---|
| 839 | # Not enough. Initialize value with the data we |
|---|
| 840 | # have (after the colon) and stop processing for |
|---|
| 841 | # now. |
|---|
| 842 | |
|---|
| 843 | set value [string range $input $where end] |
|---|
| 844 | set current data |
|---|
| 845 | set get $num |
|---|
| 846 | set input "" |
|---|
| 847 | return |
|---|
| 848 | } |
|---|
| 849 | } else { |
|---|
| 850 | return -code error "Syntax error in string length,\ |
|---|
| 851 | expected digit, or ':', got \"$ch\"" |
|---|
| 852 | } |
|---|
| 853 | incr where |
|---|
| 854 | } else { |
|---|
| 855 | # unknown state = internal error |
|---|
| 856 | return -code error "Unknown decoder state \"$current\", internal error" |
|---|
| 857 | } |
|---|
| 858 | } |
|---|
| 859 | |
|---|
| 860 | set input "" |
|---|
| 861 | return |
|---|
| 862 | } |
|---|
| 863 | |
|---|
| 864 | # ::bee::Command -- |
|---|
| 865 | # |
|---|
| 866 | # Internal helper. Runs the decoder command callback. |
|---|
| 867 | # |
|---|
| 868 | # Arguments: |
|---|
| 869 | # token The decoder invoking its callback |
|---|
| 870 | # how Which method to invoke (value, error, eof) |
|---|
| 871 | # args Arguments for the method. |
|---|
| 872 | # |
|---|
| 873 | # Results: |
|---|
| 874 | # A boolean flag. Set if further processing has to stop. |
|---|
| 875 | |
|---|
| 876 | proc ::bee::Command {token how args} { |
|---|
| 877 | variable $token |
|---|
| 878 | upvar 0 $token state |
|---|
| 879 | |
|---|
| 880 | #puts Report/$token/$how/$args/ |
|---|
| 881 | |
|---|
| 882 | set cmd $state(cmd) |
|---|
| 883 | set chan $state(chan) |
|---|
| 884 | |
|---|
| 885 | # We catch the fileevents because they will fail when this is |
|---|
| 886 | # called from the 'Close'. The channel will already be gone in |
|---|
| 887 | # that case. |
|---|
| 888 | |
|---|
| 889 | set stop 0 |
|---|
| 890 | if {($how eq "error") || ($how eq "eof")} { |
|---|
| 891 | variable bee |
|---|
| 892 | |
|---|
| 893 | set stop 1 |
|---|
| 894 | fileevent $chan readable {} |
|---|
| 895 | unset bee($chan) |
|---|
| 896 | unset state |
|---|
| 897 | |
|---|
| 898 | if {$how eq "eof"} { |
|---|
| 899 | #puts \tclosing/$chan |
|---|
| 900 | close $chan |
|---|
| 901 | } |
|---|
| 902 | } |
|---|
| 903 | |
|---|
| 904 | lappend cmd $how $token |
|---|
| 905 | foreach a $args {lappend cmd $a} |
|---|
| 906 | uplevel #0 $cmd |
|---|
| 907 | |
|---|
| 908 | if {![info exists state]} { |
|---|
| 909 | # The decoder token was killed by the callback, stop |
|---|
| 910 | # processing. |
|---|
| 911 | set stop 1 |
|---|
| 912 | } |
|---|
| 913 | |
|---|
| 914 | #puts /$stop/[file channels] |
|---|
| 915 | return $stop |
|---|
| 916 | } |
|---|
| 917 | |
|---|
| 918 | # ::bee::Complete -- |
|---|
| 919 | # |
|---|
| 920 | # Internal helper. Reports a completed value. |
|---|
| 921 | # |
|---|
| 922 | # Arguments: |
|---|
| 923 | # token The decoder reporting the value. |
|---|
| 924 | # value The value to report. |
|---|
| 925 | # |
|---|
| 926 | # Results: |
|---|
| 927 | # A boolean flag. Set if further processing has to stop. |
|---|
| 928 | |
|---|
| 929 | proc ::bee::Complete {token value} { |
|---|
| 930 | variable $token |
|---|
| 931 | upvar 0 $token state |
|---|
| 932 | upvar 0 state(pend) pend |
|---|
| 933 | |
|---|
| 934 | if {[llength $pend]} { |
|---|
| 935 | # The value is part of a container. Add the value to its end |
|---|
| 936 | # and keep processing. |
|---|
| 937 | |
|---|
| 938 | set pend [lreplace $pend end end \ |
|---|
| 939 | [linsert [lindex $pend end] end \ |
|---|
| 940 | $value]] |
|---|
| 941 | |
|---|
| 942 | # Don't stop. |
|---|
| 943 | return 0 |
|---|
| 944 | } |
|---|
| 945 | |
|---|
| 946 | # The value is at the top, report it. The callback determines if |
|---|
| 947 | # we keep processing. |
|---|
| 948 | |
|---|
| 949 | return [Command $token value $value] |
|---|
| 950 | } |
|---|
| 951 | |
|---|
| 952 | # ::bee::decodeCancel -- |
|---|
| 953 | # |
|---|
| 954 | # Destroys the decoder referenced by the token. |
|---|
| 955 | # |
|---|
| 956 | # Arguments: |
|---|
| 957 | # token The decoder to destroy. |
|---|
| 958 | # |
|---|
| 959 | # Results: |
|---|
| 960 | # None. |
|---|
| 961 | |
|---|
| 962 | proc ::bee::decodeCancel {token} { |
|---|
| 963 | variable bee |
|---|
| 964 | variable $token |
|---|
| 965 | upvar 0 $token state |
|---|
| 966 | unset bee($state(chan)) |
|---|
| 967 | unset state |
|---|
| 968 | return |
|---|
| 969 | } |
|---|
| 970 | |
|---|
| 971 | # ::bee::decodePush -- |
|---|
| 972 | # |
|---|
| 973 | # Push data into the decoder input buffer. |
|---|
| 974 | # |
|---|
| 975 | # Arguments: |
|---|
| 976 | # token The decoder to extend. |
|---|
| 977 | # string The characters to add. |
|---|
| 978 | # |
|---|
| 979 | # Results: |
|---|
| 980 | # None. |
|---|
| 981 | |
|---|
| 982 | proc ::bee::decodePush {token string} { |
|---|
| 983 | variable $token |
|---|
| 984 | upvar 0 $token state |
|---|
| 985 | append state(read) $string |
|---|
| 986 | return |
|---|
| 987 | } |
|---|
| 988 | |
|---|
| 989 | |
|---|
| 990 | package provide bee 0.1 |
|---|