有关如何解析自定义文件格式的提示

时间:2010-01-10 05:39:37

标签: python parsing file-format

对于模糊的标题感到抱歉,但我真的不知道如何简洁地描述这个问题。

我创建了一个(或多或少)简单domain-specific language,我将用它来指定要应用于不同实体的验证规则(通常是从网页提交的表单)。我在这篇文章的底部附上了一个示例语言的样本。

我的问题是我不知道如何开始将这种语言解析为我可以使用的形式(我将使用Python进行解析)。我的目标是最终得到一个规则/过滤器列表(作为字符串,包括参数,例如'cocoa(99)'),应该(按顺序)应用于每个对象/实体(也是一个字符串,例如{{1} },'chocolate'等。)。

我不确定使用什么技术开始,甚至不存在这样的问题的技术。您认为最好的解决方法是什么?我不是在寻找一个完整的解决方案,只是在正确的方向上进行一般性的推动。

感谢。

语言示例文件:

'chocolate.lindt'

7 个答案:

答案 0 :(得分:9)

首先,如果你想学习解析,那么编写你自己的递归下降解析器。您定义的语言只需要少量的制作。我建议使用Python的tokenize库来节省将字节流转换为令牌流的无聊任务。

对于实用的解析选项,请继续阅读......

快速而肮脏的解决方案是使用python本身:

NINETY_NINE = 99       # Defines the constant `NINETY_NINE` to have the value `99`

rules = {
  '*': {     # Applies to all data
    'isYummy': {},      # Everything must be yummy

    'chocolate': {        # To validate, say `validate("chocolate", object)`
      'sweet': {},        # chocolate must be sweet (but not necessarily chocolate.*)

      'lindt': {          # To validate, say `validate("chocolate.lindt", object)`
        'tasty':{}        # Applies only to chocolate.lindt (and not to chocolate.lindt.dark, for e.g.)

        '*': {            # Applies to all data under chocolate.lindt
          'smooth': {}  # Could also be written smooth()
          'creamy': 1   # Level 1 creamy
        },
# ...
    }
  }
}

有几种方法可以解决这个问题,例如,这是一种使用类的更清洁(虽然有点不寻常)的方法:

class _:
    class isYummy: pass

    class chocolate:
        class sweet: pass

        class lindt:
            class tasty: pass

            class _:
                class smooth: pass
                class creamy: level = 1
# ...

作为完整解析器的中间步骤,您可以使用“包含电池”的Python解析器,它解析Python语法并返回AST。 AST非常深,有很多(IMO)不必要的级别。您可以通过剔除只有一个子节点的任何节点将这些结构过滤到更简单的结构。通过这种方法,您可以执行以下操作:

import parser, token, symbol, pprint

_map = dict(token.tok_name.items() + symbol.sym_name.items())

def clean_ast(ast):
    if not isinstance(ast, list):
        return ast
    elif len(ast) == 2: # Elide single-child nodes.
        return clean_ast(ast[1])
    else:
        return [_map[ast[0]]] + [clean_ast(a) for a in ast[1:]]

ast = parser.expr('''{

'*': {     # Applies to all data
  isYummy: _,    # Everything must be yummy

  chocolate: {        # To validate, say `validate("chocolate", object)`
    sweet: _,        # chocolate must be sweet (but not necessarily chocolate.*)

    lindt: {          # To validate, say `validate("chocolate.lindt", object)`
      tasty: _,        # Applies only to chocolate.lindt (and not to chocolate.lindt.dark, for e.g.)

      '*': {            # Applies to all data under chocolate.lindt
        smooth: _,  # Could also be written smooth()
        creamy: 1   # Level 1 creamy
      }
# ...
    }
  }
}

}''').tolist()
pprint.pprint(clean_ast(ast))

这种方法确实有其局限性。最终的AST仍然有点嘈杂,你定义的语言必须可以解释为有效的python代码。例如,你不能支持这个......

*:
    isYummy

...因为此语法不解析为python代码。然而,它的最大优点是你可以控制AST转​​换,因此不可能注入任意Python代码。

答案 1 :(得分:5)

再次没有教您解析,但您的格式非常接近合法YAML,您可能只想将您的语言重新定义为YAML的子集并使用standard YAML parser

答案 2 :(得分:2)

如果您的目标是了解解析,我强烈推荐像PyParsing这样的OO样式库。它们没有更复杂的鹿茸,lex,yac选项那么快,但你马上开始解析。

答案 3 :(得分:1)

正如'Marcelo Cantos'建议你可以使用python dict,好处是你不需要解析任何东西,你可以在服务器端使用与python dict相同的规则,在客户端使用javascript对象,并且可以传递它们从服务器到客户端或反之为JSON。

如果你真的想自己解析,请看这个 http://nedbatchelder.com/text/python-parsers.html

但我不确定你能否轻松解析缩进的语言。

答案 4 :(得分:1)

您展示的示例语言可能过于复杂,无法编写简单(且无错误)的解析函数。我建议阅读解析技术,如递归下降或表驱动解析,如LL(1),LL(k)等。

但这可能过于笼统和/或复杂。将规则语言简化为简单的分隔文本可能更容易。

例如,像

巧克力:甜
chocolate.lindt:美味
chocolate.lindt *:光滑,奶油(1)

这将更容易解析,并且可以在没有正式解析器的情况下完成。

答案 5 :(得分:0)

有一些库和工具可以使解析更容易。其中一个比较着名的是lex / yacc。有一个名为“lex”和使用tutorial的python库。

答案 6 :(得分:0)

自定义文件结构的动机是什么?是否有可能将数据重新构建为更好的已知结构,如XML?如果是这样,您可以使用其中一个来解析您的文件。使用可接受的解析工具可以节省大量的调试时间,如果需要考虑,它可能会使您的文件更具可读性