如何在Python中将上下文无关设计语法表示为内部DSL?

5 投票
2 回答
1234 浏览
提问于 2025-04-16 20:41

[注意:在提交之前重新阅读时,我意识到这个问题变得有点长篇大论。感谢你耐心听我解释我追求这个目标的原因。我觉得,如果我能帮助另一个进行类似项目的人,如果我知道他们提问的动机,我会更愿意参与其中。]

最近,我开始接触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 个回答

4

写一个可以解析上下文无关文法的程序是很难的。你可能用一些现成的库会更轻松。

我建议你看看 PyParsing 这个模块。下载后里面有很多例子,其中有一个简单的 SQL 解析器,看看这个例子可能会对你有帮助,至少可以作为一个入门。

3

我决定通过把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

撰写回答