Python 有关如何解析自定义文件格式的提示
很抱歉标题含糊不清,但我真的不知道如何简明扼要地描述这个问题 我已经创建了一个(或多或少)简单的方法,我将使用它来指定将哪些验证规则应用于不同的实体(通常是从网页提交的表单)。我在这篇文章的底部提供了一个关于这种语言的示例 我的问题是,我不知道如何开始将这种语言解析为我可以使用的形式(我将使用Python进行解析)。我的目标是最终得到一个规则/过滤器列表(作为字符串,包括参数,例如Python 有关如何解析自定义文件格式的提示,python,parsing,file-format,Python,Parsing,File Format,很抱歉标题含糊不清,但我真的不知道如何简明扼要地描述这个问题 我已经创建了一个(或多或少)简单的方法,我将使用它来指定将哪些验证规则应用于不同的实体(通常是从网页提交的表单)。我在这篇文章的底部提供了一个关于这种语言的示例 我的问题是,我不知道如何开始将这种语言解析为我可以使用的形式(我将使用Python进行解析)。我的目标是最终得到一个规则/过滤器列表(作为字符串,包括参数,例如'cocoa(99)),应该(按顺序)应用于每个对象/实体(也是一个字符串,例如'chocolate','choco
'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
首先,如果您想学习解析,那么就编写自己的递归下降解析器。您定义的语言只需要少量产品。我建议使用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代码。有一些库和工具可以简化解析。其中一个比较著名的是lex/yacc。有一个名为“”的python库和一个关于使用它的函数。正如“Marcelo Cantos”所建议的,您可以使用python dict,好处是您不必解析任何东西,您可以在服务器端使用与python dict相同的规则,在客户端使用javascript对象,并可以将它们作为JSON从服务器传递到客户端或反之亦然 如果你真的想自己做语法分析,看看这个
但我不确定您是否能够轻松解析缩进语言。您所展示的示例语言可能太复杂,无法为其编写简单(且无bug)的解析函数。我建议阅读解析技术,如递归下降或表驱动解析,如LL(1)、LL(k)等 但这可能过于笼统和/或复杂。将规则语言简化为类似分隔文本这样的简单内容可能更容易 比如说 巧克力:甜的
巧克力。林德:好吃
巧克力。林德。*:光滑,奶油状(1)
这将更容易解析,并且可以在没有正式解析器的情况下完成。如果您的目标是学习解析,我强烈推荐一个类似的OO风格库。它们的速度不如更复杂的antler、lex和yac选项快,但您可以立即开始解析 定制文件结构的动机是什么?是否有可能将您的数据重新建模为更为人熟知的结构,如XML?如果是这样的话,您可以使用多种方法中的一种来解析您的文件。使用一个公认的解析工具可以为您节省大量调试时间,如果这是一个考虑因素,那么它可能会使您的文件更具可读性。再次不教您解析,但您的格式非常接近法律,您可能只想将您的语言重新定义为YAML的一个子集并使用。嗯,很有趣。这将使解析变得非常简单,但我真的很想自己学习如何进行解析。此外,该文件可以通过web界面进行编辑,因此我不允许输入任意Python代码,即使它只能由管理员编辑;-)+1,是的,dict似乎是这里的最佳解决方案,可以在javascript端轻松使用并作为jsonYes传递,我发明这种伪语言的原因之一是有一种可以“编译”成Python和javascript对象的自定义格式。谢谢大家的评论。我在前面添加了一个部分来介绍如何学习解析,在最后添加了一个部分来介绍更干净(在某些方面更脏)的实用解决方案。+1:不要发明DSL。只需使用Python。它简单得多,而且已经起作用了。当你有一个DSL解决的问题时,你现在有两个问题。原始问题加上对DSL的支持。感谢链接。在Pycon2010上,我做这个项目更多的是为了学习,而不是实际完成事情(尽管那也很好),Andrew Dalke表明,虽然PyParsing可能很慢,但PLY实际上是一个相当快的替代方案。他的幻灯片已经在播放,他的演讲视频应该很快就会播放出来。如果你对解析感兴趣的话,他的博客文章也不容错过@gotgenes我很想参加那个演讲+1.我不久前就意识到了这一点,并开始以这种方式实施。它工作得很好,尽管我对语法分析了解不多。从那以后,我开始转向不使用DSL的另一个方向,但是谢谢你的回复!