summaryrefslogtreecommitdiff
path: root/kconfiglib.py
diff options
context:
space:
mode:
authorUlf Magnusson <ulfalizer@gmail.com>2017-10-25 22:41:57 +0200
committerUlf Magnusson <ulfalizer@gmail.com>2017-10-25 22:41:57 +0200
commita90d18d8dca4e50b6f305599caa96828a520be01 (patch)
tree9cc52a34d997179215f4cc8de678e33aff817b0c /kconfiglib.py
parent463cebd7f8bf26673732bf5afcd113ee5e2268b7 (diff)
Backup
Diffstat (limited to 'kconfiglib.py')
-rw-r--r--kconfiglib.py648
1 files changed, 330 insertions, 318 deletions
diff --git a/kconfiglib.py b/kconfiglib.py
index 04edb67..0d92b48 100644
--- a/kconfiglib.py
+++ b/kconfiglib.py
@@ -110,16 +110,17 @@ A || B (OR, A, B)
A = B (EQUAL, A, B)
A != "foo" (UNEQUAL, A, "foo")
A || (B && C && D) (OR, A, (AND, B, (AND, C, D)))
-y "y"
-"y" "y"
+y config.y
+"y" config.y
-As seen in the final two examples, n/m/y are always represented as the strings
-(constant symbols) "n"/"m"/"y" in Kconfiglib, regardless of whether they're
-written with or without quotes. This simplifies some internals.
+As seen in the final two examples, n/m/y are always represented as the constant
+symbols config.n/m/y, regardless of whether they're written with or without
+quotes. 'config' represents the Config instance of the configuration the
+expression appears in.
A missing expression (e.g. <cond> if the 'if <cond>' part is removed from
-'default A if <conf>') is represented as "y". The standard __str__() functions
-avoid printing 'if y' conditions to give cleaner output.
+'default A if <conf>') is represented as config.y. The standard __str__()
+functions avoid printing 'if y' conditions to give cleaner output.
Implementation note
-------------------
@@ -131,6 +132,8 @@ Send bug reports, suggestions, and questions to ulfalizer a.t Google's email
service (or open a ticket on the GitHub page).
"""
+# TODO: document n/m/y
+
import errno
import os
import platform
@@ -253,13 +256,17 @@ class Config(object):
"_set_re",
"_unset_re",
"config_prefix",
+ "const_syms",
"defconfig_list",
"defined_syms",
+ "m",
"modules",
+ "n",
"named_choices",
"srctree",
"syms",
"top_node",
+ "y",
)
#
@@ -308,20 +315,40 @@ class Config(object):
self._print_undef_assign = False
self.syms = {}
+ self.const_syms = {}
self.defined_syms = []
self.named_choices = {}
# Used for quickly invalidating all choices
self._choices = []
- self.modules = self._lookup_sym("MODULES")
+ for nmy in "n", "m", "y":
+ sym = Symbol()
+ sym.config = self
+ sym.name = nmy
+ sym.is_constant = True
+ sym._type = TRISTATE
+ sym._cached_val = nmy
+
+ self.const_syms[nmy] = sym
+
+ self.n = self.const_syms["n"]
+ self.m = self.const_syms["m"]
+ self.y = self.const_syms["y"]
+
+ # Just to make n/m/y well-formed symbols
+ for nmy in "n", "m", "y":
+ sym = self.const_syms[nmy]
+ sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+ self.modules = self._lookup_sym("MODULES", False)
self.defconfig_list = None
# Predefined symbol. DEFCONFIG_LIST uses this.
- uname_sym = Symbol()
+ uname_sym = self._lookup_const_sym("UNAME_RELEASE", False)
uname_sym._type = STRING
- uname_sym.name = "UNAME_RELEASE"
- uname_sym.config = self
- uname_sym.defaults.append((platform.uname()[2], "y"))
+ uname_sym.defaults.append(
+ (self._lookup_const_sym(platform.uname()[2], False),
+ self.y))
# env_var doubles as the SYMBOL_AUTO flag from the C implementation, so
# just set it to something. The naming breaks a bit here, but it's
# pretty obscure.
@@ -331,10 +358,10 @@ class Config(object):
self.top_node = MenuNode()
self.top_node.config = self
self.top_node.item = MENU
- self.top_node.visibility = None
- self.top_node.prompt = ("Linux Kernel Configuration", "y")
+ self.top_node.visibility = self.y
+ self.top_node.prompt = ("Linux Kernel Configuration", self.y)
self.top_node.parent = None
- self.top_node.dep = "y"
+ self.top_node.dep = self.y
self.top_node.filename = filename
self.top_node.linenr = 1
@@ -342,7 +369,7 @@ class Config(object):
self._parse_block(_FileFeed(self._open(filename), filename),
None, # end_token
self.top_node, # parent
- "y", # visible_if_deps
+ self.y, # visible_if_deps
None, # prev_line
self.top_node) # prev_node
@@ -372,7 +399,7 @@ class Config(object):
return None
for filename, cond_expr in self.defconfig_list.defaults:
if eval_expr(cond_expr) != "n":
- filename = self._expand_sym_refs(filename)
+ filename = self._expand_sym_refs(filename.value)
try:
with self._open(filename) as f:
return f.name
@@ -502,11 +529,17 @@ class Config(object):
conditional ('if ...') expressions in the configuration (as well as in
the C tools). m is rewritten to 'm && MODULES'.
"""
- return eval_expr(self._parse_expr(self._tokenize(s, True),
- s,
- None, # filename
- None, # linenr
- True)) # transform_m
+ return eval_expr(
+ self._parse_expr(
+ self._tokenize(
+ s,
+ True, # for_eval_string
+ None, # filename
+ None), # linenr
+ s,
+ None, # filename
+ None, # linenr
+ True)) # transform_m
def unset_values(self):
"""
@@ -612,7 +645,7 @@ class Config(object):
# Kconfig parsing
#
- def _tokenize(self, s, for_eval, filename=None, linenr=None):
+ def _tokenize(self, s, for_eval_string, filename, linenr):
"""
Returns a _Feed instance containing tokens derived from the string 's'.
Registers any new symbols encountered (via _lookup_sym()).
@@ -620,7 +653,7 @@ class Config(object):
Tries to be reasonably speedy by processing chunks of text via regexes
and string operations where possible. This is a hotspot during parsing.
- for_eval:
+ for_eval_string:
True when parsing an expression for a call to Config.eval_string(),
in which case we should not treat the first token specially nor
register new symbols.
@@ -629,7 +662,7 @@ class Config(object):
# Tricky implementation detail: While parsing a token, 'token' refers
# to the previous token. See _NOT_REF for why this is needed.
- if for_eval:
+ if for_eval_string:
token = None
tokens = []
@@ -678,13 +711,14 @@ class Config(object):
token = keyword
elif token not in _STRING_LEX:
- # It's a symbol
+ # It's a non-const symbol...
if name in ("n", "m", "y"):
- # Always represent n, m, y as strings (constant
- # symbols). This simplifies the expression logic.
- token = name
+ # ...except we translate n, m, and y into the
+ # corresponding constant symbols, like the C
+ # implementation
+ token = self._lookup_const_sym(name, for_eval_string)
else:
- token = self._lookup_sym(name, for_eval)
+ token = self._lookup_sym(name, for_eval_string)
else:
# It's a case of missing quotes. For example, the
@@ -699,7 +733,7 @@ class Config(object):
token = name
else:
- # Not an identifier/keyword
+ # Not keyword/non-const symbol
# Note: _id_keyword_match and _initial_token_match strip
# trailing whitespace, making it safe to assume s[i] is the
@@ -722,7 +756,7 @@ class Config(object):
end = s.find(c, i)
if end == -1:
_tokenization_error(s, filename, linenr)
- token = s[i:end]
+ val = s[i:end]
i = end + 1
else:
# Slow path: This could probably be sped up, but it's a
@@ -744,7 +778,17 @@ class Config(object):
val += c
i += 1
i += 1
- token = val
+
+ # This is the only place where we don't survive with a
+ # single token of lookback, hence the kludge:
+ # 'option env="FOO"' does not refer to a constant symbol
+ # named "FOO".
+ token = \
+ val \
+ if token in _STRING_LEX or \
+ (not for_eval_string and \
+ tokens[0] == _T_OPTION) else \
+ self._lookup_const_sym(val, for_eval_string)
elif c == "&":
# Invalid characters are ignored
@@ -935,10 +979,10 @@ class Config(object):
node.filename = line_feeder.filename
node.linenr = line_feeder.linenr
node.dep = \
- _make_and(parent.dep,
- self._parse_expr(tokens, line,
- line_feeder.filename,
- line_feeder.linenr, True))
+ self._make_and(parent.dep,
+ self._parse_expr(tokens, line,
+ line_feeder.filename,
+ line_feeder.linenr, True))
self._parse_block(line_feeder,
_T_ENDIF,
@@ -956,7 +1000,7 @@ class Config(object):
node = MenuNode()
node.config = self
node.item = MENU
- node.visibility = "y"
+ node.visibility = self.y
node.parent = parent
node.filename = line_feeder.filename
node.linenr = line_feeder.linenr
@@ -968,7 +1012,8 @@ class Config(object):
self._parse_block(line_feeder,
_T_ENDMENU,
node, # parent
- _make_and(visible_if_deps, node.visibility),
+ self._make_and(visible_if_deps,
+ node.visibility),
prev_line,
node) # prev_node
node.list = node.next
@@ -1033,7 +1078,7 @@ class Config(object):
prev_node.next = prev_node = node
elif t0 == _T_MAINMENU:
- self.top_node.prompt = (tokens.next(), "y")
+ self.top_node.prompt = (tokens.next(), self.y)
self.top_node.filename = line_feeder.filename
self.top_node.linenr = line_feeder.linenr
@@ -1044,16 +1089,16 @@ class Config(object):
def _parse_cond(self, tokens, line, filename, linenr):
"""
Parses an optional 'if <expr>' construct and returns the parsed <expr>,
- or "y" if the next token is not _T_IF
+ or self.y if the next token is not _T_IF
"""
return self._parse_expr(tokens, line, filename, linenr, True) \
- if tokens.check(_T_IF) else "y"
+ if tokens.check(_T_IF) else self.y
def _parse_val_and_cond(self, tokens, line, filename, linenr):
"""
Parses '<expr1> if <expr2>' constructs, where the 'if' part is
- optional. Returns a tuple containing the parsed expressions, with "y"
- as the second element if the 'if' part is missing.
+ optional. Returns a tuple containing the parsed expressions, with
+ config.y as the second element if the 'if' part is missing.
"""
return (self._parse_expr(tokens, line, filename, linenr, False),
self._parse_cond(tokens, line, filename, linenr))
@@ -1092,7 +1137,7 @@ class Config(object):
# Menu node dependency from 'depends on'. Will get propagated to the
# properties above.
- node.dep = "y"
+ node.dep = self.y
# The cached (line, tokens) tuple that we return
last_line = None
@@ -1117,9 +1162,9 @@ class Config(object):
filename, linenr)
node.dep = \
- _make_and(node.dep,
- self._parse_expr(tokens, line, filename,
- linenr, True))
+ self._make_and(node.dep,
+ self._parse_expr(tokens, line, filename,
+ linenr, True))
elif t0 == _T_HELP:
# Find first non-blank (not all-space) line and get its
@@ -1164,34 +1209,24 @@ class Config(object):
_parse_error(line, "only symbols can select", filename,
linenr)
- # HACK: We always represent n/m/y using the constant symbol
- # "n"/"m"/"y" forms, but that causes a crash if a Kconfig file
- # does e.g. 'select n' (which is meaningless and probably stems
- # from a misunderstanding). Seen in U-Boot. Just skip the
- # select.
- target = tokens.next()
- if target not in ("n", "m", "y"):
- selects.append(
- (target,
- self._parse_cond(tokens, line, filename, linenr)))
+ selects.append(
+ (tokens.next(),
+ self._parse_cond(tokens, line, filename, linenr)))
elif t0 == _T_IMPLY:
if not isinstance(node.item, Symbol):
_parse_error(line, "only symbols can imply", filename,
linenr)
- # See above
- target = tokens.next()
- if target not in ("n", "m", "y"):
- implies.append(
- (target,
- self._parse_cond(tokens, line, filename, linenr)))
+ implies.append(
+ (tokens.next(),
+ self._parse_cond(tokens, line, filename, linenr)))
elif t0 in (_T_BOOL, _T_TRISTATE, _T_INT, _T_HEX, _T_STRING):
node.item._type = _TOKEN_TO_TYPE[t0]
if tokens.peek() is not None:
- prompt = self._parse_val_and_cond(tokens, line,
- filename, linenr)
+ prompt = (tokens.next(),
+ self._parse_cond(tokens, line, filename, linenr))
elif t0 == _T_DEFAULT:
defaults.append(
@@ -1199,17 +1234,16 @@ class Config(object):
elif t0 in (_T_DEF_BOOL, _T_DEF_TRISTATE):
node.item._type = _TOKEN_TO_TYPE[t0]
- if tokens.peek() is not None:
- defaults.append(
- self._parse_val_and_cond(tokens, line, filename,
- linenr))
+ defaults.append(
+ self._parse_val_and_cond(tokens, line, filename,
+ linenr))
elif t0 == _T_PROMPT:
# 'prompt' properties override each other within a single
# definition of a symbol, but additional prompts can be added
# by defining the symbol multiple times
- prompt = self._parse_val_and_cond(tokens, line, filename,
- linenr)
+ prompt = (tokens.next(),
+ self._parse_cond(tokens, line, filename, linenr))
elif t0 == _T_RANGE:
ranges.append(
@@ -1236,7 +1270,10 @@ class Config(object):
"service.".format(node.item.name, env_var),
filename, linenr)
else:
- defaults.append((os.environ[env_var], "y"))
+ defaults.append(
+ (self._lookup_const_sym(os.environ[env_var],
+ False),
+ self.y))
elif tokens.check(_T_DEFCONFIG_LIST):
if self.defconfig_list is None:
@@ -1283,9 +1320,9 @@ class Config(object):
filename, linenr)
node.visibility = \
- _make_and(node.visibility,
- self._parse_expr(tokens, line, filename, linenr,
- True))
+ self._make_and(node.visibility,
+ self._parse_expr(tokens, line, filename,
+ linenr, True))
elif t0 == _T_OPTIONAL:
if not isinstance(node.item, Choice):
@@ -1306,54 +1343,57 @@ class Config(object):
# from node.dep propagated.
# First propagate parent dependencies to node.dep
- node.dep = _make_and(node.dep, node.parent.dep)
+ node.dep = self._make_and(node.dep, node.parent.dep)
if isinstance(node.item, (Symbol, Choice)):
if isinstance(node.item, Symbol):
- node.item.direct_deps = \
- _make_or(node.item.direct_deps, node.dep)
+ node.item.direct_dep = \
+ self._make_or(node.item.direct_dep, node.dep)
# Set the prompt, with dependencies propagated
if prompt is not None:
node.prompt = (prompt[0],
- _make_and(_make_and(prompt[1], node.dep),
- visible_if_deps))
+ self._make_and(self._make_and(prompt[1],
+ node.dep),
+ visible_if_deps))
else:
node.prompt = None
# Add the new defaults, with dependencies propagated
for val_expr, cond_expr in defaults:
node.item.defaults.append(
- (val_expr, _make_and(cond_expr, node.dep)))
+ (val_expr, self._make_and(cond_expr, node.dep)))
# Add the new ranges, with dependencies propagated
for low, high, cond_expr in ranges:
node.item.ranges.append(
- (low, high, _make_and(cond_expr, node.dep)))
+ (low, high, self._make_and(cond_expr, node.dep)))
# Handle selects
for target, cond_expr in selects:
# Only stored for convenience. Not used during evaluation.
node.item.selects.append(
- (target, _make_and(cond_expr, node.dep)))
+ (target, self._make_and(cond_expr, node.dep)))
# Modify the dependencies of the selected symbol
target.rev_dep = \
- _make_or(target.rev_dep,
- _make_and(node.item,
- _make_and(cond_expr, node.dep)))
+ self._make_or(target.rev_dep,
+ self._make_and(node.item,
+ self._make_and(cond_expr,
+ node.dep)))
# Handle implies
for target, cond_expr in implies:
# Only stored for convenience. Not used during evaluation.
node.item.implies.append(
- (target, _make_and(cond_expr, node.dep)))
+ (target, self._make_and(cond_expr, node.dep)))
# Modify the dependencies of the implied symbol
target.weak_rev_dep = \
- _make_or(target.weak_rev_dep,
- _make_and(node.item,
- _make_and(cond_expr, node.dep)))
+ self._make_or(target.weak_rev_dep,
+ self._make_and(node.item,
+ self._make_and(cond_expr,
+ node.dep)))
# Return cached non-property line
return last_line
@@ -1438,7 +1478,7 @@ class Config(object):
def _parse_factor(self, feed, line, filename, linenr, transform_m):
token = feed.next()
- if isinstance(token, (Symbol, str)):
+ if isinstance(token, Symbol):
# Plain symbol or relation
next_token = feed.peek()
@@ -1448,8 +1488,8 @@ class Config(object):
# For conditional expressions ('depends on <expr>',
# '... if <expr>', etc.), "m" and m are rewritten to
# "m" && MODULES.
- if transform_m and token == "m":
- return (AND, "m", self.modules)
+ if transform_m and token is self.m:
+ return (AND, self.m, self.modules)
return token
@@ -1473,12 +1513,12 @@ class Config(object):
# Symbol lookup
#
- def _lookup_sym(self, name, for_eval=False):
+ def _lookup_sym(self, name, for_eval_string):
"""
Fetches the symbol 'name' from the symbol table, creating and
- registering it if it does not exist. If 'for_eval' is True, the symbol
- won't be added to the symbol table if it does not exist. This is for
- Config.eval_string().
+ registering it if it does not exist. TODO If 'for_eval_string' is True,
+ the symbol won't be added to the symbol table if it does not exist.
+ This is for Config.eval_string().
"""
if name in self.syms:
return self.syms[name]
@@ -1486,10 +1526,32 @@ class Config(object):
sym = Symbol()
sym.config = self
sym.name = name
- if for_eval:
+ sym.is_constant = False
+ sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+ if for_eval_string:
self._warn("no symbol {} in configuration".format(name))
else:
self.syms[name] = sym
+
+ return sym
+
+ def _lookup_const_sym(self, name, for_eval_string):
+ """
+ TODO: say something
+ """
+ if name in self.const_syms:
+ return self.const_syms[name]
+
+ sym = Symbol()
+ sym.config = self
+ sym.name = name
+ sym.is_constant = True
+ sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+ if not for_eval_string:
+ self.const_syms[name] = sym
+
return sym
#
@@ -1534,6 +1596,7 @@ class Config(object):
elif (node.item == MENU and eval_expr(node.dep) != "n" and
eval_expr(node.visibility) != "n") or \
(node.item == COMMENT and eval_expr(node.dep) != "n"):
+
add_fn("\n#\n# {}\n#\n".format(node.prompt[0]))
# Iterative tree walk using parent pointers
@@ -1557,21 +1620,14 @@ class Config(object):
def _build_dep(self):
"""
- Populates the Symbol._direct_dependents sets, linking the symbol to the
- symbols that immediately depend on it in the sense that changing the
- value of the symbol might affect the values of those other symbols.
- This is used for caching/invalidation purposes. The calculated sets
- might be larger than necessary as we don't do any complicated analysis
- of the expressions.
- """
+ Populates the Symbol._direct_dependents sets, which link symbols to the
+ symbols that immediately depend on them in the sense that changing the
+ value of the symbol might affect the values of the dependent symbols.
+ This is used for caching/invalidation purposes.
- # Adds 'sym' as a directly dependent symbol to all symbols that appear
- # in the expression 'expr'
- def add_expr_deps(expr, sym):
- res = []
- _expr_syms(expr, res)
- for expr_sym in res:
- expr_sym._direct_dependents.add(sym)
+ The calculated sets might be larger than necessary as we don't do any
+ complex analysis of the expressions.
+ """
# The directly dependent symbols of a symbol S are:
#
@@ -1579,13 +1635,12 @@ class Config(object):
# condition), weak_rev_dep (imply condition), or ranges depend on S
#
# - Any symbol that has S as a direct dependency (has S in
- # direct_deps). This is needed to get invalidation right for
+ # direct_dep). This is needed to get invalidation right for
# 'imply'.
#
- # - Any symbols that belong to the same choice statement as S
- # (these won't be included in S._direct_dependents as that makes the
- # dependency graph unwieldy, but S._get_dependent() will include
- # them)
+ # - Any symbols that belong to the same choice statement as S. These
+ # won't be included in S._direct_dependents as it creates dependency
+ # loops, but S._get_dependent() includes them.
#
# - Any symbols in a choice statement that depends on S
@@ -1597,28 +1652,29 @@ class Config(object):
for sym in self.defined_syms:
for node in sym.nodes:
if node.prompt is not None:
- add_expr_deps(node.prompt[1], sym)
+ _make_depend_on(sym, node.prompt[1])
for value, cond in sym.defaults:
- add_expr_deps(value, sym)
- add_expr_deps(cond, sym)
+ _make_depend_on(sym, value)
+ _make_depend_on(sym, cond)
- add_expr_deps(sym.rev_dep, sym)
- add_expr_deps(sym.weak_rev_dep, sym)
+ _make_depend_on(sym, sym.rev_dep)
+ _make_depend_on(sym, sym.weak_rev_dep)
- for l, u, e in sym.ranges:
- add_expr_deps(l, sym)
- add_expr_deps(u, sym)
- add_expr_deps(e, sym)
+ for low, high, cond in sym.ranges:
+ _make_depend_on(sym, low)
+ _make_depend_on(sym, high)
+ _make_depend_on(sym, cond)
- add_expr_deps(sym.direct_deps, sym)
+ _make_depend_on(sym, sym.direct_dep)
if sym.choice is not None:
for node in sym.choice.nodes:
if node.prompt is not None:
- add_expr_deps(node.prompt[1], sym)
- for _, e in sym.choice.defaults:
- add_expr_deps(e, sym)
+ _make_depend_on(sym, node.prompt[1])
+
+ for _, cond in sym.choice.defaults:
+ _make_depend_on(sym, cond)
def _invalidate_all(self):
# Undefined symbols never change value and don't need to be
@@ -1674,6 +1730,36 @@ class Config(object):
'attempt to assign the value "{}" to the undefined symbol {}' \
.format(val, name), filename, linenr)
+ def _make_and(self, e1, e2):
+ """
+ Constructs an AND (&&) expression. Performs trivial simplification.
+ """
+ if e1 is self.y:
+ return e2
+
+ if e2 is self.y:
+ return e1
+
+ if e1 is self.n or e2 is self.n:
+ return self.n
+
+ return (AND, e1, e2)
+
+ def _make_or(self, e1, e2):
+ """
+ Constructs an OR (||) expression. Performs trivial simplification.
+ """
+ if e1 is self.n:
+ return e2
+
+ if e2 is self.n:
+ return e1
+
+ if e1 is self.y or e2 is self.y:
+ return self.y
+
+ return (OR, e1, e2)
+
class Symbol(object):
"""
Represents a configuration symbol:
@@ -1774,23 +1860,23 @@ class Symbol(object):
defaults:
List of (default, cond) tuples for the symbol's 'default's. For example,
'default A && B if C || D' is represented as ((AND, A, B), (OR, C, D)).
- If there is no condition, 'cond' is None.
+ If no condition was given, 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to
'default' conditions.
selects:
List of (symbol, cond) tuples for the symbol's 'select's. For example,
- 'select A if B' is represented as (A, B). If there is no condition,
- 'cond' is None.
+ 'select A if B' is represented as (A, B). If no condition was given,
+ 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to 'select'
conditions.
implies:
List of (symbol, cond) tuples for the symbol's 'imply's. For example,
- 'imply A if B' is represented as (A, B). If there is no condition, 'cond'
- is None.
+ 'imply A if B' is represented as (A, B). If no condition was given,
+ 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to 'imply'
conditions.
@@ -1798,7 +1884,7 @@ class Symbol(object):
ranges:
List of (low, high, cond) tuples for the symbol's 'range's. For example,
'range 1 2 if A' is represented as (1, 2, A). If there is no condition,
- 'cond' is None.
+ 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to 'range'
conditions.
@@ -1816,7 +1902,7 @@ class Symbol(object):
weak_rev_dep:
Like rev_dep, for imply.
- direct_deps:
+ direct_dep:
The 'depends on' dependencies. If a symbol is defined in multiple
locations, the dependencies at each location are ORed together.
@@ -1854,10 +1940,11 @@ class Symbol(object):
"choice",
"config",
"defaults",
- "direct_deps",
+ "direct_dep",
"env_var",
"implies",
"is_allnoconfig_y",
+ "is_constant",
"name",
"nodes",
"ranges",
@@ -1926,7 +2013,7 @@ class Symbol(object):
# Weak reverse dependencies are only considered if our
# direct dependencies are met
- if eval_expr(self.direct_deps) != "n":
+ if eval_expr(self.direct_dep) != "n":
weak_rev_dep_val = \
eval_expr(self.weak_rev_dep)
if weak_rev_dep_val != "n":
@@ -1974,13 +2061,10 @@ class Symbol(object):
if eval_expr(cond_expr) != "n":
has_active_range = True
- low_str = _str_val(low_expr)
- high_str = _str_val(high_expr)
-
- low = int(low_str, base) if \
- _is_base_n(low_str, base) else 0
- high = int(high_str, base) if \
- _is_base_n(high_str, base) else 0
+ low = int(low_expr.value, base) if \
+ _is_base_n(low_expr.value, base) else 0
+ high = int(high_expr.value, base) if \
+ _is_base_n(high_expr.value, base) else 0
break
else:
@@ -2009,7 +2093,7 @@ class Symbol(object):
# preserved as is. Defaults that do not satisfy a range
# constraints are clamped and take on a standard form.
- val = _str_val(val_expr)
+ val = val_expr.value
if _is_base_n(val, base):
val_num = int(val, base)
@@ -2044,7 +2128,7 @@ class Symbol(object):
for val_expr, cond_expr in self.defaults:
if eval_expr(cond_expr) != "n":
self._write_to_conf = True
- val = _str_val(val_expr)
+ val = val_expr.value
break
self._cached_val = val
@@ -2187,7 +2271,7 @@ class Symbol(object):
if self is self.config.modules:
fields.append("is the modules symbol")
- fields.append("direct deps " + eval_expr(self.direct_deps))
+ fields.append("direct deps " + eval_expr(self.direct_dep))
fields.append("{} menu node{}"
.format(len(self.nodes),
@@ -2207,24 +2291,24 @@ class Symbol(object):
# These attributes are always set on the instance from outside and
# don't need defaults:
+ # _already_written
# config
+ # direct_dep
+ # is_constant
# name
- # _already_written
+ # rev_dep
+ # weak_rev_dep
self._type = UNKNOWN
self.defaults = []
self.selects = []
self.implies = []
self.ranges = []
- self.rev_dep = "n"
- self.weak_rev_dep = "n"
self.nodes = []
self.user_value = None
- self.direct_deps = "n"
-
# Populated in Config._build_dep() after parsing. Links the symbol to
# the symbols that immediately depend on it (in a caching/invalidation
# sense). The total set of dependent symbols for the symbol is
@@ -2245,9 +2329,8 @@ class Symbol(object):
# Flags
- self.env_var = None
-
self.choice = None
+ self.env_var = None
self.is_allnoconfig_y = False
# Should the symbol get an entry in .config? Calculated along with the
@@ -2488,7 +2571,7 @@ class Choice(object):
defaults:
List of (symbol, cond) tuples for the choices 'defaults's. For example,
'default A if B && C' is represented as (A, (AND, B, C)). If there is no
- condition, 'cond' is None.
+ condition, 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to
'default' conditions.
@@ -2789,7 +2872,7 @@ class MenuNode(object):
visibility:
The 'visible if' dependencies for the menu node (which must represent a
- menu). None if there are no 'visible if' dependencies. 'visible if'
+ menu). config.y if there are no 'visible if' dependencies. 'visible if'
dependencies are recursively propagated to the prompts of symbols and
choices within the menu.
@@ -2826,18 +2909,23 @@ class MenuNode(object):
if isinstance(self.item, Symbol):
fields.append("menu node for symbol " + self.item.name)
+
elif isinstance(self.item, Choice):
s = "menu node for choice"
if self.item.name is not None:
s += " " + self.item.name
fields.append(s)
+
elif self.item == MENU:
fields.append("menu node for menu")
+
elif self.item == COMMENT:
fields.append("menu node for comment")
+
elif self.item is None:
fields.append("menu node for if (should not appear in the final "
" tree)")
+
else:
raise InternalError("unable to determine type in "
"MenuNode.__repr__()")
@@ -2923,9 +3011,6 @@ def eval_expr(expr):
# regardless of their value
return expr.value if expr._type in (BOOL, TRISTATE) else "n"
- if isinstance(expr, str):
- return expr if expr in ("m", "y") else "n"
-
if expr[0] == AND:
ev1 = eval_expr(expr[1])
@@ -2955,25 +3040,20 @@ def eval_expr(expr):
# pythonic way to structure this.
oper, op1, op2 = expr
- op1_type, op1_str = _type_and_val(op1)
- op2_type, op2_str = _type_and_val(op2)
# If both operands are strings...
- if op1_type == STRING and op2_type == STRING:
+ if op1._type == STRING and op2._type == STRING:
# ...then compare them lexicographically
- comp = _strcmp(op1_str, op2_str)
+ comp = _strcmp(op1.value, op2.value)
else:
- # Otherwise, try to compare them as numbers
+ # Otherwise, try to compare them as numbers...
try:
- comp = int(op1_str, _TYPE_TO_BASE[op1_type]) - \
- int(op2_str, _TYPE_TO_BASE[op2_type])
+ comp = int(op1.value, _TYPE_TO_BASE[op1._type]) - \
+ int(op2.value, _TYPE_TO_BASE[op2._type])
except ValueError:
- # They're not both valid numbers. If the comparison is
- # anything but = or !=, return 'n'. Otherwise, reuse
- # _strcmp() to check for (in)equality.
- if oper not in (EQUAL, UNEQUAL):
- return "n"
- comp = _strcmp(op1_str, op2_str)
+ # Fall back on a lexicographic comparison if the operands don't
+ # parse as numbers
+ comp = _strcmp(op1.value, op2.value)
if oper == EQUAL: res = comp == 0
elif oper == UNEQUAL: res = comp != 0
@@ -2987,6 +3067,26 @@ def eval_expr(expr):
_internal_error("Internal error while evaluating expression: "
"unknown operation {}.".format(expr[0]))
+def expr_str(expr):
+ if isinstance(expr, Symbol):
+ return expr.name if not expr.is_constant else '"{}"'.format(expr.name)
+
+ if expr[0] == NOT:
+ if isinstance(expr[1], Symbol):
+ return "!" + expr_str(expr[1])
+ return "!({})".format(expr_str(expr[1]))
+
+ if expr[0] == AND:
+ return "{} && {}".format(_format_and_op(expr[1]),
+ _format_and_op(expr[2]))
+
+ if expr[0] == OR:
+ return "{} || {}".format(expr_str(expr[1]), expr_str(expr[2]))
+
+ # Relation
+ return "{} {} {}".format(expr_str(expr[1]),
+ _RELATION_TO_STR[expr[0]],
+ expr_str(expr[2]))
#
# Internal classes
@@ -3109,36 +3209,6 @@ def _get_visibility(sc):
return vis
-def _make_and(e1, e2):
- """
- Constructs an AND (&&) expression. Performs trivial simplification.
- """
- if e1 == "y":
- return e2
-
- if e2 == "y":
- return e1
-
- if e1 == "n" or e2 == "n":
- return "n"
-
- return (AND, e1, e2)
-
-def _make_or(e1, e2):
- """
- Constructs an OR (||) expression. Performs trivial simplification.
- """
- if e1 == "n":
- return e2
-
- if e2 == "n":
- return e1
-
- if e1 == "y" or e2 == "y":
- return "y"
-
- return (OR, e1, e2)
-
def _tri_min(v1, v2):
"""
Returns the smallest tristate value among v1 and v2.
@@ -3151,88 +3221,40 @@ def _tri_max(v1, v2):
"""
return v1 if _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] else v2
-def _expr_syms_rec(expr, res):
+def _make_depend_on(sym, expr):
"""
- _expr_syms() helper. Recurses through expressions.
+ Adds 'sym' as a dependency to all symbols in 'expr'. Constant symbols in
+ 'expr' are skipped as they can never change value anyway.
"""
if isinstance(expr, Symbol):
- res.append(expr)
- elif isinstance(expr, str):
- return
+ if not expr.is_constant:
+ expr._direct_dependents.add(sym)
+
elif expr[0] in (AND, OR):
- _expr_syms_rec(expr[1], res)
- _expr_syms_rec(expr[2], res)
+ _make_depend_on(sym, expr[1])
+ _make_depend_on(sym, expr[2])
+
elif expr[0] == NOT:
- _expr_syms_rec(expr[1], res)
+ _make_depend_on(sym, expr[1])
+
elif expr[0] in _RELATIONS:
- if isinstance(expr[1], Symbol):
- res.append(expr[1])
- if isinstance(expr[2], Symbol):
- res.append(expr[2])
+ if not expr[1].is_constant:
+ expr[1]._direct_dependents.add(sym)
+ if not expr[2].is_constant:
+ expr[2]._direct_dependents.add(sym)
+
else:
_internal_error("Internal error while fetching symbols from an "
"expression with token stream {}.".format(expr))
-def _expr_syms(expr, res):
- """
- append()s the symbols in 'expr' to 'res'. Does not remove duplicates.
- """
- if expr is not None:
- _expr_syms_rec(expr, res)
-
-def _str_val(obj):
- """
- Returns the value of obj as a string. If obj is not a string (constant
- symbol), it must be a Symbol.
- """
- return obj if isinstance(obj, str) else obj.value
-
def _format_and_op(expr):
"""
- _expr_to_str() helper. Returns the string representation of 'expr', which
- is assumed to be an operand to AND, with parentheses added if needed.
+ expr_str() helper. Returns the string representation of 'expr', which is
+ assumed to be an operand to AND, with parentheses added if needed.
"""
if isinstance(expr, tuple) and expr[0] == OR:
- return "({})".format(_expr_to_str(expr))
- return _expr_to_str(expr)
-
-def _expr_to_str(expr):
- if isinstance(expr, str):
- if expr in ("n", "m", "y"):
- # Don't print spammy quotes for these
- return expr
- return '"{}"'.format(expr)
-
- if isinstance(expr, Symbol):
- return expr.name
-
- if expr[0] == NOT:
- if isinstance(expr[1], (str, Symbol)):
- return "!" + _expr_to_str(expr[1])
- return "!({})".format(_expr_to_str(expr[1]))
-
- if expr[0] == AND:
- return "{} && {}".format(_format_and_op(expr[1]),
- _format_and_op(expr[2]))
-
- if expr[0] == OR:
- return "{} || {}".format(_expr_to_str(expr[1]),
- _expr_to_str(expr[2]))
-
- # Relation
- return "{} {} {}".format(_expr_to_str(expr[1]),
- _RELATION_TO_STR[expr[0]],
- _expr_to_str(expr[2]))
-
-def _type_and_val(obj):
- """
- Helper to hack around the fact that we don't represent plain strings as
- Symbols. Takes either a plain string or a Symbol and returns a (<type>,
- <value>) tuple.
- """
- return (obj._type, obj.value) \
- if not isinstance(obj, str) \
- else (STRING, obj)
+ return "({})".format(expr_str(expr))
+ return expr_str(expr)
def _indentation(line):
"""
@@ -3335,8 +3357,8 @@ def _sym_choice_str(sc):
if node.prompt is not None:
prompt_str = 'prompt "{}"'.format(node.prompt[0])
- if node.prompt[1] != "y":
- prompt_str += " if " + _expr_to_str(node.prompt[1])
+ if node.prompt[1] is not sc.config.y:
+ prompt_str += " if " + expr_str(node.prompt[1])
indent_add(prompt_str)
if node is sc.nodes[0]:
@@ -3353,16 +3375,16 @@ def _sym_choice_str(sc):
if isinstance(sc, Symbol):
for range_ in sc.ranges:
range_string = "range {} {}" \
- .format(_expr_to_str(range_[0]),
- _expr_to_str(range_[1]))
- if range_[2] != "y":
- range_string += " if " + _expr_to_str(range_[2])
+ .format(expr_str(range_[0]),
+ expr_str(range_[1]))
+ if range_[2] is not sc.config.y:
+ range_string += " if " + expr_str(range_[2])
indent_add(range_string)
for default in sc.defaults:
- default_string = "default " + _expr_to_str(default[0])
- if default[1] != "y":
- default_string += " if " + _expr_to_str(default[1])
+ default_string = "default " + expr_str(default[0])
+ if default[1] is not sc.config.y:
+ default_string += " if " + expr_str(default[1])
indent_add(default_string)
if isinstance(sc, Choice) and sc.is_optional:
@@ -3371,14 +3393,14 @@ def _sym_choice_str(sc):
if isinstance(sc, Symbol):
for select in sc.selects:
select_string = "select " + select[0].name
- if select[1] != "y":
- select_string += " if " + _expr_to_str(select[1])
+ if select[1] is not sc.config.y:
+ select_string += " if " + expr_str(select[1])
indent_add(select_string)
for imply in sc.implies:
imply_string = "imply " + imply[0].name
- if imply[1] != "y":
- imply_string += " if " + _expr_to_str(imply[1])
+ if imply[1] is not sc.config.y:
+ imply_string += " if " + expr_str(imply[1])
indent_add(imply_string)
if node.help is not None:
@@ -3394,45 +3416,36 @@ def _sym_choice_str(sc):
# Menu manipulation
-def _eq_to_sym(eq):
- """
- _expr_depends_on() helper. For (in)equalities of the form sym = y/m or
- sym != n, returns sym. For other (in)equalities, returns None.
- """
- relation, left, right = eq
-
- # Make sure the symbol (if any) appears to the left
- if not isinstance(left, Symbol):
- left, right = right, left
- if not isinstance(left, Symbol):
- return None
- if (relation == EQUAL and right in ("m", "y")) or \
- (relation == UNEQUAL and right == "n"):
- return left
- return None
-
def _expr_depends_on(expr, sym):
"""
Reimplementation of expr_depends_symbol() from mconf.c. Used to
determine if a submenu should be implicitly created, which influences
what items inside choice statements are considered choice items.
"""
- if expr is None:
- return False
+ if isinstance(expr, Symbol):
+ return expr is sym
- def rec(expr):
- if isinstance(expr, str):
+ if expr[0] in (EQUAL, UNEQUAL):
+ # Check for one of the following:
+ # sym = m/y, m/y = sym, sym != n, n != sym
+
+ left, right = expr[1:]
+
+ if right is sym:
+ left, right = right, left
+
+ if left is not sym:
return False
- if isinstance(expr, Symbol):
- return expr is sym
- if expr[0] in (EQUAL, UNEQUAL):
- return _eq_to_sym(expr) is sym
- if expr[0] == AND:
- return rec(expr[1]) or rec(expr[2])
- return False
+ return (expr[0] == EQUAL and right is sym.config.m or \
+ right is sym.config.y) or \
+ (expr[0] == UNEQUAL and right is sym.config.n)
- return rec(expr)
+ if expr[0] == AND:
+ return _expr_depends_on(expr[1], sym) or \
+ _expr_depends_on(expr[2], sym)
+
+ return False
def _has_auto_menu_dep(node1, node2):
"""
@@ -3440,7 +3453,6 @@ def _has_auto_menu_dep(node1, node2):
has a prompt, we check its condition. Otherwise, we look directly at
node2.dep.
"""
-
if node2.prompt:
return _expr_depends_on(node2.prompt[1], node1.item)
@@ -3776,8 +3788,8 @@ _DEFAULT_VALUE = {
_NO_CACHED_SELECTION = object()
# Used in comparisons. 0 means the base is inferred from the format of the
-# string. The entries for BOOL and TRISTATE are a convenience - they should
-# never convert to valid numbers.
+# string. The entries for BOOL and TRISTATE are an implementation convenience:
+# They should never convert to valid numbers.
_TYPE_TO_BASE = {
BOOL: 0,
HEX: 16,