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

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

  •  3
  • Cameron  · 技术社区  · 15 年前

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

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

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

    我不知道从哪种技术开始,甚至不知道有什么技术可以解决这样的问题。你认为最好的方法是什么?我并不是在寻找一个完整的解决方案,只是朝着正确的方向推动。

    谢谢。

    语言示例文件:

    # Comments start with the '#' character and last until the end of the line
    # Indentation is significant (as in Python)
    
    
    constant NINETY_NINE = 99       # Defines the constant `NINETY_NINE` to have the value `99`
    
    
    *:      # 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
            dark:           # dark has no special validation rules
                extraDark:
                    melt            # Filter that modifies the object being examined
                    c:bitter        # Must be bitter, but only validated on client
                    s:cocoa(NINETY_NINE)    # Must contain 99% cocoa, but only validated on server. Note constant
            milk:
                creamy(2)   # Level 2 creamy, overrides creamy(1) of chocolate.lindt.* for chocolate.lindt.milk
                creamy(3)   # Overrides creamy(2) of previous line (all but the last specification of a given rule are ignored)
    
    
    
    ruleset food:       # To define a chunk of validation rules that can be expanded from the placeholder `food` (think macro)
        caloriesWithin(10, 2000)        # Unlimited parameters allowed
        edible
        leftovers:      # Nested rules allowed in rulesets
            stale
    
    # Rulesets may be nested and/or include other rulesets in their definition
    
    
    
    chocolate:              # Previously defined groups can be re-opened and expanded later
        ferrero:
            hasHazelnut
    
    
    
    cake:
        tasty               # Same rule used for different data (see chocolate.lindt)
        isLie
        ruleset food        # Substitutes with rules defined for food; cake.leftovers must now be stale
    
    
    pasta:
        ruleset food        # pasta.leftovers must also be stale
    
    
    
    
    # Sample use (in JavaScript):
    
    # var choc = {
    #   lindt: {
    #       cocoa: {
    #           percent: 67,
    #           mass:    '27g'
    #       }
    #   }
    #   // Objects/groups that are ommitted (e.g. ferrro in this example) are not validated and raise no errors
    #   // Objects that are not defined in the validation rules do not raise any errors (e.g. cocoa in this example)
    # };
    # validate('chocolate', choc);
    
    # `validate` called isYummy(choc), sweet(choc), isYummy(choc.lindt), smooth(choc.lindt), creamy(choc.lindt, 1), and tasty(choc.lindt) in that order
    # `validate` returned an array of any validation errors that were found
    
    # Order of rule validation for objects:
    # The current object is initially the object passed in to the validation function (second argument).
    # The entry point in the rule group hierarchy is given by the first argument to the validation function.
    # 1. First all rules that apply to all objects (defined using '*') are applied to the current object,
    #    starting with the most global rules and ending with the most local ones.
    # 2. Then all specific rules for the current object are applied.
    # 3. Then a depth-first traversal of the current object is done, repeating steps 1 and 2 with each object found as the current object
    # When two rules have equal priority, they are applied in the order they were defined in the file.
    
    
    
    # No need to end on blank line
    
    7 回复  |  直到 15 年前
        1
  •  9
  •   Marcelo Cantos    15 年前

    首先,如果您想学习解析,那么就编写自己的递归下降解析器。您定义的语言只需要少量的产品。我建议使用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代码。

        2
  •  5
  •   Beni Cherniavsky-Paskin    15 年前

    同样,不要教你解析,但是你的格式非常接近于法律 YAML 你可能只想重新定义你的语言作为yaml的一个子集并使用 standard YAML parser .

        3
  •  2
  •   Engineero    6 年前

    如果你的目标是学习解析,我强烈推荐一个OO风格的库,比如 PyParsing . 它们不像更复杂的鹿角,lex,yac选项那么快,但是您可以马上开始解析。

        4
  •  1
  •   Anurag Uniyal    15 年前

    正如“marcelo cantos”所建议的,您可以使用python dict,好处是您不必解析任何东西,您可以在服务器端使用与python dict相同的规则,在客户端使用javascript对象,并可以将它们作为json从服务器传递到客户机或viceversa。

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

    但我不确定您是否能够轻松地解析缩进语言。

        5
  •  1
  •   BenMorel Manish Pradhan    11 年前

    您所展示的示例语言可能太复杂,无法为编写简单(并且没有bug)的解析函数。我建议阅读解析技术,如递归下降或表驱动解析,如ll(1)、ll(k)等。

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

    例如,

    巧克力:甜
    巧克力。林特:好吃。
    巧克力。林德*:光滑,乳脂状(1)

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

        6
  •  0
  •   SpliFF    15 年前

    有一些库和工具可以简化解析。其中一个更著名的是雷克斯/雅克。有一个叫做“的python库 lex 和A tutorial 使用它。

        7
  •  0
  •   D.C.    15 年前

    定制文件结构的动机是什么?是否可以将您的数据重新构造为一个更为知名的结构,如XML?如果是这样,您可以使用多个文件中的一个来解析您的文件。使用一个可接受的分析工具可能会节省大量的调试时间,如果考虑到这一点,它可能会使您的文件更具可读性。