| 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
 |