298 lines
7.7 KiB
Python
298 lines
7.7 KiB
Python
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import operator
|
|
import re
|
|
|
|
import pyparsing
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
|
|
|
|
def _operatorOperands(tokenList):
|
|
it = iter(tokenList)
|
|
while True:
|
|
try:
|
|
op1 = next(it)
|
|
op2 = next(it)
|
|
yield(op1, op2)
|
|
except StopIteration:
|
|
break
|
|
|
|
|
|
class EvalConstant(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
result = self.value
|
|
if (isinstance(result, str) and
|
|
re.match(r"^[a-zA-Z_]+\.[a-zA-Z_]+$", result)):
|
|
(which_dict, entry) = result.split('.')
|
|
try:
|
|
result = _vars[which_dict][entry]
|
|
except KeyError:
|
|
raise exception.EvaluatorParseException(
|
|
_("KeyError evaluating string"))
|
|
except TypeError:
|
|
raise exception.EvaluatorParseException(
|
|
_("TypeError evaluating string"))
|
|
|
|
try:
|
|
result = int(result)
|
|
except ValueError:
|
|
try:
|
|
result = float(result)
|
|
except ValueError:
|
|
if isinstance(result, str):
|
|
result = result.replace('"', '').replace('\'', '')
|
|
|
|
return result
|
|
|
|
|
|
class EvalSignOp(object):
|
|
operations = {
|
|
'+': 1,
|
|
'-': -1,
|
|
}
|
|
|
|
def __init__(self, toks):
|
|
self.sign, self.value = toks[0]
|
|
|
|
def eval(self):
|
|
return self.operations[self.sign] * self.value.eval()
|
|
|
|
|
|
class EvalAddOp(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
sum = self.value[0].eval()
|
|
for op, val in _operatorOperands(self.value[1:]):
|
|
if op == '+':
|
|
sum += val.eval()
|
|
elif op == '-':
|
|
sum -= val.eval()
|
|
return sum
|
|
|
|
|
|
class EvalMultOp(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
prod = self.value[0].eval()
|
|
for op, val in _operatorOperands(self.value[1:]):
|
|
try:
|
|
if op == '*':
|
|
prod *= val.eval()
|
|
elif op == '/':
|
|
prod /= float(val.eval())
|
|
except ZeroDivisionError as e:
|
|
raise exception.EvaluatorParseException(
|
|
_("ZeroDivisionError: %s") % e)
|
|
return prod
|
|
|
|
|
|
class EvalPowerOp(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
prod = self.value[0].eval()
|
|
for op, val in _operatorOperands(self.value[1:]):
|
|
prod = pow(prod, val.eval())
|
|
return prod
|
|
|
|
|
|
class EvalNegateOp(object):
|
|
def __init__(self, toks):
|
|
self.negation, self.value = toks[0]
|
|
|
|
def eval(self):
|
|
return not self.value.eval()
|
|
|
|
|
|
class EvalComparisonOp(object):
|
|
operations = {
|
|
"<": operator.lt,
|
|
"<=": operator.le,
|
|
">": operator.gt,
|
|
">=": operator.ge,
|
|
"!=": operator.ne,
|
|
"==": operator.eq,
|
|
"<>": operator.ne,
|
|
}
|
|
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
val1 = self.value[0].eval()
|
|
for op, val in _operatorOperands(self.value[1:]):
|
|
fn = self.operations[op]
|
|
val2 = val.eval()
|
|
if not fn(val1, val2):
|
|
break
|
|
val1 = val2
|
|
else:
|
|
return True
|
|
return False
|
|
|
|
|
|
class EvalTernaryOp(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
condition = self.value[0].eval()
|
|
if condition:
|
|
return self.value[2].eval()
|
|
else:
|
|
return self.value[4].eval()
|
|
|
|
|
|
class EvalFunction(object):
|
|
functions = {
|
|
"abs": abs,
|
|
"max": max,
|
|
"min": min,
|
|
}
|
|
|
|
def __init__(self, toks):
|
|
self.func, self.value = toks[0]
|
|
|
|
def eval(self):
|
|
args = self.value.eval()
|
|
if type(args) is list:
|
|
return self.functions[self.func](*args)
|
|
else:
|
|
return self.functions[self.func](args)
|
|
|
|
|
|
class EvalCommaSeperator(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
val1 = self.value[0].eval()
|
|
val2 = self.value[2].eval()
|
|
if type(val2) is list:
|
|
val_list = []
|
|
val_list.append(val1)
|
|
for val in val2:
|
|
val_list.append(val)
|
|
return val_list
|
|
|
|
return [val1, val2]
|
|
|
|
|
|
class EvalBoolAndOp(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
left = self.value[0].eval()
|
|
right = self.value[2].eval()
|
|
return left and right
|
|
|
|
|
|
class EvalBoolOrOp(object):
|
|
def __init__(self, toks):
|
|
self.value = toks[0]
|
|
|
|
def eval(self):
|
|
left = self.value[0].eval()
|
|
right = self.value[2].eval()
|
|
return left or right
|
|
|
|
|
|
_parser = None
|
|
_vars = {}
|
|
|
|
|
|
def _def_parser():
|
|
# Enabling packrat parsing greatly speeds up the parsing.
|
|
pyparsing.ParserElement.enablePackrat()
|
|
|
|
alphas = pyparsing.alphas
|
|
Combine = pyparsing.Combine
|
|
nums = pyparsing.nums
|
|
quoted_string = pyparsing.quotedString
|
|
|
|
oneOf = pyparsing.oneOf
|
|
opAssoc = pyparsing.opAssoc
|
|
operatorPrecedence = pyparsing.operatorPrecedence
|
|
Word = pyparsing.Word
|
|
|
|
integer = Word(nums)
|
|
real = Combine(Word(nums) + '.' + Word(nums))
|
|
variable = Word(alphas + '_' + '.')
|
|
number = real | integer
|
|
fn = Word(alphas + '_' + '.')
|
|
operand = number | variable | fn | quoted_string
|
|
|
|
signop = oneOf('+ -')
|
|
addop = oneOf('+ -')
|
|
multop = oneOf('* /')
|
|
comparisonop = oneOf(' '.join(EvalComparisonOp.operations.keys()))
|
|
ternaryop = ('?', ':')
|
|
boolandop = oneOf('AND and &&')
|
|
boolorop = oneOf('OR or ||')
|
|
negateop = oneOf('NOT not !')
|
|
|
|
operand.setParseAction(EvalConstant)
|
|
expr = operatorPrecedence(operand, [
|
|
(fn, 1, opAssoc.RIGHT, EvalFunction),
|
|
("^", 2, opAssoc.RIGHT, EvalPowerOp),
|
|
(signop, 1, opAssoc.RIGHT, EvalSignOp),
|
|
(multop, 2, opAssoc.LEFT, EvalMultOp),
|
|
(addop, 2, opAssoc.LEFT, EvalAddOp),
|
|
(negateop, 1, opAssoc.RIGHT, EvalNegateOp),
|
|
(comparisonop, 2, opAssoc.LEFT, EvalComparisonOp),
|
|
(ternaryop, 3, opAssoc.LEFT, EvalTernaryOp),
|
|
(boolandop, 2, opAssoc.LEFT, EvalBoolAndOp),
|
|
(boolorop, 2, opAssoc.LEFT, EvalBoolOrOp),
|
|
(',', 2, opAssoc.RIGHT, EvalCommaSeperator), ])
|
|
|
|
return expr
|
|
|
|
|
|
def evaluate(expression, **kwargs):
|
|
"""Evaluates an expression.
|
|
|
|
Provides the facility to evaluate mathematical expressions, and to
|
|
substitute variables from dictionaries into those expressions.
|
|
|
|
Supports both integer and floating point values, and automatic
|
|
promotion where necessary.
|
|
"""
|
|
global _parser
|
|
if _parser is None:
|
|
_parser = _def_parser()
|
|
|
|
global _vars
|
|
_vars = kwargs
|
|
|
|
try:
|
|
result = _parser.parseString(expression, parseAll=True)[0]
|
|
except pyparsing.ParseException as e:
|
|
raise exception.EvaluatorParseException(
|
|
_("ParseException: %s") % e)
|
|
|
|
return result.eval()
|