home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Applications / Tcl-Tk 8.0 / Pre-installed version / tcl8.0 / library / opt0.1 / optparse.tcl next >
Encoding:
Text File  |  1997-08-15  |  32.0 KB  |  1,068 lines  |  [TEXT/ALFA]

  1. # optparse.tcl --
  2. #
  3. #       (Private) option parsing package
  4. #
  5. #       This might be documented and exported in 8.1
  6. #       and some function hopefully moved to the C core for
  7. #       efficiency, if there is enough demand. (mail! ;-)
  8. #
  9. #  Author:    Laurent Demailly  - Laurent.Demailly@sun.com - dl@mail.box.eu.org
  10. #
  11. #  Credits:
  12. #             this is a complete 'over kill' rewrite by me, from a version
  13. #             written initially with Brent Welch, itself initially
  14. #             based on work with Steve Uhler. Thanks them !
  15. #
  16. # SCCS: @(#) optparse.tcl 1.11 97/08/11 16:39:15
  17.  
  18. package provide opt 0.1
  19.  
  20. namespace eval ::tcl {
  21.  
  22.     # Exported APIs
  23.     namespace export OptKeyRegister OptKeyDelete OptKeyError OptKeyParse \
  24.              OptProc OptProcArgGiven OptParse \
  25.              Lassign Lvarpop Lvarset Lvarincr Lfirst \
  26.              SetMax SetMin
  27.  
  28.  
  29. #################  Example of use / 'user documentation'  ###################
  30.  
  31.     proc OptCreateTestProc {} {
  32.  
  33.     # Defines ::tcl::OptParseTest as a test proc with parsed arguments
  34.     # (can't be defined before the code below is loaded (before "OptProc"))
  35.  
  36.     # Every OptProc give usage information on "procname -help".
  37.     # Try "tcl::OptParseTest -help" and "tcl::OptParseTest -a" and
  38.     # then other arguments.
  39.     # 
  40.     # example of 'valid' call:
  41.     # ::tcl::OptParseTest save -4 -pr 23 -libsok SybTcl\
  42.     #        -nostatics false ch1
  43.     OptProc OptParseTest {
  44.             {subcommand -choice {save print} "sub command"}
  45.             {arg1 3 "some number"}
  46.             {-aflag}
  47.             {-intflag      7}
  48.             {-weirdflag                    "help string"}
  49.             {-noStatics                    "Not ok to load static packages"}
  50.             {-nestedloading1 true           "OK to load into nested slaves"}
  51.             {-nestedloading2 -boolean true "OK to load into nested slaves"}
  52.             {-libsOK        -choice {Tk SybTcl}
  53.                               "List of packages that can be loaded"}
  54.             {-precision     -int 12        "Number of digits of precision"}
  55.             {-intval        7               "An integer"}
  56.             {-scale         -float 1.0     "Scale factor"}
  57.             {-zoom          1.0             "Zoom factor"}
  58.             {-arbitrary     foobar          "Arbitrary string"}
  59.             {-random        -string 12   "Random string"}
  60.             {-listval       -list {}       "List value"}
  61.             {-blahflag       -blah abc       "Funny type"}
  62.         {arg2 -boolean "a boolean"}
  63.         {arg3 -choice "ch1 ch2"}
  64.         {?optarg? -list {} "optional argument"}
  65.         } {
  66.         foreach v [info locals] {
  67.         puts stderr [format "%14s : %s" $v [set $v]]
  68.         }
  69.     }
  70.     }
  71.  
  72. ###################  No User serviceable part below ! ###############
  73. # You should really not look any further :
  74. # The following is private unexported undocumented unblessed... code 
  75. # time to hit "q" ;-) !
  76.  
  77. # Hmmm... ok, you really want to know ?
  78.  
  79. # You've been warned... Here it is...
  80.  
  81.     # Array storing the parsed descriptions
  82.     variable OptDesc;
  83.     array set OptDesc {};
  84.     # Next potentially free key id (numeric)
  85.     variable OptDescN 0;
  86.  
  87. # Inside algorithm/mechanism description:
  88. # (not for the faint hearted ;-)
  89. #
  90. # The argument description is parsed into a "program tree"
  91. # It is called a "program" because it is the program used by
  92. # the state machine interpreter that use that program to
  93. # actually parse the arguments at run time.
  94. #
  95. # The general structure of a "program" is
  96. # notation (pseudo bnf like)
  97. #    name :== definition        defines "name" as being "definition" 
  98. #    { x y z }                  means list of x, y, and z  
  99. #    x*                         means x repeated 0 or more time
  100. #    x+                         means "x x*"
  101. #    x?                         means optionally x
  102. #    x | y                      means x or y
  103. #    "cccc"                     means the literal string
  104. #
  105. #    program        :== { programCounter programStep* }
  106. #
  107. #    programStep    :== program | singleStep
  108. #
  109. #    programCounter :== {"P" integer+ }
  110. #
  111. #    singleStep     :== { instruction parameters* }
  112. #
  113. #    instruction    :== single element list
  114. #
  115. # (the difference between singleStep and program is that \
  116. #   llength [Lfirst $program] >= 2
  117. # while
  118. #   llength [Lfirst $singleStep] == 1
  119. # )
  120. #
  121. # And for this application:
  122. #
  123. #    singleStep     :== { instruction varname {hasBeenSet currentValue} type 
  124. #                         typeArgs help }
  125. #    instruction    :== "flags" | "value"
  126. #    type           :== knowType | anyword
  127. #    knowType       :== "string" | "int" | "boolean" | "boolflag" | "float"
  128. #                       | "choice"
  129. #
  130. # for type "choice" typeArgs is a list of possible choices, the first one
  131. # is the default value. for all other types the typeArgs is the default value
  132. #
  133. # a "boolflag" is the type for a flag whose presence or absence, without
  134. # additional arguments means respectively true or false (default flag type).
  135. #
  136. # programCounter is the index in the list of the currently processed
  137. # programStep (thus starting at 1 (0 is {"P" prgCounterValue}).
  138. # If it is a list it points toward each currently selected programStep.
  139. # (like for "flags", as they are optional, form a set and programStep).
  140.  
  141. # Performance/Implementation issues
  142. # ---------------------------------
  143. # We use tcl lists instead of arrays because with tcl8.0
  144. # they should start to be much faster.
  145. # But this code use a lot of helper procs (like Lvarset)
  146. # which are quite slow and would be helpfully optimized
  147. # for instance by being written in C. Also our struture
  148. # is complex and there is maybe some places where the
  149. # string rep might be calculated at great exense. to be checked.
  150.  
  151. #
  152. # Parse a given description and saves it here under the given key
  153. # generate a unused keyid if not given
  154. #
  155. proc ::tcl::OptKeyRegister {desc {key ""}} {
  156.     variable OptDesc;
  157.     variable OptDescN;
  158.     if {[string compare $key ""] == 0} {
  159.         # in case a key given to us as a parameter was a number
  160.         while {[info exists OptDesc($OptDescN)]} {incr OptDescN}
  161.         set key $OptDescN;
  162.         incr OptDescN;
  163.     }
  164.     # program counter
  165.     set program [list [list "P" 1]];
  166.  
  167.     # are we processing flags (which makes a single program step)
  168.     set inflags 0;
  169.     set state {};
  170.  
  171.     foreach item $desc {
  172.     if {$state == "args"} {
  173.         # more items after 'args'...
  174.         return -code error "'args' special argument must be the last one";
  175.     }
  176.         set res [OptNormalizeOne $item];
  177.         set state [Lfirst $res];
  178.         if {$inflags} {
  179.             if {$state == "flags"} {
  180.         # add to 'subprogram'
  181.                 lappend flagsprg $res;
  182.             } else {
  183.                 # put in the flags
  184.                 # structure for flag programs items is a list of
  185.                 # {subprgcounter {prg flag 1} {prg flag 2} {...}}
  186.                 lappend program $flagsprg;
  187.                 # put the other regular stuff
  188.                 lappend program $res;
  189.         set inflags 0;
  190.             }
  191.         } else {
  192.            if {$state == "flags"} {
  193.                set inflags 1;
  194.                # sub program counter + first sub program
  195.                set flagsprg [list [list "P" 1] $res];
  196.            } else {
  197.                lappend program $res;
  198.            }
  199.        }
  200.    }
  201.    if {$inflags} {
  202.        lappend program $flagsprg;
  203.    }
  204.  
  205.    set OptDesc($key) $program;
  206.  
  207.    return $key;
  208. }
  209.  
  210. #
  211. # Free the storage for that given key
  212. #
  213. proc ::tcl::OptKeyDelete {key} {
  214.     variable OptDesc;
  215.     unset OptDesc($key);
  216. }
  217.  
  218.     # Get the parsed description stored under the given key.
  219.     proc OptKeyGetDesc {descKey} {
  220.         variable OptDesc;
  221.         if {![info exists OptDesc($descKey)]} {
  222.             return -code error "Unknown option description key \"$descKey\"";
  223.         }
  224.         set OptDesc($descKey);
  225.     }
  226.  
  227. # Parse entry point for ppl who don't want to register with a key,
  228. # for instance because the description changes dynamically.
  229. #  (otherwise one should really use OptKeyRegister once + OptKeyParse
  230. #   as it is way faster or simply OptProc which does it all)
  231. # Assign a temporary key, call OptKeyParse and then free the storage
  232. proc ::tcl::OptParse {desc arglist} {
  233.     set tempkey [OptKeyRegister $desc];
  234.     set ret [catch {uplevel [list ::tcl::OptKeyParse $tempkey $arglist]} res];
  235.     OptKeyDelete $tempkey;
  236.     return -code $ret $res;
  237. }
  238.  
  239. # Helper function, replacement for proc that both
  240. # register the description under a key which is the name of the proc
  241. # (and thus unique to that code)
  242. # and add a first line to the code to call the OptKeyParse proc
  243. # Stores the list of variables that have been actually given by the user
  244. # (the other will be sets to their default value)
  245. # into local variable named "Args".
  246. proc ::tcl::OptProc {name desc body} {
  247.     set namespace [uplevel namespace current];
  248.     if {   ([string match $name "::*"]) 
  249.         || ([string compare $namespace "::"]==0)} {
  250.         # absolute name or global namespace, name is the key
  251.         set key $name;
  252.     } else {
  253.         # we are relative to some non top level namespace:
  254.         set key "${namespace}::${name}";
  255.     }
  256.     OptKeyRegister $desc $key;
  257.     uplevel [list proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"];
  258.     return $key;
  259. }
  260. # Check that a argument has been given
  261. # assumes that "OptProc" has been used as it will check in "Args" list
  262. proc ::tcl::OptProcArgGiven {argname} {
  263.     upvar Args alist;
  264.     expr {[lsearch $alist $argname] >=0}
  265. }
  266.  
  267.     #######
  268.     # Programs/Descriptions manipulation
  269.  
  270.     # Return the instruction word/list of a given step/(sub)program
  271.     proc OptInstr {lst} {
  272.     Lfirst $lst;
  273.     }
  274.     # Is a (sub) program or a plain instruction ?
  275.     proc OptIsPrg {lst} {
  276.     expr {[llength [OptInstr $lst]]>=2}
  277.     }
  278.     # Is this instruction a program counter or a real instr
  279.     proc OptIsCounter {item} {
  280.     expr {[Lfirst $item]=="P"}
  281.     }
  282.     # Current program counter (2nd word of first word)
  283.     proc OptGetPrgCounter {lst} {
  284.     Lget $lst {0 1}
  285.     }
  286.     # Current program counter (2nd word of first word)
  287.     proc OptSetPrgCounter {lstName newValue} {
  288.     upvar $lstName lst;
  289.     set lst [lreplace $lst 0 0 [concat "P" $newValue]];
  290.     }
  291.     # returns a list of currently selected items.
  292.     proc OptSelection {lst} {
  293.     set res {};
  294.     foreach idx [lrange [Lfirst $lst] 1 end] {
  295.         lappend res [Lget $lst $idx];
  296.     }
  297.     return $res;
  298.     }
  299.  
  300.     # Advance to next description
  301.     proc OptNextDesc {descName} {
  302.         uplevel [list Lvarincr $descName {0 1}];
  303.     }
  304.  
  305.     # Get the current description, eventually descend
  306.     proc OptCurDesc {descriptions} {
  307.         lindex $descriptions [OptGetPrgCounter $descriptions];
  308.     }
  309.     # get the current description, eventually descend
  310.     # through sub programs as needed.
  311.     proc OptCurDescFinal {descriptions} {
  312.         set item [OptCurDesc $descriptions];
  313.     # Descend untill we get the actual item and not a sub program
  314.         while {[OptIsPrg $item]} {
  315.             set item [OptCurDesc $item];
  316.         }
  317.     return $item;
  318.     }
  319.     # Current final instruction adress
  320.     proc OptCurAddr {descriptions {start {}}} {
  321.     set adress [OptGetPrgCounter $descriptions];
  322.     lappend start $adress;
  323.     set item [lindex $descriptions $adress];
  324.     if {[OptIsPrg $item]} {
  325.         return [OptCurAddr $item $start];
  326.     } else {
  327.         return $start;
  328.     }
  329.     }
  330.     # Set the value field of the current instruction
  331.     proc OptCurSetValue {descriptionsName value} {
  332.     upvar $descriptionsName descriptions
  333.     # get the current item full adress
  334.         set adress [OptCurAddr $descriptions];
  335.     # use the 3th field of the item  (see OptValue / OptNewInst)
  336.     lappend adress 2
  337.     Lvarset descriptions $adress [list 1 $value];
  338.     #                                  ^hasBeenSet flag
  339.     }
  340.  
  341.     # empty state means done/paste the end of the program
  342.     proc OptState {item} {
  343.         Lfirst $item
  344.     }
  345.     
  346.     # current state
  347.     proc OptCurState {descriptions} {
  348.         OptState [OptCurDesc $descriptions];
  349.     }
  350.  
  351.     #######
  352.     # Arguments manipulation
  353.  
  354.     # Returns the argument that has to be processed now
  355.     proc OptCurrentArg {lst} {
  356.         Lfirst $lst;
  357.     }
  358.     # Advance to next argument
  359.     proc OptNextArg {argsName} {
  360.         uplevel [list Lvarpop $argsName];
  361.     }
  362.     #######
  363.  
  364.  
  365.  
  366.  
  367.  
  368.     # Loop over all descriptions, calling OptDoOne which will
  369.     # eventually eat all the arguments.
  370.     proc OptDoAll {descriptionsName argumentsName} {
  371.     upvar $descriptionsName descriptions
  372.     upvar $argumentsName arguments;
  373. #    puts "entered DoAll";
  374.     # Nb: the places where "state" can be set are tricky to figure
  375.     #     because DoOne sets the state to flagsValue and return -continue
  376.     #     when needed...
  377.     set state [OptCurState $descriptions];
  378.     # We'll exit the loop in "OptDoOne" or when state is empty.
  379.         while 1 {
  380.         set curitem [OptCurDesc $descriptions];
  381.         # Do subprograms if needed, call ourselves on the sub branch
  382.         while {[OptIsPrg $curitem]} {
  383.         OptDoAll curitem arguments
  384. #        puts "done DoAll sub";
  385.         # Insert back the results in current tree;
  386.         Lvarset1nc descriptions [OptGetPrgCounter $descriptions]\
  387.             $curitem;
  388.         OptNextDesc descriptions;
  389.         set curitem [OptCurDesc $descriptions];
  390.                 set state [OptCurState $descriptions];
  391.         }
  392. #           puts "state = \"$state\" - arguments=($arguments)";
  393.         if {[Lempty $state]} {
  394.         # Nothing left to do, we are done in this branch:
  395.         break;
  396.         }
  397.         # The following statement can make us terminate/continue
  398.         # as it use return -code {break, continue, return and error}
  399.         # codes
  400.             OptDoOne descriptions state arguments;
  401.         # If we are here, no special return code where issued,
  402.         # we'll step to next instruction :
  403. #           puts "new state  = \"$state\"";
  404.         OptNextDesc descriptions;
  405.         set state [OptCurState $descriptions];
  406.         }
  407.         if  {![Lempty $arguments]} {
  408.             return -code error [OptTooManyArgs $descriptions $arguments];
  409.         }
  410.     }
  411.  
  412.     # Process one step for the state machine,
  413.     # eventually consuming the current argument.
  414.     proc OptDoOne {descriptionsName stateName argumentsName} {
  415.         upvar $argumentsName arguments;
  416.         upvar $descriptionsName descriptions;
  417.     upvar $stateName state;
  418.  
  419.     # the special state/instruction "args" eats all
  420.     # the remaining args (if any)
  421.     if {($state == "args")} {
  422.         OptCurSetValue descriptions $arguments;
  423.         set arguments {};
  424. #            puts "breaking out ('args' state: consuming every reminding args)"
  425.         return -code break;
  426.     }
  427.  
  428.     if {[Lempty $arguments]} {
  429.         if {$state == "flags"} {
  430.         # no argument and no flags : we're done
  431. #                puts "returning to previous (sub)prg (no more args)";
  432.         return -code return;
  433.         } elseif {$state == "optValue"} {
  434.         set state next; # not used, for debug only
  435.         # go to next state
  436.         return ;
  437.         } else {
  438.         return -code error [OptMissingValue $descriptions];
  439.         }
  440.     } else {
  441.         set arg [OptCurrentArg $arguments];
  442.     }
  443.  
  444.         switch $state {
  445.             flags {
  446.                 # A non-dash argument terminates the options, as does --
  447.  
  448.                 # Still a flag ?
  449.                 if {![OptIsFlag $arg]} {
  450.                     # don't consume the argument, return to previous prg
  451.                     return -code return;
  452.                 }
  453.                 # consume the flag
  454.                 OptNextArg arguments;
  455.                 if {[string compare "--" $arg] == 0} {
  456.                     # return from 'flags' state
  457.                     return -code return;
  458.                 }
  459.  
  460.                 set hits [OptHits descriptions $arg];
  461.                 if {$hits > 1} {
  462.                     return -code error [OptAmbigous $descriptions $arg]
  463.                 } elseif {$hits == 0} {
  464.                     return -code error [OptFlagUsage $descriptions $arg]
  465.                 }
  466.         set item [OptCurDesc $descriptions];
  467.                 if {[OptNeedValue $item]} {
  468.             # we need a value, next state is
  469.             set state flagValue;
  470.                 } else {
  471.                     OptCurSetValue descriptions 1;
  472.                 }
  473.         # continue
  474.         return -code continue;
  475.             }
  476.         flagValue -
  477.         value {
  478.         set item [OptCurDesc $descriptions];
  479.                 # Test the values against their required type
  480.         if [catch {OptCheckType $arg\
  481.             [OptType $item] [OptTypeArgs $item]} val] {
  482.             return -code error [OptBadValue $item $arg $val]
  483.         }
  484.                 # consume the value
  485.                 OptNextArg arguments;
  486.         # set the value
  487.         OptCurSetValue descriptions $val;
  488.         # go to next state
  489.         if {$state == "flagValue"} {
  490.             set state flags
  491.             return -code continue;
  492.         } else {
  493.             set state next; # not used, for debug only
  494.             return ; # will go on next step
  495.         }
  496.         }
  497.         optValue {
  498.         set item [OptCurDesc $descriptions];
  499.                 # Test the values against their required type
  500.         if ![catch {OptCheckType $arg\
  501.             [OptType $item] [OptTypeArgs $item]} val] {
  502.             # right type, so :
  503.             # consume the value
  504.             OptNextArg arguments;
  505.             # set the value
  506.             OptCurSetValue descriptions $val;
  507.         }
  508.         # go to next state
  509.         set state next; # not used, for debug only
  510.         return ; # will go on next step
  511.         }
  512.         }
  513.     # If we reach this point: an unknown
  514.     # state as been entered !
  515.     return -code error "Bug! unknown state in DoOne \"$state\"\
  516.         (prg counter [OptGetPrgCounter $descriptions]:\
  517.             [OptCurDesc $descriptions])";
  518.     }
  519.  
  520. # Parse the options given the key to previously registered description
  521. # and arguments list
  522. proc ::tcl::OptKeyParse {descKey arglist} {
  523.  
  524.     set desc [OptKeyGetDesc $descKey];
  525.  
  526.     # make sure -help always give usage
  527.     if {[string compare "-help" [string tolower $arglist]] == 0} {
  528.     return -code error [OptError "Usage information:" $desc 1];
  529.     }
  530.  
  531.     OptDoAll desc arglist;
  532.     
  533.     # Analyse the result
  534.     # Walk through the tree:
  535.     OptTreeVars $desc "#[expr [info level]-1]" ;
  536. }
  537.  
  538.     # determine string length for nice tabulated output
  539.     proc OptTreeVars {desc level {vnamesLst {}}} {
  540.     foreach item $desc {
  541.         if {[OptIsCounter $item]} continue;
  542.         if {[OptIsPrg $item]} {
  543.         set vnamesLst [OptTreeVars $item $level $vnamesLst];
  544.         } else {
  545.         set vname [OptVarName $item];
  546.         upvar $level $vname var
  547.         if {[OptHasBeenSet $item]} {
  548. #            puts "adding $vname"
  549.             # lets use the input name for the returned list
  550.             # it is more usefull, for instance you can check that
  551.             # no flags at all was given with expr
  552.             # {![string match "*-*" $Args]}
  553.             lappend vnamesLst [OptName $item];
  554.             set var [OptValue $item];
  555.         } else {
  556.             set var [OptDefaultValue $item];
  557.         }
  558.         }
  559.     }
  560.     return $vnamesLst
  561.     }
  562.  
  563.  
  564. # Check the type of a value
  565. # and emit an error if arg is not of the correct type
  566. # otherwise returns the canonical value of that arg (ie 0/1 for booleans)
  567. proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
  568. #    puts "checking '$arg' against '$type' ($typeArgs)";
  569.  
  570.     # only types "any", "choice", and numbers can have leading "-"
  571.  
  572.     switch -exact -- $type {
  573.         int {
  574.             if ![regexp {^(-+)?[0-9]+$} $arg] {
  575.                 error "not an integer"
  576.             }
  577.         return $arg;
  578.         }
  579.         float {
  580.             return [expr double($arg)]
  581.         }
  582.     script -
  583.         list {
  584.         # if llength fail : malformed list
  585.             if {[llength $arg]==0} {
  586.         if {[OptIsFlag $arg]} {
  587.             error "no values with leading -"
  588.         }
  589.         }
  590.         return $arg;
  591.         }
  592.         boolean {
  593.         if ![regexp -nocase {^(true|false|0|1)$} $arg] {
  594.         error "non canonic boolean"
  595.             }
  596.         # convert true/false because expr/if is broken with "!,...
  597.         if {$arg} {
  598.         return 1
  599.         } else {
  600.         return 0
  601.         }
  602.         }
  603.         choice {
  604.             if {[lsearch -exact $typeArgs $arg] < 0} {
  605.                 error "invalid choice"
  606.             }
  607.         return $arg;
  608.         }
  609.     any {
  610.         return $arg;
  611.     }
  612.     string -
  613.     default {
  614.             if {[OptIsFlag $arg]} {
  615.                 error "no values with leading -"
  616.             }
  617.         return $arg
  618.         }
  619.     }
  620.     return neverReached;
  621. }
  622.  
  623.     # internal utilities
  624.  
  625.     # returns the number of flags matching the given arg
  626.     # sets the (local) prg counter to the list of matches
  627.     proc OptHits {descName arg} {
  628.         upvar $descName desc;
  629.         set hits 0
  630.         set hitems {}
  631.     set i 1;
  632.         foreach item [lrange $desc 1 end] {
  633.             set flag [OptName $item]
  634.         # lets try to match case insensitively
  635.             if {[string match [string tolower $arg*] [string tolower $flag]]} {
  636.                 lappend hitems $i;
  637.                 incr hits;
  638.             }
  639.         incr i;
  640.         }
  641.     if {$hits} {
  642.         OptSetPrgCounter desc $hitems;
  643.     }
  644.         return $hits
  645.     }
  646.  
  647.     # Extract fields from the list structure:
  648.  
  649.     proc OptName {item} {
  650.         lindex $item 1;
  651.     }
  652.     # 
  653.     proc OptHasBeenSet {item} {
  654.     Lget $item {2 0};
  655.     }
  656.     # 
  657.     proc OptValue {item} {
  658.     Lget $item {2 1};
  659.     }
  660.  
  661.     proc OptIsFlag {name} {
  662.         string match "-*" $name;
  663.     }
  664.     proc OptIsOpt {name} {
  665.         string match {\?*} $name;
  666.     }
  667.     proc OptVarName {item} {
  668.         set name [OptName $item];
  669.         if {[OptIsFlag $name]} {
  670.             return [string range $name 1 end];
  671.         } elseif {[OptIsOpt $name]} {
  672.         return [string trim $name "?"];
  673.     } else {
  674.             return $name;
  675.         }
  676.     }
  677.     proc OptType {item} {
  678.         lindex $item 3
  679.     }
  680.     proc OptTypeArgs {item} {
  681.         lindex $item 4
  682.     }
  683.     proc OptHelp {item} {
  684.         lindex $item 5
  685.     }
  686.     proc OptNeedValue {item} {
  687.         string compare [OptType $item] boolflag
  688.     }
  689.     proc OptDefaultValue {item} {
  690.         set val [OptTypeArgs $item]
  691.         switch -exact -- [OptType $item] {
  692.             choice {return [lindex $val 0]}
  693.         boolean -
  694.         boolflag {
  695.         # convert back false/true to 0/1 because expr !$bool
  696.         # is broken..
  697.         if {$val} {
  698.             return 1
  699.         } else {
  700.             return 0
  701.         }
  702.         }
  703.         }
  704.         return $val
  705.     }
  706.  
  707.     # Description format error helper
  708.     proc OptOptUsage {item {what ""}} {
  709.         return -code error "invalid description format$what: $item\n\
  710.                 should be a list of {varname|-flagname ?-type? ?defaultvalue?\
  711.                 ?helpstring?}";
  712.     }
  713.  
  714.  
  715.     # Generate a canonical form single instruction
  716.     proc OptNewInst {state varname type typeArgs help} {
  717.     list $state $varname [list 0 {}] $type $typeArgs $help;
  718.     #                          ^  ^
  719.     #                          |  |
  720.     #               hasBeenSet=+  +=currentValue
  721.     }
  722.  
  723.     # Translate one item to canonical form
  724.     proc OptNormalizeOne {item} {
  725.         set lg [Lassign $item varname arg1 arg2 arg3];
  726. #       puts "called optnormalizeone '$item' v=($varname), lg=$lg";
  727.         set isflag [OptIsFlag $varname];
  728.     set isopt  [OptIsOpt  $varname];
  729.         if {$isflag} {
  730.             set state "flags";
  731.         } elseif {$isopt} {
  732.         set state "optValue";
  733.     } elseif {[string compare $varname "args"]} {
  734.         set state "value";
  735.     } else {
  736.         set state "args";
  737.     }
  738.  
  739.     # apply 'smart' 'fuzzy' logic to try to make
  740.     # description writer's life easy, and our's difficult :
  741.     # let's guess the missing arguments :-)
  742.  
  743.         switch $lg {
  744.             1 {
  745.                 if {$isflag} {
  746.                     return [OptNewInst $state $varname boolflag false ""];
  747.                 } else {
  748.                     return [OptNewInst $state $varname any "" ""];
  749.                 }
  750.             }
  751.             2 {
  752.                 # varname default
  753.                 # varname help
  754.                 set type [OptGuessType $arg1]
  755.                 if {[string compare $type "string"] == 0} {
  756.                     if {$isflag} {
  757.             set type boolflag
  758.             set def false
  759.             } else {
  760.             set type any
  761.             set def ""
  762.             }
  763.             set help $arg1
  764.                 } else {
  765.                     set help ""
  766.                     set def $arg1
  767.                 }
  768.                 return [OptNewInst $state $varname $type $def $help];
  769.             }
  770.             3 {
  771.                 # varname type value
  772.                 # varname value comment
  773.         
  774.                 if [regexp {^-(.+)$} $arg1 x type] {
  775.             # flags/optValue as they are optional, need a "value",
  776.             # on the contrary, for a variable (non optional),
  777.                 # default value is pointless, 'cept for choices :
  778.             if {$isflag || $isopt || ($type == "choice")} {
  779.             return [OptNewInst $state $varname $type $arg2 ""];
  780.             } else {
  781.             return [OptNewInst $state $varname $type "" $arg2];
  782.             }
  783.                 } else {
  784.                     return [OptNewInst $state $varname\
  785.                 [OptGuessType $arg1] $arg1 $arg2]
  786.                 }
  787.             }
  788.             4 {
  789.                 if [regexp {^-(.+)$} $arg1 x type] {
  790.             return [OptNewInst $state $varname $type $arg2 $arg3];
  791.                 } else {
  792.                     return -code error [OptOptUsage $item];
  793.                 }
  794.             }
  795.             default {
  796.                 return -code error [OptOptUsage $item];
  797.             }
  798.         }
  799.     }
  800.  
  801.     # Auto magic lasy type determination
  802.     proc OptGuessType {arg} {
  803.         if [regexp -nocase {^(true|false)$} $arg] {
  804.             return boolean
  805.         }
  806.         if [regexp {^(-+)?[0-9]+$} $arg] {
  807.             return int
  808.         }
  809.         if ![catch {expr double($arg)}] {
  810.             return float
  811.         }
  812.         return string
  813.     }
  814.  
  815.     # Error messages front ends
  816.  
  817.     proc OptAmbigous {desc arg} {
  818.         OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc]
  819.     }
  820.     proc OptFlagUsage {desc arg} {
  821.         OptError "bad flag \"$arg\", must be one of" $desc;
  822.     }
  823.     proc OptTooManyArgs {desc arguments} {
  824.         OptError "too many arguments (unexpected argument(s): $arguments),\
  825.         usage:"\
  826.         $desc 1
  827.     }
  828.     proc OptParamType {item} {
  829.     if {[OptIsFlag $item]} {
  830.         return "flag";
  831.     } else {
  832.         return "parameter";
  833.     }
  834.     }
  835.     proc OptBadValue {item arg {err {}}} {
  836. #       puts "bad val err = \"$err\"";
  837.         OptError "bad value \"$arg\" for [OptParamType $item]"\
  838.         [list $item]
  839.     }
  840.     proc OptMissingValue {descriptions} {
  841. #        set item [OptCurDescFinal $descriptions];
  842.         set item [OptCurDesc $descriptions];
  843.         OptError "no value given for [OptParamType $item] \"[OptName $item]\"\
  844.         (use -help for full usage) :"\
  845.         [list $item]
  846.     }
  847.  
  848. proc ::tcl::OptKeyError {prefix descKey} {
  849.     OptError $prefix [OptKeyGetDesc $descKey];
  850. }
  851.  
  852.     # determine string length for nice tabulated output
  853.     proc OptLengths {desc nlName tlName dlName} {
  854.     upvar $nlName nl;
  855.     upvar $tlName tl;
  856.     upvar $dlName dl;
  857.     foreach item $desc {
  858.         if {[OptIsCounter $item]} continue;
  859.         if {[OptIsPrg $item]} {
  860.         OptLengths $item nl tl dl
  861.         } else {
  862.         SetMax nl [string length [OptName $item]]
  863.         SetMax tl [string length [OptType $item]]
  864.         set dv [OptTypeArgs $item];
  865.         if {[OptState $item] != "header"} {
  866.             set dv "($dv)";
  867.         }
  868.         set l [string length $dv];
  869.         # limit the space allocated to potentially big "choices"
  870.         if {([OptType $item] != "choice") || ($l<=12)} {
  871.             SetMax dl $l
  872.         } else {
  873.             if {![info exists dl]} {
  874.             set dl 0
  875.             }
  876.         }
  877.         }
  878.     }
  879.     }
  880.     # output the tree
  881.     proc OptTree {desc nl tl dl} {
  882.     set res "";
  883.     foreach item $desc {
  884.         if {[OptIsCounter $item]} continue;
  885.         if {[OptIsPrg $item]} {
  886.         append res [OptTree $item $nl $tl $dl];
  887.         } else {
  888.         set dv [OptTypeArgs $item];
  889.         if {[OptState $item] != "header"} {
  890.             set dv "($dv)";
  891.         }
  892.         append res [format "\n    %-*s %-*s %-*s %s" \
  893.             $nl [OptName $item] $tl [OptType $item] \
  894.             $dl $dv [OptHelp $item]]
  895.         }
  896.     }
  897.     return $res;
  898.     }
  899.  
  900. # Give nice usage string
  901. proc ::tcl::OptError {prefix desc {header 0}} {
  902.     # determine length
  903.     if {$header} {
  904.     # add faked instruction
  905.     set h [list [OptNewInst header Var/FlagName Type Value Help]];
  906.     lappend h   [OptNewInst header ------------ ---- ----- ----];
  907.     lappend h   [OptNewInst header {( -help} "" "" {gives this help )}]
  908.     set desc [concat $h $desc]
  909.     }
  910.     OptLengths $desc nl tl dl
  911.     # actually output 
  912.     return "$prefix[OptTree $desc $nl $tl $dl]"
  913. }
  914.  
  915.  
  916. ################     General Utility functions   #######################
  917.  
  918. #
  919. # List utility functions
  920. # Naming convention:
  921. #     "Lvarxxx" take the list VARiable name as argument
  922. #     "Lxxxx"   take the list value as argument
  923. #               (which is not costly with Tcl8 objects system
  924. #                as it's still a reference and not a copy of the values)
  925. #
  926.  
  927. # Is that list empty ?
  928. proc ::tcl::Lempty {list} {
  929.     expr {[llength $list]==0}
  930. }
  931.  
  932. # Gets the value of one leaf of a lists tree
  933. proc ::tcl::Lget {list indexLst} {
  934.     if {[llength $indexLst] <= 1} {
  935.         return [lindex $list $indexLst];
  936.     }
  937.     Lget [lindex $list [Lfirst $indexLst]] [Lrest $indexLst];
  938. }
  939. # Sets the value of one leaf of a lists tree
  940. # (we use the version that does not create the elements because
  941. #  it would be even slower... needs to be written in C !)
  942. # (nb: there is a non trivial recursive problem with indexes 0,
  943. #  which appear because there is no difference between a list
  944. #  of 1 element and 1 element alone : [list "a"] == "a" while 
  945. #  it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1
  946. #  and [listp "a b"] maybe 0. listp does not exist either...)
  947. proc ::tcl::Lvarset {listName indexLst newValue} {
  948.     upvar $listName list;
  949.     if {[llength $indexLst] <= 1} {
  950.         Lvarset1nc list $indexLst $newValue;
  951.     } else {
  952.         set idx [Lfirst $indexLst];
  953.         set targetList [lindex $list $idx];
  954.         # reduce refcount on targetList (not really usefull now,
  955.     # could be with optimizing compiler)
  956. #        Lvarset1 list $idx {};
  957.         # recursively replace in targetList
  958.         Lvarset targetList [Lrest $indexLst] $newValue;
  959.         # put updated sub list back in the tree
  960.         Lvarset1nc list $idx $targetList;
  961.     }
  962. }
  963. # Set one cell to a value, eventually create all the needed elements
  964. # (on level-1 of lists)
  965. variable emptyList {}
  966. proc ::tcl::Lvarset1 {listName index newValue} {
  967.     upvar $listName list;
  968.     if {$index < 0} {return -code error "invalid negative index"}
  969.     set lg [llength $list];
  970.     if {$index >= $lg} {
  971.         variable emptyList;
  972.         for {set i $lg} {$i<$index} {incr i} {
  973.             lappend list $emptyList;
  974.         }
  975.         lappend list $newValue;
  976.     } else {
  977.         set list [lreplace $list $index $index $newValue];
  978.     }
  979. }
  980. # same as Lvarset1 but no bound checking / creation
  981. proc ::tcl::Lvarset1nc {listName index newValue} {
  982.     upvar $listName list;
  983.     set list [lreplace $list $index $index $newValue];
  984. }
  985. # Increments the value of one leaf of a lists tree
  986. # (which must exists)
  987. proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} {
  988.     upvar $listName list;
  989.     if {[llength $indexLst] <= 1} {
  990.         Lvarincr1 list $indexLst $howMuch;
  991.     } else {
  992.         set idx [Lfirst $indexLst];
  993.         set targetList [lindex $list $idx];
  994.         # reduce refcount on targetList
  995.         Lvarset1nc list $idx {};
  996.         # recursively replace in targetList
  997.         Lvarincr targetList [Lrest $indexLst] $howMuch;
  998.         # put updated sub list back in the tree
  999.         Lvarset1nc list $idx $targetList;
  1000.     }
  1001. }
  1002. # Increments the value of one cell of a list
  1003. proc ::tcl::Lvarincr1 {listName index {howMuch 1}} {
  1004.     upvar $listName list;
  1005.     set newValue [expr [lindex $list $index]+$howMuch];
  1006.     set list [lreplace $list $index $index $newValue];
  1007.     return $newValue;
  1008. }
  1009. # Returns the first element of a list
  1010. proc ::tcl::Lfirst {list} {
  1011.     lindex $list 0
  1012. }
  1013. # Returns the rest of the list minus first element
  1014. proc ::tcl::Lrest {list} {
  1015.     lrange $list 1 end
  1016. }
  1017. # Removes the first element of a list
  1018. proc ::tcl::Lvarpop {listName} {
  1019.     upvar $listName list;
  1020.     set list [lrange $list 1 end];
  1021. }
  1022. # Same but returns the removed element
  1023. proc ::tcl::Lvarpop2 {listName} {
  1024.     upvar $listName list;
  1025.     set el [Lfirst $list];
  1026.     set list [lrange $list 1 end];
  1027.     return $el;
  1028. }
  1029. # Assign list elements to variables and return the length of the list
  1030. proc ::tcl::Lassign {list args} {
  1031.     # faster than direct blown foreach (which does not byte compile)
  1032.     set i 0;
  1033.     set lg [llength $list];
  1034.     foreach vname $args {
  1035.         if {$i>=$lg} break
  1036.         uplevel [list set $vname [lindex $list $i]];
  1037.         incr i;
  1038.     }
  1039.     return $lg;
  1040. }
  1041.  
  1042. # Misc utilities
  1043.  
  1044. # Set the varname to value if value is greater than varname's current value
  1045. # or if varname is undefined
  1046. proc ::tcl::SetMax {varname value} {
  1047.     upvar 1 $varname var
  1048.     if {![info exists var] || $value > $var} {
  1049.         set var $value
  1050.     }
  1051. }
  1052.  
  1053. # Set the varname to value if value is smaller than varname's current value
  1054. # or if varname is undefined
  1055. proc ::tcl::SetMin {varname value} {
  1056.     upvar 1 $varname var
  1057.     if {![info exists var] || $value < $var} {
  1058.         set var $value
  1059.     }
  1060. }
  1061.  
  1062.  
  1063.     # everything loaded fine, lets create the test proc:
  1064.     OptCreateTestProc
  1065.     # Don't need the create temp proc anymore:
  1066.     rename OptCreateTestProc {}
  1067. }
  1068.