代码之家  ›  专栏  ›  技术社区  ›  orange

对上下文执行Pyparsing操作

  •  2
  • orange  · 技术社区  · 12 年前

    我如何构建 pyparsing 允许在上下文/状态对象上执行操作的程序?

    我的程序示例如下:

    load 'data.txt'
    remove line 1
    remove line 4
    

    第一行应该加载文件,第2行和第3行是对文件内容进行操作的命令。因此,我希望在执行完所有命令后,文件的内容都会显示出来。

    load_cmd = Literal('load') + filename
    remove_cmd = Literal('remove line') + line_no
    more_cmd = ...
    
    def load_action(s, loc, toks):
        # load file, where should I store it?
    
    load_cmd.setParseAction(load_action)
    
    def remove_line_action(s, loc, toks):
        # remove line, how to obtain data to operate on? where to write result?
    
    remove_line_cmd.setParseAction(remove_cmd)
    
    # Is this the right way to define a whole program, i.e. not only one line?
    program = load_cmd + remove_cmd | more_cmd |...
    
    # How do I obtain the result?
    program.scanString("""
        load 'data.txt'
        remove line 1
        remove line 4
    """)
    
    2 回复  |  直到 12 年前
        1
  •  3
  •   PaulMcG    12 年前

    我已经写了一些这种命令解析风格的pyparsing示例,您可以在以下网站上找到它们: http://pyparsing.wikispaces.com/file/view/simpleBool.py/451074414/simpleBool.py http://pyparsing.wikispaces.com/file/view/eval_arith.py/68273277/eval_arith.py

    我还编写了一个简单的冒险风格的游戏处理器,它接受解析的命令结构,并对游戏“世界”执行它们,该游戏充当命令执行器。我在2006年PyCon上展示了这个,但会议页面的链接已经过时了——现在可以在 http://www.ptmcg.com/geo/python/confs/pyCon2006_pres2.html (演示文稿使用S5鼠标右下角查看导航按钮)。代码位于 http://www.ptmcg.com/geo/python/confs/adventureEngine.py.txt ,代码的UML图位于 http://www.ptmcg.com/geo/python/confs/pyparsing_adventure.pdf .

    我发现最有效的通用模式类似于旧的模型视图控制器模式。

    模型是您的虚拟机,它维护从命令到命令的上下文。在里面 simple_bool 上下文只是推断的局部变量范围,因为每个解析的语句都是 eval ed.输入 eval_arith ,此上下文保留在 EvalConstant._vars dict,包含预定义和已解析变量的名称和值。在Adventure引擎中,上下文保存在Player对象中(包含指向当前Room和Item集合的属性),并传递给解析的命令对象以执行命令。

    视图是解析器本身。它提取每个命令的片段并组成一个命令类的实例。命令类的接口 exec 方法取决于如何设置模型。但总的来说,你可以想象 执行 您定义的方法将把Model作为参数之一(如果不是唯一的话)。

    然后,Controller是一个简单的循环,它实现了以下伪代码:

    while not finished
        read a command, assign to commandstring
    
        parse commandstring, use parsed results to create commandobj (null if bad parse)
    
        if commandobj is not null:
    
            commandobj.exec(context)
    
        finished = context.is_finished()
    

    如果使用pyparsing实现解析器,则可以将Command类定义为该抽象类的子类:

    class Command(object):
        def __init__(self, s, l, t):
            self.parameters = t
        def exec(self, context):
            self._do_exec(context)
    

    定义每个命令时,可以直接将相应的子类作为命令表达式的解析操作传递。例如,用于在迷宫中移动的简化GO命令如下所示:

    goExpression = Literal("GO") + oneOf("NORTH SOUTH EAST WEST")("direction")
    goExpression.setParseAction(GoCommand)
    

    对于上面的抽象Command类,GoCommand类可能如下所示:

    class GoCommand(Command):
        def _do_exec(self, context):
            if context.is_valid_move(self.parameters.direction):
                context.move(self.parameters.direction)
            else:
                context.report("Sorry, you can't go " + 
                                self.parameters.direction + 
                                " from here.")
    

    通过解析像“GO NORTH”这样的语句,您将得到的不是包含标记“GO”和“NORTH”的ParseResults,而是一个GoCommand实例,其参数包括命名标记“direction”,为GO命令提供方向参数。

    因此,设计步骤如下:

    • 设计虚拟机及其命令界面

    • 创建一个类以捕获虚拟机中的状态/上下文

    • 设计命令及其相应的命令子类

    • 为每个命令创建pyparsing解析器表达式

    • 将Command子类作为解析操作附加到每个命令的pyparsing表达式

    • 通过使用“|”组合所有命令表达式来创建整体解析器

    • 实现命令处理器循环

        2
  •  1
  •   ssm    12 年前

    我会这样做:

    cmdStrs = '''
    load
    remove line
    add line
    some other command
    '''
    
    def loadParse(val): print 'Load --> ' + val
    def removeParse(val): print 'remove --> ' + val
    def addLineParse(val): print 'addLine --> ' + val
    def someOtherCommandParse(val): print 'someOther --> ' + val
    
    
    commands  = [ l.strip() for l in cmdStrs.split('\n') if l.strip() !='' ]
    functions = [loadParse,
                 removeParse,
                 addLineParse,
                 someOtherCommandParse]
    
    funcDict = dict( zip(commands, functions) )
    
    program = '''
    
        # This is a comment
    
        load 'data.txt' # This is another comment
        remove line 1
        remove line 4
    '''
    
    for l in program.split('\n'):
        l = l.strip().split('#')[0].strip() # remove comments
        if l == '': continue
    
        commandFound = False
        for c in commands:
            if c in l: 
                funcDict[c](l.split(c)[-1])
                commandFound = True
    
        if not commandFound:
            print 'Error: Unknown command : ', l 
    

    当然,您可以将整个内容放在一个类中并使其成为一个对象,但您可以看到一般结构。如果您有一个对象,那么您可以继续创建一个可以处理上下文/状态信息的版本。然后,上面的函数将只是成员函数。

    为什么我觉得你是在学习Haskell之后才开始使用Python的?通常情况下,人们会走相反的路。在Python中,您可以免费获得状态。你不需要上课。您可以使用类来处理同一程序中的多个状态:)。