• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- coding: utf-8 -*-
2# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Parser used to parse the boolean expression."""
7
8from __future__ import print_function
9
10import ast
11import pyparsing
12import re
13
14
15class BoolParseError(Exception):
16    """Base exception for this module."""
17
18
19class _BoolOperand(object):
20    """Read pyparsing.Keyword as operand and evaluate its boolean value."""
21
22    def __init__(self, t):
23        """Initialize the object.
24
25        Read boolean operands from pyparsing.Keyword and evaluate into the
26        corresponding boolean values.
27
28        Args:
29          t: t[0] is pyparsing.Keyword corresponding to False or True.
30        """
31        self.label = t[0]
32        self.value = ast.literal_eval(t[0])
33
34    def __bool__(self):
35        return self.value
36
37    def __str__(self):
38        return self.label
39
40    # Python 2 glue.
41    __nonzero__ = __bool__
42
43
44class _BoolBinOp(object):
45    """General class for binary operation."""
46
47    def __init__(self, t):
48        """Initialize object.
49
50        Extract the operand from the input. The operand is the
51        pyparsing.Keyword.
52
53        Args:
54          t: A list containing a list of operand and operator, such as
55          [[True, 'and', False]]. t[0] is [True, 'and', False]. t[0][0::2] are
56          the two operands.
57        """
58        self.args = t[0][0::2]
59
60    def __bool__(self):
61        """Evaluate the boolean value of the binary boolean expression.
62
63        evalop is the method used to evaluate, which is overwritten in the
64        children class of _BoolBinOp.
65
66        Returns:
67          boolean result.
68        """
69        return self.evalop(bool(a) for a in self.args)
70
71    # Python 2 glue.
72    __nonzero__ = __bool__
73
74
75class _BoolAnd(_BoolBinOp):
76    """And boolean binary operation."""
77
78    evalop = all
79
80
81class _BoolOr(_BoolBinOp):
82    """Or boolean binary operation."""
83
84    evalop = any
85
86
87class _BoolNot(object):
88    """Not operation."""
89
90    def __init__(self, t):
91        self.arg = t[0][1]
92
93    def __bool__(self):
94        v = bool(self.arg)
95        return not v
96
97    # Python 2 glue.
98    __nonzero__ = __bool__
99
100
101def _ExprOverwrite(expr, true_variables):
102    """Overwrite variables in |expr| based on |true_variables|.
103
104    Overwrite variables in |expr| with 'True' if they occur in |true_variables|,
105    'False' otherwise.
106
107    Args:
108      expr: The orginal boolean expression, like 'A and B'.
109      true_variables: Collection of variable names to be considered True, e.g.
110      {'A'}.
111
112    Returns:
113      A boolean expression string with pyparsing.Keyword, like 'True and False'.
114    """
115    # When true_variables is None, replace it with empty collection ()
116    target_set = set(true_variables or ())
117    items = {
118        x.strip() for x in re.split(r'(?i) and | or |not |\(|\)', expr)
119        if x.strip()}
120    boolstr = expr
121    for item in items:
122        boolstr = boolstr.replace(
123            item, 'True' if item in target_set else 'False')
124
125    return boolstr
126
127
128def BoolstrResult(expr, true_variables):
129    """Determine if a boolean expression is satisfied.
130
131    BoolstrResult('A and B and not C', {'A', 'C'}) -> False
132
133    Args:
134      expr: The orginal boolean expression, like 'A and B'.
135      true_variables: Collection to be checked whether satisfy the boolean expr.
136
137    Returns:
138      True if the given |true_variables| cause the boolean expression |expr| to
139      be satisfied, False otherwise.
140    """
141    boolstr = _ExprOverwrite(expr, true_variables)
142
143    # Define the boolean logic
144    TRUE = pyparsing.Keyword('True')
145    FALSE = pyparsing.Keyword('False')
146    boolOperand = TRUE | FALSE
147    boolOperand.setParseAction(_BoolOperand)
148
149    # Define expression, based on expression operand and list of operations in
150    # precedence order.
151    boolExpr = pyparsing.infixNotation(
152        boolOperand, [('not', 1, pyparsing.opAssoc.RIGHT, _BoolNot),
153                      ('and', 2, pyparsing.opAssoc.LEFT, _BoolAnd),
154                      ('or', 2, pyparsing.opAssoc.LEFT, _BoolOr),])
155
156    try:
157        res = boolExpr.parseString(boolstr)[0]
158        return bool(res)
159    except (AttributeError, pyparsing.ParseException):
160        raise BoolParseError('Cannot parse the boolean expression string "%s".'
161                             % expr)
162