我正在用 python 编写游戏,并决定为地图数据文件创建一个 DSL。我知道我可以用正则表达式编写自己的解析器,但我想知道是否有现有的 python 工具可以更容易地做到这一点,比如 PHP 引擎中使用的 re2c。
一些额外的信息:
- 是的,我确实需要 DSL,即使我不需要,我仍然想要在项目中构建和使用 DSL。
DSL 只包含数据(声明性的?),它不会被“执行”。大多数行看起来像:
SOMETHING: !abc @123 #xyz/123
我只需要读取数据树。
这是一种非常有效的方法。
abc= ONETHING( ... )
xyz= ANOTHERTHING( ... )
pqr= SOMETHING( this=abc, that=123, more=(xyz,123) )
声明性的。易于解析。
和...
它实际上是 Python。一些类声明,工作就完成了。DSL 实际上是类声明。
重要的是 DSL 只是创建对象。定义 DSL 时,首先必须从对象模型开始。稍后,您围绕该对象模型添加了一些语法。您不是从语法开始,而是从模型开始。
我在工作中编写了类似的东西来读取 SNMP 通知定义并从中自动生成 Java 类和 SNMP MIB 文件。使用这个小 DSL,我可以编写 20 行规范,它会生成大约 80 行 Java 代码和一个 100 行 MIB 文件。
为了实现这一点,我实际上只是使用了直接的 Python 字符串处理(split()、切片等)来解析文件。我发现 Python 的字符串功能足以满足我的大多数(简单)解析需求。
除了其他人提到的库之外,如果我正在编写更复杂的东西并且需要适当的解析功能,我可能会使用支持 Python(和其他语言)的ANTLR 。
DSL 是个好东西,所以你不需要为自己辩护 :-) 但是,你考虑过内部 DSL 吗?与外部(解析的)DSL 相比,它们有很多优点,至少值得考虑。将 DSL 与本机语言的强大功能混合在一起确实可以为您解决很多问题,而且 Python 在内部 DSL 方面也不是很差,而且with
声明很方便。
对于您所描述的“小语言”,我使用简单的拆分、shlex(请注意 # 定义注释)或正则表达式。
>>> line = 'SOMETHING: !abc @123 #xyz/123'
>>> line.split()
['SOMETHING:', '!abc', '@123', '#xyz/123']
>>> import shlex
>>> list(shlex.shlex(line))
['SOMETHING', ':', '!', 'abc', '@', '123']
以下是一个示例,因为我不知道您在寻找什么。
>>> import re
>>> result = re.match(r'([A-Z]*): !([a-z]*) @([0-9]*) #([a-z0-9/]*)', line)
>>> result.groups()
('SOMETHING', 'abc', '123', 'xyz/123')
在声明性 python 的行中,我编写了一个名为“bpyml”的辅助模块,它可以让你以一种更 XML 结构化的方式在 python 中声明数据,而不需要详细的标签,它也可以转换为 XML 或从 XML 转换,但它是有效的 python。
https://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts/modules/bpyml.py
示例使用 http://wiki.blender.org/index.php/User:Ideasman42#Declarative_UI_In_Blender
如果我可以使用新的运算符扩展 python 语法以在语言中引入新的功能怎么办?例如,<=>
用于交换两个变量值的 new 运算符。
我怎样才能实现这种行为?AST 模块来了。最后一个模块是处理抽象语法树的便捷工具。这个模块最酷的地方在于它允许我编写生成树然后将其编译为 python 代码的 python 代码。
假设我们想将超集语言(或类似 Python 的语言)编译为 Python:
从 :
a <=> b
至:
a , b = b , a
我需要将我的“python like”源代码转换为令牌列表。所以我需要一个分词器,一个 Python 源代码的词法扫描器。标记模块
我可以使用相同的元语言来定义新的“类 Python”语言的语法,然后构建抽象语法树AST的结构
为什么要使用 AST?
from tokenize import untokenize, tokenize, NUMBER, STRING, NAME, OP, COMMA
import io
import ast
s = b"a <=> b\n" # i may read it from file
b = io.BytesIO(s)
g = tokenize(b.readline)
result = []
for token_num, token_val, _, _, _ in g:
# naive simple approach to compile a<=>b to a,b = b,a
if token_num == OP and token_val == '<=' and next(g).string == '>':
first = result.pop()
next_token = next(g)
second = (NAME, next_token.string)
result.extend([
first,
(COMMA, ','),
second,
(OP, '='),
second,
(COMMA, ','),
first,
])
else:
result.append((token_num, token_val))
src = untokenize(result).decode('utf-8')
exp = ast.parse(src)
code = compile(exp, filename='', mode='exec')
def my_swap(a, b):
global code
env = {
"a": a,
"b": b
}
exec(code, env)
return env['a'], env['b']
print(my_swap(1,10))
其他使用 AST 的模块,其源代码可能是有用的参考: