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

Subversion Repositories c0or1k

[/] [c0or1k/] [trunk/] [tools/] [cml2-tools/] [cmlsystem.py] - Blame information for rev 2

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 2 drasko
"""
2
cmlsystem.py -- CML2 configurator front-end support
3
 
4
by Eric S. Raymond, <esr@thyrsus.com>
5
 
6
Mode-independent code for front ends.  This supplies one big class that you
7
initialize by loading a compiled rulebase.  The class provides functions for
8
doing tests and manipulations on the rulebase, and for writing out
9
configuration files.
10
"""
11
 
12
import os, sys, re
13
import cml
14
 
15
_eng = {
16
    "ABOUT":"About to write %s=%s (type %s)",
17
    "BADEQUALS":"bad token `%s' while expecting '='.",
18
    "BADVERSION":"Compiler/configurator version mismatch (%s/%s), recompile the rulebase please.\n",
19
    "BADTOKEN":"unrecognized name `%s' while expecting known symbol.",
20
    "BADTRIT":"Boolean symbol %s cannot have value m.",
21
    "BINDING":"    %sBinding from constraint: %s=%s (source %s)",
22
    "CHECKDONE":"    ...menu %s check done",
23
    "CHECKING":"    Checking menu %s...",
24
    "COMMIT":"    Committing new bindings.",
25
    "CONSTRAINT":"    Value %s for %s failed constraint %s",
26
    "DERIVED":"Cannot set derived symbol %s",
27
    "EXCLUDED":"    %s value %s excluded by %s",
28
    "FAILREQ":"    Failed constraint %s",
29
    #"FORBIDDEN":"%s=%s would violate %s",
30
    "FROZEN":" (frozen)",
31
    "HELPFLAG":"Help-required flag is %s",
32
    "INCONST":"Symbol %s forced to n during recovery attempt.\n",
33
    "INVISANC":"    %s not visible, ancestor %s false",
34
    "INVISANC2":"    %s not visible, ancestor %s invisible",
35
    "INVISHELP":"    %s not visible, no help",
36
    "INVISME":"    %s not visible, %s guard %s is false",
37
    "INVISSTART":"    is_visible(%s) called",
38
    "INVISUP":"    %s not visible, upward visibility",
39
    "MODULESN":"All M-valued symbols will be forced to Y.",
40
    "MODULESM":"All tristate symbols will default to M.",
41
    "MODULESY":"Tristate symbols won't default to M.",
42
    "NOHELP":"    %s not visible, it has no help",
43
    "NOTSAVED": "(not saveable) ",
44
    "NOVISIBLE":"No visible items at %s",
45
    "OLDVAL": "    %sOld value of %s is %s",
46
    "RADIOINVIS":"    Query of choices menu %s elided, button pressed",
47
    "READING":"Reading configuration from %s",
48
    "RECOVERY":"Attempting recovery from invalid configuration:",
49
    "RECOVEROK":"Recovery OK.",
50
    "REDUNDANT":"    %sRedundant assignment forced by %s",
51
    "RENAME":"Attempt to rename %s to %s failed.",
52
    "ROLLBACK":"    Rolling back new bindings: ",
53
    "SAVEEND":"#\n# That's all, folks!\n",
54
    "SETFAILED":"%sAttempt to set frozen symbol %s failed",
55
    "SETTING":"%s=%s",
56
    "SHAUTOGEN":"#\n# Automatically generated, don't edit\n#\n",
57
    "SHDERIVED":"#\n# Derived symbols\n#\n",
58
    "SIDEEFFECT":" (deduced from %s)",
59
    "SUBINVIS":"    %s not visible, all subqueries invisible.",
60
    "TRIGGER":"    Set of %s = %s triggered by guard %s",
61
    "TRITFLAG":"Trit flag is now %s",
62
    "TRITSOFF":"    %s not visible, trits are suppressed",
63
    "TYPEUNKNOWN":"Node %s unknown value type: %s %s",
64
    "UNCHANGED":"    %sSymbol %s unchanged",
65
    "UNCOMMIT": "#\n# Uncommitted bindings\n#\n",
66
    "UNLOAD1":"File load violated these constraints:",
67
    "UNLOAD2":"Undoing file loads, recovery failed these constraints:",
68
    #"UNSATISFIABLE":"Ruleset found unsatisfiable while setting %s",
69
    "UNSAVEABLE":"%s is not saveable",
70
    "USERSETTING":"User action on %s.",
71
    "VALIDRANGE":"    Valid range of %s is %s",
72
    "VALUNKNOWN":"Node %s %s has unknown value: %s",
73
    "VISIBLE":"    Query of %s *not* elided",
74
}
75
 
76
class CMLSystem(cml.CMLRulebase):
77
    "A rulebase, from the point of view of a front end."
78
    relational_map ={ \
79
                    "==":"!=", "!=":"==", \
80
                    ">":"<=", "<=":">", \
81
                    "<":">=", ">=":"<", \
82
                    }
83
 
84
    def clear(self):
85
        "Clear the runtime value state."
86
        for entry in self.dictionary.values():
87
            entry.iced = 0               # True if it has been frozen
88
            if entry.type == "choices":
89
                entry.menuvalue = entry.default
90
            else:
91
                entry.menuvalue = None
92
            entry.bindingcache = []     # Symbol's value stack
93
        self.oldbindings = {}
94
        self.newbindings = {}
95
        self.chilled = {}
96
        self.touched = []               # Does it have an uncommitted binding?
97
        self.changes_to_frozen = []     # Frozen change violations
98
        self.inclusions = []
99
 
100
    def __init__(self, rules):
101
        "Make a configuration state object from a compiled rulebase."
102
        # Interpret a string as the name of a file containing pickled rules.
103
        if type(rules) == type(""):
104
            import cPickle
105
            rules = cPickle.load(open(rules, "rb"))
106
 
107
        # Copy the symbol table.   Since a python object's members are all
108
        # stored in a single hash table, we can steal its contents and
109
        # then discard the original object.
110
        self.__dict__ = rules.__dict__
111
 
112
        self.clear()
113
 
114
        # Enhance the ConfigSymbol methods to deal with the value state.
115
        if 'oldeval' not in dir(cml.ConfigSymbol):
116
            cml.ConfigSymbol.oldeval = cml.ConfigSymbol.eval
117
            def _neweval(symbol, debug=0, self=self):    # Not a method!
118
                value = self.__bindeval(symbol)
119
                if value != None:
120
                    return value
121
                value = cml.ConfigSymbol.oldeval(symbol, debug)
122
                if value != None:
123
                    return value
124
                elif symbol.type in ("decimal", "hexadecimal"):
125
                    return 0
126
                elif symbol.type == "string":
127
                    return ""
128
                elif symbol.is_logical():
129
                    return cml.n
130
                else:
131
                    return None # for menu, choices, and message-valued symbols
132
            cml.ConfigSymbol.eval = _neweval
133
 
134
            def _newstr(symbol):                # Not a method!
135
                res = "%s={" % (symbol.name)
136
                res = res + symbol.dump()
137
                if symbol.setcount:
138
                    res = res + " has been set,"
139
                if symbol.included:
140
                    res = res + " was loaded,"
141
                if symbol.frozen():
142
                    res = res + " frozen,"
143
                value = symbol.eval()
144
                if value != None:
145
                    res = res + " value " + str(value) + ","
146
                return res[:-1] + "}"
147
            cml.ConfigSymbol.__str__ = _newstr
148
 
149
            def _freeze(symbol):
150
                "Freeze a symbol."
151
                symbol.iced = 1
152
                if symbol.menu and symbol.menu.type == "choices":
153
                    symbol.menu.iced = 1
154
                    symbol.menu.value = symbol.name
155
                    for sibling in symbol.menu.items:
156
                        sibling.iced = 1
157
            cml.ConfigSymbol.freeze = _freeze
158
 
159
            def _frozen(symbol, self=self):
160
                "Is the symbol frozen?"
161
                if symbol.iced or symbol.type == "message":
162
                    return 1
163
                if symbol.type in ("menu", "choices"):
164
                    for item in symbol.items:
165
                        if self.is_visible(item) and not item.frozen():
166
                            return 0
167
                    return 1
168
                return 0
169
            cml.ConfigSymbol.frozen = _frozen
170
 
171
        # Extra state for the configuration object
172
        self.debug = 0                   # Initially, no status logging
173
        self.cdepth = 0                  # Constraint recursion xdepth
174
        self.errout = sys.stderr        # Where to do status logging
175
        self.suppressions = 1           # Yes, do visibility checks
176
        self.interactive = 1            # Are we doing a file inclusion?
177
        self.commits = 0         # Value sets since last save 
178
        self.lang = _eng                # Someday we'll support more languages.
179
        self.side_effects = []          # Track side effects
180
        self.trits_enabled = not self.trit_tie or self.trit_tie.eval()
181
 
182
        if rules.version != cml.version:
183
            sys.stderr.write(self.lang["BADVERSION"] % (rules.version, cml.version))
184
            raise SystemExit, 1
185
 
186
    def is_new(self, symbol):
187
        return self.inclusions and not symbol.included and symbol.is_symbol()
188
 
189
    # Utility code
190
 
191
    def debug_emit(self, threshold, msg):
192
        "Conditionally emit a debug message to the designated error stream."
193
        if self.debug >= threshold:
194
            self.errout.write(msg + "\n")
195
 
196
    # Handling of symbol bindings is encapsulated here.  The semantics
197
    # we want is for every side-effect to be associated with the
198
    # symbol whose user-specified change in value triggered it (the
199
    # primary).  That way, if and when the user changes the primary's
200
    # value again, we can back out all previous side-effects
201
    # contingent on that symbol.
202
    #
203
    # Essentially what we're doing here is journalling all bindings.
204
    # It has to be this way, because different values of the same
205
    # symbol could trigger completely different side effects depending
206
    # on how the constraints are written.
207
    #
208
    class __Binding:
209
        def __init__(self, symbol, value, link):
210
            # We don't track a binding's source in the __Binding
211
            # object itself because they always live in chains hanging
212
            # off a primary-symbol cell.
213
            self.symbol = symbol
214
            self.value = value
215
            self.link = link    # Next link in the primary's side-effect chain
216
            self.visible = 1
217
        def __repr__(self):
218
            return "%s=%s" % (self.symbol.name, self.value)
219
 
220
    def __bindeval(self, symbol):
221
        "Get the most recent visible value for symbol off the binding stack."
222
        #self.debug_emit(2, "    bindeval(%s)" % (symbol.name))
223
        if not hasattr(symbol, "bindingcache"):
224
            return None
225
        for binding in symbol.bindingcache:
226
            if binding.visible:
227
                return binding.value
228
    def __bindmark(self, symbol):
229
        # Mark it to be written
230
        menu = symbol
231
        while menu:
232
            menu.setcount += 1
233
            menu = menu.menu
234
    def __bindsymbol(self, symbol, value, source=None, sort=0, suppress=0):
235
        "Bind symbol to a given value."
236
        self.debug_emit(2, "    %sbindsymbol(%s, %s, %s, %s)" % (' '*self.cdepth,symbol.name, value, `source`, sort))
237
        if not source:
238
            source = symbol
239
        # Avoid creating duplicate bindings.
240
        if self.newbindings.has_key(source):
241
            bindings = self.newbindings[source]
242
            while bindings:
243
                if bindings.symbol == symbol and bindings.value == value:
244
                    return
245
                bindings = bindings.link
246
        # Side-effect tracking.  Note we don't record side effects
247
        # unless the binding is actually modified.  Otherwise we'd get
248
        # a huge plethora of mostly redundant side effects on file loads.
249
        if value != symbol.eval():
250
            if source == None or source == symbol:
251
                side = ""
252
            else:
253
                side = self.lang["SIDEEFFECT"] % (`source`,)
254
            side = self.lang["SETTING"] % (symbol.name, value) + side
255
            if source and source != symbol and not suppress:
256
                self.side_effects.append(side)
257
            if symbol == self.trit_tie:
258
                if value == cml.n:
259
                    self.side_effects.append(self.lang["MODULESN"])
260
                elif value == cml.m:
261
                    self.side_effects.append(self.lang["MODULESM"])
262
                elif value == cml.y:
263
                    self.side_effects.append(self.lang["MODULESY"])
264
            # Debugging support
265
            if self.debug:
266
                self.debug_emit(1, "    " + side)
267
        # Here is the actual binding-stack hack
268
        newbinding = self.__Binding(symbol,value, self.newbindings.get(source))
269
        self.newbindings[source] = newbinding
270
        insertpoint = 0
271
        if sort != 0:
272
            # Hide new binding behind any binding with greater (or lesser)
273
            # value, according to the sort type.
274
            for i in range(len(symbol.bindingcache)):
275
                if cmp(symbol.bindingcache[i].value, value) == sort:
276
                    insertpoint = i + 1
277
                else:
278
                    break
279
        symbol.bindingcache.insert(insertpoint, newbinding)
280
        self.__bindmark(symbol)
281
 
282
    def __unbindsymbol(self, symbol, context):
283
        "Remove all bindings of a given symbol from a given context"
284
        listhead = context.get(symbol)
285
        if listhead:
286
            while listhead:
287
                listhead.symbol.bindingcache.remove(listhead)
288
                listhead = listhead.link
289
            del context[symbol]
290
            if symbol.menu.type == "choices":
291
                for sibling in symbol.menu.items:
292
                    if sibling.eval():
293
                        symbol.menu.menuvalue = sibling
294
                        break
295
    def __find_commit_set(self):
296
        "Find all symbols which need to have their old bindings removed"
297
        undo = []
298
        # When we undo side-effects from a choice symbol, all the side-effects
299
        # from setting its siblings have to be backed out too.
300
        for primary in self.newbindings.keys():
301
            if primary.menu.type == "choices":
302
                undo.extend(primary.menu.items)
303
            else:
304
                undo.append(primary)
305
        return undo
306
    def __bindcommit(self):
307
        "Commit all new bindings."
308
        # This is the magic moment that undoes side-effects
309
        for symbol in self.__find_commit_set():
310
            self.__unbindsymbol(symbol, self.oldbindings)
311
        self.oldbindings.update(self.newbindings)
312
        self.newbindings.clear()
313
        self.commits = self.commits + 1
314
    def __bindreveal(self, primary):
315
        "Make every old binding of symbol visible."
316
        bindings = self.oldbindings.get(primary)
317
        if bindings:
318
            listhead = bindings
319
            while listhead:
320
                listhead.visible = 1
321
                listhead = listhead.link
322
    def __bindconceal(self, primary):
323
        "Temporarily make old bindings hanging on a given primary invisible."
324
        bindings = self.oldbindings.get(primary)
325
        if bindings:
326
            listhead = bindings
327
            while listhead:
328
                listhead.visible = 0
329
                listhead = listhead.link
330
    def binddump(self, context=None):
331
        "Dump the state of the bindings stack."
332
        # Each line consists of a bound symbol and its side effects.
333
        # Most recent bindings are listed first.
334
        res = ""
335
        if context == None:
336
            context=self.oldbindings
337
        for (primary, bindings) in context.items():
338
            res = res + "# %s(%s, touched=%d): " % (primary.name,("inactive","active")[bindings.visible], primary in self.touched)
339
            while bindings:
340
                res = res + `bindings` + ", "
341
                bindings = bindings.link
342
            res = res[:-2] + "\n"
343
        return res
344
 
345
    #
346
    # Loading and saving
347
    #
348
    def loadcommit(self, freeze):
349
        "Attempt to commit the results of a file load."
350
        errors = ""
351
        violations = self.sanecheck()
352
        if not violations:
353
            self.__commit(freeze)
354
        else:
355
            errors += self.lang["UNLOAD1"]+"\n" + "\n".join(map(repr,violations)) + "\n"
356
            # This is an attempt to recover from inconsistent
357
            # configurations.  (Unusual case -- typically happens only
358
            # when a new constraint is added to a rulebase, not in the
359
            # more common case of new symbols.)  General recovery is
360
            # very hard, it involves constrained satisfaction problems
361
            # for which there are not just no good algorithms, there
362
            # are no clean definitions.  We settle for a simple,
363
            # stupid hack.  Force all the unfrozen symbols in the
364
            # violated constraints to n and see what happens.
365
            nukem = []
366
            self.debug_emit(1, self.lang["RECOVERY"])
367
            for i in range(len(self.constraints)):
368
                if not cml.evaluate(self.reduced[i], self.debug):
369
                    flattened = cml.flatten_expr(self.constraints[i].predicate)
370
                    for sym in flattened:
371
                        if not sym in nukem and not sym.frozen() and sym.is_logical():
372
                            nukem.append(sym)
373
            if nukem:
374
                errors += self.lang["RECOVERY"] + "\n"
375
                for sym in nukem:
376
                    errors += self.lang["INCONST"] % (sym.name,)
377
                    self.__set_symbol_internal(sym, cml.n)
378
                    self.chilled.clear()
379
                violations = self.sanecheck()
380
                if not violations:
381
                    self.__commit(freeze)
382
                    errors += self.lang["RECOVEROK"]
383
                else:
384
                    self.__rollback()
385
                    errors += self.lang["UNLOAD2"]+"\n" + "\n".join(map(repr, violations)) + "\n"
386
        return errors
387
 
388
    def load(self, file, freeze=0):
389
        "Load bindings from a defconfig-format configuration file."
390
        import shlex
391
        stream = shlex.shlex(open(file), file)
392
        stream.wordchars += "$"
393
        self.debug_emit(1, self.lang["READING"] % (file,))
394
        changes = 0
395
        errors = ""
396
        stash = self.interactive
397
        self.interactive = 0
398
        self.side_effects = []  # Not needed if we're using set_symbol below 
399
        while 1:
400
            dobind = 1
401
            symname = stream.get_token()
402
            if not symname:
403
                break
404
            # Parse directives
405
            if symname == "$$commit":
406
                self.loadcommit(0)
407
                changes = 0
408
                continue
409
            elif symname == "$$freeze":
410
                self.loadcommit(1)
411
                changes = 0
412
                continue
413
            # Now parse ordinary symbol sets
414
            if len(self.prefix) and symname[0:len(self.prefix)] == self.prefix:
415
                symname = symname[len(self.prefix):]
416
            if self.dictionary.has_key(symname):
417
                symbol = self.dictionary[symname]
418
            else:
419
                symbol = None
420
                errmsg = stream.error_leader()+self.lang["BADTOKEN"]%(symname)
421
                errors = errors + errmsg + "\n"
422
                dobind = 0
423
 
424
            sep = stream.get_token()
425
            if sep != '=':
426
                errmsg = stream.error_leader()+self.lang["BADEQUALS"]%(sep,)
427
                errors = errors + errmsg + "\n"
428
                dobind = 0
429
            value = stream.get_token()
430
 
431
            # Do this check early to avoid Python errors if the file
432
            # is malformed.
433
            if not dobind:
434
                continue
435
 
436
            if value[0] in stream.quotes:
437
                value = value[1:-1]
438
 
439
            if not symbol:
440
                continue
441
            # We can't permit these files to override derivations
442
            if symbol.is_derived():
443
                self.debug_emit(2, stream.error_leader()+self.lang["DERIVED"]%(symbol.name,))
444
                continue
445
 
446
            # Michael Chastain's case -- treat variable as new if
447
            # value is m but the type has been changed to bool
448
            if value == 'm' and symbol.type == "bool":
449
                errmsg = stream.error_leader()+self.lang["BADTRIT"]%(symbol.name,)
450
                errors = errors + errmsg + "\n"
451
                continue
452
 
453
            # Note that we've seen this in an inclusion -- it's not new.
454
            symbol.included = 1
455
 
456
            # If we load a configuration with trit values,
457
            # force those to be enabled.
458
            if symbol.type == "trit" and value == "m":
459
                self.trits_enabled = 1
460
 
461
            # Don't count changes to variables that are already set at the
462
            # desired value.  Do them, though, so they can be frozen.
463
            oldval = symbol.eval()
464
            # Use this for consistency checking by file
465
            newval = self.value_from_string(symbol,value)
466
            self.__set_symbol_internal(symbol,
467
                                       newval)
468
            self.chilled.clear()
469
            if newval != oldval:
470
                changes = changes + 1
471
 
472
        stream.instream.close()
473
        if changes:
474
            errors += self.loadcommit(freeze)
475
        self.inclusions.append(file)
476
        self.interactive = stash
477
        return (changes, errors)
478
 
479
    def saveable(self, node, mode="normal"):
480
        "Should this symbol be visible in the configuration written out?"
481
        # Fix for loaded symbols from old configuration being written back without visibility check.
482
        if node.setcount > 0 and self.is_visible(node):
483
            return 1
484
        if node.is_derived() and (not node.visibility or cml.evaluate(node.visibility)):
485
            return 1
486
        if node.saveability:
487
            return cml.evaluate(node.saveability)
488
        if mode == "probe":
489
            return 0
490
        if not self.is_visible(node):
491
            return 0
492
        return 1
493
 
494
    def save(self, outfile=sys.stdout, baton=None, mode="normal"):
495
        "Save a configuration to the named output stream or file."
496
        #print "save(outfile=%s, baton=%s, mode=%s)" % (outfile, baton, mode)
497
        newbindings = None
498
        if self.newbindings:
499
            newbindings = self.newbindings
500
            self.newbindings = {}
501
        try:
502
            if type(outfile) == type(""):
503
                shelltemp = ".tmpconfig%d.sh" % os.getpid()
504
                outfp = open(shelltemp, "w")
505
                outfp.write(self.lang["SHAUTOGEN"])
506
            else:
507
                outfp = outfile
508
            # Write an informative header so we can identify this file.
509
            if mode != "list":
510
                try:
511
                    from time import gmtime, strftime
512
                    import socket
513
                    outfp.write("# Generated on: "+socket.gethostname()+"\n")
514
                    outfp.write("# At: " + strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) + "\n")
515
                    infp = open("/proc/version", "r")
516
                    outfp.write("# " + infp.read())
517
                    infp.close()
518
                except:
519
                    pass
520
            # Write mutable symbols, including defaulted modular symbols.
521
            self.__save_recurse(self.start, outfp, baton, mode)
522
            # Write all derived symbols
523
            if mode != "list":
524
                if filter(lambda x: x.is_derived(), self.dictionary.values()):
525
                    outfp.write(self.lang["SHDERIVED"])
526
                for entry in self.dictionary.values():
527
                    if entry.is_derived():
528
                        if baton:
529
                            baton.twirl()
530
                        self.__save_recurse(entry, outfp, baton, mode)
531
            # Perhaps this is a crash dump from an inconsistent ruleset?
532
            if newbindings:
533
                self.newbindings = newbindings
534
                outfp.write(self.lang["UNCOMMIT"])
535
                outfp.write(self.binddump(self.newbindings))
536
            if mode == "normal":
537
                outfp.write(self.lang["SAVEEND"])
538
            if type(outfile) == type(""):
539
                outfp.close()
540
                try:
541
                    os.rename(shelltemp, outfile)
542
                except OSError:
543
                    reason  = self.lang["RENAME"] % (shelltemp, outfile,)
544
                    raise IOError, reason
545
            self.commits = 0
546
            if baton:
547
                baton.end()
548
            return None
549
        except IOError, details:
550
            return details.args[0]
551
 
552
    def save_symbol(self, symbol, shellstream, label=""):
553
        symname = self.prefix + symbol.name
554
        value = symbol.eval(self.debug)
555
        self.debug_emit(2, self.lang["ABOUT"] %(symname,value,type(value)))
556
        try:
557
            if symbol.type == "decimal":
558
                shellstream.write("%s=%d" % (symname, value))
559
            elif symbol.type == "hexadecimal":
560
                shellstream.write("%s=0x%x" % (symname, value))
561
            elif symbol.type == "string":
562
                shellstream.write("%s=\"%s\"" % (symname, value))
563
            elif symbol.type in ("bool", "trit"):
564
                shellstream.write("%s=%s" % (symname, `value`))
565
            elif value == None and symbol.is_logical():
566
                shellstream.write("%s=n" % (symname,))
567
            else:
568
                raise ValueError, self.lang["VALUNKNOWN"] % (symbol,symbol.type,value)
569
            if label:
570
                shellstream.write("\t# " + label)
571
            shellstream.write("\n")
572
        except:
573
            (errtype, errval, errtrace) = sys.exc_info()
574
            print "Internal error %s while writing %s." % (errtype, symbol)
575
            raise SystemExit, 1
576
 
577
    def __save_recurse(self, node, shellstream, baton=None, mode="normal"):
578
        saveable = self.saveable(node, mode)
579
        if not saveable:
580
            self.debug_emit(2, self.lang["UNSAVEABLE"] % node.name)
581
            return
582
        elif node.items:
583
            shellstream.write("\n#\n# %s\n#\n" % (node.prompt,))
584
            # In case this is a choice menu not previously visited.
585
            self.visit(node)
586
            for child in node.items:
587
                self.__save_recurse(child, shellstream, baton, mode)
588
            shellstream.write("\n")
589
        elif node.type != 'message':
590
            label = ""
591
            if node.properties:
592
                label += node.showprops()
593
            if node.properties and mode=="list" and not saveable:
594
                label += " "
595
            if mode == "list" and not saveable:
596
                label += self.lang["NOTSAVED"]
597
            self.save_symbol(node, shellstream, label)
598
        if baton:
599
            baton.twirl()
600
 
601
    # Symbol predicates.
602
 
603
    def is_mutable(self, symbol):
604
        "Is a term mutable (symbol, not frozen)?"
605
        return isinstance(symbol, cml.ConfigSymbol) and not symbol.frozen()
606
 
607
    def is_visible(self, query):
608
        "Should we ask this question?"
609
        self.debug_emit(2, self.lang["INVISSTART"] % (query.name,))
610
        # Maybe we're not doing elisions
611
        if not self.suppressions:
612
            return 1
613
        # Maybe it has no help.
614
        if not self.__help_visible(query):
615
            self.debug_emit(2, self.lang["INVISHELP"] % (query.name,))
616
            return 0
617
        # Check to see if the symbol or any menu in the chain above it
618
        # is suppressed by a visibility constraint or ancestry.
619
        if not self.__upward_visible(query):
620
            self.debug_emit(2, self.lang["INVISUP"] % (query.name,))
621
            return 0
622
        # Elide a message if everything between it and the next message
623
        # is invisible.
624
        if query.type == "message":
625
            for i in range(query.menu.items.index(query)+1, len(query.menu.items)):
626
                if query.menu.items[i].type == "message":
627
                    break
628
                elif self.is_visible(query.menu.items[i]):
629
                    return 1
630
            return 0
631
        # Elide a menu if all subqueries are invisible, or a choices if one is.
632
        if not self.__subqueries_visible(query):
633
            return 0
634
        # All tests passed, it's visible
635
        self.debug_emit(2, self.lang["VISIBLE"] % (query.name))
636
        return 1
637
 
638
    def is_visible_menus_choices(self, query):
639
        # Maybe we're not doing elisions
640
        if not self.suppressions:
641
            return 1
642
        # Maybe it has no help.
643
        if not self.__help_visible(query):
644
            return 0
645
        # OK, now check that all ancestors are visible.   
646
        if not self.__dep_visible(query):
647
            return 0
648
        # Elide a menu if all subqueries are invisible, or a choices if one is.
649
        if not self.__subqueries_visible(query):
650
            return 0
651
        # All tests passed, it's visible
652
        return 1
653
    #
654
    # All the properties of visibility are implemented here
655
    #
656
    def __help_visible(self, query):
657
        if query.is_symbol() and not query.help() and self.help_tie and not self.help_tie.eval():
658
            self.debug_emit(2, self.lang["NOHELP"] % query.name)
659
            return 0
660
        return 1
661
 
662
    def __upward_visible(self, query):
663
        upward = query
664
        while upward != self.start:
665
            if upward.visibility != None and not cml.evaluate(upward.visibility):
666
                self.debug_emit(2, self.lang["INVISME"] % (query.name, upward.name, cml.display_expression(upward.visibility)))
667
                return 0
668
            elif not self.__dep_visible(upward):
669
                return 0
670
            upward = upward.menu
671
        return 1
672
 
673
    def __subqueries_visible(self, query):
674
        if query.items:
675
            setcount = 0
676
            if query.type == 'menu' or query.type == 'choices':
677
                for child in query.items:
678
                    if child.type != "message":
679
                        if self.is_visible_menus_choices(child):
680
                            setcount = 1
681
                            break
682
                if setcount == 0:
683
                    return 0
684
        return 1
685
 
686
    #
687
    # All the properties of the dependency relationship are implemented here
688
    #
689
    def __dep_value_ok(self, symbol, value):
690
        "Do ancestry relationships allow given value of given symbol"
691
        for ancestor in symbol.ancestors:
692
            v = cml.evaluate(ancestor, self.debug)
693
            if (symbol.type == "trit" and value > v):
694
                break
695
            elif (symbol.type =="bool" and value > (v != cml.n)):
696
                break
697
        else:
698
            return 1    # Tricky use of for-else
699
        self.debug_emit(2, self.lang["EXCLUDED"] % (`symbol`, `cml.trit(value)`, `ancestor`))
700
        return 0
701
 
702
    def __dep_visible(self, symbol):
703
        "Do ancestry relations allow a symbol to be visible?"
704
        # Note: we don't need to recurse here, assuming dependencies
705
        # get propagated correctly.
706
        for super in symbol.ancestors:
707
            if not cml.evaluate(super):
708
                self.debug_emit(2,self.lang["INVISANC"]%(symbol.name,super.name))
709
                return 0
710
            elif super.visibility and not cml.evaluate(super.visibility):
711
                self.debug_emit(2,self.lang["INVISANC2"]%(symbol.name,super.name))
712
                return 0
713
        return 1
714
 
715
    def __dep_force_ancestors(self, dependent, source, dependvalue, ancestor):
716
        "Force a symbol's ancestors up, based on the symbol's value."
717
        self.debug_emit(2, "    dep_force_ancestors(%s, %s, %s, %s)" % (`dependent`, `source`, `dependvalue`, `ancestor`))
718
        if dependent.is_logical() and ancestor.is_logical():
719
            anctype = ancestor.type
720
            if dependvalue > cml.n:
721
                if dependent.type == anctype:
722
                    newval = dependvalue
723
                elif not self.trits_enabled:
724
                    newval = cml.y;
725
                elif anctype == "bool": # dependent is trit
726
                    newval = cml.y;
727
                elif anctype == "trit": # dependent is bool
728
                    newval = cml.m;
729
                else:
730
                    newval = dependvalue
731
                self.__set_symbol_internal(ancestor, newval, source, sort=1)
732
 
733
        # Recurse upwards, first through ancestors... 
734
        for upper in ancestor.ancestors:
735
            self.__dep_force_ancestors(dependent, source, dependvalue, upper)
736
        # ...and then through the containing menu.
737
        for upper in ancestor.menu.ancestors:
738
            self.__dep_force_ancestors(dependent, source, dependvalue, upper)
739
 
740
    def __dep_force_dependents(self, guard, source, guardvalue, dependent):
741
        "Force a symbol's descendents down, based on the symbol's value."
742
        self.debug_emit(2, "    dep_force_dependents(%s, %s, %s, %s)" % (`guard`, `source`, `guardvalue`, `dependent`))
743
        if guard.is_logical() and dependent.is_logical():
744
            deptype = dependent.type
745
            depvalue = cml.evaluate(dependent)
746
            if guardvalue < depvalue:
747
                if guard.type == deptype:
748
                    newval = guardvalue
749
                elif not self.trits_enabled:
750
                    newval = cml.n
751
                elif deptype == "trit":         # Ancestor is bool
752
                    newval = guardvalue
753
                elif guardvalue == cml.n:       # Ancestor is trit
754
                    newval = guardvalue
755
                else:
756
                    newval = depvalue           # No change
757
                self.__set_symbol_internal(dependent, newval, source, sort=-1)
758
        # Recurse downwards...
759
        if dependent.items:
760
            for child in dependent.items:
761
                self.__dep_force_dependents(guard, source, guardvalue, child)
762
        else:
763
            for lower in dependent.dependents:
764
                self.__dep_force_dependents(guard, source, guardvalue, lower)
765
 
766
    #
767
    # The following methods handle variable bindings
768
    #
769
    def __rollback(self):
770
        "Roll back all new bindings."
771
        self.debug_emit(1, self.lang["ROLLBACK"] + `self.touched`)
772
        self.touched = []
773
        for symbol in self.newbindings.keys():
774
            self.__unbindsymbol(symbol, self.newbindings)
775
        self.side_effects = []
776
 
777
    def __commit(self, freeze=0, baton=None):
778
        "Commit all new bindings."
779
        if freeze:
780
            self.debug_emit(1, self.lang["COMMIT"] + self.lang["FROZEN"])
781
        else:
782
            self.debug_emit(1, self.lang["COMMIT"])
783
            if self.trit_tie and self.trit_tie in self.touched:
784
                self.trits_enabled = cml.evaluate(self.trit_tie)
785
                self.debug_emit(1, self.lang["TRITFLAG"] % (`cml.trit(self.trits_enabled)`,))
786
            if self.help_tie and self.help_tie in self.touched:
787
                self.debug_emit(1, self.lang["HELPFLAG"] % self.help_tie.eval())
788
        for entry in self.touched:
789
            if freeze:
790
                entry.freeze()
791
        self.touched = []
792
        if baton:
793
            baton.twirl("#")
794
        if freeze:
795
            # Optimization hack -- undo this if variables can ever be unfrozen.
796
            # In the meantime, this greatly reduces the amount of expression
797
            # evaluation needed after variables have been frozen.
798
            for i in range(len(self.reduced)):
799
                simplified = self.eval_frozen(self.reduced[i])
800
                if simplified != None:
801
                    self.reduced[i] = simplified
802
            self.optimize_constraint_access()
803
            if baton:
804
                baton.twirl("#")
805
        # Must do this *after* checking freezes
806
        self.__bindcommit()             # The magic moment
807
        if baton:
808
            baton.end()
809
 
810
    def sanecheck(self):
811
        "Sanity-check a configuration and report on its side effects."
812
        violations = self.changes_to_frozen
813
        for i in range(len(self.constraints)):
814
            if not cml.evaluate(self.reduced[i], self.debug):
815
                violations.append(self.constraints[i]);
816
                self.debug_emit(1, self.lang["FAILREQ"]%(self.constraints[i],))
817
        return violations
818
 
819
    def set_symbol(self, symbol, value, freeze=0):
820
        "Bind a symbol, tracking side effects."
821
        self.debug_emit(1, self.lang["USERSETTING"] % (symbol.name,))
822
        self.side_effects = []
823
        self.changes_to_frozen = []
824
        self.__set_symbol_internal(symbol, value)
825
        self.chilled.clear()
826
        self.cdepth = 0
827
        # conceal all the bindings a commit would remove
828
        # this way, the sane check is checking the final
829
        # configuration
830
        commit_set = self.__find_commit_set()
831
        for symbol in commit_set:
832
            self.__bindconceal(symbol)
833
        violations = self.sanecheck()
834
        if not violations:
835
            self.__commit(freeze)
836
            return (1, self.side_effects, [])
837
        else:
838
            # make the bindings visible before a rollback
839
            for symbol in commit_set:
840
                self.__bindreveal(symbol)
841
            effects = self.side_effects
842
            self.__rollback()           # This will clear self.side_effects
843
            return (0, effects, violations)
844
 
845
    def __set_symbol_internal(self, symbol, value, source=None, sort=0):
846
        "Recursively bind a symbol, with side effects."
847
        self.debug_emit(2, "    %sset_symbol_internal(%s, %s, %s, %s)" % (' ' * self.cdepth, symbol.name, value, `source`, sort))
848
        self.cdepth += 1
849
        if not source:
850
            source = symbol
851
        # The "touched" property marks this symbol changed for freeze purposes.
852
        # It has to stay on until the next commit.
853
        self.touched.append(symbol)
854
        # If it already has the desired value, we're done.
855
        oldval = cml.evaluate(symbol, self.debug)
856
        self.debug_emit(2, self.lang["OLDVAL"] % (' '*self.cdepth, symbol.name, oldval))
857
        if oldval == value:
858
            self.debug_emit(1, self.lang["UNCHANGED"] % (' '*self.cdepth, symbol.name,))
859
            # However, mark it set anyway.  This is useful for
860
            # distinguishing value by default from value by user action.
861
            self.__bindmark(symbol)
862
            # If this was a user setting or a frozen symbol.
863
            # Not actually going through the motions for a frozen
864
            # symbol is a speed hack.  We know the symbol can't change
865
            # so there's no reason to keep its binding cache full...
866
            if symbol == source or symbol.frozen():
867
                self.cdepth -= 1
868
                return
869
        elif symbol.frozen():
870
            # It's a violation if someone tries to raise the value of a symbol.
871
            # Log the violation so sanecheck() will cause a rollback
872
            if value > oldval:
873
                self.changes_to_frozen.append(  \
874
                   self.lang["SETFAILED"] % (' '*self.cdepth, symbol.name))
875
            self.cdepth -= 1
876
            return
877
        # Barf on attempt to change the value of a changed value --
878
        # but only if the attempt comes from a different source,
879
        # because ancestry forcing will often result in the same symbol
880
        # being changed multiple times in succession from the same source.
881
        if self.chilled.has_key(symbol) and self.chilled[symbol] != source:
882
            self.__bindsymbol(symbol, value, source)    # record for debugging
883
            raise "UNSATISFIABLE"
884
        # Membership in chilled means we should treat the binding as frozen for
885
        # simplification purposes.  It has to be turned off when the current
886
        # call to set_symbol is done; otherwise side effects from inclusion
887
        # sequences would collide with each other.
888
        self.chilled[symbol] = source
889
        # Here's where the value actually gets set
890
        self.__bindsymbol(symbol, value, source, sort)
891
        # Make the side-effects of this symbol's previous bindings
892
        # temporarily invisible while computing side effects.  This
893
        # is necessary because things like dependent suppressions
894
        # need to be calculated according to the effects they would
895
        # have when the old bindings are removed, as they will be
896
        # if this change is committed (whew!)
897
        self.__bindconceal(symbol)
898
        # If this symbol was in a choice group and is being set true,
899
        # note this in the menu state
900
        if symbol.menu and symbol.menu.type == "choices" and value:
901
            symbol.menu.visits += 1
902
            symbol.menu.menuvalue = symbol
903
        # Unset all siblings if we're setting one to a non-n value.
904
        if value:
905
            for sibling in symbol.choicegroup:
906
                self.__set_symbol_internal(sibling, cml.n, source)
907
        # Other side effects...
908
        if self.trit_tie and symbol == self.trit_tie and value == cml.n:
909
            for entry in self.dictionary.values():
910
                if entry.type == "trit" and not entry.is_derived() and entry.eval() == cml.m:
911
                    self.__set_symbol_internal(entry, cml.y, source)
912
        # Now propagate the value change through ancestry chains.
913
        # Checking the 'sort' argument prevents nasty recursions
914
        # by suppressing dependent setting if we're setting an
915
        # ancestor, and vice-versa.
916
        if symbol.is_logical():
917
            if value > cml.n and sort != -1:
918
                for ancestor in symbol.ancestors + symbol.menu.ancestors:
919
                    self.__dep_force_ancestors(symbol, source, value, ancestor)
920
            if value < cml.y and sort != 1:
921
                for dependent in symbol.dependents:
922
                    self.__dep_force_dependents(symbol,source,value,dependent)
923
        # Perhaps we can deduce other values through explicit constraints?
924
        # This is where we'd plug in a full SAT algorithm if we were going
925
        # to use one.
926
        if self.interactive and not self.suppressions and symbol.visibility:
927
            self.__constrain(symbol.visibility, source)
928
        # Now loop through the constraints associated with this
929
        # symbol, simplifying out assigned variables and trying to
930
        # freeze more variables each time.  The outer loop guarantees
931
        # that as long as the constraints imply at least one more
932
        # tentative setting, we'll keep going.
933
        while 1:
934
            cc = 0
935
            for wff in symbol.constraints:
936
                cc += self.__constrain(wff, source)
937
            if not cc:
938
                break;
939
        # OK, now make the old bindings of this symbol visible again
940
        # (the change we just made might get rolled back later).
941
        self.__bindreveal(symbol)
942
        self.cdepth -= 1
943
 
944
    def value_from_string(self, sym, val):
945
        "Set symbol from string according to the symbol type."
946
        if sym.is_logical():
947
            if val == "y":
948
                val = cml.y
949
            elif val == "m":
950
                val = cml.m
951
            elif val == "n":
952
                val = cml.n
953
        elif sym.type == "decimal":
954
            val = int(val)
955
        elif sym.type == "hexadecimal":
956
            val = long(val, 16)
957
        return val
958
 
959
    def eval_frozen(self, wff):
960
        "Test whether a given expr is entirely constant, chilled or frozen."
961
        if isinstance(wff,cml.trit) or type(wff) in (type(0),type(0L),type("")):
962
            return wff
963
        elif isinstance(wff, cml.ConfigSymbol):
964
            if wff.frozen() or self.chilled.has_key(wff):
965
                return wff.eval()
966
            elif wff.is_derived():
967
                return self.eval_frozen(wff.default)
968
            else:
969
                return None
970
        elif wff[0] == 'not':
971
            below = self.eval_frozen(wff[1])
972
            if below == None:
973
                return None
974
            else:
975
                return cml.trit(not below)
976
        elif wff[0] == '?':
977
            guard = self.eval_frozen(wff[1])
978
            if guard == None:
979
                return None
980
            if guard:
981
                return self.eval_frozen(wff[2])
982
            else:
983
                return self.eval_frozen(wff[3])
984
        else:
985
            left = self.eval_frozen(wff[1])
986
            right = self.eval_frozen(wff[2])
987
            if left != None and right != None:
988
                return cml.evaluate((wff[0], left, right))
989
            elif left == None and right == None:
990
                return None
991
            # OK, now the grotty part starts
992
            elif wff[0] == 'and':
993
                if left == cml.n or right == cml.n:
994
                    return cml.n
995
                elif left in (cml.y, cml.m):
996
                    return right
997
                elif right in (cml.y, cml.m):
998
                    return left
999
                else:
1000
                    return None
1001
            elif wff[0] == 'or':
1002
                if left in (cml.y, cml.m) or right in (cml.y, cml.m):
1003
                    return cml.y
1004
                elif left == cml.n:
1005
                    return right
1006
                elif right == cml.n:
1007
                    return left
1008
                else:
1009
                    return None
1010
            elif wff[0] == 'implies':
1011
                if left in (cml.y, cml.m):
1012
                    return right
1013
                elif left == cml.n:
1014
                    return cml.y
1015
                elif right == cml.n:
1016
                    return not left
1017
                else:
1018
                    return None
1019
            else:
1020
                return None
1021
 
1022
    def __constrain(self, wff, source, fixedval=cml.y):
1023
        "Set symbols based on asserted equalities or inequalities."
1024
        self.debug_emit(2, self.lang["BINDING"] % (self.cdepth*' ', cml.display_expression(wff), fixedval, `source`))
1025
        self.cdepth += 1
1026
        ret = self.__inner_constrain(wff, source, fixedval)
1027
        self.cdepth -= 1
1028
        return ret
1029
 
1030
    def __inner_constrain(self, wff, source, fixedval=cml.y):
1031
        if isinstance(wff, cml.ConfigSymbol):
1032
            if wff.is_derived():
1033
                return self.__constrain(wff.default, source, fixedval)
1034
            else:
1035
                oldval = cml.evaluate(wff, self.debug)
1036
                if oldval == fixedval:
1037
                    self.__bindsymbol(wff, fixedval, source)
1038
                    self.debug_emit(2, self.lang["REDUNDANT"] % (' '*self.cdepth,wff.name,))
1039
                    return 0
1040
                else:
1041
                    self.__set_symbol_internal(wff, fixedval, source)
1042
                    return 1
1043
        op = wff[0]
1044
        left = wff[1]
1045
        right = wff[2]
1046
        if op == '?':
1047
            guard = cml.evaluate(left)
1048
            if guard == None:
1049
                return 0
1050
            elif guard:
1051
                return self.__constrain(right, source, fixedval)
1052
            else:
1053
                return self.__constrain(wff[3], source, fixedval)
1054
        elif fixedval == cml.y and op == 'implies':
1055
            if cml.evaluate(left):
1056
                return self.__constrain(right, source, fixedval)
1057
            else:
1058
                return 0
1059
        elif fixedval == cml.y and op == 'and':
1060
            return self.__constrain(left, source, fixedval) + \
1061
                   self.__constrain(right, source, fixedval)
1062
        elif fixedval == cml.n and op == 'or':
1063
            return self.__constrain(left, source, fixedval) + \
1064
                   self.__constrain(right, source, fixedval)
1065
        elif op in CMLSystem.relational_map.keys() \
1066
             and (isinstance(left, cml.trit) or (isinstance(left, cml.ConfigSymbol) and left.is_logical())) \
1067
             and (isinstance(right, cml.trit) or (isinstance(right, cml.ConfigSymbol) and right.is_logical())):
1068
            if fixedval == cml.n:
1069
                op = CMLSystem.relational_map[op]
1070
            # Before we can force a binding, we need exactly one operand
1071
            # to be mutable...
1072
            left_mutable = self.is_mutable(left)
1073
            right_mutable = self.is_mutable(right)
1074
            if left_mutable == right_mutable:
1075
                self.debug_emit(3, "0 or 2 mutables in %s, %s" % (left, right))
1076
                return 0
1077
            leftval = cml.evaluate(left)
1078
            rightval = cml.evaluate(right)
1079
            # Now we may have the conditions to force a binding.
1080
            # The bindsymbols in the `redundant' assignments are needed
1081
            # in order to make the backout logic work when a binding is unset.
1082
            if op == '==':
1083
                if left_mutable:
1084
                    return self.__constrain(left, source, rightval)
1085
                elif right_mutable:
1086
                    return self.__constrain(right, source, leftval)
1087
            elif op == '!=':
1088
                if left.type == "bool" and right.type == "bool":
1089
                    if left_mutable:
1090
                        return self.__constrain(left, source, not rightval)
1091
                    elif right_mutable and right.type == "bool":
1092
                        return self.__constrain(right, source, not leftval)
1093
            elif op == '<':
1094
                if leftval < rightval:
1095
                    return 0
1096
                elif left_mutable:
1097
                    return self.__constrain(left, source,
1098
                                            trit(max(0, rightval.value-1)))
1099
                elif right_mutable:
1100
                    return self.__constrain(right, source,
1101
                                            trit(min(2, leftval.value+1)))
1102
            elif op == '<=':
1103
                if leftval <= rightval:
1104
                    return 0
1105
                elif left_mutable:
1106
                    return self.__constrain(left, source, rightval)
1107
                elif right_mutable:
1108
                    return self.__constrain(right, source, leftval)
1109
            elif op == '>':
1110
                if leftval > rightval:
1111
                    return 0
1112
                elif left_mutable:
1113
                    return self.__constrain(left, source,
1114
                                            trit(min(2, rightval.value+1)))
1115
                elif right_mutable:
1116
                    return self.__constrain(right, source,
1117
                                            trit(max(0, leftval.value-1)))
1118
            elif op == '>=':
1119
                if leftval >= rightval:
1120
                    return 0
1121
                elif left_mutable:
1122
                    return self.__constrain(left, source, rightval)
1123
                elif right_mutable:
1124
                    return self.__constrain(right, source, leftval)
1125
        return 0
1126
 
1127
    #
1128
    # Navigation helpers 
1129
    #
1130
 
1131
    def visit(self, entry):
1132
        "Register the fact that we've visited a menu."
1133
        if not entry.menu or not self.is_visible(entry):
1134
            return
1135
        self.debug_emit(2,"Visiting %s (%s) starts" % (entry.name, entry.type))
1136
        entry.visits = entry.visits + 1
1137
        # Set choices defaults -- do it now for the side-effects
1138
        # (If you do it sooner you can get weird constraint failures)
1139
        if entry.visits==1 and entry.type=="choices" and not entry.frozen():
1140
            base = ind = entry.items.index(entry.default)
1141
            try:
1142
                # Starting from the declared default, or the implicit default
1143
                # of first item, seek forward until we find something visible.
1144
                while not self.is_visible(entry.items[ind]):
1145
                    ind += 1
1146
            except IndexError:
1147
                # Kluge -- if we find no visible items, turn off suppressions
1148
                # and drop back to the original default.
1149
                self.debug_emit(1, self.lang["NOVISIBLE"] % (`entry`,))
1150
                ind = base
1151
                self.suppressions = 0
1152
            self.set_symbol(entry.items[ind], cml.y)
1153
        self.debug_emit(2, "Visiting %s (%s) ends" % (entry.name,entry.type))
1154
 
1155
    def next_node(self, here):
1156
        "Return the next menu or symbol in depth-first order."
1157
        if here.type == 'menu' and not here.default:
1158
            here = here.items[0];
1159
        else:
1160
            while here.menu:
1161
                up = here.menu
1162
                where = up.items.index(here)
1163
                if where >= len(here.menu.items) - 1:
1164
                    here = up
1165
                else:
1166
                    here = up.items[where+1]
1167
                    break
1168
        if here == None:
1169
            here = self.start
1170
        return here
1171
 
1172
    def previous_node(self, here):
1173
        "Return the previous menu or symbol in depth-first order."
1174
        if here.type == 'menu' and not here.default:
1175
            here = here.items[0];
1176
        else:
1177
            while here.menu:
1178
                up = here.menu
1179
                where = up.items.index(here)
1180
                if where == 0:
1181
                    here = up
1182
                else:
1183
                    here = up.items[where-1]
1184
                    break
1185
        if here == None:
1186
            here = self.start
1187
        return here
1188
 
1189
    def search(self, pattern, hook):
1190
        "Return a menu composed of symbols matching a given regexp."
1191
        regexp = re.compile(pattern)
1192
        hits = cml.ConfigSymbol("search", "menu")
1193
        for entry in self.dictionary.values():
1194
            if entry.prompt and entry.type != "message":
1195
                text = hook(entry)
1196
                if text == None:
1197
                    continue
1198
                elif regexp.search(text):
1199
                    hits.items.append(entry)
1200
        # Give the result menu a parent only if all members have same parent
1201
        hits.menu = None
1202
        for symbol in hits.items:
1203
            if hits.menu == None:
1204
                hits.menu = symbol.menu
1205
            elif symbol.menu != hits.menu:
1206
                hits.menu = None
1207
                break
1208
        # Sort the results for a nice look
1209
        hits.items.sort()
1210
        hits.nosuppressions = 1
1211
        return hits
1212
 
1213
    def symbolsearch(self, pattern):
1214
        "Return a menu composed of symbols matching a given regexp."
1215
        return self.search(pattern, lambda x: x.name + x.prompt)
1216
 
1217
    def helpsearch(self, pattern):
1218
        "Return a menu composed of symbols matching a given regexp."
1219
        return self.search(pattern, lambda x: x.help())
1220
 
1221
    # Input validation
1222
 
1223
    def range_check(self, symbol, value):
1224
        "Check whether a value is within a symbol's specified valid range."
1225
        if not symbol.range:
1226
            return 1
1227
        elif symbol.enum:
1228
            for (label, possibility) in symbol.range:
1229
                if value == possibility:
1230
                    return 1
1231
        else:
1232
            for span in symbol.range:
1233
                if type(span) in (type(0), type(0L)):
1234
                    if value == span:
1235
                        return 1
1236
                elif value >= span[0] and value <= span[1]:
1237
                    return 1
1238
        return 0
1239
 
1240
# End

powered by: WebSVN 2.1.0

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