使用 Python 的 parsec.py 库进行递归解析

Recursive parsing with Python's parsec.py library

我有一个关于使用 Python 的 parsec.py 库进行解析的基本问题。

我想提取文本中某处的日期。例如,

Lorem ipsum dolor sit amet. A number 42 is present here. But here is a date 11/05/2017. Can you extract this?

Lorem ipsum dolor sit amet.
A number 42 is present here.

But here is a date 11/05/2017. Can you extract this?

在这两种情况下,我都希望解析器 return 11/05/2017.

我只想使用 parsec.py 解析库,不想使用正则表达式。 parsec 内置的正则表达式函数没问题。

我试过

from parsec import *

ss = "Lorem ipsum dolor sit amet. A number 42 is present here. But here is a date 11/05/2017. Can you extract this?"

date_parser = regex(r'[0-9]{2}/[0-9]{2}/[0-9]{4}')

date = date_parser.parse(ss)

我得到ParseError: expected [0-9]{2}/[0-9]{2}/[0-9]{4} at 0:0

有没有办法在到达 date_parser 模式之前忽略文本?不报错?

您想要的是一个解析器,它可以跳过任何不匹配的字符,然后解析后面的正则表达式模式。

日期模式可以用 regex 解析器定义,

date_pattern = regex(r'[0-9]{2}/[0-9]{2}/[0-9]{4}')

我们首先定义一个解析器,它使用任意字符(将包含在库中(编辑:已包含在 v3.9 中)),

def any():
    '''Parse a random character.'''
    @Parser
    def any_parser(text, index=0):
        if index < len(text):
            return Value.success(index + 1, text[index])
        else:
            return Value.failure(index, 'a random char')
    return any_parser

为了表达“跳过任何字符并匹配模式”的想法,我们需要将递归解析器定义为

date_parser = date_pattern ^ (any() >> date_parser)

但它不是一个有效的 python 表达式,因此我们需要

@generate
def date_with_prefix():
    matched = yield(any() >> date_parser)
    return matched

date_parser = date_pattern ^ date_with_prefix

(这里的组合符 ^ 表示 try_choice,你可以在文档中找到它。)

然后它将按预期工作:

>>> date_parser.parse("Lorem ipsum dolor sit amet.")
---------------------------------------------------------------------------
ParseError                                Traceback (most recent call last)
...

ParseError: expected date_with_prefix at 0:27

>>> date_parser.parse("A number 42 is present here.")
---------------------------------------------------------------------------
ParseError                                Traceback (most recent call last)
...

ParseError: expected date_with_prefix at 0:28

>>> date_parser.parse("But here is a date 11/05/2017. Can you extract this?")
'11/05/2017'

为了避免对无效输入的期望和 returns 一个 None,您可以将其定义为 optional 解析器:

date_parser = optional(date_pattern ^ date_with_prefix)