menuconfig: Add jump-to for choices, menus, and comments

Update menuconfig (and Kconfiglib, just to sync) to upstream revision
bf1701b36634b, to add this commit:

    menuconfig: Add jump-to for choices, menus, and comments

    For choices, search the name and the prompt. This is the same as for
    symbols, except names are optional (and rare) for choices.

    For menus and comments, search the prompt (title text).

    When jumping to a non-empty choice or menu, jump into it instead of
    jumping to its menu node. If show-all mode is off and there are
    visible items in the choice/menu, then jump to the first visible
    node. Otherwise, enable show-all and jump to the first node.

Previously, only symbols could be jumped to.

Various other small fixes/improvements are included too:

 - The "no range constraints" text was dropped from the input dialog
   when settings int/hex symbols without an active 'range'. It might be
   more confusing than helpful.

 - A crash when pressing Ctrl-F (the view-help shortcut) with no matches
   in the jump-to dialog was fixed

 - Some gnome-terminal shoddiness was worked around to remove minor
   jumpiness when reducing the height of the terminal

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
Ulf Magnusson 2018-10-15 02:05:45 +02:00 committed by Anas Nashif
commit 85f8c0d4eb
2 changed files with 529 additions and 403 deletions

View file

@ -415,18 +415,30 @@ def_tristate, allowing int, hex, and string symbols to be given a type and a
default at the same time. default at the same time.
Warnings for undefined symbols Extra optional warnings
------------------------------ -----------------------
Setting the environment variable KCONFIG_STRICT to "y" will cause warnings to Some optional warnings can be controlled via environment variables:
be printed for all references to undefined Kconfig symbols within Kconfig
files. The only gotcha is that all hex literals must be prefixed by "0x" or
"0X", to make it possible to distuinguish them from symbol references.
Some projects (e.g. the Linux kernel) use multiple Kconfig trees with many - KCONFIG_WARN_UNDEF: If set to 'y', warnings will be generated for all
shared Kconfig files, leading to some safe undefined symbol references. references to undefined symbols within Kconfig files. The only gotcha is
KCONFIG_STRICT is useful in projects that only have a single Kconfig tree that all hex literals must be prefixed with "0x" or "0X", to make it
though. possible to distinguish them from symbol references.
Some projects (e.g. the Linux kernel) use multiple Kconfig trees with many
shared Kconfig files, leading to some safe undefined symbol references.
KCONFIG_WARN_UNDEF is useful in projects that only have a single Kconfig
tree though.
KCONFIG_STRICT is an older alias for this environment variable, supported
for backwards compatibility.
- KCONFIG_WARN_UNDEF_ASSIGN: If set to 'y', warnings will be generated for
all assignments to undefined symbols within .config files. By default, no
such warnings are generated.
This warning can also be enabled/disabled via
Kconfig.enable/disable_undef_warnings().
Preprocessor user functions defined in Python Preprocessor user functions defined in Python
@ -770,19 +782,9 @@ class Kconfig(object):
KconfigError on syntax errors. Note that Kconfig files are not the same KconfigError on syntax errors. Note that Kconfig files are not the same
as .config files (which store configuration symbol values). as .config files (which store configuration symbol values).
If the environment variable KCONFIG_STRICT is set to "y", warnings will See the module docstring for some environment variables that influence
be generated for all references to undefined symbols within Kconfig default warning settings (KCONFIG_WARN_UNDEF and
files. The reason this isn't the default is that some projects (e.g. KCONFIG_WARN_UNDEF_ASSIGN).
the Linux kernel) use multiple Kconfig trees (one per architecture)
with many shared Kconfig files, leading to some safe references to
undefined symbols.
KCONFIG_STRICT relies on literal hex values being prefixed with 0x/0X.
They are indistinguishable from references to undefined symbols
otherwise.
KCONFIG_STRICT might enable other warnings that depend on there being
just a single Kconfig tree in the future.
filename (default: "Kconfig"): filename (default: "Kconfig"):
The Kconfig file to load. For the Linux kernel, you'll want "Kconfig" The Kconfig file to load. For the Linux kernel, you'll want "Kconfig"
@ -844,7 +846,8 @@ class Kconfig(object):
self._warnings_enabled = warn self._warnings_enabled = warn
self._warn_to_stderr = warn_to_stderr self._warn_to_stderr = warn_to_stderr
self._warn_for_undef_assign = False self._warn_for_undef_assign = \
os.environ.get("KCONFIG_WARN_UNDEF_ASSIGN") == "y"
self._warn_for_redun_assign = True self._warn_for_redun_assign = True
@ -974,16 +977,15 @@ class Kconfig(object):
self._finalize_tree(self.top_node, self.y) self._finalize_tree(self.top_node, self.y)
# Do sanity checks. Some of these depend on everything being # Do sanity checks. Some of these depend on everything being finalized.
# finalized. self._check_sym_sanity()
self._check_choice_sanity()
for sym in self.unique_defined_syms: # KCONFIG_STRICT is an older alias for KCONFIG_WARN_UNDEF, supported
_check_sym_sanity(sym) # for backwards compatibility
if os.environ.get("KCONFIG_WARN_UNDEF") == "y" or \
os.environ.get("KCONFIG_STRICT") == "y":
for choice in self.unique_choices:
_check_choice_sanity(choice)
if os.environ.get("KCONFIG_STRICT") == "y":
self._check_undef_syms() self._check_undef_syms()
@ -1626,8 +1628,8 @@ class Kconfig(object):
def enable_undef_warnings(self): def enable_undef_warnings(self):
""" """
Enables warnings for assignments to undefined symbols. Disabled by Enables warnings for assignments to undefined symbols. Disabled by
default since they tend to be spammy for Kernel configurations (and default unless the KCONFIG_WARN_UNDEF_ASSIGN environment variable was
mostly suggests cleanups). set to 'y' when the Kconfig instance was created.
""" """
self._warn_for_undef_assign = True self._warn_for_undef_assign = True
@ -2450,8 +2452,6 @@ class Kconfig(object):
while self._next_line(): while self._next_line():
t0 = self._next_token() t0 = self._next_token()
if t0 is None:
continue
if t0 in (_T_CONFIG, _T_MENUCONFIG): if t0 in (_T_CONFIG, _T_MENUCONFIG):
# The tokenizer allocates Symbol objects for us # The tokenizer allocates Symbol objects for us
@ -2479,6 +2479,10 @@ class Kconfig(object):
# Tricky Python semantics: This assigns prev.next before prev # Tricky Python semantics: This assigns prev.next before prev
prev.next = prev = node prev.next = prev = node
elif t0 is None:
# Blank line
continue
elif t0 in (_T_SOURCE, _T_RSOURCE, _T_OSOURCE, _T_ORSOURCE): elif t0 in (_T_SOURCE, _T_RSOURCE, _T_OSOURCE, _T_ORSOURCE):
pattern = self._expect_str_and_eol() pattern = self._expect_str_and_eol()
@ -2579,8 +2583,6 @@ class Kconfig(object):
if self._peek_token() is None: if self._peek_token() is None:
choice = Choice() choice = Choice()
choice.direct_dep = self.n choice.direct_dep = self.n
self.choices.append(choice)
else: else:
# Named choice # Named choice
name = self._expect_str_and_eol() name = self._expect_str_and_eol()
@ -2589,10 +2591,10 @@ class Kconfig(object):
choice = Choice() choice = Choice()
choice.name = name choice.name = name
choice.direct_dep = self.n choice.direct_dep = self.n
self.choices.append(choice)
self.named_choices[name] = choice self.named_choices[name] = choice
self.choices.append(choice)
choice.kconfig = self choice.kconfig = self
node = MenuNode() node = MenuNode()
@ -2645,7 +2647,8 @@ class Kconfig(object):
def _parse_properties(self, node): def _parse_properties(self, node):
# Parses and adds properties to the MenuNode 'node' (type, 'prompt', # Parses and adds properties to the MenuNode 'node' (type, 'prompt',
# 'default's, etc.) Properties are later copied up to symbols and # 'default's, etc.) Properties are later copied up to symbols and
# choices in a separate pass after parsing, in _add_props_to_sc(). # choices in a separate pass after parsing, in e.g.
# _add_props_to_sym().
# #
# An older version of this code added properties directly to symbols # An older version of this code added properties directly to symbols
# and choices instead of to their menu nodes (and handled dependency # and choices instead of to their menu nodes (and handled dependency
@ -2664,8 +2667,6 @@ class Kconfig(object):
while self._next_line(): while self._next_line():
t0 = self._next_token() t0 = self._next_token()
if t0 is None:
continue
if t0 in _TYPE_TOKENS: if t0 in _TYPE_TOKENS:
self._set_type(node, _TOKEN_TO_TYPE[t0]) self._set_type(node, _TOKEN_TO_TYPE[t0])
@ -2689,12 +2690,9 @@ class Kconfig(object):
node.selects.append((self._expect_nonconst_sym(), node.selects.append((self._expect_nonconst_sym(),
self._parse_cond())) self._parse_cond()))
elif t0 is _T_IMPLY: elif t0 is None:
if not isinstance(node.item, Symbol): # Blank line
self._parse_error("only symbols can imply") continue
node.implies.append((self._expect_nonconst_sym(),
self._parse_cond()))
elif t0 is _T_DEFAULT: elif t0 is _T_DEFAULT:
node.defaults.append((self._parse_expr(False), node.defaults.append((self._parse_expr(False),
@ -2714,6 +2712,21 @@ class Kconfig(object):
self._expect_sym(), self._expect_sym(),
self._parse_cond())) self._parse_cond()))
elif t0 is _T_IMPLY:
if not isinstance(node.item, Symbol):
self._parse_error("only symbols can imply")
node.implies.append((self._expect_nonconst_sym(),
self._parse_cond()))
elif t0 is _T_VISIBLE:
if not self._check_token(_T_IF):
self._parse_error('expected "if" after "visible"')
node.visibility = self._make_and(node.visibility,
self._expect_expr_and_eol())
elif t0 is _T_OPTION: elif t0 is _T_OPTION:
if self._check_token(_T_ENV): if self._check_token(_T_ENV):
if not self._check_token(_T_EQUAL): if not self._check_token(_T_EQUAL):
@ -2779,13 +2792,6 @@ class Kconfig(object):
else: else:
self._parse_error("unrecognized option") self._parse_error("unrecognized option")
elif t0 is _T_VISIBLE:
if not self._check_token(_T_IF):
self._parse_error('expected "if" after "visible"')
node.visibility = self._make_and(node.visibility,
self._expect_expr_and_eol())
elif t0 is _T_OPTIONAL: elif t0 is _T_OPTIONAL:
if not isinstance(node.item, Choice): if not isinstance(node.item, Choice):
self._parse_error('"optional" is only valid for choices') self._parse_error('"optional" is only valid for choices')
@ -2983,6 +2989,9 @@ class Kconfig(object):
# The calculated sets might be larger than necessary as we don't do any # The calculated sets might be larger than necessary as we don't do any
# complex analysis of the expressions. # complex analysis of the expressions.
# Optimization
make_depend_on = _make_depend_on
# Only calculate _dependents for defined symbols. Constant and # Only calculate _dependents for defined symbols. Constant and
# undefined symbols could theoretically be selected/implied, but it # undefined symbols could theoretically be selected/implied, but it
# wouldn't change their value, so it's not a true dependency. # wouldn't change their value, so it's not a true dependency.
@ -2992,29 +3001,29 @@ class Kconfig(object):
# The prompt conditions # The prompt conditions
for node in sym.nodes: for node in sym.nodes:
if node.prompt: if node.prompt:
_make_depend_on(sym, node.prompt[1]) make_depend_on(sym, node.prompt[1])
# The default values and their conditions # The default values and their conditions
for value, cond in sym.defaults: for value, cond in sym.defaults:
_make_depend_on(sym, value) make_depend_on(sym, value)
_make_depend_on(sym, cond) make_depend_on(sym, cond)
# The reverse and weak reverse dependencies # The reverse and weak reverse dependencies
_make_depend_on(sym, sym.rev_dep) make_depend_on(sym, sym.rev_dep)
_make_depend_on(sym, sym.weak_rev_dep) make_depend_on(sym, sym.weak_rev_dep)
# The ranges along with their conditions # The ranges along with their conditions
for low, high, cond in sym.ranges: for low, high, cond in sym.ranges:
_make_depend_on(sym, low) make_depend_on(sym, low)
_make_depend_on(sym, high) make_depend_on(sym, high)
_make_depend_on(sym, cond) make_depend_on(sym, cond)
# The direct dependencies. This is usually redundant, as the direct # The direct dependencies. This is usually redundant, as the direct
# dependencies get propagated to properties, but it's needed to get # dependencies get propagated to properties, but it's needed to get
# invalidation solid for 'imply', which only checks the direct # invalidation solid for 'imply', which only checks the direct
# dependencies (even if there are no properties to propagate it # dependencies (even if there are no properties to propagate it
# to). # to).
_make_depend_on(sym, sym.direct_dep) make_depend_on(sym, sym.direct_dep)
# In addition to the above, choice symbols depend on the choice # In addition to the above, choice symbols depend on the choice
# they're in, but that's handled automatically since the Choice is # they're in, but that's handled automatically since the Choice is
@ -3027,11 +3036,11 @@ class Kconfig(object):
# The prompt conditions # The prompt conditions
for node in choice.nodes: for node in choice.nodes:
if node.prompt: if node.prompt:
_make_depend_on(choice, node.prompt[1]) make_depend_on(choice, node.prompt[1])
# The default symbol conditions # The default symbol conditions
for _, cond in choice.defaults: for _, cond in choice.defaults:
_make_depend_on(choice, cond) make_depend_on(choice, cond)
def _add_choice_deps(self): def _add_choice_deps(self):
# Choices also depend on the choice symbols themselves, because the # Choices also depend on the choice symbols themselves, because the
@ -3105,7 +3114,7 @@ class Kconfig(object):
elif isinstance(node.item, Symbol): elif isinstance(node.item, Symbol):
# Add the node's non-node-specific properties (defaults, ranges, # Add the node's non-node-specific properties (defaults, ranges,
# etc.) to the Symbol # etc.) to the Symbol
self._add_props_to_sc(node) self._add_props_to_sym(node)
# See if we can create an implicit menu rooted at the Symbol and # See if we can create an implicit menu rooted at the Symbol and
# finalize each child menu node in that menu if so, like for the # finalize each child menu node in that menu if so, like for the
@ -3135,8 +3144,12 @@ class Kconfig(object):
# Empty choices (node.list None) are possible, so this needs to go # Empty choices (node.list None) are possible, so this needs to go
# outside # outside
if isinstance(node.item, Choice): if isinstance(node.item, Choice):
# Add the node's non-node-specific properties to the choice # Add the node's non-node-specific properties to the choice, like
self._add_props_to_sc(node) # _add_props_to_sym() does
choice = node.item
choice.direct_dep = self._make_or(choice.direct_dep, node.dep)
choice.defaults += node.defaults
_finalize_choice(node) _finalize_choice(node)
def _propagate_deps(self, node, visible_if): def _propagate_deps(self, node, visible_if):
@ -3189,52 +3202,197 @@ class Kconfig(object):
cur = cur.next cur = cur.next
def _add_props_to_sc(self, node): def _add_props_to_sym(self, node):
# Copies properties from the menu node 'node' up to its contained # Copies properties from the menu node 'node' up to its contained
# symbol or choice. # symbol, and adds (weak) reverse dependencies to selected/implied
# symbols.
# #
# This can't be rolled into _propagate_deps(), because that function # This can't be rolled into _propagate_deps(), because that function
# traverses the menu tree roughly breadth-first order, meaning # traverses the menu tree roughly breadth-first, meaning properties on
# properties on symbols and choices defined in multiple locations could # symbols defined in multiple locations could end up in the wrong
# end up in the wrong order. # order.
# Symbol or choice sym = node.item
sc = node.item
# See the Symbol class docstring # See the Symbol class docstring
sc.direct_dep = self._make_or(sc.direct_dep, node.dep) sym.direct_dep = self._make_or(sym.direct_dep, node.dep)
sc.defaults += node.defaults sym.defaults += node.defaults
sym.ranges += node.ranges
sym.selects += node.selects
sym.implies += node.implies
# The properties below aren't available on choices # Modify the reverse dependencies of the selected symbol
for target, cond in node.selects:
target.rev_dep = self._make_or(
target.rev_dep,
self._make_and(sym, cond))
if node.ranges: # Modify the weak reverse dependencies of the implied
sc.ranges += node.ranges # symbol
for target, cond in node.implies:
if node.selects: target.weak_rev_dep = self._make_or(
sc.selects += node.selects target.weak_rev_dep,
self._make_and(sym, cond))
# Modify the reverse dependencies of the selected symbol
for target, cond in node.selects:
target.rev_dep = self._make_or(
target.rev_dep,
self._make_and(sc, cond))
if node.implies:
sc.implies += node.implies
# Modify the weak reverse dependencies of the implied
# symbol
for target, cond in node.implies:
target.weak_rev_dep = self._make_or(
target.weak_rev_dep,
self._make_and(sc, cond))
# #
# Misc. # Misc.
# #
def _check_sym_sanity(self):
# Checks various symbol properties that are handiest to check after
# parsing. Only generates errors and warnings.
def num_ok(sym, type_):
# Returns True if the (possibly constant) symbol 'sym' is valid as a value
# for a symbol of type type_ (INT or HEX)
# 'not sym.nodes' implies a constant or undefined symbol, e.g. a plain
# "123"
if not sym.nodes:
return _is_base_n(sym.name, _TYPE_TO_BASE[type_])
return sym.orig_type is type_
for sym in self.unique_defined_syms:
if sym.orig_type in (BOOL, TRISTATE):
# A helper function could be factored out here, but keep it
# speedy/straightforward
for target_sym, _ in sym.selects:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
self._warn("{} selects the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
for target_sym, _ in sym.implies:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
self._warn("{} implies the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
elif sym.orig_type in (STRING, INT, HEX):
for default, _ in sym.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"the {} symbol {} has a malformed default {} -- expected "
"a single symbol"
.format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym),
expr_str(default)))
if sym.orig_type is STRING:
if not default.is_constant and not default.nodes and \
not default.name.isupper():
# 'default foo' on a string symbol could be either a symbol
# reference or someone leaving out the quotes. Guess that
# the quotes were left out if 'foo' isn't all-uppercase
# (and no symbol named 'foo' exists).
self._warn("style: quotes recommended around "
"default value for string symbol "
+ _name_and_loc(sym))
elif sym.orig_type in (INT, HEX) and \
not num_ok(default, sym.orig_type):
self._warn("the {0} symbol {1} has a non-{0} default {2}"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(default)))
if sym.selects or sym.implies:
self._warn("the {} symbol {} has selects or implies"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym)))
else: # UNKNOWN
self._warn("{} defined without a type"
.format(_name_and_loc(sym)))
if sym.ranges:
if sym.orig_type not in (INT, HEX):
self._warn(
"the {} symbol {} has ranges, but is not int or hex"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym)))
else:
for low, high, _ in sym.ranges:
if not num_ok(low, sym.orig_type) or \
not num_ok(high, sym.orig_type):
self._warn("the {0} symbol {1} has a non-{0} "
"range [{2}, {3}]"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(low),
_name_and_loc(high)))
def _check_choice_sanity(self):
# Checks various choice properties that are handiest to check after
# parsing. Only generates errors and warnings.
def warn_select_imply(sym, expr, expr_type):
msg = "the choice symbol {} is {} by the following symbols, which " \
"has no effect: ".format(_name_and_loc(sym), expr_type)
# si = select/imply
for si in split_expr(expr, OR):
msg += "\n - " + _name_and_loc(split_expr(si, AND)[0])
self._warn(msg)
for choice in self.unique_choices:
if choice.orig_type not in (BOOL, TRISTATE):
self._warn("{} defined with type {}"
.format(_name_and_loc(choice),
TYPE_TO_STR[choice.orig_type]))
for node in choice.nodes:
if node.prompt:
break
else:
self._warn(_name_and_loc(choice) + " defined without a prompt")
for default, _ in choice.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"{} has a malformed default {}"
.format(_name_and_loc(choice), expr_str(default)))
if default.choice is not choice:
self._warn("the default selection {} of {} is not "
"contained in the choice"
.format(_name_and_loc(default),
_name_and_loc(choice)))
for sym in choice.syms:
if sym.defaults:
self._warn("default on the choice symbol {} will have "
"no effect, as defaults do not affect choice "
"symbols".format(_name_and_loc(sym)))
if sym.rev_dep is not sym.kconfig.n:
warn_select_imply(sym, sym.rev_dep, "selected")
if sym.weak_rev_dep is not sym.kconfig.n:
warn_select_imply(sym, sym.weak_rev_dep, "implied")
for node in sym.nodes:
if node.parent.item is choice:
if not node.prompt:
self._warn("the choice symbol {} has no prompt"
.format(_name_and_loc(sym)))
elif node.prompt:
self._warn("the choice symbol {} is defined with a "
"prompt outside the choice"
.format(_name_and_loc(sym)))
def _parse_error(self, msg): def _parse_error(self, msg):
if self._filename is None: if self._filename is None:
loc = "" loc = ""
@ -3285,6 +3443,29 @@ class Kconfig(object):
# Prints warnings for all references to undefined symbols within the # Prints warnings for all references to undefined symbols within the
# Kconfig files # Kconfig files
def is_num(s):
# Returns True if the string 's' looks like a number.
#
# Internally, all operands in Kconfig are symbols, only undefined symbols
# (which numbers usually are) get their name as their value.
#
# Only hex numbers that start with 0x/0X are classified as numbers.
# Otherwise, symbols whose names happen to contain only the letters A-F
# would trigger false positives.
try:
int(s)
except ValueError:
if not s.startswith(("0x", "0X")):
return False
try:
int(s, 16)
except ValueError:
return False
return True
for sym in (self.syms.viewvalues if _IS_PY2 else self.syms.values)(): for sym in (self.syms.viewvalues if _IS_PY2 else self.syms.values)():
# - sym.nodes empty means the symbol is undefined (has no # - sym.nodes empty means the symbol is undefined (has no
# definition locations) # definition locations)
@ -3293,7 +3474,7 @@ class Kconfig(object):
# symbols, but shouldn't be flagged # symbols, but shouldn't be flagged
# #
# - The MODULES symbol always exists # - The MODULES symbol always exists
if not sym.nodes and not _is_num(sym.name) and \ if not sym.nodes and not is_num(sym.name) and \
sym.name != "MODULES": sym.name != "MODULES":
msg = "undefined symbol {}:".format(sym.name) msg = "undefined symbol {}:".format(sym.name)
@ -3450,8 +3631,29 @@ class Symbol(object):
config_string: config_string:
The .config assignment string that would get written out for the symbol The .config assignment string that would get written out for the symbol
by Kconfig.write_config(). Returns the empty string if no .config by Kconfig.write_config(). Returns the empty string if no .config
assignment would get written out. In general, visible symbols, symbols assignment would get written out.
with (active) defaults, and selected symbols get written out.
In general, visible symbols, symbols with (active) defaults, and selected
symbols get written out. This includes all non-n-valued bool/tristate
symbols, and all visible string/int/hex symbols.
Symbols with the (no longer needed) 'option env=...' option generate no
configuration output, and neither does the special
'option defconfig_list' symbol.
Tip: This field is useful when generating custom configuration output,
even for non-.config-like formats. To write just the symbols that would
get written out to .config files, do this:
if sym.config_string:
*Write symbol, e.g. by looking sym.str_value*
This is a superset of the symbols written out by write_autoconf().
That function skips all n-valued symbols.
There usually won't be any great harm in just writing all symbols either,
though you might get some special symbols and possibly some "redundant"
n-valued symbol entries in there.
nodes: nodes:
A list of MenuNodes for this symbol. Will contain a single MenuNode for A list of MenuNodes for this symbol. Will contain a single MenuNode for
@ -5091,7 +5293,11 @@ class Variable(object):
expanded_value: expanded_value:
The expanded value of the variable. For simple variables (those defined The expanded value of the variable. For simple variables (those defined
with :=), this will equal 'value'. Accessing this property will raise a with :=), this will equal 'value'. Accessing this property will raise a
KconfigError if any variable in the expansion expands to itself. KconfigError if the expansion seems to be stuck in a loop.
Note: Accessing this field is the same as calling expanded_value_w_args()
with no arguments. I hadn't considered function arguments when adding it.
It is retained for backwards compatibility though.
is_recursive: is_recursive:
True if the variable is recursive (defined with =). True if the variable is recursive (defined with =).
@ -5109,7 +5315,22 @@ class Variable(object):
""" """
See the class documentation. See the class documentation.
""" """
return self.kconfig._expand_whole(self.value, ()) return self.expanded_value_w_args()
def expanded_value_w_args(self, *args):
"""
Returns the expanded value of the variable/function. Any arguments
passed will be substituted for $(1), $(2), etc.
Raises a KconfigError if the expansion seems to be stuck in a loop.
"""
return self.kconfig._fn_val((self.name,) + args)
def __repr__(self):
return "<variable {}, {}, value '{}'>" \
.format(self.name,
"recursive" if self.is_recursive else "immediate",
self.value)
class KconfigError(Exception): class KconfigError(Exception):
""" """
@ -5437,29 +5658,6 @@ def _strcmp(s1, s2):
return (s1 > s2) - (s1 < s2) return (s1 > s2) - (s1 < s2)
def _is_num(s):
# Returns True if the string 's' looks like a number.
#
# Internally, all operands in Kconfig are symbols, only undefined symbols
# (which numbers usually are) get their name as their value.
#
# Only hex numbers that start with 0x/0X are classified as numbers.
# Otherwise, symbols whose names happen to contain only the letters A-F
# would trigger false positives.
try:
int(s)
except ValueError:
if not s.startswith(("0x", "0X")):
return False
try:
int(s, 16)
except ValueError:
return False
return True
def _sym_to_num(sym): def _sym_to_num(sym):
# expr_value() helper for converting a symbol to a number. Raises # expr_value() helper for converting a symbol to a number. Raises
# ValueError for symbols that can't be converted. # ValueError for symbols that can't be converted.
@ -5504,7 +5702,9 @@ def _decoding_error(e, filename, macro_linenr=None):
def _name_and_loc(sc): def _name_and_loc(sc):
# Helper for giving the symbol/choice name and location(s) in e.g. warnings # Helper for giving the symbol/choice name and location(s) in e.g. warnings
name = sc.name or "<choice>" # Reuse the expression format. That way choices show up as
# '<choice (name, if any)>'
name = standard_sc_expr_str(sc)
if not sc.nodes: if not sc.nodes:
return name + " (undefined)" return name + " (undefined)"
@ -5781,158 +5981,6 @@ def _found_dep_loop(loop, cur):
raise KconfigError(msg) raise KconfigError(msg)
def _check_sym_sanity(sym):
# Checks various symbol properties that are handiest to check after
# parsing. Only generates errors and warnings.
if sym.orig_type in (BOOL, TRISTATE):
# A helper function could be factored out here, but keep it
# speedy/straightforward for now. bool/tristate symbols are by far the
# most common, and most lack selects and implies.
for target_sym, _ in sym.selects:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
sym.kconfig._warn("{} selects the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
for target_sym, _ in sym.implies:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
sym.kconfig._warn("{} implies the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
elif sym.orig_type in (STRING, INT, HEX):
for default, _ in sym.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"the {} symbol {} has a malformed default {} -- expected "
"a single symbol"
.format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym),
expr_str(default)))
if sym.orig_type is STRING:
if not default.is_constant and not default.nodes and \
not default.name.isupper():
# 'default foo' on a string symbol could be either a symbol
# reference or someone leaving out the quotes. Guess that
# the quotes were left out if 'foo' isn't all-uppercase
# (and no symbol named 'foo' exists).
sym.kconfig._warn("style: quotes recommended around "
"default value for string symbol "
+ _name_and_loc(sym))
elif sym.orig_type in (INT, HEX) and \
not _int_hex_ok(default, sym.orig_type):
sym.kconfig._warn("the {0} symbol {1} has a non-{0} default {2}"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(default)))
if sym.selects or sym.implies:
sym.kconfig._warn("the {} symbol {} has selects or implies"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym)))
else: # UNKNOWN
sym.kconfig._warn("{} defined without a type"
.format(_name_and_loc(sym)))
if sym.ranges:
if sym.orig_type not in (INT, HEX):
sym.kconfig._warn(
"the {} symbol {} has ranges, but is not int or hex"
.format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym)))
else:
for low, high, _ in sym.ranges:
if not _int_hex_ok(low, sym.orig_type) or \
not _int_hex_ok(high, sym.orig_type):
sym.kconfig._warn("the {0} symbol {1} has a non-{0} range "
"[{2}, {3}]"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(low),
_name_and_loc(high)))
def _int_hex_ok(sym, type_):
# Returns True if the (possibly constant) symbol 'sym' is valid as a value
# for a symbol of type type_ (INT or HEX)
# 'not sym.nodes' implies a constant or undefined symbol, e.g. a plain
# "123"
if not sym.nodes:
return _is_base_n(sym.name, _TYPE_TO_BASE[type_])
return sym.orig_type is type_
def _check_choice_sanity(choice):
# Checks various choice properties that are handiest to check after
# parsing. Only generates errors and warnings.
if choice.orig_type not in (BOOL, TRISTATE):
choice.kconfig._warn("{} defined with type {}"
.format(_name_and_loc(choice),
TYPE_TO_STR[choice.orig_type]))
for node in choice.nodes:
if node.prompt:
break
else:
choice.kconfig._warn(_name_and_loc(choice) +
" defined without a prompt")
for default, _ in choice.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"{} has a malformed default {}"
.format(_name_and_loc(choice), expr_str(default)))
if default.choice is not choice:
choice.kconfig._warn("the default selection {} of {} is not "
"contained in the choice"
.format(_name_and_loc(default),
_name_and_loc(choice)))
for sym in choice.syms:
if sym.defaults:
sym.kconfig._warn("default on the choice symbol {} will have "
"no effect".format(_name_and_loc(sym)))
if sym.rev_dep is not sym.kconfig.n:
_warn_choice_select_imply(sym, sym.rev_dep, "selected")
if sym.weak_rev_dep is not sym.kconfig.n:
_warn_choice_select_imply(sym, sym.weak_rev_dep, "implied")
for node in sym.nodes:
if node.parent.item is choice:
if not node.prompt:
sym.kconfig._warn("the choice symbol {} has no prompt"
.format(_name_and_loc(sym)))
elif node.prompt:
sym.kconfig._warn("the choice symbol {} is defined with a "
"prompt outside the choice"
.format(_name_and_loc(sym)))
def _warn_choice_select_imply(sym, expr, expr_type):
msg = "the choice symbol {} is {} by the following symbols, which has " \
"no effect: ".format(_name_and_loc(sym), expr_type)
# si = select/imply
for si in split_expr(expr, OR):
msg += "\n - " + _name_and_loc(split_expr(si, AND)[0])
sym.kconfig._warn(msg)
# Predefined preprocessor functions # Predefined preprocessor functions

View file

@ -22,8 +22,8 @@ inspired by Vi:
The mconf feature where pressing a key jumps to a menu entry with that The mconf feature where pressing a key jumps to a menu entry with that
character in it in the current menu isn't supported. A jump-to feature for character in it in the current menu isn't supported. A jump-to feature for
jumping directly to any symbol (including invisible symbols) is available jumping directly to any symbol (including invisible symbols), choice, menu or
instead. comment (as in a Kconfig 'comment "Foo"') is available instead.
Space and Enter are "smart" and try to do what you'd expect for the given Space and Enter are "smart" and try to do what you'd expect for the given
menu entry. menu entry.
@ -226,7 +226,7 @@ _MAIN_HELP_LINES = """
# Lines of help text shown at the bottom of the information dialog # Lines of help text shown at the bottom of the information dialog
_INFO_HELP_LINES = """ _INFO_HELP_LINES = """
[ESC/q] Return to menu [ESC/q] Return to menu [/] Jump to symbol
"""[1:-1].split("\n") """[1:-1].split("\n")
# Lines of help text shown at the bottom of the search dialog # Lines of help text shown at the bottom of the search dialog
@ -658,7 +658,21 @@ def menuconfig(kconf):
if _CONVERT_C_LC_CTYPE_TO_UTF8: if _CONVERT_C_LC_CTYPE_TO_UTF8:
_convert_c_lc_ctype_to_utf8() _convert_c_lc_ctype_to_utf8()
# Get rid of the delay between pressing ESC and jumping to the parent menu # Get rid of the delay between pressing ESC and jumping to the parent menu,
# unless the user has set ESCDELAY (see ncurses(3)). This makes the UI much
# smoother to work with.
#
# Note: This is strictly pretty iffy, since escape codes for e.g. cursor
# keys start with ESC, but I've never seen it cause problems in practice
# (probably because it's unlikely that the escape code for a key would get
# split up across read()s, at least with a terminal emulator). Please
# report if you run into issues. Some suitable small default value could be
# used here instead in that case. Maybe it's silly to not put in the
# smallest imperceptible delay here already, though I don't like guessing.
#
# (From a quick glance at the ncurses source code, ESCDELAY might only be
# relevant for mouse events there, so maybe escapes are assumed to arrive
# in one piece already...)
os.environ.setdefault("ESCDELAY", "0") os.environ.setdefault("ESCDELAY", "0")
# Enter curses mode. _menuconfig() returns a string to print on exit, after # Enter curses mode. _menuconfig() returns a string to print on exit, after
@ -1019,18 +1033,33 @@ def _jump_to(node):
# parent menus before. # parent menus before.
_parent_screen_rows = [] _parent_screen_rows = []
_cur_menu = _parent_menu(node) old_show_all = _show_all
jump_into = (isinstance(node.item, Choice) or node.item == MENU) and \
node.list
# If we're jumping to a non-empty choice or menu, jump to the first entry
# in it instead of jumping to its menu node
if jump_into:
_cur_menu = node
node = node.list
else:
_cur_menu = _parent_menu(node)
_shown = _shown_nodes(_cur_menu) _shown = _shown_nodes(_cur_menu)
if node not in _shown: if node not in _shown:
# Turn on show-all mode if the node wouldn't be shown. Checking whether # The node wouldn't be shown. Turn on show-all to show it.
# the node is visible instead would needlessly turn on show-all mode in
# an obscure case: when jumping to an invisible symbol with visible
# children from an implicit submenu.
_show_all = True _show_all = True
_shown = _shown_nodes(_cur_menu) _shown = _shown_nodes(_cur_menu)
_sel_node_i = _shown.index(node) _sel_node_i = _shown.index(node)
if jump_into and not old_show_all and _show_all:
# If we're jumping into a choice or menu and were forced to turn on
# show-all because the first entry wasn't visible, try turning it off.
# That will land us at the first visible node if there are visible
# nodes, and is a no-op otherwise.
_toggle_show_all()
_center_vertically() _center_vertically()
def _leave_menu(): def _leave_menu():
@ -1115,7 +1144,8 @@ def _select_first_menu_entry():
_sel_node_i = _menu_scroll = 0 _sel_node_i = _menu_scroll = 0
def _toggle_show_all(): def _toggle_show_all():
# Toggles show-all mode on/off # Toggles show-all mode on/off. If turning it off would give no visible
# items in the current menu, it is left on.
global _show_all global _show_all
global _shown global _shown
@ -1178,46 +1208,6 @@ def _draw_main():
term_width = _stdscr.getmaxyx()[1] term_width = _stdscr.getmaxyx()[1]
#
# Update the top row with the menu path
#
_path_win.erase()
# Draw the menu path ("(top menu) -> menu -> submenu -> ...")
menu_prompts = []
menu = _cur_menu
while menu is not _kconf.top_node:
# Promptless choices can be entered in show-all mode. Use
# standard_sc_expr_str() for them, so they show up as
# '<choice (name if any)>'.
menu_prompts.append(menu.prompt[0] if menu.prompt else
standard_sc_expr_str(menu.item))
menu = _parent_menu(menu)
menu_prompts.append("(top menu)")
menu_prompts.reverse()
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
# represent it with NULL. Maybe using a Unicode character would be better.
menu_path_str = " \0 ".join(menu_prompts)
# Scroll the menu path to the right if needed to make the current menu's
# title visible
if len(menu_path_str) > term_width:
menu_path_str = menu_path_str[len(menu_path_str) - term_width:]
# Print the path with the arrows reinserted
split_path = menu_path_str.split("\0")
_safe_addstr(_path_win, split_path[0])
for s in split_path[1:]:
_safe_addch(_path_win, curses.ACS_RARROW)
_safe_addstr(_path_win, s)
_path_win.noutrefresh()
# #
# Update the separator row below the menu path # Update the separator row below the menu path
# #
@ -1236,6 +1226,7 @@ def _draw_main():
_top_sep_win.noutrefresh() _top_sep_win.noutrefresh()
# Note: The menu path at the top is deliberately updated last. See below.
# #
# Update the symbol window # Update the symbol window
@ -1298,6 +1289,51 @@ def _draw_main():
_help_win.noutrefresh() _help_win.noutrefresh()
#
# Update the top row with the menu path.
#
# Doing this last leaves the cursor on the top row, which avoids some minor
# annoying jumpiness in gnome-terminal when reducing the height of the
# terminal. It seems to happen whenever the row with the cursor on it
# disappears.
#
_path_win.erase()
# Draw the menu path ("(top menu) -> menu -> submenu -> ...")
menu_prompts = []
menu = _cur_menu
while menu is not _kconf.top_node:
# Promptless choices can be entered in show-all mode. Use
# standard_sc_expr_str() for them, so they show up as
# '<choice (name if any)>'.
menu_prompts.append(menu.prompt[0] if menu.prompt else
standard_sc_expr_str(menu.item))
menu = _parent_menu(menu)
menu_prompts.append("(top menu)")
menu_prompts.reverse()
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
# represent it with NULL. Maybe using a Unicode character would be better.
menu_path_str = " \0 ".join(menu_prompts)
# Scroll the menu path to the right if needed to make the current menu's
# title visible
if len(menu_path_str) > term_width:
menu_path_str = menu_path_str[len(menu_path_str) - term_width:]
# Print the path with the arrows reinserted
split_path = menu_path_str.split("\0")
_safe_addstr(_path_win, split_path[0])
for s in split_path[1:]:
_safe_addch(_path_win, curses.ACS_RARROW)
_safe_addstr(_path_win, s)
_path_win.noutrefresh()
def _parent_menu(node): def _parent_menu(node):
# Returns the menu node of the menu that contains 'node'. In addition to # Returns the menu node of the menu that contains 'node'. In addition to
# proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'. # proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'.
@ -1854,13 +1890,20 @@ def _jump_to_dialog():
# List of matching nodes # List of matching nodes
matches = [] matches = []
for node in _searched_nodes(): # Search symbols and choices
for node in _sorted_sc_nodes():
# Symbol/choice
sc = node.item
for search in regex_searches: for search in regex_searches:
# Both the name and the prompt might be missing, since
# we're searching both symbols and choices
# Does the regex match either the symbol name or the # Does the regex match either the symbol name or the
# prompt (if any)? # prompt (if any)?
if not (search(node.item.name.lower()) or if not (sc.name and search(sc.name.lower()) or
(node.prompt and node.prompt and search(node.prompt[0].lower())):
search(node.prompt[0].lower()))):
# Give up on the first regex that doesn't match, to # Give up on the first regex that doesn't match, to
# speed things up a bit when multiple regexes are # speed things up a bit when multiple regexes are
@ -1870,6 +1913,15 @@ def _jump_to_dialog():
else: else:
matches.append(node) matches.append(node)
# Search menus and comments
for node in _sorted_menu_comment_nodes():
for search in regex_searches:
if not search(node.prompt[0].lower()):
break
else:
matches.append(node)
except re.error as e: except re.error as e:
# Bad regex. Remember the error message so we can show it. # Bad regex. Remember the error message so we can show it.
bad_re = "Bad regular expression" bad_re = "Bad regular expression"
@ -1909,13 +1961,14 @@ def _jump_to_dialog():
sel_node_i, scroll) sel_node_i, scroll)
elif c == "\x06": # \x06 = Ctrl-F elif c == "\x06": # \x06 = Ctrl-F
_safe_curs_set(0) if matches:
_info_dialog(matches[sel_node_i], True) _safe_curs_set(0)
_safe_curs_set(1) _info_dialog(matches[sel_node_i], True)
_safe_curs_set(2)
scroll = _resize_jump_to_dialog( scroll = _resize_jump_to_dialog(
edit_box, matches_win, bot_sep_win, help_win, edit_box, matches_win, bot_sep_win, help_win,
sel_node_i, scroll) sel_node_i, scroll)
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
select_next_match() select_next_match()
@ -1937,20 +1990,45 @@ def _jump_to_dialog():
s, s_i, hscroll = _edit_text(c, s, s_i, hscroll, s, s_i, hscroll = _edit_text(c, s, s_i, hscroll,
edit_box.getmaxyx()[1] - 2) edit_box.getmaxyx()[1] - 2)
# Obscure Python: We never pass a value for cached_search_nodes, and it keeps # Obscure Python: We never pass a value for cached_nodes, and it keeps pointing
# pointing to the same list. This avoids a global. # to the same list. This avoids a global.
def _searched_nodes(cached_search_nodes=[]): def _sorted_sc_nodes(cached_nodes=[]):
# Returns a list of menu nodes to search, sorted by symbol name # Returns a sorted list of symbol and choice nodes to search. The symbol
# nodes appear first, sorted by name, and then the choice nodes, sorted by
# prompt and (secondarily) name.
if not cached_search_nodes: if not cached_nodes:
# Sort symbols by name, then add all nodes for each symbol # Add symbol nodes
for sym in sorted(_kconf.unique_defined_syms, for sym in sorted(_kconf.unique_defined_syms,
key=lambda sym: sym.name): key=lambda sym: sym.name):
# += is in-place for lists # += is in-place for lists
cached_search_nodes += sym.nodes cached_nodes += sym.nodes
return cached_search_nodes # Add choice nodes
choices = sorted(_kconf.unique_choices,
key=lambda choice: choice.name or "")
cached_nodes += sorted(
[node
for choice in choices
for node in choice.nodes],
key=lambda node: node.prompt[0] if node.prompt else "")
return cached_nodes
def _sorted_menu_comment_nodes(cached_nodes=[]):
# Returns a list of menu and comment nodes to search, sorted by prompt,
# with the menus first
if not cached_nodes:
def prompt_text(mc):
return mc.prompt[0]
cached_nodes += sorted(_kconf.menus, key=prompt_text) + \
sorted(_kconf.comments, key=prompt_text)
return cached_nodes
def _resize_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win, def _resize_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
sel_node_i, scroll): sel_node_i, scroll):
@ -2009,13 +2087,18 @@ def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
for i in range(scroll, for i in range(scroll,
min(scroll + matches_win.getmaxyx()[0], len(matches))): min(scroll + matches_win.getmaxyx()[0], len(matches))):
sym = matches[i].item node = matches[i]
sym_str = _name_and_val_str(sym) if isinstance(node.item, (Symbol, Choice)):
if matches[i].prompt: node_str = _name_and_val_str(node.item)
sym_str += ' "{}"'.format(matches[i].prompt[0]) if node.prompt:
node_str += ' "{}"'.format(node.prompt[0])
elif node.item == MENU:
node_str = 'menu "{}"'.format(node.prompt[0])
else: # node.item == COMMENT
node_str = 'comment "{}"'.format(node.prompt[0])
_safe_addstr(matches_win, i - scroll, 0, sym_str, _safe_addstr(matches_win, i - scroll, 0, node_str,
_style["selection" if i == sel_node_i else "list"]) _style["selection" if i == sel_node_i else "list"])
else: else:
@ -2189,27 +2272,7 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win,
text_win_height, text_win_width = text_win.getmaxyx() text_win_height, text_win_width = text_win.getmaxyx()
# # Note: The top row is deliberately updated last. See _draw_main().
# Update top row
#
top_line_win.erase()
# Draw arrows pointing up if the information window is scrolled down. Draw
# them before drawing the title, so the title ends up on top for small
# windows.
if scroll > 0:
_safe_hline(top_line_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
title = ("Symbol" if isinstance(node.item, Symbol) else
"Choice" if isinstance(node.item, Choice) else
"Menu" if node.item == MENU else
"Comment") + " information"
_safe_addstr(top_line_win, 0, max((text_win_width - len(title))//2, 0),
title)
top_line_win.noutrefresh()
# #
# Update text display # Update text display
@ -2247,6 +2310,28 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win,
help_win.noutrefresh() help_win.noutrefresh()
#
# Update top row
#
top_line_win.erase()
# Draw arrows pointing up if the information window is scrolled down. Draw
# them before drawing the title, so the title ends up on top for small
# windows.
if scroll > 0:
_safe_hline(top_line_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
title = ("Symbol" if isinstance(node.item, Symbol) else
"Choice" if isinstance(node.item, Choice) else
"Menu" if node.item == MENU else
"Comment") + " information"
_safe_addstr(top_line_win, 0, max((text_win_width - len(title))//2, 0),
title)
top_line_win.noutrefresh()
def _info_str(node): def _info_str(node):
# Returns information about the menu node 'node' as a string. # Returns information about the menu node 'node' as a string.
# #
@ -2503,24 +2588,20 @@ def _menu_path_info(node):
return "(top menu)" + path return "(top menu)" + path
def _name_and_val_str(sc): def _name_and_val_str(sc):
# Custom symbol printer that shows the symbol value after the symbol, used # Custom symbol/choice printer that shows symbol values after symbols
# for the information display
# Show the values of non-constant (non-quoted) symbols that don't look like # Show the values of non-constant (non-quoted) symbols that don't look like
# numbers. Things like 123 are actually symbol references, and only work as # numbers. Things like 123 are actually symbol references, and only work as
# expected due to undefined symbols getting their name as their value. # expected due to undefined symbols getting their name as their value.
# Showing the symbol value for those isn't helpful though. # Showing the symbol value for those isn't helpful though.
if isinstance(sc, Symbol) and \ if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name):
not sc.is_constant and \
not _is_num(sc.name):
if not sc.nodes: if not sc.nodes:
# Undefined symbol reference # Undefined symbol reference
return "{}(undefined/n)".format(sc.name) return "{}(undefined/n)".format(sc.name)
return '{}(={})'.format(sc.name, sc.str_value) return '{}(={})'.format(sc.name, sc.str_value)
# For other symbols, use the standard format # For other items, use the standard format
return standard_sc_expr_str(sc) return standard_sc_expr_str(sc)
def _expr_str(expr): def _expr_str(expr):
@ -2662,11 +2743,10 @@ def _node_str(node):
s += " " + standard_sc_expr_str(node.item) s += " " + standard_sc_expr_str(node.item)
if node.prompt: if node.prompt:
s += " "
if node.item == COMMENT: if node.item == COMMENT:
s += "*** {} ***".format(node.prompt[0]) s += " *** {} ***".format(node.prompt[0])
else: else:
s += node.prompt[0] s += " " + node.prompt[0]
if isinstance(node.item, Symbol): if isinstance(node.item, Symbol):
sym = node.item sym = node.item
@ -2678,14 +2758,14 @@ def _node_str(node):
s += " (NEW)" s += " (NEW)"
if isinstance(node.item, Choice) and node.item.tri_value == 2: if isinstance(node.item, Choice) and node.item.tri_value == 2:
# Print the prompt of the selected symbol after the choice for # Print the prompt of the selected symbol after the choice for
# choices in y mode # choices in y mode
sym = node.item.selection sym = node.item.selection
if sym: if sym:
for node_ in sym.nodes: for node_ in sym.nodes:
if node_.prompt: if node_.prompt:
s += " ({})".format(node_.prompt[0]) s += " ({})".format(node_.prompt[0])
# Print "--->" next to nodes that have menus that can potentially be # Print "--->" next to nodes that have menus that can potentially be
# entered. Add "(empty)" if the menu is empty. We don't allow those to be # entered. Add "(empty)" if the menu is empty. We don't allow those to be
@ -2779,16 +2859,14 @@ def _check_validity(sym, s):
def _range_info(sym): def _range_info(sym):
# Returns a string with information about the valid range for the symbol # Returns a string with information about the valid range for the symbol
# 'sym', or None if 'sym' isn't an int/hex symbol # 'sym', or None if 'sym' doesn't have a range
if sym.type not in (INT, HEX): if sym.type in (INT, HEX):
return None for low, high, cond in sym.ranges:
if expr_value(cond):
return "Range: {}-{}".format(low.str_value, high.str_value)
for low, high, cond in sym.ranges: return None
if expr_value(cond):
return "Range: {}-{}".format(low.str_value, high.str_value)
return "No range constraints."
def _is_num(name): def _is_num(name):
# Heuristic to see if a symbol name looks like a number, for nicer output # Heuristic to see if a symbol name looks like a number, for nicer output