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
|