bee.tcl

Go to the documentation of this file.
00001 /*  bee.tcl --*/
00002 /* */
00003 /*  BitTorrent Bee de- and encoder.*/
00004 /* */
00005 /*  Copyright (c) 2004 by Andreas Kupries <andreas_kupries@users.sourceforge.net>*/
00006 /*  See the file license.terms.*/
00007 
00008 package require Tcl 8.4
00009 
00010 namespace ::bee {
00011     /*  Encoder commands*/
00012     namespace export \
00013         encodeString encodeNumber \
00014         encodeListArgs encodeList \
00015         encodeDictArgs encodeDict
00016 
00017     /*  Decoder commands.*/
00018     namespace export \
00019         decode \
00020         decodeChannel \
00021         decodeCancel \
00022         decodePush
00023 
00024     /*  Channel decoders, reference to state information, keyed by*/
00025     /*  channel handle.*/
00026 
00027     variable  bee
00028     array  bee =  {}
00029 
00030     /*  Counter for generation of names for the state variables.*/
00031 
00032     variable count 0
00033 
00034     /*  State information for the channel decoders.*/
00035 
00036     /*  stateN, with N an integer number counting from 0 on up.*/
00037     /*  ...(chan)   Handle of channel the decoder is for.*/
00038     /*  ...(cmd)    Command prefix, completion callback*/
00039     /*  ...(exact)  Boolean flag, set for exact processing.*/
00040     /*  ...(read)   Buffer for new characters to process.*/
00041     /*  ...(type)   Type of current value (integer, string, list, dict)*/
00042     /*  ...(value)  Buffer for assembling the current value.*/
00043     /*  ...(pend)   Stack of pending 'value' buffers, for nested*/
00044     /*              containers.*/
00045     /*  ...(state)  Current state of the decoding state machine.*/
00046 
00047     /*  States of the finite automaton ...*/
00048     /*  intro  - One char, type of value, or 'e' as stop of container.*/
00049     /*  signum - sign or digit, for integer.*/
00050     /*  idigit - digit, for integer, or 'e' as stop*/
00051     /*  ldigit - digit, for length of string, or :*/
00052     /*  data   - string data, 'get' characters.*/
00053     /*  Containers via 'pend'.*/
00054 
00055     /* Debugging help, nesting level*/
00056     /* variable X 0*/
00057 }
00058 
00059 
00060 /*  ::bee::encodeString --*/
00061 /* */
00062 /*  Encode a string to bee-format.*/
00063 /* */
00064 /*  Arguments:*/
00065 /*  string  The string to encode.*/
00066 /* */
00067 /*  Results:*/
00068 /*  The bee-encoded form of the string.*/
00069 
00070 ret  ::bee::encodeString (type string) {
00071     return "[string length $string]:$string"
00072 }
00073 
00074 
00075 /*  ::bee::encodeNumber --*/
00076 /* */
00077 /*  Encode an integer number to bee-format.*/
00078 /* */
00079 /*  Arguments:*/
00080 /*  num The integer number to encode.*/
00081 /* */
00082 /*  Results:*/
00083 /*  The bee-encoded form of the integer number.*/
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 /*  ::bee::encodeList --*/
00100 /* */
00101 /*  Encode a list of bee-coded values to bee-format.*/
00102 /* */
00103 /*  Arguments:*/
00104 /*  list    The list to encode.*/
00105 /* */
00106 /*  Results:*/
00107 /*  The bee-encoded form of the list.*/
00108 
00109 ret  ::bee::encodeList (type list) {
00110     return "l[join $list ""]e"
00111 }
00112 
00113 
00114 /*  ::bee::encodeListArgs --*/
00115 /* */
00116 /*  Encode a variable list of bee-coded values to bee-format.*/
00117 /* */
00118 /*  Arguments:*/
00119 /*  args    The values to encode.*/
00120 /* */
00121 /*  Results:*/
00122 /*  The bee-encoded form of the list of values.*/
00123 
00124 ret  ::bee::encodeListArgs (type args) {
00125     return [encodeList $args]
00126 }
00127 
00128 
00129 /*  ::bee::encodeDict --*/
00130 /* */
00131 /*  Encode a dictionary of keys and bee-coded values to bee-format.*/
00132 /* */
00133 /*  Arguments:*/
00134 /*  dict    The dictionary to encode.*/
00135 /* */
00136 /*  Results:*/
00137 /*  The bee-encoded form of the dictionary.*/
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 /*  ::bee::encodeDictArgs --*/
00158 /* */
00159 /*  Encode a variable dictionary of keys and bee-coded values to bee-format.*/
00160 /* */
00161 /*  Arguments:*/
00162 /*  args    The keys and values to encode.*/
00163 /* */
00164 /*  Results:*/
00165 /*  The bee-encoded form of the dictionary.*/
00166 
00167 ret  ::bee::encodeDictArgs (type args) {
00168     return [encodeDict $args]
00169 }
00170 
00171 
00172 /*  ::bee::decode --*/
00173 /* */
00174 /*  Decode a bee-encoded value and returns the embedded tcl*/
00175 /*  value. For containers this recurses into the contained value.*/
00176 /* */
00177 /*  Arguments:*/
00178 /*  value   The string containing the bee-encoded value to decode.*/
00179 /*  evar    Optional. If set the name of the variable to store the*/
00180 /*      index of the first character after the decoded value to.*/
00181 /*  start   Optional. If set the index of the first character of the*/
00182 /*      value to decode. Defaults to 0, i.e. the beginning of the*/
00183 /*      string.*/
00184 /* */
00185 /*  Results:*/
00186 /*  The tcl value embedded in the encoded string.*/
00187 
00188 ret  ::bee::decode (type value , optional evar ={) {start 0}} {
00189     /* variable X*/
00190     /* puts -nonewline "[string repeat "    " $X]decode @$start" ; flush stdout*/
00191 
00192     if {$evar ne ""} {upvar 1 $evar end} else { end =  _}
00193 
00194     if {[string length $value] < ($start+2)} {
00195     /*  This checked that the 'start' index is still in the string,*/
00196     /*  and the end of the value most likely as well. Note that each*/
00197     /*  encoded value consists of at least two characters (the*/
00198     /*  bracketing characters for integer, list, and dict, and for*/
00199     /*  string at least one digit length and the colon).*/
00200 
00201     /* puts \t[string length $value]\ <\ ($start+2)*/
00202     return -code error "String not large enough for value"
00203     }
00204 
00205      type =  [string index $value $start]
00206 
00207     /* puts -nonewline " $type=" ; flush stdout*/
00208 
00209     if {$type eq "i"} {
00210     /*  Extract integer*/
00211     /* puts -nonewline integer ; flush stdout*/
00212 
00213     incr start ; /*  Skip over intro 'i'.*/
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 ; /*  Get last character before closing 'e'.*/
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 ; /*  Step after closing 'e' to the beginning of*/
00228     /*  ........ ; # the next bee-value behind the current one.*/
00229 
00230     /* puts " ($num) @$end"*/
00231     return $num
00232 
00233     } elseif {($type eq "l") || ($type eq "d")} {
00234     /* puts -nonewline $type\n ; flush stdout*/
00235 
00236     /*  Extract list or dictionary, recursively each contained*/
00237     /*  element. From the perspective of the decoder this is the*/
00238     /*  same, the tcl representation of both is a list, and for a*/
00239     /*  dictionary keys and values are also already in the correct*/
00240     /*  order.*/
00241 
00242      result =  [list]
00243     incr start ; /*  Step over intro 'e' to beginning of the first*/
00244     /*  ........ ; # contained value, or behind the container (if*/
00245     /*  ........ ; # empty).*/
00246 
00247      end =  $start
00248     /* incr X*/
00249     while {[string index $value $start] ne "e"} {
00250         lappend result [decode $value end $start]
00251          start =  $end
00252     }
00253     /* incr X -1*/
00254     incr end
00255 
00256     /* puts "[string repeat "    " $X]($result) @$end"*/
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     /* puts -nonewline string ; flush stdout*/
00265 
00266     /*  Extract string. First the length, bounded by a colon, then*/
00267     /*  the appropriate number of characters.*/
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 ;/*  Skip to beginning of the string after the colon*/
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         /*  This cannot happen. To happen "-" has to be first character,*/
00281         /*  and this is caught as unknown bee-type.*/
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     /* puts -nonewline \[$length\] ; flush stdout*/
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     /* puts " ($result) @$end"*/
00299     return $result
00300 
00301     } else {
00302     return -code error "Unknown bee-type \"$type\""
00303     }
00304 }
00305 
00306 /*  ::bee::decodeIndices --*/
00307 /* */
00308 /*  Similar to 'decode', but does not return the decoded tcl values,*/
00309 /*  but a structure containing the start- and end-indices for all*/
00310 /*  values in the structure.*/
00311 /* */
00312 /*  Arguments:*/
00313 /*  value   The string containing the bee-encoded value to decode.*/
00314 /*  evar    Optional. If set the name of the variable to store the*/
00315 /*      index of the first character after the decoded value to.*/
00316 /*  start   Optional. If set the index of the first character of the*/
00317 /*      value to decode. Defaults to 0, i.e. the beginning of the*/
00318 /*      string.*/
00319 /* */
00320 /*  Results:*/
00321 /*  The structure of the value, with indices and types for all*/
00322 /*  contained elements.*/
00323 
00324 ret  ::bee::decodeIndices (type value , optional evar ={) {start 0}} {
00325     /* variable X*/
00326     /* puts -nonewline "[string repeat "    " $X]decode @$start" ; flush stdout*/
00327 
00328     if {$evar ne ""} {upvar 1 $evar end} else { end =  _}
00329 
00330     if {[string length $value] < ($start+2)} {
00331     /*  This checked that the 'start' index is still in the string,*/
00332     /*  and the end of the value most likely as well. Note that each*/
00333     /*  encoded value consists of at least two characters (the*/
00334     /*  bracketing characters for integer, list, and dict, and for*/
00335     /*  string at least one digit length and the colon).*/
00336 
00337     /* puts \t[string length $value]\ <\ ($start+2)*/
00338     return -code error "String not large enough for value"
00339     }
00340 
00341      type =  [string index $value $start]
00342 
00343     /* puts -nonewline " $type=" ; flush stdout*/
00344 
00345     if {$type eq "i"} {
00346     /*  Extract integer*/
00347     /* puts -nonewline integer ; flush stdout*/
00348 
00349      begin =  $start
00350 
00351     incr start ; /*  Skip over intro 'i'.*/
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 ; /*  Get last character before closing 'e'.*/
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 ; /*  Step after closing 'e' to the beginning of*/
00368     /*  ........ ; # the next bee-value behind the current one.*/
00369 
00370     /* puts " ($num) @$end"*/
00371     return [list integer $begin $stop]
00372 
00373     } elseif {$type eq "l"} {
00374     /* puts -nonewline $type\n ; flush stdout*/
00375 
00376     /*  Extract list, recursively each contained element.*/
00377 
00378      result =  [list]
00379 
00380     lappend result list $start @
00381 
00382     incr start ; /*  Step over intro 'e' to beginning of the first*/
00383     /*  ........ ; # contained value, or behind the container (if*/
00384     /*  ........ ; # empty).*/
00385 
00386      end =  $start
00387     /* incr X*/
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     /* incr X -1*/
00396      stop =  $end
00397     incr end
00398 
00399     /* puts "[string repeat "    " $X]($result) @$end"*/
00400 
00401     return [lreplace $result 2 2 $stop]
00402 
00403     } elseif {($type eq "l") || ($type eq "d")} {
00404     /* puts -nonewline $type\n ; flush stdout*/
00405 
00406     /*  Extract dictionary, recursively each contained element.*/
00407 
00408      result =  [list]
00409 
00410     lappend result dict $start @
00411 
00412     incr start ; /*  Step over intro 'e' to beginning of the first*/
00413     /*  ........ ; # contained value, or behind the container (if*/
00414     /*  ........ ; # empty).*/
00415 
00416      end =  $start
00417      atkey =  1
00418     /* incr X*/
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     /* incr X -1*/
00437      stop =  $end
00438     incr end
00439 
00440     /* puts "[string repeat "    " $X]($result) @$end"*/
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     /* puts -nonewline string ; flush stdout*/
00449 
00450     /*  Extract string. First the length, bounded by a colon, then*/
00451     /*  the appropriate number of characters.*/
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 ;/*  Skip to beginning of the string after the colon*/
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         /*  This cannot happen. To happen "-" has to be first character,*/
00465         /*  and this is caught as unknown bee-type.*/
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     /* puts -nonewline \[$length\] ; flush stdout*/
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     /* puts " ($result) @$end"*/
00482     return [list string $start $stop]
00483 
00484     } else {
00485     return -code error "Unknown bee-type \"$type\""
00486     }
00487 }
00488 
00489 
00490 /*  ::bee::decodeChannel --*/
00491 /* */
00492 /*  Attach decoder for a bee-value to a channel. See the*/
00493 /*  documentation for details.*/
00494 /* */
00495 /*  Arguments:*/
00496 /*  chan            Channel to attach to.*/
00497 /*  -command cmdprefix  Completion callback. Required.*/
00498 /*  -exact          Keep running after completion.*/
00499 /*  -prefix data        Seed for decode buffer.*/
00500 /* */
00501 /*  Results:*/
00502 /*  A token to use when referring to the decoder.*/
00503 /*  For example when canceling it.*/
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 /*  ::bee::Parse --*/
00581 /* */
00582 /*  Internal helper. Fileevent handler for a decoder.*/
00583 /*  Parses input and handles both error and eof conditions.*/
00584 /* */
00585 /*  Arguments:*/
00586 /*  token   The decoder to run on its input.*/
00587 /* */
00588 /*  Results:*/
00589 /*  None.*/
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 /*  ::bee::Parse --*/
00621 /* */
00622 /*  Internal helper. Reading from the channel and parsing the input.*/
00623 /*  Uses a hardwired state machine.*/
00624 /* */
00625 /*  Arguments:*/
00626 /*  token   The decoder to run on its input.*/
00627 /* */
00628 /*  Results:*/
00629 /*  None.*/
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 /*  ::bee::Command --*/
00865 /* */
00866 /*  Internal helper. Runs the decoder command callback.*/
00867 /* */
00868 /*  Arguments:*/
00869 /*  token   The decoder invoking its callback*/
00870 /*  how Which method to invoke (value, error, eof)*/
00871 /*  args    Arguments for the method.*/
00872 /* */
00873 /*  Results:*/
00874 /*  A boolean flag. Set if further processing has to stop.*/
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 /*  ::bee::Complete --*/
00919 /* */
00920 /*  Internal helper. Reports a completed value.*/
00921 /* */
00922 /*  Arguments:*/
00923 /*  token   The decoder reporting the value.*/
00924 /*  value   The value to report.*/
00925 /* */
00926 /*  Results:*/
00927 /*  A boolean flag. Set if further processing has to stop.*/
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 /*  ::bee::decodeCancel --*/
00953 /* */
00954 /*  Destroys the decoder referenced by the token.*/
00955 /* */
00956 /*  Arguments:*/
00957 /*  token   The decoder to destroy.*/
00958 /* */
00959 /*  Results:*/
00960 /*  None.*/
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 /*  ::bee::decodePush --*/
00972 /* */
00973 /*  Push data into the decoder input buffer.*/
00974 /* */
00975 /*  Arguments:*/
00976 /*  token   The decoder to extend.*/
00977 /*  string  The characters to add.*/
00978 /* */
00979 /*  Results:*/
00980 /*  None.*/
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 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1