00001
00002
00003
00004
00005
00006
00007
00008 package require Tcl 8.4
00009
00010 namespace ::bee {
00011
00012 namespace export \
00013 encodeString encodeNumber \
00014 encodeListArgs encodeList \
00015 encodeDictArgs encodeDict
00016
00017
00018 namespace export \
00019 decode \
00020 decodeChannel \
00021 decodeCancel \
00022 decodePush
00023
00024
00025
00026
00027 variable bee
00028 array bee = {}
00029
00030
00031
00032 variable count 0
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057 }
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070 ret ::bee::encodeString (type string) {
00071 return "[string length $string]:$string"
00072 }
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085 ret ::bee::encodeNumber (type num) {
00086 if {![string is integer -strict $num]} {
00087 return -code error "Expected integer number, got \"$num\""
00088 }
00089
00090 # The reformatting deals with hex, octal and other tcl
00091 # representation of the value. In other words we normalize the
00092 # string representation of the input value.
00093
00094 set num [format %d $num]
00095 return "i${num}e"
00096 }
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109 ret ::bee::encodeList (type list) {
00110 return "l[join $list ""]e"
00111 }
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124 ret ::bee::encodeListArgs (type args) {
00125 return [encodeList $args]
00126 }
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139 ret ::bee::encodeDict (type dict) {
00140 if {([llength $dict] % 2) == 1} {
00141 return -code error "Expected even number of elements, got \"[llength $dict]\""
00142 }
00143 set temp [list]
00144 foreach {k v} $dict {
00145 lappend temp [list $k $v]
00146 }
00147 set res "d"
00148 foreach item [lsort -index 0 $temp] {
00149 foreach {k v} $item break
00150 append res [encodeString $k]$v
00151 }
00152 append res "e"
00153 return $res
00154 }
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167 ret ::bee::encodeDictArgs (type args) {
00168 return [encodeDict $args]
00169 }
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188 ret ::bee::decode (type value , optional evar ={) {start 0}} {
00189
00190
00191
00192 if {$evar ne ""} {upvar 1 $evar end} else { end = _}
00193
00194 if {[string length $value] < ($start+2)} {
00195
00196
00197
00198
00199
00200
00201
00202 return -code error "String not large enough for value"
00203 }
00204
00205 type = [string index $value $start]
00206
00207
00208
00209 if {$type eq "i"} {
00210
00211
00212
00213 incr start ;
00214 end = [string first e $value $start]
00215 if {$end < 0} {
00216 return -code error "End of integer number not found"
00217 }
00218 incr end -1 ;
00219 num = [string range $value $start $end]
00220 if {
00221 [regexp {^-0+$} $num] ||
00222 ![string is integer -strict $num] ||
00223 (([string length $num] > 1) && [string match 0* $num])
00224 } {
00225 return -code error "Expected integer number, got \"$num\""
00226 }
00227 incr end 2 ;
00228
00229
00230
00231 return $num
00232
00233 } elseif {($type eq "l") || ($type eq "d")} {
00234
00235
00236
00237
00238
00239
00240
00241
00242 result = [list]
00243 incr start ;
00244
00245
00246
00247 end = $start
00248
00249 while {[string index $value $start] ne "e"} {
00250 lappend result [decode $value end $start]
00251 start = $end
00252 }
00253
00254 incr end
00255
00256
00257
00258 if {$type eq "d" && ([llength $result] % 2 == 1)} {
00259 return -code error "Dictionary has to be of even length"
00260 }
00261 return $result
00262
00263 } elseif {[string match {[0-9]} $type]} {
00264
00265
00266
00267
00268
00269 end = [string first : $value $start]
00270 if {$end < 0} {
00271 return -code error "End of string length not found"
00272 }
00273 incr end -1
00274 length = [string range $value $start $end]
00275 incr end 2 ;
00276
00277 if {![string is integer -strict $length]} {
00278 return -code error "Expected integer number for string length, got \"$length\""
00279 } elseif {$length < 0} {
00280
00281
00282 return -code error "Illegal negative string length"
00283 } elseif {($end + $length) > [string length $value]} {
00284 return -code error "String not large enough for value"
00285 }
00286
00287
00288 if {$length > 0} {
00289 start = $end
00290 incr end $length
00291 incr end -1
00292 result = [string range $value $start $end]
00293 incr end
00294 } else {
00295 result = ""
00296 }
00297
00298
00299 return $result
00300
00301 } else {
00302 return -code error "Unknown bee-type \"$type\""
00303 }
00304 }
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324 ret ::bee::decodeIndices (type value , optional evar ={) {start 0}} {
00325
00326
00327
00328 if {$evar ne ""} {upvar 1 $evar end} else { end = _}
00329
00330 if {[string length $value] < ($start+2)} {
00331
00332
00333
00334
00335
00336
00337
00338 return -code error "String not large enough for value"
00339 }
00340
00341 type = [string index $value $start]
00342
00343
00344
00345 if {$type eq "i"} {
00346
00347
00348
00349 begin = $start
00350
00351 incr start ;
00352 end = [string first e $value $start]
00353 if {$end < 0} {
00354 return -code error "End of integer number not found"
00355 }
00356 incr end -1 ;
00357 num = [string range $value $start $end]
00358 if {
00359 [regexp {^-0+$} $num] ||
00360 ![string is integer -strict $num] ||
00361 (([string length $num] > 1) && [string match 0* $num])
00362 } {
00363 return -code error "Expected integer number, got \"$num\""
00364 }
00365 incr end
00366 stop = $end
00367 incr end 1 ;
00368
00369
00370
00371 return [list integer $begin $stop]
00372
00373 } elseif {$type eq "l"} {
00374
00375
00376
00377
00378 result = [list]
00379
00380 lappend result list $start @
00381
00382 incr start ;
00383
00384
00385
00386 end = $start
00387
00388
00389 contained = [list]
00390 while {[string index $value $start] ne "e"} {
00391 lappend contained [decodeIndices $value end $start]
00392 start = $end
00393 }
00394 lappend result $contained
00395
00396 stop = $end
00397 incr end
00398
00399
00400
00401 return [lreplace $result 2 2 $stop]
00402
00403 } elseif {($type eq "l") || ($type eq "d")} {
00404
00405
00406
00407
00408 result = [list]
00409
00410 lappend result dict $start @
00411
00412 incr start ;
00413
00414
00415
00416 end = $start
00417 atkey = 1
00418
00419
00420 contained = [list]
00421 val = [list]
00422 while {[string index $value $start] ne "e"} {
00423 if {$atkey} {
00424 lappend contained [decode $value {} $start]
00425 lappend val [decodeIndices $value end $start]
00426 atkey = 0
00427 } else {
00428 lappend val [decodeIndices $value end $start]
00429 lappend contained $val
00430 val = [list]
00431 atkey = 1
00432 }
00433 start = $end
00434 }
00435 lappend result $contained
00436
00437 stop = $end
00438 incr end
00439
00440
00441
00442 if {[llength $result] % 2 == 1} {
00443 return -code error "Dictionary has to be of even length"
00444 }
00445 return [lreplace $result 2 2 $stop]
00446
00447 } elseif {[string match {[0-9]} $type]} {
00448
00449
00450
00451
00452
00453 end = [string first : $value $start]
00454 if {$end < 0} {
00455 return -code error "End of string length not found"
00456 }
00457 incr end -1
00458 length = [string range $value $start $end]
00459 incr end 2 ;
00460
00461 if {![string is integer -strict $length]} {
00462 return -code error "Expected integer number for string length, got \"$length\""
00463 } elseif {$length < 0} {
00464
00465
00466 return -code error "Illegal negative string length"
00467 } elseif {($end + $length) > [string length $value]} {
00468 return -code error "String not large enough for value"
00469 }
00470
00471
00472 incr end -1
00473 if {$length > 0} {
00474 incr end $length
00475 stop = $end
00476 } else {
00477 stop = $end
00478 }
00479 incr end
00480
00481
00482 return [list string $start $stop]
00483
00484 } else {
00485 return -code error "Unknown bee-type \"$type\""
00486 }
00487 }
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499
00500
00501
00502
00503
00504
00505 ret ::bee::decodeChannel (type chan , type args) {
00506 variable bee
00507 if {[info exists bee($chan)]} {
00508 return -code error "bee-Decoder already active for channel"
00509 }
00510
00511 # Create state and token.
00512
00513 variable count
00514 variable [set st state$count]
00515 array set $st {}
00516 set bee($chan) $st
00517 upvar 0 $st state
00518 incr count
00519
00520 # Initialize the decoder state, process the options. When
00521 # encountering errors here destroy the half-baked state before
00522 # throwing the message.
00523
00524 set state(chan) $chan
00525 array set state {
00526 exact 0
00527 type ?
00528 read {}
00529 value {}
00530 pend {}
00531 state intro
00532 get 1
00533 }
00534
00535 while {[llength $args]} {
00536 set option [lindex $args 0]
00537 set args [lrange $args 1 end]
00538 if {$option eq "-command"} {
00539 if {![llength $args]} {
00540 unset bee($chan)
00541 unset state
00542 return -code error "Missing value for option -command."
00543 }
00544 set state(cmd) [lindex $args 0]
00545 set args [lrange $args 1 end]
00546
00547 } elseif {$option eq "-prefix"} {
00548 if {![llength $args]} {
00549 unset bee($chan)
00550 unset state
00551 return -code error "Missing value for option -prefix."
00552 }
00553 set state(read) [lindex $args 0]
00554 set args [lrange $args 1 end]
00555
00556 } elseif {$option eq "-exact"} {
00557 set state(exact) 1
00558 } else {
00559 unset bee($chan)
00560 unset state
00561 return -code error "Illegal option \"$option\",\
00562 expected \"-command\", \"-prefix\", or \"-keep\""
00563 }
00564 }
00565
00566 if {![info exists state(cmd)]} {
00567 unset bee($chan)
00568 unset state
00569 return -code error "Missing required completion callback."
00570 }
00571
00572 # Set up the processing of incoming data.
00573
00574 fileevent $chan readable [list ::bee::Process $chan $bee($chan)]
00575
00576 # Return the name of the state array as token.
00577 return $bee($chan)
00578 }
00579
00580
00581
00582
00583
00584
00585
00586
00587
00588
00589
00590
00591 ret ::bee::Process (type chan , type token) {
00592 if {[catch {Parse $token} msg]} {
00593 # Something failed. Destroy and report.
00594 Command $token error $msg
00595 return
00596 }
00597
00598 if {[eof $chan]} {
00599 # Having data waiting, either in the input queue, or in the
00600 # output stack (of nested containers) is a failure. Report
00601 # this instead of the eof.
00602
00603 variable $token
00604 upvar 0 $token state
00605
00606 if {
00607 [string length $state(read)] ||
00608 [llength $state(pend)] ||
00609 [string length $state(value)] ||
00610 ($state(state) ne "intro")
00611 } {
00612 Command $token error "Incomplete value at end of channel"
00613 } else {
00614 Command $token eof
00615 }
00616 }
00617 return
00618 }
00619
00620
00621
00622
00623
00624
00625
00626
00627
00628
00629
00630
00631 ret ::bee::Parse (type token) {
00632 variable $token
00633 upvar 0 $token state
00634 upvar 0 state(state) current
00635 upvar 0 state(read) input
00636 upvar 0 state(type) type
00637 upvar 0 state(value) value
00638 upvar 0 state(pend) pend
00639 upvar 0 state(exact) exact
00640 upvar 0 state(get) get
00641 set chan $state(chan)
00642
00643 #puts Parse/$current
00644
00645 if {!$exact} {
00646 # Add all waiting characters to the buffer so that we can process as
00647 # much as is possible in one go.
00648 append input [read $chan]
00649 } else {
00650 # Exact reading. Usually one character, but when in the data
00651 # section for a string value we know for how many characters
00652 # we are looking for.
00653
00654 append input [read $chan $get]
00655 }
00656
00657 # We got nothing, do nothing.
00658 if {![string length $input]} return
00659
00660
00661 if {$current eq "data"} {
00662 # String data, this can be done faster, as we read longer
00663 # sequences of characters for this.
00664 set l [string length $input]
00665 if {$l < $get} {
00666 # Not enough, wait for more.
00667 append value $input
00668 incr get -$l
00669 return
00670 } elseif {$l == $get} {
00671 # Got all, exactly. Prepare state machine for next value.
00672
00673 if {[Complete $token $value$input]} return
00674
00675 set current intro
00676 set get 1
00677 set value ""
00678 set input ""
00679
00680 return
00681 } else {
00682 # Got more than required (only for !exact).
00683
00684 incr get -1
00685 if {[Complete $token $value[string range $input 0 $get]]} {return}
00686
00687 incr get
00688 set input [string range $input $get end]
00689 set get 1
00690 set value ""
00691 set current intro
00692 # This now falls into the loop below.
00693 }
00694 }
00695
00696 set where 0
00697 set n [string length $input]
00698
00699 #puts Parse/$n
00700
00701 while {$where < $n} {
00702 # Hardwired state machine. Get current character.
00703 set ch [string index $input $where]
00704
00705 #puts Parse/@$where/$current/$ch/
00706 if {$current eq "intro"} {
00707 # First character of a value.
00708
00709 if {$ch eq "i"} {
00710 # Begin reading integer.
00711 set type integer
00712 set current signum
00713 } elseif {$ch eq "l"} {
00714 # Begin a list.
00715 set type list
00716 lappend pend list {}
00717 #set current intro
00718
00719 } elseif {$ch eq "d"} {
00720 # Begin a dictionary.
00721 set type dict
00722 lappend pend dict {}
00723 #set current intro
00724
00725 } elseif {$ch eq "e"} {
00726 # Close a container. Throw an error if there is no
00727 # container to close.
00728
00729 if {![llength $pend]} {
00730 return -code error "End of container outside of container."
00731 }
00732
00733 set v [lindex $pend end]
00734 set t [lindex $pend end-1]
00735 set pend [lrange $pend 0 end-2]
00736
00737 if {$t eq "dict" && ([llength $v] % 2 == 1)} {
00738 return -code error "Dictionary has to be of even length"
00739 }
00740
00741 if {[Complete $token $v]} {return}
00742 set current intro
00743
00744 } elseif {[string match {[0-9]} $ch]} {
00745 # Begin reading a string, length section first.
00746 set type string
00747 set current ldigit
00748 set value $ch
00749
00750 } else {
00751 # Unknown type. Throw error.
00752 return -code error "Unknown bee-type \"$ch\""
00753 }
00754
00755 # To next character.
00756 incr where
00757 } elseif {$current eq "signum"} {
00758 # Integer number, a minus sign, or a digit.
00759 if {[string match {[-0-9]} $ch]} {
00760 append value $ch
00761 set current idigit
00762 } else {
00763 return -code error "Syntax error in integer,\
00764 expected sign or digit, got \"$ch\""
00765 }
00766 incr where
00767
00768 } elseif {$current eq "idigit"} {
00769 # Integer number, digit or closing 'e'.
00770
00771 if {[string match {[-0-9]} $ch]} {
00772 append value $ch
00773 } elseif {$ch eq "e"} {
00774 # Integer closes. Validate and report.
00775 #puts validate
00776 if {
00777 [regexp {^-0+$} $value] ||
00778 ![string is integer -strict $value] ||
00779 (([string length $value] > 1) && [string match 0* $value])
00780 } {
00781 return -code error "Expected integer number, got \"$value\""
00782 }
00783
00784 if {[Complete $token $value]} {return}
00785 set value ""
00786 set current intro
00787 } else {
00788 return -code error "Syntax error in integer,\
00789 expected digit, or 'e', got \"$ch\""
00790 }
00791 incr where
00792
00793 } elseif {$current eq "ldigit"} {
00794 # String, length section, digit, or :
00795
00796 if {[string match {[-0-9]} $ch]} {
00797 append value $ch
00798
00799 } elseif {$ch eq ":"} {
00800 # Length section closes, validate,
00801 # then perform data processing.
00802
00803 set num $value
00804 if {
00805 [regexp {^-0+$} $num] ||
00806 ![string is integer -strict $num] ||
00807 (([string length $num] > 1) && [string match 0* $num])
00808 } {
00809 return -code error "Expected integer number as string length, got \"$num\""
00810 }
00811
00812 set value ""
00813
00814 # We may have already part of the data in
00815 # memory. Process that piece before looking for more.
00816
00817 incr where
00818 set have [expr {$n - $where}]
00819 if {$num < $have} {
00820 # More than enough in the buffer.
00821
00822 set end $where
00823 incr end $num
00824 incr end -1
00825
00826 if {[Complete $token [string range $input $where $end]]} {return}
00827
00828 set where $end ;# Further processing behind the string.
00829 set current intro
00830
00831 } elseif {$num == $have} {
00832 # Just enough.
00833
00834 if {[Complete $token [string range $input $where end]]} {return}
00835
00836 set where $n
00837 set current intro
00838 } else {
00839 # Not enough. Initialize value with the data we
00840 # have (after the colon) and stop processing for
00841 # now.
00842
00843 set value [string range $input $where end]
00844 set current data
00845 set get $num
00846 set input ""
00847 return
00848 }
00849 } else {
00850 return -code error "Syntax error in string length,\
00851 expected digit, or ':', got \"$ch\""
00852 }
00853 incr where
00854 } else {
00855 # unknown state = internal error
00856 return -code error "Unknown decoder state \"$current\", internal error"
00857 }
00858 }
00859
00860 set input ""
00861 return
00862 }
00863
00864
00865
00866
00867
00868
00869
00870
00871
00872
00873
00874
00875
00876 ret ::bee::Command (type token , type how , type args) {
00877 variable $token
00878 upvar 0 $token state
00879
00880 #puts Report/$token/$how/$args/
00881
00882 set cmd $state(cmd)
00883 set chan $state(chan)
00884
00885 # We catch the fileevents because they will fail when this is
00886 # called from the 'Close'. The channel will already be gone in
00887 # that case.
00888
00889 set stop 0
00890 if {($how eq "error") || ($how eq "eof")} {
00891 variable bee
00892
00893 set stop 1
00894 fileevent $chan readable {}
00895 unset bee($chan)
00896 unset state
00897
00898 if {$how eq "eof"} {
00899 #puts \tclosing/$chan
00900 close $chan
00901 }
00902 }
00903
00904 lappend cmd $how $token
00905 foreach a $args {lappend cmd $a}
00906 uplevel #0 $cmd
00907
00908 if {![info exists state]} {
00909 # The decoder token was killed by the callback, stop
00910 # processing.
00911 set stop 1
00912 }
00913
00914 #puts /$stop/[file channels]
00915 return $stop
00916 }
00917
00918
00919
00920
00921
00922
00923
00924
00925
00926
00927
00928
00929 ret ::bee::Complete (type token , type value) {
00930 variable $token
00931 upvar 0 $token state
00932 upvar 0 state(pend) pend
00933
00934 if {[llength $pend]} {
00935 # The value is part of a container. Add the value to its end
00936 # and keep processing.
00937
00938 set pend [lreplace $pend end end \
00939 [linsert [lindex $pend end] end \
00940 $value]]
00941
00942 # Don't stop.
00943 return 0
00944 }
00945
00946 # The value is at the top, report it. The callback determines if
00947 # we keep processing.
00948
00949 return [Command $token value $value]
00950 }
00951
00952
00953
00954
00955
00956
00957
00958
00959
00960
00961
00962 ret ::bee::decodeCancel (type token) {
00963 variable bee
00964 variable $token
00965 upvar 0 $token state
00966 unset bee($state(chan))
00967 unset state
00968 return
00969 }
00970
00971
00972
00973
00974
00975
00976
00977
00978
00979
00980
00981
00982 ret ::bee::decodePush (type token , type string) {
00983 variable $token
00984 upvar 0 $token state
00985 append state(read) $string
00986 return
00987 }
00988
00989
00990 package provide bee 0.1
00991