OSDN Git Service

Initial revision
[pf3gnuchains/sourceware.git] / tcl / library / opt0.1 / optparse.tcl
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 # RCS: @(#) $Id$
17
18 package provide opt 0.3
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 \f
77 # Hmmm... ok, you really want to know ?
78 \f
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
170     set state {};
171
172     # flag used to detect that we just have a single (flags set) subprogram.
173     set empty 1;
174
175     foreach item $desc {
176         if {$state == "args"} {
177             # more items after 'args'...
178             return -code error "'args' special argument must be the last one";
179         }
180         set res [OptNormalizeOne $item];
181         set state [Lfirst $res];
182         if {$inflags} {
183             if {$state == "flags"} {
184                 # add to 'subprogram'
185                 lappend flagsprg $res;
186             } else {
187                 # put in the flags
188                 # structure for flag programs items is a list of
189                 # {subprgcounter {prg flag 1} {prg flag 2} {...}}
190                 lappend program $flagsprg;
191                 # put the other regular stuff
192                 lappend program $res;
193                 set inflags 0;
194                 set empty 0;
195             }
196         } else {
197            if {$state == "flags"} {
198                set inflags 1;
199                # sub program counter + first sub program
200                set flagsprg [list [list "P" 1] $res];
201            } else {
202                lappend program $res;
203                set empty 0;
204            }
205        }
206    }
207    if {$inflags} {
208        if {$empty} {
209            # We just have the subprogram, optimize and remove
210            # unneeded level:
211            set program $flagsprg;
212        } else {
213            lappend program $flagsprg;
214        }
215    }
216
217    set OptDesc($key) $program;
218
219    return $key;
220 }
221
222 #
223 # Free the storage for that given key
224 #
225 proc ::tcl::OptKeyDelete {key} {
226     variable OptDesc;
227     unset OptDesc($key);
228 }
229
230     # Get the parsed description stored under the given key.
231     proc OptKeyGetDesc {descKey} {
232         variable OptDesc;
233         if {![info exists OptDesc($descKey)]} {
234             return -code error "Unknown option description key \"$descKey\"";
235         }
236         set OptDesc($descKey);
237     }
238
239 # Parse entry point for ppl who don't want to register with a key,
240 # for instance because the description changes dynamically.
241 #  (otherwise one should really use OptKeyRegister once + OptKeyParse
242 #   as it is way faster or simply OptProc which does it all)
243 # Assign a temporary key, call OptKeyParse and then free the storage
244 proc ::tcl::OptParse {desc arglist} {
245     set tempkey [OptKeyRegister $desc];
246     set ret [catch {uplevel [list ::tcl::OptKeyParse $tempkey $arglist]} res];
247     OptKeyDelete $tempkey;
248     return -code $ret $res;
249 }
250
251 # Helper function, replacement for proc that both
252 # register the description under a key which is the name of the proc
253 # (and thus unique to that code)
254 # and add a first line to the code to call the OptKeyParse proc
255 # Stores the list of variables that have been actually given by the user
256 # (the other will be sets to their default value)
257 # into local variable named "Args".
258 proc ::tcl::OptProc {name desc body} {
259     set namespace [uplevel namespace current];
260     if {   ([string match $name "::*"]) 
261         || ([string compare $namespace "::"]==0)} {
262         # absolute name or global namespace, name is the key
263         set key $name;
264     } else {
265         # we are relative to some non top level namespace:
266         set key "${namespace}::${name}";
267     }
268     OptKeyRegister $desc $key;
269     uplevel [list proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"];
270     return $key;
271 }
272 # Check that a argument has been given
273 # assumes that "OptProc" has been used as it will check in "Args" list
274 proc ::tcl::OptProcArgGiven {argname} {
275     upvar Args alist;
276     expr {[lsearch $alist $argname] >=0}
277 }
278
279     #######
280     # Programs/Descriptions manipulation
281
282     # Return the instruction word/list of a given step/(sub)program
283     proc OptInstr {lst} {
284         Lfirst $lst;
285     }
286     # Is a (sub) program or a plain instruction ?
287     proc OptIsPrg {lst} {
288         expr {[llength [OptInstr $lst]]>=2}
289     }
290     # Is this instruction a program counter or a real instr
291     proc OptIsCounter {item} {
292         expr {[Lfirst $item]=="P"}
293     }
294     # Current program counter (2nd word of first word)
295     proc OptGetPrgCounter {lst} {
296         Lget $lst {0 1}
297     }
298     # Current program counter (2nd word of first word)
299     proc OptSetPrgCounter {lstName newValue} {
300         upvar $lstName lst;
301         set lst [lreplace $lst 0 0 [concat "P" $newValue]];
302     }
303     # returns a list of currently selected items.
304     proc OptSelection {lst} {
305         set res {};
306         foreach idx [lrange [Lfirst $lst] 1 end] {
307             lappend res [Lget $lst $idx];
308         }
309         return $res;
310     }
311
312     # Advance to next description
313     proc OptNextDesc {descName} {
314         uplevel [list Lvarincr $descName {0 1}];
315     }
316
317     # Get the current description, eventually descend
318     proc OptCurDesc {descriptions} {
319         lindex $descriptions [OptGetPrgCounter $descriptions];
320     }
321     # get the current description, eventually descend
322     # through sub programs as needed.
323     proc OptCurDescFinal {descriptions} {
324         set item [OptCurDesc $descriptions];
325         # Descend untill we get the actual item and not a sub program
326         while {[OptIsPrg $item]} {
327             set item [OptCurDesc $item];
328         }
329         return $item;
330     }
331     # Current final instruction adress
332     proc OptCurAddr {descriptions {start {}}} {
333         set adress [OptGetPrgCounter $descriptions];
334         lappend start $adress;
335         set item [lindex $descriptions $adress];
336         if {[OptIsPrg $item]} {
337             return [OptCurAddr $item $start];
338         } else {
339             return $start;
340         }
341     }
342     # Set the value field of the current instruction
343     proc OptCurSetValue {descriptionsName value} {
344         upvar $descriptionsName descriptions
345         # get the current item full adress
346         set adress [OptCurAddr $descriptions];
347         # use the 3th field of the item  (see OptValue / OptNewInst)
348         lappend adress 2
349         Lvarset descriptions $adress [list 1 $value];
350         #                                  ^hasBeenSet flag
351     }
352
353     # empty state means done/paste the end of the program
354     proc OptState {item} {
355         Lfirst $item
356     }
357     
358     # current state
359     proc OptCurState {descriptions} {
360         OptState [OptCurDesc $descriptions];
361     }
362
363     #######
364     # Arguments manipulation
365
366     # Returns the argument that has to be processed now
367     proc OptCurrentArg {lst} {
368         Lfirst $lst;
369     }
370     # Advance to next argument
371     proc OptNextArg {argsName} {
372         uplevel [list Lvarpop $argsName];
373     }
374     #######
375
376
377
378
379
380     # Loop over all descriptions, calling OptDoOne which will
381     # eventually eat all the arguments.
382     proc OptDoAll {descriptionsName argumentsName} {
383         upvar $descriptionsName descriptions
384         upvar $argumentsName arguments;
385 #       puts "entered DoAll";
386         # Nb: the places where "state" can be set are tricky to figure
387         #     because DoOne sets the state to flagsValue and return -continue
388         #     when needed...
389         set state [OptCurState $descriptions];
390         # We'll exit the loop in "OptDoOne" or when state is empty.
391         while 1 {
392             set curitem [OptCurDesc $descriptions];
393             # Do subprograms if needed, call ourselves on the sub branch
394             while {[OptIsPrg $curitem]} {
395                 OptDoAll curitem arguments
396 #               puts "done DoAll sub";
397                 # Insert back the results in current tree;
398                 Lvarset1nc descriptions [OptGetPrgCounter $descriptions]\
399                         $curitem;
400                 OptNextDesc descriptions;
401                 set curitem [OptCurDesc $descriptions];
402                 set state [OptCurState $descriptions];
403             }
404 #           puts "state = \"$state\" - arguments=($arguments)";
405             if {[Lempty $state]} {
406                 # Nothing left to do, we are done in this branch:
407                 break;
408             }
409             # The following statement can make us terminate/continue
410             # as it use return -code {break, continue, return and error}
411             # codes
412             OptDoOne descriptions state arguments;
413             # If we are here, no special return code where issued,
414             # we'll step to next instruction :
415 #           puts "new state  = \"$state\"";
416             OptNextDesc descriptions;
417             set state [OptCurState $descriptions];
418         }
419     }
420
421     # Process one step for the state machine,
422     # eventually consuming the current argument.
423     proc OptDoOne {descriptionsName stateName argumentsName} {
424         upvar $argumentsName arguments;
425         upvar $descriptionsName descriptions;
426         upvar $stateName state;
427
428         # the special state/instruction "args" eats all
429         # the remaining args (if any)
430         if {($state == "args")} {
431             if {![Lempty $arguments]} {
432                 # If there is no additional arguments, leave the default value
433                 # in.
434                 OptCurSetValue descriptions $arguments;
435                 set arguments {};
436             }
437 #            puts "breaking out ('args' state: consuming every reminding args)"
438             return -code break;
439         }
440
441         if {[Lempty $arguments]} {
442             if {$state == "flags"} {
443                 # no argument and no flags : we're done
444 #                puts "returning to previous (sub)prg (no more args)";
445                 return -code return;
446             } elseif {$state == "optValue"} {
447                 set state next; # not used, for debug only
448                 # go to next state
449                 return ;
450             } else {
451                 return -code error [OptMissingValue $descriptions];
452             }
453         } else {
454             set arg [OptCurrentArg $arguments];
455         }
456
457         switch $state {
458             flags {
459                 # A non-dash argument terminates the options, as does --
460
461                 # Still a flag ?
462                 if {![OptIsFlag $arg]} {
463                     # don't consume the argument, return to previous prg
464                     return -code return;
465                 }
466                 # consume the flag
467                 OptNextArg arguments;
468                 if {[string compare "--" $arg] == 0} {
469                     # return from 'flags' state
470                     return -code return;
471                 }
472
473                 set hits [OptHits descriptions $arg];
474                 if {$hits > 1} {
475                     return -code error [OptAmbigous $descriptions $arg]
476                 } elseif {$hits == 0} {
477                     return -code error [OptFlagUsage $descriptions $arg]
478                 }
479                 set item [OptCurDesc $descriptions];
480                 if {[OptNeedValue $item]} {
481                     # we need a value, next state is
482                     set state flagValue;
483                 } else {
484                     OptCurSetValue descriptions 1;
485                 }
486                 # continue
487                 return -code continue;
488             }
489             flagValue -
490             value {
491                 set item [OptCurDesc $descriptions];
492                 # Test the values against their required type
493                 if {[catch {OptCheckType $arg\
494                         [OptType $item] [OptTypeArgs $item]} val]} {
495                     return -code error [OptBadValue $item $arg $val]
496                 }
497                 # consume the value
498                 OptNextArg arguments;
499                 # set the value
500                 OptCurSetValue descriptions $val;
501                 # go to next state
502                 if {$state == "flagValue"} {
503                     set state flags
504                     return -code continue;
505                 } else {
506                     set state next; # not used, for debug only
507                     return ; # will go on next step
508                 }
509             }
510             optValue {
511                 set item [OptCurDesc $descriptions];
512                 # Test the values against their required type
513                 if {![catch {OptCheckType $arg\
514                         [OptType $item] [OptTypeArgs $item]} val]} {
515                     # right type, so :
516                     # consume the value
517                     OptNextArg arguments;
518                     # set the value
519                     OptCurSetValue descriptions $val;
520                 }
521                 # go to next state
522                 set state next; # not used, for debug only
523                 return ; # will go on next step
524             }
525         }
526         # If we reach this point: an unknown
527         # state as been entered !
528         return -code error "Bug! unknown state in DoOne \"$state\"\
529                 (prg counter [OptGetPrgCounter $descriptions]:\
530                         [OptCurDesc $descriptions])";
531     }
532
533 # Parse the options given the key to previously registered description
534 # and arguments list
535 proc ::tcl::OptKeyParse {descKey arglist} {
536
537     set desc [OptKeyGetDesc $descKey];
538
539     # make sure -help always give usage
540     if {[string compare "-help" [string tolower $arglist]] == 0} {
541         return -code error [OptError "Usage information:" $desc 1];
542     }
543
544     OptDoAll desc arglist;
545
546     if {![Lempty $arglist]} {
547         return -code error [OptTooManyArgs $desc $arglist];
548     }
549     
550     # Analyse the result
551     # Walk through the tree:
552     OptTreeVars $desc "#[expr {[info level]-1}]" ;
553 }
554
555     # determine string length for nice tabulated output
556     proc OptTreeVars {desc level {vnamesLst {}}} {
557         foreach item $desc {
558             if {[OptIsCounter $item]} continue;
559             if {[OptIsPrg $item]} {
560                 set vnamesLst [OptTreeVars $item $level $vnamesLst];
561             } else {
562                 set vname [OptVarName $item];
563                 upvar $level $vname var
564                 if {[OptHasBeenSet $item]} {
565 #                   puts "adding $vname"
566                     # lets use the input name for the returned list
567                     # it is more usefull, for instance you can check that
568                     # no flags at all was given with expr
569                     # {![string match "*-*" $Args]}
570                     lappend vnamesLst [OptName $item];
571                     set var [OptValue $item];
572                 } else {
573                     set var [OptDefaultValue $item];
574                 }
575             }
576         }
577         return $vnamesLst
578     }
579
580
581 # Check the type of a value
582 # and emit an error if arg is not of the correct type
583 # otherwise returns the canonical value of that arg (ie 0/1 for booleans)
584 proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
585 #    puts "checking '$arg' against '$type' ($typeArgs)";
586
587     # only types "any", "choice", and numbers can have leading "-"
588
589     switch -exact -- $type {
590         int {
591             if {![regexp {^(-+)?[0-9]+$} $arg]} {
592                 error "not an integer"
593             }
594             return $arg;
595         }
596         float {
597             return [expr {double($arg)}]
598         }
599         script -
600         list {
601             # if llength fail : malformed list
602             if {[llength $arg]==0} {
603                 if {[OptIsFlag $arg]} {
604                     error "no values with leading -"
605                 }
606             }
607             return $arg;
608         }
609         boolean {
610             if {![regexp -nocase {^(true|false|0|1)$} $arg]} {
611                 error "non canonic boolean"
612             }
613             # convert true/false because expr/if is broken with "!,...
614             if {$arg} {
615                 return 1
616             } else {
617                 return 0
618             }
619         }
620         choice {
621             if {[lsearch -exact $typeArgs $arg] < 0} {
622                 error "invalid choice"
623             }
624             return $arg;
625         }
626         any {
627             return $arg;
628         }
629         string -
630         default {
631             if {[OptIsFlag $arg]} {
632                 error "no values with leading -"
633             }
634             return $arg
635         }
636     }
637     return neverReached;
638 }
639
640     # internal utilities
641
642     # returns the number of flags matching the given arg
643     # sets the (local) prg counter to the list of matches
644     proc OptHits {descName arg} {
645         upvar $descName desc;
646         set hits 0
647         set hitems {}
648         set i 1;
649
650         set larg [string tolower $arg];
651         set len  [string length $larg];
652         set last [expr {$len-1}];
653
654         foreach item [lrange $desc 1 end] {
655             set flag [OptName $item]
656             # lets try to match case insensitively
657             # (string length ought to be cheap)
658             set lflag [string tolower $flag];
659             if {$len == [string length $lflag]} {
660                 if {[string compare $larg $lflag]==0} {
661                     # Exact match case
662                     OptSetPrgCounter desc $i;
663                     return 1;
664                 }
665             } else {
666                 if {[string compare $larg [string range $lflag 0 $last]]==0} {
667                     lappend hitems $i;
668                     incr hits;
669                 }
670             }
671             incr i;
672         }
673         if {$hits} {
674             OptSetPrgCounter desc $hitems;
675         }
676         return $hits
677     }
678
679     # Extract fields from the list structure:
680
681     proc OptName {item} {
682         lindex $item 1;
683     }
684     # 
685     proc OptHasBeenSet {item} {
686         Lget $item {2 0};
687     }
688     # 
689     proc OptValue {item} {
690         Lget $item {2 1};
691     }
692
693     proc OptIsFlag {name} {
694         string match "-*" $name;
695     }
696     proc OptIsOpt {name} {
697         string match {\?*} $name;
698     }
699     proc OptVarName {item} {
700         set name [OptName $item];
701         if {[OptIsFlag $name]} {
702             return [string range $name 1 end];
703         } elseif {[OptIsOpt $name]} {
704             return [string trim $name "?"];
705         } else {
706             return $name;
707         }
708     }
709     proc OptType {item} {
710         lindex $item 3
711     }
712     proc OptTypeArgs {item} {
713         lindex $item 4
714     }
715     proc OptHelp {item} {
716         lindex $item 5
717     }
718     proc OptNeedValue {item} {
719         string compare [OptType $item] boolflag
720     }
721     proc OptDefaultValue {item} {
722         set val [OptTypeArgs $item]
723         switch -exact -- [OptType $item] {
724             choice {return [lindex $val 0]}
725             boolean -
726             boolflag {
727                 # convert back false/true to 0/1 because expr !$bool
728                 # is broken..
729                 if {$val} {
730                     return 1
731                 } else {
732                     return 0
733                 }
734             }
735         }
736         return $val
737     }
738
739     # Description format error helper
740     proc OptOptUsage {item {what ""}} {
741         return -code error "invalid description format$what: $item\n\
742                 should be a list of {varname|-flagname ?-type? ?defaultvalue?\
743                 ?helpstring?}";
744     }
745
746
747     # Generate a canonical form single instruction
748     proc OptNewInst {state varname type typeArgs help} {
749         list $state $varname [list 0 {}] $type $typeArgs $help;
750         #                          ^  ^
751         #                          |  |
752         #               hasBeenSet=+  +=currentValue
753     }
754
755     # Translate one item to canonical form
756     proc OptNormalizeOne {item} {
757         set lg [Lassign $item varname arg1 arg2 arg3];
758 #       puts "called optnormalizeone '$item' v=($varname), lg=$lg";
759         set isflag [OptIsFlag $varname];
760         set isopt  [OptIsOpt  $varname];
761         if {$isflag} {
762             set state "flags";
763         } elseif {$isopt} {
764             set state "optValue";
765         } elseif {[string compare $varname "args"]} {
766             set state "value";
767         } else {
768             set state "args";
769         }
770
771         # apply 'smart' 'fuzzy' logic to try to make
772         # description writer's life easy, and our's difficult :
773         # let's guess the missing arguments :-)
774
775         switch $lg {
776             1 {
777                 if {$isflag} {
778                     return [OptNewInst $state $varname boolflag false ""];
779                 } else {
780                     return [OptNewInst $state $varname any "" ""];
781                 }
782             }
783             2 {
784                 # varname default
785                 # varname help
786                 set type [OptGuessType $arg1]
787                 if {[string compare $type "string"] == 0} {
788                     if {$isflag} {
789                         set type boolflag
790                         set def false
791                     } else {
792                         set type any
793                         set def ""
794                     }
795                     set help $arg1
796                 } else {
797                     set help ""
798                     set def $arg1
799                 }
800                 return [OptNewInst $state $varname $type $def $help];
801             }
802             3 {
803                 # varname type value
804                 # varname value comment
805                 
806                 if {[regexp {^-(.+)$} $arg1 x type]} {
807                     # flags/optValue as they are optional, need a "value",
808                     # on the contrary, for a variable (non optional),
809                     # default value is pointless, 'cept for choices :
810                     if {$isflag || $isopt || ($type == "choice")} {
811                         return [OptNewInst $state $varname $type $arg2 ""];
812                     } else {
813                         return [OptNewInst $state $varname $type "" $arg2];
814                     }
815                 } else {
816                     return [OptNewInst $state $varname\
817                             [OptGuessType $arg1] $arg1 $arg2]
818                 }
819             }
820             4 {
821                 if {[regexp {^-(.+)$} $arg1 x type]} {
822                     return [OptNewInst $state $varname $type $arg2 $arg3];
823                 } else {
824                     return -code error [OptOptUsage $item];
825                 }
826             }
827             default {
828                 return -code error [OptOptUsage $item];
829             }
830         }
831     }
832
833     # Auto magic lasy type determination
834     proc OptGuessType {arg} {
835         if {[regexp -nocase {^(true|false)$} $arg]} {
836             return boolean
837         }
838         if {[regexp {^(-+)?[0-9]+$} $arg]} {
839             return int
840         }
841         if {![catch {expr {double($arg)}}]} {
842             return float
843         }
844         return string
845     }
846
847     # Error messages front ends
848
849     proc OptAmbigous {desc arg} {
850         OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc]
851     }
852     proc OptFlagUsage {desc arg} {
853         OptError "bad flag \"$arg\", must be one of" $desc;
854     }
855     proc OptTooManyArgs {desc arguments} {
856         OptError "too many arguments (unexpected argument(s): $arguments),\
857                 usage:"\
858                 $desc 1
859     }
860     proc OptParamType {item} {
861         if {[OptIsFlag $item]} {
862             return "flag";
863         } else {
864             return "parameter";
865         }
866     }
867     proc OptBadValue {item arg {err {}}} {
868 #       puts "bad val err = \"$err\"";
869         OptError "bad value \"$arg\" for [OptParamType $item]"\
870                 [list $item]
871     }
872     proc OptMissingValue {descriptions} {
873 #        set item [OptCurDescFinal $descriptions];
874         set item [OptCurDesc $descriptions];
875         OptError "no value given for [OptParamType $item] \"[OptName $item]\"\
876                 (use -help for full usage) :"\
877                 [list $item]
878     }
879
880 proc ::tcl::OptKeyError {prefix descKey {header 0}} {
881     OptError $prefix [OptKeyGetDesc $descKey] $header;
882 }
883
884     # determine string length for nice tabulated output
885     proc OptLengths {desc nlName tlName dlName} {
886         upvar $nlName nl;
887         upvar $tlName tl;
888         upvar $dlName dl;
889         foreach item $desc {
890             if {[OptIsCounter $item]} continue;
891             if {[OptIsPrg $item]} {
892                 OptLengths $item nl tl dl
893             } else {
894                 SetMax nl [string length [OptName $item]]
895                 SetMax tl [string length [OptType $item]]
896                 set dv [OptTypeArgs $item];
897                 if {[OptState $item] != "header"} {
898                     set dv "($dv)";
899                 }
900                 set l [string length $dv];
901                 # limit the space allocated to potentially big "choices"
902                 if {([OptType $item] != "choice") || ($l<=12)} {
903                     SetMax dl $l
904                 } else {
905                     if {![info exists dl]} {
906                         set dl 0
907                     }
908                 }
909             }
910         }
911     }
912     # output the tree
913     proc OptTree {desc nl tl dl} {
914         set res "";
915         foreach item $desc {
916             if {[OptIsCounter $item]} continue;
917             if {[OptIsPrg $item]} {
918                 append res [OptTree $item $nl $tl $dl];
919             } else {
920                 set dv [OptTypeArgs $item];
921                 if {[OptState $item] != "header"} {
922                     set dv "($dv)";
923                 }
924                 append res [format "\n    %-*s %-*s %-*s %s" \
925                         $nl [OptName $item] $tl [OptType $item] \
926                         $dl $dv [OptHelp $item]]
927             }
928         }
929         return $res;
930     }
931
932 # Give nice usage string
933 proc ::tcl::OptError {prefix desc {header 0}} {
934     # determine length
935     if {$header} {
936         # add faked instruction
937         set h [list [OptNewInst header Var/FlagName Type Value Help]];
938         lappend h   [OptNewInst header ------------ ---- ----- ----];
939         lappend h   [OptNewInst header {( -help} "" "" {gives this help )}]
940         set desc [concat $h $desc]
941     }
942     OptLengths $desc nl tl dl
943     # actually output 
944     return "$prefix[OptTree $desc $nl $tl $dl]"
945 }
946
947
948 ################     General Utility functions   #######################
949
950 #
951 # List utility functions
952 # Naming convention:
953 #     "Lvarxxx" take the list VARiable name as argument
954 #     "Lxxxx"   take the list value as argument
955 #               (which is not costly with Tcl8 objects system
956 #                as it's still a reference and not a copy of the values)
957 #
958
959 # Is that list empty ?
960 proc ::tcl::Lempty {list} {
961     expr {[llength $list]==0}
962 }
963
964 # Gets the value of one leaf of a lists tree
965 proc ::tcl::Lget {list indexLst} {
966     if {[llength $indexLst] <= 1} {
967         return [lindex $list $indexLst];
968     }
969     Lget [lindex $list [Lfirst $indexLst]] [Lrest $indexLst];
970 }
971 # Sets the value of one leaf of a lists tree
972 # (we use the version that does not create the elements because
973 #  it would be even slower... needs to be written in C !)
974 # (nb: there is a non trivial recursive problem with indexes 0,
975 #  which appear because there is no difference between a list
976 #  of 1 element and 1 element alone : [list "a"] == "a" while 
977 #  it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1
978 #  and [listp "a b"] maybe 0. listp does not exist either...)
979 proc ::tcl::Lvarset {listName indexLst newValue} {
980     upvar $listName list;
981     if {[llength $indexLst] <= 1} {
982         Lvarset1nc list $indexLst $newValue;
983     } else {
984         set idx [Lfirst $indexLst];
985         set targetList [lindex $list $idx];
986         # reduce refcount on targetList (not really usefull now,
987         # could be with optimizing compiler)
988 #        Lvarset1 list $idx {};
989         # recursively replace in targetList
990         Lvarset targetList [Lrest $indexLst] $newValue;
991         # put updated sub list back in the tree
992         Lvarset1nc list $idx $targetList;
993     }
994 }
995 # Set one cell to a value, eventually create all the needed elements
996 # (on level-1 of lists)
997 variable emptyList {}
998 proc ::tcl::Lvarset1 {listName index newValue} {
999     upvar $listName list;
1000     if {$index < 0} {return -code error "invalid negative index"}
1001     set lg [llength $list];
1002     if {$index >= $lg} {
1003         variable emptyList;
1004         for {set i $lg} {$i<$index} {incr i} {
1005             lappend list $emptyList;
1006         }
1007         lappend list $newValue;
1008     } else {
1009         set list [lreplace $list $index $index $newValue];
1010     }
1011 }
1012 # same as Lvarset1 but no bound checking / creation
1013 proc ::tcl::Lvarset1nc {listName index newValue} {
1014     upvar $listName list;
1015     set list [lreplace $list $index $index $newValue];
1016 }
1017 # Increments the value of one leaf of a lists tree
1018 # (which must exists)
1019 proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} {
1020     upvar $listName list;
1021     if {[llength $indexLst] <= 1} {
1022         Lvarincr1 list $indexLst $howMuch;
1023     } else {
1024         set idx [Lfirst $indexLst];
1025         set targetList [lindex $list $idx];
1026         # reduce refcount on targetList
1027         Lvarset1nc list $idx {};
1028         # recursively replace in targetList
1029         Lvarincr targetList [Lrest $indexLst] $howMuch;
1030         # put updated sub list back in the tree
1031         Lvarset1nc list $idx $targetList;
1032     }
1033 }
1034 # Increments the value of one cell of a list
1035 proc ::tcl::Lvarincr1 {listName index {howMuch 1}} {
1036     upvar $listName list;
1037     set newValue [expr {[lindex $list $index]+$howMuch}];
1038     set list [lreplace $list $index $index $newValue];
1039     return $newValue;
1040 }
1041 # Returns the first element of a list
1042 proc ::tcl::Lfirst {list} {
1043     lindex $list 0
1044 }
1045 # Returns the rest of the list minus first element
1046 proc ::tcl::Lrest {list} {
1047     lrange $list 1 end
1048 }
1049 # Removes the first element of a list
1050 proc ::tcl::Lvarpop {listName} {
1051     upvar $listName list;
1052     set list [lrange $list 1 end];
1053 }
1054 # Same but returns the removed element
1055 proc ::tcl::Lvarpop2 {listName} {
1056     upvar $listName list;
1057     set el [Lfirst $list];
1058     set list [lrange $list 1 end];
1059     return $el;
1060 }
1061 # Assign list elements to variables and return the length of the list
1062 proc ::tcl::Lassign {list args} {
1063     # faster than direct blown foreach (which does not byte compile)
1064     set i 0;
1065     set lg [llength $list];
1066     foreach vname $args {
1067         if {$i>=$lg} break
1068         uplevel [list set $vname [lindex $list $i]];
1069         incr i;
1070     }
1071     return $lg;
1072 }
1073
1074 # Misc utilities
1075
1076 # Set the varname to value if value is greater than varname's current value
1077 # or if varname is undefined
1078 proc ::tcl::SetMax {varname value} {
1079     upvar 1 $varname var
1080     if {![info exists var] || $value > $var} {
1081         set var $value
1082     }
1083 }
1084
1085 # Set the varname to value if value is smaller than varname's current value
1086 # or if varname is undefined
1087 proc ::tcl::SetMin {varname value} {
1088     upvar 1 $varname var
1089     if {![info exists var] || $value < $var} {
1090         set var $value
1091     }
1092 }
1093
1094
1095     # everything loaded fine, lets create the test proc:
1096     OptCreateTestProc
1097     # Don't need the create temp proc anymore:
1098     rename OptCreateTestProc {}
1099 }