OpenCores
URL https://opencores.org/ocsvn/or1k_old/or1k_old/trunk

Subversion Repositories or1k_old

[/] [or1k_old/] [trunk/] [insight/] [tcl/] [library/] [opt0.1/] [optparse.tcl] - Blame information for rev 1782

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 578 markom
# 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: optparse.tcl,v 1.1.1.1 2002-01-16 10:25:30 markom Exp $
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
 
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
 
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
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.