【问题标题】:pyParsing Evaluating ExpressionpyParsing 评估表达式
【发布时间】:2020-11-06 05:25:09
【问题描述】:

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

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

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

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

这是我的 python 脚本的副本:

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)

【问题讨论】:

    标签: python parsing pyparsing


    【解决方案1】:

    解析你的表达式后,你的工作只完成了一半(如果那样的话)。

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

    但是,这样做几乎只是追溯 pyparsing 已经完成的步骤,以及生成的 infixNotation 表达式。

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

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

    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
    

    (我不得不猜测“时间(IST)”应该做什么,但由于您与 1030 进行比较,我认为它会以 int 形式返回 24 小时 HHMM 时间。)

    现在您可以解析这些简单的操作数并对其求值:

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

    逻辑节点、比较节点和算术节点现在也是如此:

    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)。

    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
    

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

    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

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

    你得到:

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

    pyparsing examples directory中有类似的例子,搜索使用infixNotation的。

    【讨论】:

    • 很好的答案!谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-28
    • 2010-12-05
    • 2016-07-26
    相关资源
    最近更新 更多