pyParsing 评估表达式 [英] pyParsing Evaluating Expression

查看:56
本文介绍了pyParsing 评估表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在浏览了本网站上的几个 pyparsing 和帖子示例后,我设法编写了可以完全按照我想要的方式解析表达式的代码.但现在我被困在如何评估它,因此在这里寻求您的帮助.

After going through several examples of pyparsing and posts from this site, i managed to write the code that will parse the expressions exactly the way i wanted. But now i m stuck with how to evaluate it, and hence seeking your help here.

所以当我给出这个字符串时:(Number(3) > Number(5)) AND (Time(IST) [[[['Number', [3]], '>', ['Number', [5]]], 'AND', [['Time', ['IST']], '<', ['数字', [1030]]]]]

So when i give this string: "(Number(3) > Number(5)) AND (Time(IST) < Number(1030))" the parse will output me:
[[[['Number', [3]], '>', ['Number', [5]]], 'AND', [['Time', ['IST']], '<', ['Number', [1030]]]]]

Number 是一个自定义函数,它接受 int 输入并返回 int.Time 是一个自定义函数,它接受字符串输入并以 int 形式返回当前系统时间.像这样,我将有许多自定义函数,它们将接受一些输入和返回值.

Number is a custom function that takes int input and returns int. Time is a custom function that takes string input and returns current system time as int. Like this i will have many custom functions which will take some inputs and return values.

那么有人可以帮我评估解析出来的输出,所以最后我应该得到一个 True 或一个 False 作为最终结果?

So can someone please help me to evaluate the parsed out put, so finally I should get a True or a False as final result ?

这是我的python脚本的副本:

Here is the copy of my python script:

from pyparsing import (
    CaselessKeyword,Suppress,Word,alphas,alphanums,nums,Optional,Group,oneOf,Forward,infixNotation,
    opAssoc,dblQuotedString,delimitedList,Combine,Literal,QuotedString,ParserElement,Keyword,
    OneOrMore,pyparsing_common as ppc,
)

ParserElement.enablePackrat()

LPAR, RPAR = map(Suppress, "()")

expr = Forward()

alps = Word(alphas, alphanums) 

def stat_function(name):
    return ( 
        Group(CaselessKeyword(name) + Group(LPAR + delimitedList(expr) + RPAR)) |
        Group(CaselessKeyword(name) + Group(LPAR + delimitedList(alps) + RPAR))
    )

timeFunc = stat_function("Time")
dateFunc = stat_function("Date")
numFunc  = stat_function("Number")
funcCall = timeFunc | dateFunc | numFunc

Compop = oneOf("< = > >= <= != <>")
 
multOp = oneOf("* /")
addOp = oneOf("+ -")
logicalOp = oneOf("and or AND OR")
numericLiteral = ppc.number

operand = numericLiteral | funcCall | alps  
arithExpr = infixNotation(
    operand, [(multOp, 2, opAssoc.LEFT), 
              (addOp, 2, opAssoc.LEFT),
              (Compop, 2, opAssoc.LEFT),
              (logicalOp, 2, opAssoc.LEFT),
              ]  
)

expr <<= arithExpr  

s1 = "(Number(3) > Number(5)) AND (Time(IST) < Number(1030))"
result = expr.parseString(s1)
print(result)

推荐答案

解析您的表达式后,您的工作只完成了一半(如果是).

Having parsed your expression, your work is only half done (if that).

在将您的输入字符串转换为标记的嵌套结构后,典型的下一步是递归遍历此结构并解释您的标记(例如数字"、与"等)并执行相关功能.

The typical next step, after converting your input string into a nested structure of tokens, is to recursively walk this structure and interpret your markers such as "Number", "AND", etc. and perform the associated functions.

然而,这样做几乎只是回顾了 pyparsing 已经完成的步骤,以及生成的 infixNotation 表达式.

However, doing so will be pretty much just retracing the steps already done by pyparsing, and the generated infixNotation expression.

我建议为语法中的每个表达式定义可评估的节点类,并将它们作为解析操作传递给 pyparsing.然后当你完成后,你可以调用 result.eval().评估的实际功能在每个相关的节点类中实现.这样,pyparsing 会在解析时构建您的节点,而不必事后再次构建.

I recommend defining eval'able node classes for each expression in your grammar, and pass them to pyparsing as parse actions. Then when you are done, you can just call result.eval(). The actual functionality of the evaluation is implemented in each related node class. This way, pyparsing does the construction of your nodes while it parses, instead of you having to do it again after the fact.

以下是您的函数的类(我稍微修改了 stat_function 以适应它们):

Here are the classes for your functions (and I have slightly modified stat_function to accommodate them):

class Node:
    """
    Base class for all of the parsed node classes.
    """
    def __init__(self, tokens):
        self.tokens = tokens[0]

class Function(Node):
    def __init__(self, tokens):
        super().__init__(tokens)
        self.arg = self.tokens[1][0]

class Number(Function):
    def eval(self):
        return self.arg

class Time(Function):
    def eval(self):
        from datetime import datetime
        if self.arg == "IST":
            return int(datetime.now().strftime("%H%M"))
        else:
            return 0

class Date(Function):
    def eval(self):
        return 0

def stat_function(name, eval_fn):
    return ( 
        Group(CaselessKeyword(name) + Group(LPAR + delimitedList(expr) + RPAR)) |
        Group(CaselessKeyword(name) + Group(LPAR + delimitedList(alps) + RPAR))
    ).addParseAction(eval_fn)

timeFunc = stat_function("Time", Time)
dateFunc = stat_function("Date", Date)
numFunc  = stat_function("Number", Number)
funcCall = timeFunc | dateFunc | numFunc

(我不得不猜测Time(IST)"应该做什么,但由于您是在与 1030 进行比较,我假设它会以整数形式返回 24 小时 HHMM 时间.)

(I had to guess at what "Time(IST)" was supposed to do, but since you were comparing to 1030, I assumed it would return 24-hour HHMM time as an int.)

现在您可以解析这些简单的操作数并计算它们:

Now you can parse these simple operands and evaluate them:

s1 = "Number(27)"
s1 = "Time(IST)"
result = expr.parseString(s1)
print(result)
print(result[0].eval())

现在同样适用于您的逻辑节点、比较节点和算术节点:

The same now goes for your logical, comparison, and arithmetic nodes:

class Logical(Node):
    def eval(self):
        # do not make this a list comprehension, else
        # you will defeat any/all short-circuiting
        eval_exprs = (t.eval() for t in self.tokens[::2])
        return self.logical_fn(eval_exprs)

class AndLogical(Logical):
    logical_fn = all
    
class OrLogical(Logical):
    logical_fn = any

class Comparison(Node):
    op_map = {
        '<': operator.lt,
        '>': operator.gt,
        '=': operator.eq,
        '!=': operator.ne,
        '<>': operator.ne,
        '>=': operator.ge,
        '<=': operator.le,
    }
    def eval(self):
        op1, op, op2 = self.tokens
        comparison_fn = self.op_map[op]
        return comparison_fn(op1.eval(), op2.eval())

class BinArithOp(Node):
    op_map = {
        '*': operator.mul,
        '/': operator.truediv,
        '+': operator.add,
        '-': operator.sub,
    }
    def eval(self):
        # start by eval()'ing the first operand
        ret = self.tokens[0].eval()

        # get following operators and operands in pairs
        ops = self.tokens[1::2]
        operands = self.tokens[2::2]
        for op, operand in zip(ops, operands):
            # update cumulative value by add/subtract/mult/divide the next operand
            arith_fn = self.op_map[op]
            ret = arith_fn(ret, operand.eval())
        return ret

operand = numericLiteral | funcCall | alps  

infixNotation 的每个运算符级别都有一个可选的第四个参数,用作该操作的解析操作.(我还将 AND 和 OR 作为单独的级别进行了划分,因为 AND 的计算优先级通常高于 OR).

Each operator level to infixNotation takes an optional fourth argument, which is used as a parse action for that operation. (I also broke out AND and OR as separate levels, as AND's are typically evaluated at higher precedence than OR).

arithExpr = infixNotation(
    operand, [(multOp, 2, opAssoc.LEFT, BinArithOp), 
              (addOp, 2, opAssoc.LEFT, BinArithOp),
              (Compop, 2, opAssoc.LEFT, Comparison),
              (CaselessKeyword("and"), 2, opAssoc.LEFT, AndLogical),
              (CaselessKeyword("or"), 2, opAssoc.LEFT, OrLogical),
              ]  
)

expr <<= arithExpr

现在评估您的输入字符串:

Now to evaluate your input string:

s1 = "(Number(3) > Number(5)) AND (Time(IST) < Number(1030))"
result = expr.parseString(s1)
print(result)
print(result[0].eval())

打印:

[<__main__.AndLogical object at 0xb64bc370>]
False

如果你改变 s1 来比较 3 <5(并在上午 10:30 之前运行此示例):

If you change s1 to compare 3 < 5 (and run this example before 10:30am):

s1 = "(Number(3) < Number(5)) AND (Time(IST) < Number(1030))"

你得到:

[<__main__.AndLogical object at 0xb6392110>]
True

pyparsing examples目录中有类似的例子,搜索那些使用 infixNotation.

There are similar examples in the pyparsing examples directory, search for those using infixNotation.

这篇关于pyParsing 评估表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆