From 105c835e70a5bb80a256b3d10d1d03dee7bb23db Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Wed, 11 Apr 2018 20:35:40 +0200 Subject: Add helper for splitting expressions I've had to implement the logic for walking reverse dependencies (from select) a couple of times now, and it's always a bit tricky to get right. Reduce code duplication and simplify things by adding a helper function split_expr() that splits an expression into a list of either its AND or OR operands. A nice side effect is that e.g. the warning generated for selecting a symbol with unsatisfied direct dependencies now lists the selecting symbols in the order that they appear in the Kconfig files. split_expr() might be helpful for splitting other types of expressions as well, e.g. to put operands on separate lines when generating documentation. --- kconfiglib.py | 109 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 43 deletions(-) (limited to 'kconfiglib.py') diff --git a/kconfiglib.py b/kconfiglib.py index d9cd4f0..487fd56 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -3390,53 +3390,36 @@ class Symbol(object): # unsatisfied direct dependencies (dependencies from 'depends on', ifs, # and menus) is selected by some other symbol - def check_select(select): - # Returns a warning string if 'select' is actually selecting us, - # and the empty string otherwise. nonlocal would be handy for - # appending to warn_msg directly, but it's Python 3 only. + msg = "{} has unsatisfied direct dependencies ({}), but is " \ + "currently being selected by the following symbols:" \ + .format(_name_and_loc(self), expr_str(self.direct_dep)) + # The reverse dependencies from each select are ORed together + for select in split_expr(self.rev_dep, OR): select_val = expr_value(select) if not select_val: # Only include selects that are not n - return "" + continue - if isinstance(select, tuple): - # (AND, , ) - selecting_sym = select[1] - else: - # - selecting_sym = select + # - 'select A if B' turns into A && B + # - 'select A' just turns into A + # + # In both cases, we can split on AND and pick the first operand + selecting_sym = split_expr(select, AND)[0] - msg = "\n - {}, with value {}, direct dependencies {} " \ - "(value: {})" \ - .format(_name_and_loc(selecting_sym), - selecting_sym.str_value, - expr_str(selecting_sym.direct_dep), - TRI_TO_STR[expr_value(selecting_sym.direct_dep)]) + msg += "\n - {}, with value {}, direct dependencies {} " \ + "(value: {})" \ + .format(_name_and_loc(selecting_sym), + selecting_sym.str_value, + expr_str(selecting_sym.direct_dep), + TRI_TO_STR[expr_value(selecting_sym.direct_dep)]) if isinstance(select, tuple): msg += ", and select condition {} (value: {})" \ .format(expr_str(select[2]), TRI_TO_STR[expr_value(select[2])]) - return msg - - warn_msg = "{} has unsatisfied direct dependencies ({}), but is " \ - "currently being selected by the following symbols:" \ - .format(_name_and_loc(self), expr_str(self.direct_dep)) - - # This relies on us using the following format for the select - # expression: - # - # (OR, (OR, (OR, , ), ), ) - expr = self.rev_dep - while isinstance(expr, tuple) and expr[0] == OR: - warn_msg += check_select(expr[2]) - # Go to next select - expr = expr[1] - warn_msg += check_select(expr) - - self.kconfig._warn(warn_msg) + self.kconfig._warn(msg) class Choice(object): """ @@ -4159,6 +4142,50 @@ def expr_str(expr): _REL_TO_STR[expr[0]], expr_str(expr[2])) +def split_expr(expr, op): + """ + Returns a list containing the top-level AND or OR operands in the + expression 'expr', in the same (left-to-right) order as they appear in + the expression. + + This can be handy e.g. for splitting (weak) reverse dependencies + from 'select' and 'imply' into individual selects/implies. + + op: + Either AND to get AND operands, or OR to get OR operands. + + (Having this as an operand might be more future-safe than having two + hardcoded functions.) + + + Pseudo-code examples: + + split_expr( A , OR ) -> [A] + split_expr( A && B , OR ) -> [A && B] + split_expr( A || B , OR ) -> [A, B] + split_expr( A || B , AND ) -> [A || B] + split_expr( A || B || (C && D) , OR ) -> [A, B, C && D] + + # Second || is not at the top level + split_expr( A || (B && (C || D)) , OR ) -> [A, B && (C || D)] + + # Parentheses don't matter as long as we stay at the top level (don't + # encounter any non-'op' nodes) + split_expr( (A || B) || C , OR ) -> [A, B, C] + split_expr( A || (B || C) , OR ) -> [A, B, C] + """ + res = [] + + def rec(subexpr): + if isinstance(subexpr, tuple) and subexpr[0] == op: + rec(subexpr[1]) + rec(subexpr[2]) + else: + res.append(subexpr) + + rec(expr) + return res + def escape(s): r""" Escapes the string 's' in the same fashion as is done for display in @@ -4697,15 +4724,11 @@ 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) - while isinstance(expr, tuple) and expr[0] == OR: - msg += _select_imply_str(expr[2]) - expr = expr[1] - - sym.kconfig._warn(msg + _select_imply_str(expr)) + # si = select/imply + for si in split_expr(expr, OR): + msg += "\n - " + _name_and_loc(split_expr(si, AND)[0]) -def _select_imply_str(select): - return "\n - " + \ - _name_and_loc(select[1] if isinstance(select, tuple) else select) + sym.kconfig._warn(msg) # # Public global constants -- cgit v1.2.3