blob: 37b43ccdcd2da7b1ed3ba9c14ab0befc5d6f2106 [file] [log] [blame]
# -*- 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)