expander.tcl

Go to the documentation of this file.
00001 /* ---------------------------------------------------------------------*/
00002 /*  TITLE:*/
00003 /*  expander.tcl*/
00004 /* */
00005 /*  AUTHOR:*/
00006 /*  Will Duquette*/
00007 /* */
00008 /*  DESCRIPTION:*/
00009 /* */
00010 /*  An expander is an object that takes as input text with embedded*/
00011 /*  Tcl code and returns text with the embedded code expanded.  The*/
00012 /*  text can be provided all at once or incrementally.*/
00013 /* */
00014 /*  See  expander.[e]html for usage info.*/
00015 /*  Also expander.n*/
00016 /* */
00017 /*  LICENSE:*/
00018 /*        Copyright (C) 2001 by William H. Duquette.  See expander_license.txt,*/
00019 /*        distributed with this file, for license information.*/
00020 /* */
00021 /*  CHANGE LOG:*/
00022 /* */
00023 /*        10/31/01: V0.9 code is complete.*/
00024 /*        11/23/01: Added "evalcmd"; V1.0 code is complete.*/
00025 
00026 /*  Provide the package.*/
00027 
00028 /*  Create the package's namespace.*/
00029 
00030 namespace ::textutil {
00031     namespace expander {
00032     /*  All indices are prefixed by "$exp-".*/
00033     /* */
00034     /*  lb          The left bracket sequence*/
00035     /*  rb          The right bracket sequence*/
00036     /*  errmode     How to handle macro errors: */
00037     /*          nothing, macro, error, fail.*/
00038         /*  evalcmd           The evaluation command.*/
00039     /*  textcmd           The plain text processing command.*/
00040     /*  level           The context level*/
00041     /*  output-$level     The accumulated text at this context level.*/
00042     /*  name-$level       The tag name of this context level*/
00043     /*  data-$level-$var  A variable of this context level     */
00044     
00045     variable Info
00046     
00047     /*  In methods, the current object:*/
00048     variable This ""
00049     
00050     /*  Export public commands*/
00051     namespace export expander
00052     }
00053 
00054     /* namespace import expander::**/
00055     namespace export expander
00056 
00057     ret  expander (type name) {uplevel ::textutil::expander::expander [list $name]}
00058 }
00059 
00060 /* ---------------------------------------------------------------------*/
00061 /*  FUNCTION:*/
00062 /*      expander name*/
00063 /* */
00064 /*  INPUTS:*/
00065 /*  name        A proc name for the new object.  If not*/
00066 /*                        fully-qualified, it is assumed to be relative*/
00067 /*                        to the caller's namespace.*/
00068 /* */
00069 /*  RETURNS:*/
00070 /*  nothing*/
00071 /* */
00072 /*  DESCRIPTION:*/
00073 /*  Creates a new expander object.*/
00074 
00075 ret  ::textutil::expander::expander (type name) {
00076     variable Info
00077 
00078     # FIRST, qualify the name.
00079     if {![string match "::*" $name]} {
00080         # Get caller's namespace; append :: if not global namespace.
00081         set ns [uplevel 1 namespace current]
00082         if {"::" != $ns} {
00083             append ns "::"
00084         }
00085         
00086         set name "$ns$name"
00087     }
00088 
00089     # NEXT, Check the name
00090     if {"" != [info command $name]} {
00091         return -code error "command name \"$name\" already exists"
00092     }
00093 
00094     # NEXT, Create the object.
00095     proc $name {method args} [format {
00096         if {[catch {::textutil::expander::Methods %s $method $args} result]} {
00097             return -code error $result
00098         } else {
00099             return $result
00100         }
00101     } $name]
00102 
00103     # NEXT, Initialize the object
00104     Op_reset $name
00105     
00106     return $name
00107 }
00108 
00109 /* ---------------------------------------------------------------------*/
00110 /*  FUNCTION:*/
00111 /*      Methods name method argList*/
00112 /* */
00113 /*  INPUTS:*/
00114 /*  name        The object's fully qualified procedure name.*/
00115 /*          This argument is provided by the object command*/
00116 /*          itself.*/
00117 /*  method      The method to call.*/
00118 /*  argList     Arguments for the specific method.*/
00119 /* */
00120 /*  RETURNS:*/
00121 /*  Depends on the method*/
00122 /* */
00123 /*  DESCRIPTION:*/
00124 /*  Handles all method dispatch for a expander object.*/
00125 /*        The expander's object command merely passes its arguments to*/
00126 /*  this function, which dispatches the arguments to the*/
00127 /*  appropriate method procedure.  If the method raises an error,*/
00128 /*  the method procedure's name in the error message is replaced*/
00129 /*  by the object and method names.*/
00130 
00131 ret  ::textutil::expander::Methods (type name , type method , type argList) {
00132     variable Info
00133     variable This
00134 
00135     switch -exact -- $method {
00136         expand -
00137         lb -
00138         rb -
00139         setbrackets -
00140         errmode -
00141         evalcmd -
00142     textcmd -
00143         cpush -
00144     ctopandclear -
00145         cis -
00146         cname -
00147         cset -
00148         cget -
00149         cvar -
00150         cpop -
00151         cappend -
00152     where -
00153         reset {
00154             # FIRST, execute the method, first setting This to the object
00155             # name; then, after the method has been called, restore the
00156             # old object name.
00157             set oldThis $This
00158             set This $name
00159 
00160             set retval [catch "Op_$method $name $argList" result]
00161 
00162             set This $oldThis
00163 
00164             # NEXT, handle the result based on the retval.
00165             if {$retval} {
00166                 regsub -- "Op_$method" $result "$name $method" result
00167                 return -code error $result
00168             } else {
00169                 return $result
00170             }
00171         }
00172         default {
00173             return -code error "\"$name $method\" is not defined"
00174         }
00175     }
00176 }
00177 
00178 /* ---------------------------------------------------------------------*/
00179 /*  FUNCTION:*/
00180 /*      Get key*/
00181 /* */
00182 /*  INPUTS:*/
00183 /*  key     A key into the Info array, excluding the*/
00184 /*                  object name.  E.g., "lb"*/
00185 /* */
00186 /*  RETURNS:*/
00187 /*  The value from the array*/
00188 /* */
00189 /*  DESCRIPTION:*/
00190 /*  Gets the value of an entry from Info for This.*/
00191 
00192 ret  ::textutil::expander::Get (type key) {
00193     variable Info
00194     variable This
00195 
00196     return $Info($This-$key)
00197 }
00198 
00199 /* ---------------------------------------------------------------------*/
00200 /*  FUNCTION:*/
00201 /*      Set key value*/
00202 /* */
00203 /*  INPUTS:*/
00204 /*  key     A key into the Info array, excluding the*/
00205 /*                  object name.  E.g., "lb"*/
00206 /* */
00207 /*  value       A Tcl value*/
00208 /* */
00209 /*  RETURNS:*/
00210 /*  The value*/
00211 /* */
00212 /*  DESCRIPTION:*/
00213 /*  Sets the value of an entry in Info for This.*/
00214 
00215 ret  ::textutil::expander::Set (type key , type value) {
00216     variable Info
00217     variable This
00218 
00219     return [set Info($This-$key) $value]
00220 }
00221 
00222 /* ---------------------------------------------------------------------*/
00223 /*  FUNCTION:*/
00224 /*      Var key*/
00225 /* */
00226 /*  INPUTS:*/
00227 /*  key     A key into the Info array, excluding the*/
00228 /*                  object name.  E.g., "lb"*/
00229 /* */
00230 /*  RETURNS:*/
00231 /*  The full variable name, suitable for setting or lappending*/
00232 
00233 ret  ::textutil::expander::Var (type key) {
00234     variable Info
00235     variable This
00236 
00237     return ::textutil::expander::Info($This-$key)
00238 }
00239 
00240 /* ---------------------------------------------------------------------*/
00241 /*  FUNCTION:*/
00242 /*      Contains list value*/
00243 /* */
00244 /*  INPUTS:*/
00245 /*        list      any list*/
00246 /*  value       any value*/
00247 /* */
00248 /*  RETURNS:*/
00249 /*  TRUE if the list contains the value, and false otherwise.*/
00250 
00251 ret  ::textutil::expander::Contains (type list , type value) {
00252     if {[lsearch -exact $list $value] == -1} {
00253         return 0
00254     } else {
00255         return 1
00256     }
00257 }
00258 
00259 
00260 /* ---------------------------------------------------------------------*/
00261 /*  FUNCTION:*/
00262 /*      Op_lb ?newbracket?*/
00263 /* */
00264 /*  INPUTS:*/
00265 /*  newbracket      If given, the new bracket token.*/
00266 /* */
00267 /*  RETURNS:*/
00268 /*  The current left bracket*/
00269 /* */
00270 /*  DESCRIPTION:*/
00271 /*  Returns the current left bracket token.*/
00272 
00273 ret  ::textutil::expander::Op_lb (type name , optional newbracket ="") {
00274     if {[string length $newbracket] != 0} {
00275         Set lb $newbracket
00276     }
00277     return [Get lb]
00278 }
00279 
00280 /* ---------------------------------------------------------------------*/
00281 /*  FUNCTION:*/
00282 /*      Op_rb ?newbracket?*/
00283 /* */
00284 /*  INPUTS:*/
00285 /*  newbracket      If given, the new bracket token.*/
00286 /* */
00287 /*  RETURNS:*/
00288 /*  The current left bracket*/
00289 /* */
00290 /*  DESCRIPTION:*/
00291 /*  Returns the current left bracket token.*/
00292 
00293 ret  ::textutil::expander::Op_rb (type name , optional newbracket ="") {
00294     if {[string length $newbracket] != 0} {
00295         Set rb $newbracket
00296     }
00297     return [Get rb]
00298 }
00299 
00300 /* ---------------------------------------------------------------------*/
00301 /*  FUNCTION:*/
00302 /*      Op_setbrackets lbrack rbrack*/
00303 /* */
00304 /*  INPUTS:*/
00305 /*  lbrack      The new left bracket*/
00306 /*  rbrack      The new right bracket*/
00307 /* */
00308 /*  RETURNS:*/
00309 /*  nothing*/
00310 /* */
00311 /*  DESCRIPTION:*/
00312 /*  Sets the brackets as a pair.*/
00313 
00314 ret  ::textutil::expander::Op_setbrackets (type name , type lbrack , type rbrack) {
00315     Set lb $lbrack
00316     Set rb $rbrack
00317     return
00318 }
00319 
00320 /* ---------------------------------------------------------------------*/
00321 /*  FUNCTION:*/
00322 /*      Op_errmode ?newErrmode?*/
00323 /* */
00324 /*  INPUTS:*/
00325 /*  newErrmode      If given, the new error mode.*/
00326 /* */
00327 /*  RETURNS:*/
00328 /*  The current error mode*/
00329 /* */
00330 /*  DESCRIPTION:*/
00331 /*  Returns the current error mode.*/
00332 
00333 ret  ::textutil::expander::Op_errmode (type name , optional newErrmode ="") {
00334     if {[string length $newErrmode] != 0} {
00335         if {![Contains "macro nothing error fail" $newErrmode]} {
00336             error "$name errmode: Invalid error mode: $newErrmode"
00337         }
00338 
00339         Set errmode $newErrmode
00340     }
00341     return [Get errmode]
00342 }
00343 
00344 /* ---------------------------------------------------------------------*/
00345 /*  FUNCTION:*/
00346 /*      Op_evalcmd ?newEvalCmd?*/
00347 /* */
00348 /*  INPUTS:*/
00349 /*  newEvalCmd      If given, the new eval command.*/
00350 /* */
00351 /*  RETURNS:*/
00352 /*  The current eval command*/
00353 /* */
00354 /*  DESCRIPTION:*/
00355 /*  Returns the current eval command.  This is the command used to*/
00356 /*  evaluate macros; it defaults to "uplevel #0".*/
00357 
00358 ret  ::textutil::expander::Op_evalcmd (type name , optional newEvalCmd ="") {
00359     if {[string length $newEvalCmd] != 0} {
00360         Set evalcmd $newEvalCmd
00361     }
00362     return [Get evalcmd]
00363 }
00364 
00365 /* ---------------------------------------------------------------------*/
00366 /*  FUNCTION:*/
00367 /*      Op_textcmd ?newTextCmd?*/
00368 /* */
00369 /*  INPUTS:*/
00370 /*  newTextCmd      If given, the new text command.*/
00371 /* */
00372 /*  RETURNS:*/
00373 /*  The current text command*/
00374 /* */
00375 /*  DESCRIPTION:*/
00376 /*  Returns the current text command.  This is the command used to*/
00377 /*  process plain text. It defaults to {}, meaning identity.*/
00378 
00379 ret  ::textutil::expander::Op_textcmd (type name , type args) {
00380     switch -exact [llength $args] {
00381     0 {}
00382     1 {Set textcmd [lindex $args 0]}
00383     default {
00384         return -code error "wrong#args for textcmd: name ?newTextcmd?"
00385     }
00386     }
00387     return [Get textcmd]
00388 }
00389 
00390 /* ---------------------------------------------------------------------*/
00391 /*  FUNCTION:*/
00392 /*      Op_reset*/
00393 /* */
00394 /*  INPUTS:*/
00395 /*  none*/
00396 /* */
00397 /*  RETURNS:*/
00398 /*  nothing*/
00399 /* */
00400 /*  DESCRIPTION:*/
00401 /*  Resets all object values, as though it were brand new.*/
00402 
00403 ret  ::textutil::expander::Op_reset (type name) {
00404     variable Info 
00405 
00406     if {[info exists Info($name-lb)]} {
00407         foreach elt [array names Info "$name-*"] {
00408             unset Info($elt)
00409         }
00410     }
00411 
00412     set Info($name-lb) "\["
00413     set Info($name-rb) "\]"
00414     set Info($name-errmode) "fail"
00415     set Info($name-evalcmd) "uplevel #0"
00416     set Info($name-textcmd) ""
00417     set Info($name-level) 0
00418     set Info($name-output-0) ""
00419     set Info($name-name-0) ":0"
00420 
00421     return
00422 }
00423 
00424 /* -------------------------------------------------------------------------*/
00425 /*  Context: Every expansion takes place in its own context; however, */
00426 /*  a macro can push a new context, causing the text it returns and all*/
00427 /*  subsequent text to be saved separately.  Later, a matching macro can*/
00428 /*  pop the context, acquiring all text saved since the first command,*/
00429 /*  and use that in its own output.*/
00430 
00431 /* ---------------------------------------------------------------------*/
00432 /*  FUNCTION:*/
00433 /*      Op_cpush cname*/
00434 /* */
00435 /*  INPUTS:*/
00436 /*  cname       The context name*/
00437 /* */
00438 /*  RETURNS:*/
00439 /*  nothing*/
00440 /* */
00441 /*  DESCRIPTION:*/
00442 /*        Pushes an empty macro context onto the stack.  All expanded text*/
00443 /*        will be added to this context until it is popped.*/
00444 
00445 ret  ::textutil::expander::Op_cpush (type name , type cname) {
00446     # FRINK: nocheck
00447     incr [Var level]
00448     # FRINK: nocheck
00449     set [Var output-[Get level]] {}
00450     # FRINK: nocheck
00451     set [Var name-[Get level]] $cname
00452 
00453     # The first level is init'd elsewhere (Op_expand)
00454     if {[set [Var level]] < 2} return
00455 
00456     # Initialize the location information, inherit from the outer
00457     # context.
00458 
00459     LocInit $cname
00460     catch {LocSet $cname [LocGet $name]}
00461     return    
00462 }
00463 
00464 /* ---------------------------------------------------------------------*/
00465 /*  FUNCTION:*/
00466 /*      Op_cis cname*/
00467 /* */
00468 /*  INPUTS:*/
00469 /*  cname       A context name*/
00470 /* */
00471 /*  RETURNS:*/
00472 /*  true or false*/
00473 /* */
00474 /*  DESCRIPTION:*/
00475 /*        Returns true if the current context has the specified name, and*/
00476 /*  false otherwise.*/
00477 
00478 ret  ::textutil::expander::Op_cis (type name , type cname) {
00479     return [expr {[string compare $cname [Op_cname $name]] == 0}]
00480 }
00481 
00482 /* ---------------------------------------------------------------------*/
00483 /*  FUNCTION:*/
00484 /*      Op_cname*/
00485 /* */
00486 /*  INPUTS:*/
00487 /*  none*/
00488 /* */
00489 /*  RETURNS:*/
00490 /*  The context name*/
00491 /* */
00492 /*  DESCRIPTION:*/
00493 /*        Returns the name of the current context.*/
00494 
00495 ret  ::textutil::expander::Op_cname (type name) {
00496     return [Get name-[Get level]]
00497 }
00498 
00499 /* ---------------------------------------------------------------------*/
00500 /*  FUNCTION:*/
00501 /*      Op_cset varname value*/
00502 /* */
00503 /*  INPUTS:*/
00504 /*  varname     The name of a context variable*/
00505 /*  value       The new value for the context variable*/
00506 /* */
00507 /*  RETURNS:*/
00508 /*  The value*/
00509 /* */
00510 /*  DESCRIPTION:*/
00511 /*        Sets a variable in the current context.*/
00512 
00513 ret  ::textutil::expander::Op_cset (type name , type varname , type value) {
00514     Set data-[Get level]-$varname $value
00515 }
00516 
00517 /* ---------------------------------------------------------------------*/
00518 /*  FUNCTION:*/
00519 /*      Op_cget varname*/
00520 /* */
00521 /*  INPUTS:*/
00522 /*  varname     The name of a context variable*/
00523 /* */
00524 /*  RETURNS:*/
00525 /*  The value*/
00526 /* */
00527 /*  DESCRIPTION:*/
00528 /*        Returns the value of a context variable.  It's an error if*/
00529 /*  the variable doesn't exist.*/
00530 
00531 ret  ::textutil::expander::Op_cget (type name , type varname) {
00532     if {![info exists [Var data-[Get level]-$varname]]} {
00533         error "$name cget: $varname doesn't exist in this context ([Get level])"
00534     }
00535     return [Get data-[Get level]-$varname]
00536 }
00537 
00538 /* ---------------------------------------------------------------------*/
00539 /*  FUNCTION:*/
00540 /*      Op_cvar varname*/
00541 /* */
00542 /*  INPUTS:*/
00543 /*  varname     The name of a context variable*/
00544 /* */
00545 /*  RETURNS:*/
00546 /*  The index to the variable*/
00547 /* */
00548 /*  DESCRIPTION:*/
00549 /*        Returns the index to a context variable, for use with set, */
00550 /*  lappend, etc.*/
00551 
00552 ret  ::textutil::expander::Op_cvar (type name , type varname) {
00553     if {![info exists [Var data-[Get level]-$varname]]} {
00554         error "$name cvar: $varname doesn't exist in this context"
00555     }
00556 
00557     return [Var data-[Get level]-$varname]
00558 }
00559 
00560 /* ---------------------------------------------------------------------*/
00561 /*  FUNCTION:*/
00562 /*      Op_cpop cname*/
00563 /* */
00564 /*  INPUTS:*/
00565 /*  cname       The expected context name.*/
00566 /* */
00567 /*  RETURNS:*/
00568 /*  The accumulated output in this context*/
00569 /* */
00570 /*  DESCRIPTION:*/
00571 /*        Returns the accumulated output for the current context, first*/
00572 /*  popping the context from the stack.  The expected context name*/
00573 /*  must match the real name, or an error occurs.*/
00574 
00575 ret  ::textutil::expander::Op_cpop (type name , type cname) {
00576     variable Info
00577 
00578     if {[Get level] == 0} {
00579         error "$name cpop underflow on '$cname'"
00580     }
00581 
00582     if {[string compare [Op_cname $name] $cname] != 0} {
00583         error "$name cpop context mismatch: expected [Op_cname $name], got $cname"
00584     }
00585 
00586     set result [Get output-[Get level]]
00587     # FRINK: nocheck
00588     set [Var output-[Get level]] ""
00589     # FRINK: nocheck
00590     set [Var name-[Get level]] ""
00591 
00592     foreach elt [array names "Info data-[Get level]-*"] {
00593         unset Info($elt)
00594     }
00595 
00596     # FRINK: nocheck
00597     incr [Var level] -1
00598     return $result
00599 }
00600 
00601 /* ---------------------------------------------------------------------*/
00602 /*  FUNCTION:*/
00603 /*      Op_ctopandclear*/
00604 /* */
00605 /*  INPUTS:*/
00606 /*  None.*/
00607 /* */
00608 /*  RETURNS:*/
00609 /*  The accumulated output in the topmost context, clears the context,*/
00610 /*  but does not pop it.*/
00611 /* */
00612 /*  DESCRIPTION:*/
00613 /*        Returns the accumulated output for the current context, first*/
00614 /*  popping the context from the stack.  The expected context name*/
00615 /*  must match the real name, or an error occurs.*/
00616 
00617 ret  ::textutil::expander::Op_ctopandclear (type name) {
00618     variable Info
00619 
00620     if {[Get level] == 0} {
00621         error "$name cpop underflow on '[Op_cname $name]'"
00622     }
00623 
00624     set result [Get output-[Get level]]
00625     Set output-[Get level] ""
00626     return $result
00627 }
00628 
00629 /* ---------------------------------------------------------------------*/
00630 /*  FUNCTION:*/
00631 /*      Op_cappend text*/
00632 /* */
00633 /*  INPUTS:*/
00634 /*  text        Text to add to the output*/
00635 /* */
00636 /*  RETURNS:*/
00637 /*  The accumulated output*/
00638 /* */
00639 /*  DESCRIPTION:*/
00640 /*        Appends the text to the accumulated output in the current context.*/
00641 
00642 ret  ::textutil::expander::Op_cappend (type name , type text) {
00643     # FRINK: nocheck
00644     append [Var output-[Get level]] $text
00645 }
00646 
00647 /* -------------------------------------------------------------------------*/
00648 /*  Macro-expansion:  The following code is the heart of the module.*/
00649 /*  Given a text string, and the current variable settings, this code*/
00650 /*  returns an expanded string, with all macros replaced.*/
00651 
00652 /* ---------------------------------------------------------------------*/
00653 /*  FUNCTION:*/
00654 /*      Op_expand inputString ?brackets?*/
00655 /* */
00656 /*  INPUTS:*/
00657 /*  inputString     The text to expand.*/
00658 /*  brackets        A list of two bracket tokens.*/
00659 /* */
00660 /*  RETURNS:*/
00661 /*  The expanded text.*/
00662 /* */
00663 /*  DESCRIPTION:*/
00664 /*  Finds all embedded macros in the input string, and expands them.*/
00665 /*  If ?brackets? is given, it must be list of length 2, containing*/
00666 /*  replacement left and right macro brackets; otherwise the default*/
00667 /*  brackets are used.*/
00668 
00669 ret  ::textutil::expander::Op_expand (type name , type inputString , optional brackets ="") {
00670     # FIRST, push a new context onto the stack, and save the current
00671     # brackets.
00672 
00673     Op_cpush $name expand
00674     Op_cset $name lb [Get lb]
00675     Op_cset $name rb [Get rb]
00676 
00677     # Keep position information in context variables as well.
00678     # Line we are in, counting from 1; column we are at,
00679     # counting from 0, and index of character we are at,
00680     # counting from 0. Tabs counts as '1' when computing
00681     # the column.
00682 
00683     LocInit $name
00684 
00685     # SF Tcllib Bug #530056.
00686     set start_level [Get level] ; # remember this for check at end
00687 
00688     # NEXT, use the user's brackets, if given.
00689     if {[llength $brackets] == 2} {
00690         Set lb [lindex $brackets 0]
00691         Set rb [lindex $brackets 1]
00692     }
00693 
00694     # NEXT, loop over the string, finding and expanding macros.
00695     while {[string length $inputString] > 0} {
00696         set plainText [ExtractToToken inputString [Get lb] exclude]
00697 
00698         # FIRST, If there was plain text, append it to the output, and 
00699         # continue.
00700         if {$plainText != ""} {
00701         set input $plainText
00702         set tc [Get textcmd]
00703         if {[string length $tc] > 0} {
00704         lappend tc $plainText
00705 
00706         if {![catch "[Get evalcmd] [list $tc]" result]} {
00707             set plainText $result
00708         } else {
00709             HandleError $name {plain text} $tc $result
00710         }
00711         }
00712             Op_cappend $name $plainText
00713         LocUpdate  $name $input
00714 
00715             if {[string length $inputString] == 0} {
00716                 break
00717             }
00718         }
00719 
00720         # NEXT, A macro is the next thing; process it.
00721         if {[catch {GetMacro inputString} macro]} {
00722         # SF tcllib bug 781973 ... Do not throw a regular
00723         # error. Use HandleError to give the user control of the
00724         # situation, via the defined error mode. The continue
00725         # intercepts if the user allows the expansion to run on,
00726         # yet we must not try to run the non-existing macro.
00727 
00728         HandleError $name {reading macro} $inputString $macro
00729         continue
00730         }
00731 
00732         # Expand the macro, and output the result, or
00733         # handle an error.
00734         if {![catch "[Get evalcmd] [list $macro]" result]} {
00735             Op_cappend $name $result 
00736 
00737         # We have to advance the location by the length of the
00738         # macro, plus the two brackets. They were stripped by
00739         # GetMacro, so we have to add them here again to make
00740         # computation correct.
00741 
00742         LocUpdate $name [Get lb]${macro}[Get rb]
00743             continue
00744         } 
00745 
00746     HandleError $name macro $macro $result
00747     }
00748 
00749     # SF Tcllib Bug #530056.
00750     if {[Get level] > $start_level} {
00751     # The user macros pushed additional contexts, but forgot to
00752     # pop them all. The main work here is to place all the still
00753     # open contexts into the error message, and to produce
00754     # syntactically correct english.
00755 
00756     set c [list]
00757     set n [expr {[Get level] - $start_level}]
00758     if {$n == 1} {
00759         set ctx  context
00760         set verb was
00761     } else {
00762         set ctx  contexts
00763         set verb were
00764     }
00765     for {incr n -1} {$n >= 0} {incr n -1} {
00766         lappend c [Get name-[expr {[Get level]-$n}]]
00767     }
00768     return -code error \
00769         "The following $ctx pushed by the macros $verb not popped: [join $c ,]."
00770     } elseif {[Get level] < $start_level} {
00771     set n [expr {$start_level - [Get level]}]
00772     if {$n == 1} {
00773         set ctx  context
00774     } else {
00775         set ctx  contexts
00776     }
00777     return -code error \
00778         "The macros popped $n more $ctx than they had pushed."
00779     }
00780 
00781     Op_lb $name [Op_cget $name lb]
00782     Op_rb $name [Op_cget $name rb]
00783 
00784     return [Op_cpop $name expand]
00785 }
00786 
00787 /* ---------------------------------------------------------------------*/
00788 /*  FUNCTION:*/
00789 /*      Op_where*/
00790 /* */
00791 /*  INPUTS:*/
00792 /*  None.*/
00793 /* */
00794 /*  RETURNS:*/
00795 /*  The current location in the input.*/
00796 /* */
00797 /*  DESCRIPTION:*/
00798 /*  Retrieves the current location the expander*/
00799 /*  is at during processing.*/
00800 
00801 ret  ::textutil::expander::Op_where (type name) {
00802     return [LocGet $name]
00803 }
00804 
00805 /* ---------------------------------------------------------------------*/
00806 /*  FUNCTION*/
00807 /*  HandleError name title command errmsg*/
00808 /* */
00809 /*  INPUTS:*/
00810 /*  name        The name of the expander object in question.*/
00811 /*  title       A title text*/
00812 /*  command     The command which caused the error.*/
00813 /*  errmsg      The error message to report*/
00814 /* */
00815 /*  RETURNS:*/
00816 /*  Nothing*/
00817 /* */
00818 /*  DESCRIPTIONS*/
00819 /*  Is executed when an error in a macro or the plain text handler*/
00820 /*  occurs. Generates an error message according to the current*/
00821 /*  error mode.*/
00822 
00823 ret  ::textutil::expander::HandleError (type name , type title , type command , type errmsg) {
00824     switch [Get errmode] {
00825     nothing { }
00826     macro {
00827         # The location is irrelevant here.
00828         Op_cappend $name "[Get lb]$command[Get rb]" 
00829     }
00830     error {
00831         foreach {ch line col} [LocGet $name] break
00832         set display [DisplayOf $command]
00833 
00834         Op_cappend $name "\n=================================\n"
00835         Op_cappend $name "*** Error in $title at line $line, column $col:\n"
00836         Op_cappend $name "*** [Get lb]$display[Get rb]\n--> $errmsg\n"
00837         Op_cappend $name "=================================\n"
00838     }
00839     fail   { 
00840         foreach {ch line col} [LocGet $name] break
00841         set display [DisplayOf $command]
00842 
00843         return -code error "Error in $title at line $line,\
00844             column $col:\n[Get lb]$display[Get rb]\n-->\
00845             $errmsg"
00846     }
00847     default {
00848         return -code error "Unknown error mode: [Get errmode]"
00849     }
00850     }
00851 }
00852 
00853 /* ---------------------------------------------------------------------*/
00854 /*  FUNCTION:*/
00855 /*      ExtractToToken string token mode*/
00856 /* */
00857 /*  INPUTS:*/
00858 /*  string      The text to process.*/
00859 /*  token       The token to look for*/
00860 /*  mode        include or exclude*/
00861 /* */
00862 /*  RETURNS:*/
00863 /*  The extracted text*/
00864 /* */
00865 /*  DESCRIPTION:*/
00866 /*      Extract text from a string, up to or including a particular*/
00867 /*      token.  Remove the extracted text from the string.*/
00868 /*      mode determines whether the found token is removed;*/
00869 /*      it should be "include" or "exclude".  The string is*/
00870 /*      modified in place, and the extracted text is returned.*/
00871 
00872 ret  ::textutil::expander::ExtractToToken (type string , type token , type mode) {
00873     upvar $string theString
00874 
00875     # First, determine the offset
00876     switch $mode {
00877         include { set offset [expr {[string length $token] - 1}] }
00878         exclude { set offset -1 }
00879         default { error "::expander::ExtractToToken: unknown mode $mode" }
00880     }
00881 
00882     # Next, find the first occurrence of the token.
00883     set tokenPos [string first $token $theString]
00884 
00885     # Next, return the entire string if it wasn't found, or just
00886     # the part upto or including the character.
00887     if {$tokenPos == -1} {
00888         set theText $theString
00889         set theString ""
00890     } else {
00891         set newEnd    [expr {$tokenPos + $offset}]
00892         set newBegin  [expr {$newEnd + 1}]
00893         set theText   [string range $theString 0 $newEnd]
00894         set theString [string range $theString $newBegin end]
00895     }
00896 
00897     return $theText
00898 }
00899 
00900 /* ---------------------------------------------------------------------*/
00901 /*  FUNCTION:*/
00902 /*      GetMacro string*/
00903 /* */
00904 /*  INPUTS:*/
00905 /*  string      The text to process.*/
00906 /* */
00907 /*  RETURNS:*/
00908 /*  The macro, stripped of its brackets.*/
00909 /* */
00910 /*  DESCRIPTION:*/
00911 
00912 ret  ::textutil::expander::GetMacro (type string) {
00913     upvar $string theString
00914 
00915     # FIRST, it's an error if the string doesn't begin with a
00916     # bracket.
00917     if {[string first [Get lb] $theString] != 0} {
00918         error "::expander::GetMacro: assertion failure, next text isn't a command! '$theString'"
00919     }
00920 
00921     # NEXT, extract a full macro
00922     set macro [ExtractToToken theString [Get lb] include]
00923     while {[string length $theString] > 0} {
00924         append macro [ExtractToToken theString [Get rb] include]
00925 
00926         # Verify that the command really ends with the [rb] characters,
00927         # whatever they are.  If not, break because of unexpected
00928         # end of file.
00929         if {![IsBracketed $macro]} {
00930             break;
00931         }
00932 
00933         set strippedMacro [StripBrackets $macro]
00934 
00935         if {[info complete "puts \[$strippedMacro\]"]} {
00936             return $strippedMacro
00937         }
00938     }
00939 
00940     if {[string length $macro] > 40} {
00941         set macro "[string range $macro 0 39]...\n"
00942     }
00943     error "Unexpected EOF in macro:\n$macro"
00944 }
00945 
00946 /*  Strip left and right bracket tokens from the ends of a macro,*/
00947 /*  provided that it's properly bracketed.*/
00948 ret  ::textutil::expander::StripBrackets (type macro) {
00949     set llen [string length [Get lb]]
00950     set rlen [string length [Get rb]]
00951     set tlen [string length $macro]
00952 
00953     return [string range $macro $llen [expr {$tlen - $rlen - 1}]]
00954 }
00955 
00956 /*  Return 1 if the macro is properly bracketed, and 0 otherwise.*/
00957 ret  ::textutil::expander::IsBracketed (type macro) {
00958     set llen [string length [Get lb]]
00959     set rlen [string length [Get rb]]
00960     set tlen [string length $macro]
00961 
00962     set leftEnd  [string range $macro 0       [expr {$llen - 1}]]
00963     set rightEnd [string range $macro [expr {$tlen - $rlen}] end]
00964 
00965     if {$leftEnd != [Get lb]} {
00966         return 0
00967     } elseif {$rightEnd != [Get rb]} {
00968         return 0
00969     } else {
00970         return 1
00971     }
00972 }
00973 
00974 /* ---------------------------------------------------------------------*/
00975 /*  FUNCTION:*/
00976 /*      LocInit name*/
00977 /* */
00978 /*  INPUTS:*/
00979 /*  name        The expander object to use.*/
00980 /* */
00981 /*  RETURNS:*/
00982 /*  No result.*/
00983 /* */
00984 /*  DESCRIPTION:*/
00985 /*  A convenience wrapper around LocSet. Initializes the location*/
00986 /*  to the start of the input (char 0, line 1, column 0).*/
00987 
00988 ret  ::textutil::expander::LocInit (type name) {
00989     LocSet $name {0 1 0}
00990     return
00991 }
00992 
00993 /* ---------------------------------------------------------------------*/
00994 /*  FUNCTION:*/
00995 /*      LocSet name loc*/
00996 /* */
00997 /*  INPUTS:*/
00998 /*  name        The expander object to use.*/
00999 /*  loc     Location, list containing character position,*/
01000 /*          line number and column, in this order.*/
01001 /* */
01002 /*  RETURNS:*/
01003 /*  No result.*/
01004 /* */
01005 /*  DESCRIPTION:*/
01006 /*  Sets the current location in the expander to 'loc'.*/
01007 
01008 ret  ::textutil::expander::LocSet (type name , type loc) {
01009     foreach {ch line col} $loc break
01010     Op_cset  $name char $ch
01011     Op_cset  $name line $line
01012     Op_cset  $name col  $col
01013     return
01014 }
01015 
01016 /* ---------------------------------------------------------------------*/
01017 /*  FUNCTION:*/
01018 /*      LocGet name*/
01019 /* */
01020 /*  INPUTS:*/
01021 /*  name        The expander object to use.*/
01022 /* */
01023 /*  RETURNS:*/
01024 /*  A list containing the current character position, line number*/
01025 /*  and column, in this order.*/
01026 /* */
01027 /*  DESCRIPTION:*/
01028 /*  Returns the current location as stored in the expander.*/
01029 
01030 ret  ::textutil::expander::LocGet (type name) {
01031     list [Op_cget $name char] [Op_cget $name line] [Op_cget $name col]
01032 }
01033 
01034 /* ---------------------------------------------------------------------*/
01035 /*  FUNCTION:*/
01036 /*      LocUpdate name text*/
01037 /* */
01038 /*  INPUTS:*/
01039 /*  name        The expander object to use.*/
01040 /*  text        The text to process.*/
01041 /* */
01042 /*  RETURNS:*/
01043 /*  No result.*/
01044 /* */
01045 /*  DESCRIPTION:*/
01046 /*  Takes the current location as stored in the expander, computes*/
01047 /*  a new location based on the string (its length and contents*/
01048 /*  (number of lines)), and makes that new location the current*/
01049 /*  location.*/
01050 
01051 ret  ::textutil::expander::LocUpdate (type name , type text) {
01052     foreach {ch line col} [LocGet $name] break
01053     set numchars [string length $text]
01054     #8.4+ set numlines [regexp -all "\n" $text]
01055     set numlines [expr {[llength [split $text \n]]-1}]
01056 
01057     incr ch   $numchars
01058     incr line $numlines
01059     if {$numlines} {
01060     set col [expr {$numchars - [string last \n $text] - 1}]
01061     } else {
01062     incr col $numchars
01063     }
01064 
01065     LocSet $name [list $ch $line $col]
01066     return
01067 }
01068 
01069 /* ---------------------------------------------------------------------*/
01070 /*  FUNCTION:*/
01071 /*      LocRange name text*/
01072 /* */
01073 /*  INPUTS:*/
01074 /*  name        The expander object to use.*/
01075 /*  text        The text to process.*/
01076 /* */
01077 /*  RETURNS:*/
01078 /*  A text range description, compatible with the 'location' data*/
01079 /*  used in the tcl debugger/checker.*/
01080 /* */
01081 /*  DESCRIPTION:*/
01082 /*  Takes the current location as stored in the expander object*/
01083 /*  and the length of the text to generate a character range.*/
01084 
01085 ret  ::textutil::expander::LocRange (type name , type text) {
01086     # Note that the structure is compatible with
01087     # the ranges uses by tcl debugger and checker.
01088     # {line {charpos length}}
01089 
01090     foreach {ch line col} [LocGet $name] break
01091     return [list $line [list $ch [string length $text]]]
01092 }
01093 
01094 /* ---------------------------------------------------------------------*/
01095 /*  FUNCTION:*/
01096 /*      DisplayOf text*/
01097 /* */
01098 /*  INPUTS:*/
01099 /*  text        The text to process.*/
01100 /* */
01101 /*  RETURNS:*/
01102 /*  The text, cut down to at most 30 bytes.*/
01103 /* */
01104 /*  DESCRIPTION:*/
01105 /*  Cuts the incoming text down to contain no more than 30*/
01106 /*  characters of the input. Adds an ellipsis (...) if characters*/
01107 /*  were actually removed from the input.*/
01108 
01109 ret  ::textutil::expander::DisplayOf (type text) {
01110     set ellip ""
01111     while {[string bytelength $text] > 30} {
01112     set ellip ...
01113     set text [string range $text 0 end-1]
01114     }
01115     set display $text$ellip
01116 }
01117 
01118 /* ---------------------------------------------------------------------*/
01119 /*  Provide the package only if the code above was read and executed*/
01120 /*  without error.*/
01121 
01122 package provide textutil::expander 1.3.1
01123 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1