【问题标题】:Python Pyparsing: Identify Pattern of Optional List + Keyword + Optional ListPython Pyparsing:识别可选列表+关键字+可选列表的模式
【发布时间】:2021-10-08 04:09:02
【问题描述】:

我正在尝试使用 Pyparser 解析 Csound 操作码行(以创建自定义自动格式化程序),但我一直在尝试定义以下公式:

optional comma list + keyword + optional comma list

Csound 代码行的可能变化:

        prints  "int: %d%n", 45
a1      oscil   0.5, 440, -1
aL, aR  stereo  a1
        outs    aL, aR

以及它们的语法:

                opcode  string, param
output          opcode  param, param, param
output, output  opcode  param
                opcode  param, param

到目前为止,我想出了这个:

import pyparsing as pp

samples = [
    "prints \"int: %d%n\", 45",
    "a1 oscil 0.5, 440",
    "aL, aR stereo a1",
    "outs aL, aR"
]

var = pp.Word(pp.alphanums)

outputs = pp.Optional(pp.delimitedList(var))

opcode = pp.Word(pp.alphanums)

param = var | pp.dblQuotedString
params = pp.Optional(pp.delimitedList(param))

csound_line = outputs("outputs") \
    + opcode("opcode") \
    + params("params")

parsed = csound_line.parseString(samples[1])
print(parsed.dump())
parsed = csound_line.parseString(samples[2])
print(parsed.dump())
parsed = csound_line.parseString(samples[3])
print(parsed.dump())
parsed = csound_line.parseString(samples[0])
print(parsed.dump())

但它给了我这个:

['a1', 'oscil', '0']
- opcode: 'oscil'
- outputs: ['a1']
- params: ['0']
['aL', 'aR', 'stereo', 'a1']
- opcode: 'stereo'
- outputs: ['aL', 'aR']
- params: ['a1']
['outs', 'aL']
- opcode: 'aL'
- outputs: ['outs']
Traceback (most recent call last):
  File "/home/oliver/projects/personal/local/csoundformat/./test.py", line 33, in <module>
    parsed = csound_line.parseString(samples[0])
  File "/usr/lib/python3.9/site-packages/pyparsing.py", line 1955, in parseString
    raise exc
  File "/usr/lib/python3.9/site-packages/pyparsing.py", line 3250, in parseImpl
    raise ParseException(instring, loc, self.errmsg, self)
pyparsing.ParseException: Expected W:(ABCD...), found '"'  (at char 7), (line:1, col:8)

解析第 2 行工作正常,但第 1 行和第 3 行缺少结束参数,第 0 行导致抱怨双引号,尽管我使用了dblQuotedString

我似乎无法确定Optional/ZeroOrMoredelimitedList 的正确组合。任何帮助将不胜感激。

谢谢!

【问题讨论】:

    标签: python string parsing pyparsing


    【解决方案1】:

    回答核心问题前的几点说明:

    1. 我发现您以与执行运行不同的顺序列出输入样本非常令人困惑。我想我理解你为什么按这个顺序运行它们——尽管 try 块可以解决问题——但是重新排序输入列表会更加便于阅读。只是说,以供将来参考。)

    2. oscil 行提前中断,因为您的param 仅识别字母数字和带引号的字符串。无符号整数由alphanums 组成,但由于. 不是字母数字,所以像0.5 这样的浮点数不匹配。 pp.Word(pp.alphanums)0 之后停止。

      -1 会遇到类似的问题,不同之处在于没有可以匹配的前一个数字。


    不过,根本问题是语法是模棱两可的,除非您有办法区分输出变量和操作码。没有它,a b 可以被解析为output(a) opcode(b) param()output() opcode(a) param(b)

    Pyparsing 的optional 是贪婪的,所以pp.optional(outputs) 会将第一个var 标记解析为一个元素outputs。这意味着它将a b 解析为outputs(a) opcode(b) params(),这解决了歧义,但并不总是产生正确的解析。即使在明显错误的情况下(对于可以看到整个命令的人类观察者),它也会这样做,例如命令opop "foo"op 将被解析为输出而不是操作码这一事实意味着这两者都会产生语法错误。 (这就是prints 命令的问题。)

    所以解析一行,需要区分以下几种情况(我用[...]表示可选):

    a , ...    => a is an output, ... is (more) outputs followed by opcode [params]
    a b , ...  => a is an opcode, b is a param, ... is more params
    a b c ...  => a is an output, b is an opcode, ... is [, params]
    a b        => ambiguous. Either output opcode or opcode param.
    a          => a is an opcode (and nothing follows)
    

    这只是一个近似值,因为参数可以是带引号的字符串、数字或(我想)一个表达式。我认为下面的语法可以正确处理这种情况,但您需要扩展 param 的定义才能尝试。

    使用opt_params 而不是params 组合第三种和第四种情况可能会更有效;然后,您可以通过缺少参数来检测模棱两可的情况。但我这样保留是为了让歧义更清楚。

    var = pp.Word(pp.alphanums)
    
    outputs = pp.delimitedList(var)
    opt_outputs = pp.Optional(outputs)
    
    opcode = pp.Word(pp.alphanums)
    
    # Note: Probably need to add expressions. I added a very simple
    # floating point syntax, but it doesn't handle signs. (It has to go first
    # in order to avoid 'var' matching the initial integer.)
    param = pp.Combine(pp.Word(pp.nums) + '.' + pp.Word(pp.nums)) \
            | var \
            | pp.dblQuotedString
    
    params = pp.delimitedList(param)
    opt_params = pp.Optional(params)
    
    comma = pp.Suppress(',')
    
    csound_line = ( (var + comma + outputs)("outputs")
                    + opcode("opcode")
                    + opt_params("params")
                  ) | (
                    opcode("opcode") + (param + comma + params)("params")
                  ) | (
                    pp.And((var,))("outputs") + opcode("opcode") + params("params")
                  ) | (
                    (var + var)("ambiguous")
                  ) | (
                    opcode("opcode") + opt_params("params")
                  )
    

    pp.And((var,)) 的用途是将单个输出标记放入列表中,以与其他 outputs 解析保持一致。可能有更好的方法来做到这一点。)

    请注意,pyparsing 的parseString 并不坚持解析到输入的末尾,这就是为什么您的一些测试用例会默默失败的原因。我认为失败是明确的更好,所以我在电话中添加了parseAll。我还在它周围放了一个try 块,以使测试更容易编写:

    samples = """
        prints "int: %d%n", 45
        a1 oscil 0.5, 440
        aL, aR stereo a1
        outs aL, aR
        output opcode param
        opcode param
        output opcode
        opcode
    """
    for sample in samples.splitlines()[1:]:
        print(sample)
        try:
            print(csound_line.parseString(sample, parseAll=True).dump())
        except pp.ParseException as e:
            print("Parse failed:")
            print(e)
        print('-----------------------')
    

    这是测试输出:

        prints "int: %d%n", 45
    ['prints', '"int: %d%n"', '45']
    - opcode: 'prints'
    - params: ['"int: %d%n"', '45']
    -----------------------
        a1 oscil 0.5, 440
    ['a1', 'oscil', '0.5', '440']
    - opcode: 'oscil'
    - outputs: ['a1']
    - params: ['0.5', '440']
    -----------------------
        aL, aR stereo a1
    ['aL', 'aR', 'stereo', 'a1']
    - opcode: 'stereo'
    - outputs: ['aL', 'aR']
    - params: ['a1']
    -----------------------
        outs aL, aR
    ['outs', 'aL', 'aR']
    - opcode: 'outs'
    - params: ['aL', 'aR']
    -----------------------
        output opcode param
    ['output', 'opcode', 'param']
    - opcode: 'opcode'
    - outputs: ['output']
    - params: ['param']
    -----------------------
        opcode param
    ['opcode', 'param']
    - ambiguous: ['opcode', 'param']
    -----------------------
        output opcode
    ['output', 'opcode']
    - ambiguous: ['output', 'opcode']
    -----------------------
        opcode
    ['opcode']
    - opcode: 'opcode'
    -----------------------
    

    【讨论】:

    • 我已经写了很多“循环遍历这些测试用例并尝试使用 parseAll=True 调用 parseString 并打印结果或异常”方法,我最终将 runTests 添加到 @987654357 @。了解 csound_line.runTests(samples) 如何为您工作(甚至不必致电 splitlines())。
    • 哇!谢谢,这是对我的问题的一个很棒的解释!没想到在csound_line里合并多个case,很巧妙。你是对的,opcode/param 和 output/opcode 行的语法令人讨厌。也许我会将它们视为自己的“模棱两可”类别并相应地格式化它们。另一种方法是从 Csound 文档中创建某种操作码定义的数据库(一项非常庞大的任务)。再次感谢您,这既是令人大开眼界的又是一次愉快的阅读!
    猜你喜欢
    • 2021-10-14
    • 1970-01-01
    • 2020-09-25
    • 2015-06-17
    • 2014-09-15
    • 2020-10-13
    • 2019-04-16
    • 2020-05-18
    • 1970-01-01
    相关资源
    最近更新 更多