# -*- coding: utf-8 -*- # Copyright 2015 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Parser used to parse the boolean expression.""" from __future__ import print_function import ast import pyparsing import re class BoolParseError(Exception): """Base exception for this module.""" class _BoolOperand(object): """Read pyparsing.Keyword as operand and evaluate its boolean value.""" def __init__(self, t): """Initialize the object. Read boolean operands from pyparsing.Keyword and evaluate into the corresponding boolean values. Args: t: t[0] is pyparsing.Keyword corresponding to False or True. """ self.label = t[0] self.value = ast.literal_eval(t[0]) def __bool__(self): return self.value def __str__(self): return self.label # Python 2 glue. __nonzero__ = __bool__ class _BoolBinOp(object): """General class for binary operation.""" def __init__(self, t): """Initialize object. Extract the operand from the input. The operand is the pyparsing.Keyword. Args: t: A list containing a list of operand and operator, such as [[True, 'and', False]]. t[0] is [True, 'and', False]. t[0][0::2] are the two operands. """ self.args = t[0][0::2] def __bool__(self): """Evaluate the boolean value of the binary boolean expression. evalop is the method used to evaluate, which is overwritten in the children class of _BoolBinOp. Returns: boolean result. """ return self.evalop(bool(a) for a in self.args) # Python 2 glue. __nonzero__ = __bool__ class _BoolAnd(_BoolBinOp): """And boolean binary operation.""" evalop = all class _BoolOr(_BoolBinOp): """Or boolean binary operation.""" evalop = any class _BoolNot(object): """Not operation.""" def __init__(self, t): self.arg = t[0][1] def __bool__(self): v = bool(self.arg) return not v # Python 2 glue. __nonzero__ = __bool__ def _ExprOverwrite(expr, true_variables): """Overwrite variables in |expr| based on |true_variables|. Overwrite variables in |expr| with 'True' if they occur in |true_variables|, 'False' otherwise. Args: expr: The orginal boolean expression, like 'A and B'. true_variables: Collection of variable names to be considered True, e.g. {'A'}. Returns: A boolean expression string with pyparsing.Keyword, like 'True and False'. """ # When true_variables is None, replace it with empty collection () target_set = set(true_variables or ()) items = { x.strip() for x in re.split(r'(?i) and | or |not |\(|\)', expr) if x.strip()} boolstr = expr for item in items: boolstr = boolstr.replace( item, 'True' if item in target_set else 'False') return boolstr def BoolstrResult(expr, true_variables): """Determine if a boolean expression is satisfied. BoolstrResult('A and B and not C', {'A', 'C'}) -> False Args: expr: The orginal boolean expression, like 'A and B'. true_variables: Collection to be checked whether satisfy the boolean expr. Returns: True if the given |true_variables| cause the boolean expression |expr| to be satisfied, False otherwise. """ boolstr = _ExprOverwrite(expr, true_variables) # Define the boolean logic TRUE = pyparsing.Keyword('True') FALSE = pyparsing.Keyword('False') boolOperand = TRUE | FALSE boolOperand.setParseAction(_BoolOperand) # Define expression, based on expression operand and list of operations in # precedence order. boolExpr = pyparsing.infixNotation( boolOperand, [('not', 1, pyparsing.opAssoc.RIGHT, _BoolNot), ('and', 2, pyparsing.opAssoc.LEFT, _BoolAnd), ('or', 2, pyparsing.opAssoc.LEFT, _BoolOr),]) try: res = boolExpr.parseString(boolstr)[0] return bool(res) except (AttributeError, pyparsing.ParseException): raise BoolParseError('Cannot parse the boolean expression string "%s".' % expr)