如何在Python中将上下文无关设计语法表示为内部DSL?
[注意:在提交之前重新阅读时,我意识到这个问题变得有点长篇大论。感谢你耐心听我解释我追求这个目标的原因。我觉得,如果我能帮助另一个进行类似项目的人,如果我知道他们提问的动机,我会更愿意参与其中。]
最近,我开始接触Structure Synth,这是Mikael Hvidtfeldt Christensen开发的一个工具。它可以通过一种叫做Eisenscript的(大部分是)上下文无关语法生成3D几何图形。Structure Synth本身受到上下文无关艺术的启发。上下文无关语法可以用一些非常简单的规则生成惊人的结果。
我现在的工作流程是先从Structure Synth导出一个OBJ文件,然后把它导入Blender,接着设置灯光、材质等等,最后用Luxrender进行渲染。不幸的是,导入这些OBJ文件时,Blender常常会变得非常慢,因为可能有成千上万个对象,而且这些对象的几何形状相对复杂。我说“相对”是因为Structure Synth只生成基本形状,但一个用三角形表示的球体仍然有很多面。
因此,直接在Blender中生成这些结构会比现在的流程更好(Blender对Python脚本的深度支持应该可以实现这一点)。一个智能的Python库可以利用Blender的实例化功能,用一个网格生成无数个对象,从而节省内存。而且Blender是一个功能齐全的3D软件,它解析CFDG的能力将提供远超Structure Synth的创意可能性。
所以我的问题是,如何将Eisenscript语法最好地转换成Python的领域特定语言(DSL)。下面是一个简单的Eisenscript示例:
set maxdepth 2000
{ a 0.9 hue 30 } R1
rule R1 {
{ x 1 rz 3 ry 5 } R1
{ s 1 1 0.1 sat 0.9 } box
}
rule R1 {
{ x 1 rz -3 ry 5 } R1
{ s 1 1 0.1 } box
}
简单解释一下,第二行对R1的第一次调用会随机选择R1的两个定义中的一个。每个R1的定义会递归调用R1(随机选择其中一个定义),并且还会创建一个盒子。第一行在递归达到2000层深后终止生成。
Jeremy Ashkenas(CoffeeScript的创始人)成功地在Ruby中实现了一个上下文无关的DSL,使用了块的概念。它的内部工作原理是为每个规则的“名称”创建一个哈希键,并将每个规则定义的块存储在一个数组中,以便在调用规则时随机选择。
之前的Eisenscript规则定义可以这样转换为Ruby DSL:
rule :r1 do
r1 :x => 1, :rz => 3, :ry => 5
box :s => [1, 1, 0.1], :sat => 0.9
end
rule :r1 do
r1 :x => 1, :rz => -3, :ry => 5
box :s => [1, 1, 0.1]
end
我是一名初学者Python用户,所以我在研究Python的函数式编程能力。看起来lambda的功能太有限,无法创建类似于Jeremy的Ruby DSL的东西,而且据我所知,lambda是唯一的匿名函数选项?
那么,一个有经验的Python开发者会如何设计呢?
2 个回答
写一个可以解析上下文无关文法的程序是很难的。你可能用一些现成的库会更轻松。
我建议你看看 PyParsing 这个模块。下载后里面有很多例子,其中有一个简单的 SQL 解析器,看看这个例子可能会对你有帮助,至少可以作为一个入门。
我决定通过把Ruby库里的代码块转成一些预定义的函数,来构建最初的原型。这些函数会被传入ContextFree实例中,并加上一个条件,以避免出现无限循环的问题,然后作为实例方法添加到这个实例里。现在的进展就是这样。欢迎大家提出意见;这是我写的第一批Python代码,我已经准备好把我在Ruby中用的写法改成Python的写法。
import random
class ContextFree(object):
def __init__(self):
self.rules = {}
# grab any instancemethod to get an instance of the instancemethod class
self.instancemethod = type(self.add_rule)
self.max_depth = 100
self.depth = 0
def add_rule(self, func, prob=1):
rule_name = func.__name__
if not rule_name in self.rules:
self.rules[rule_name] = { 'funcs' : [], 'total' : 0 }
total = self.rules[rule_name]['total']
self.rules[rule_name]['funcs'].append([range(total,(prob+total)), func])
self.rules[rule_name]['total'] += prob
def augmented_func(self, options={}):
if not self.depth >= self.max_depth:
self.depth += 1
pick = self.determine_rule(rule_name)
print('Generation', self.depth)
pick(self)
self.__dict__[rule_name] = self.instancemethod(augmented_func, self)
def determine_rule(self, rule_name):
rule = self.rules[rule_name]
winning_number = random.randrange(0, self.rules[rule_name]['total'])
for func in rule['funcs']:
if winning_number in func[0]:
return func[1]
cf = ContextFree()
def box(self):
print('Rule for box1')
self.box()
cf.add_rule(box)
def box(self):
print('Rule for box2')
self.box()
cf.add_rule(box)
cf.box()
# Output:
## Generation 1
## Rule for box2
## Generation 2
## Rule for box2
## Generation 3
## Rule for box1
## Generation 4
## Rule for box2
## Generation 5
## Rule for box1
## Generation 6
## Rule for box2
## Generation 7
## Rule for box2
## Generation 8
## Rule for box1
## Generation 9
## Rule for box2
## Generation 10
## Rule for box2
## Generation 11
## Rule for box1
## Generation 12
## Rule for box1
## Generation 13
## Rule for box1
## Generation 14
## Rule for box1
## Generation 15
## Rule for box2
## Generation 16
## Rule for box1
## Generation 17
## Rule for box1
## Generation 18
## Rule for box1
## Generation 19
## Rule for box1
## Generation 20
## Rule for box1
## Generation 21
## Rule for box2
## Generation 22
## Rule for box2
## Generation 23
## Rule for box1
## Generation 24
## Rule for box2
## Generation 25
## Rule for box1
## Generation 26
## Rule for box2
## Generation 27
## Rule for box2
## Generation 28
## Rule for box1
## Generation 29
## Rule for box2
## Generation 30
## Rule for box2
## Generation 31
## Rule for box2
## Generation 32
## Rule for box2
## Generation 33
## Rule for box2
## Generation 34
## Rule for box1
## Generation 35
## Rule for box2
## Generation 36
## Rule for box1
## Generation 37
## Rule for box1
## Generation 38
## Rule for box1
## Generation 39
## Rule for box1
## Generation 40
## Rule for box2
## Generation 41
## Rule for box1
## Generation 42
## Rule for box1
## Generation 43
## Rule for box1
## Generation 44
## Rule for box1
## Generation 45
## Rule for box2
## Generation 46
## Rule for box1
## Generation 47
## Rule for box2
## Generation 48
## Rule for box1
## Generation 49
## Rule for box2
## Generation 50
## Rule for box1
## Generation 51
## Rule for box1
## Generation 52
## Rule for box1
## Generation 53
## Rule for box2
## Generation 54
## Rule for box2
## Generation 55
## Rule for box2
## Generation 56
## Rule for box2
## Generation 57
## Rule for box2
## Generation 58
## Rule for box1
## Generation 59
## Rule for box1
## Generation 60
## Rule for box1
## Generation 61
## Rule for box2
## Generation 62
## Rule for box2
## Generation 63
## Rule for box2
## Generation 64
## Rule for box1
## Generation 65
## Rule for box2
## Generation 66
## Rule for box2
## Generation 67
## Rule for box2
## Generation 68
## Rule for box2
## Generation 69
## Rule for box2
## Generation 70
## Rule for box1
## Generation 71
## Rule for box2
## Generation 72
## Rule for box2
## Generation 73
## Rule for box2
## Generation 74
## Rule for box1
## Generation 75
## Rule for box2
## Generation 76
## Rule for box1
## Generation 77
## Rule for box1
## Generation 78
## Rule for box2
## Generation 79
## Rule for box1
## Generation 80
## Rule for box2
## Generation 81
## Rule for box1
## Generation 82
## Rule for box1
## Generation 83
## Rule for box1
## Generation 84
## Rule for box1
## Generation 85
## Rule for box2
## Generation 86
## Rule for box1
## Generation 87
## Rule for box1
## Generation 88
## Rule for box2
## Generation 89
## Rule for box2
## Generation 90
## Rule for box1
## Generation 91
## Rule for box1
## Generation 92
## Rule for box1
## Generation 93
## Rule for box1
## Generation 94
## Rule for box1
## Generation 95
## Rule for box1
## Generation 96
## Rule for box2
## Generation 97
## Rule for box1
## Generation 98
## Rule for box2
## Generation 99
## Rule for box1
## Generation 100
## Rule for box2