2011-07-01 25 views
5

[Lưu ý: Đọc lại điều này trước khi gửi, tôi nhận ra câu hỏi này đã trở thành một chút sử thi. Cảm ơn bạn đã thưởng thức lời giải thích dài của tôi về lý do đằng sau việc theo đuổi này. Tôi cảm thấy rằng, tôi đã ở một vị trí để giúp một dự án tương tự khác, tôi sẽ có nhiều khả năng lên tàu hơn nếu tôi biết động cơ đằng sau câu hỏi.]Làm cách nào để diễn tả ngữ cảnh thiết kế ngữ cảnh miễn phí dưới dạng DSL nội bộ trong Python?

Tôi đã tham gia Structure Synth bởi Mikael Hvidtfeldt Christensen gần đây. Nó là một công cụ để tạo ra hình học 3D từ ngữ pháp tự do (chủ yếu) được gọi là Eisenscript. Cấu trúc Synth tự lấy cảm hứng từ Context Free Art. Ngữ pháp ngữ pháp miễn phí có thể tạo ra một số kết quả tuyệt vời từ các quy tắc đơn giản đáng ngạc nhiên.

Luồng công việc Synth cấu trúc hiện tại của tôi liên quan đến việc xuất tệp OBJ từ Cấu trúc Synth, nhập nó vào Máy xay sinh tố, thiết lập đèn, vật liệu, v.v., rồi kết xuất với Luxrender. Thật không may, việc nhập các tập tin OBJ này thường khiến Blender ngừng đập vì có thể có hàng ngàn đối tượng có hình học khá phức tạp. Tôi nói 'khá' bởi vì cấu trúc Synth chỉ tạo ra các hình dạng cơ bản, nhưng một hình cầu đại diện bởi hình tam giác vẫn có nhiều khuôn mặt.

Do đó, việc tạo cấu trúc trực tiếp trong Máy xay sinh tố sẽ thích hợp hơn với quy trình hiện tại (hỗ trợ sâu của Blender cho việc tạo kịch bản Python nên làm điều này có thể). Một thư viện Python thông minh có thể sử dụng khả năng instound của Blender để sử dụng một lưới để tạo ra vô số các đối tượng, do đó tiết kiệm bộ nhớ. Plus Blender là một bộ 3D đầy đủ tính năng và khả năng diễn giải CFDG sẽ cung cấp khả năng sáng tạo vượt xa những gì mà cấu trúc Synth có thể cung cấp.

Và vì vậy câu hỏi của tôi là cách tốt nhất để dịch ngữ pháp Eisenscript thành một DSL Python. Dưới đây là những gì một Eisenscript đơn giản trông giống như:

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 
} 

Để giải thích, cuộc gọi đầu tiên đến R1 (dòng 2) sẽ ngẫu nhiên gọi một trong hai định nghĩa của R1. Mỗi định nghĩa của R1 đệ quy gọi R1 (ngẫu nhiên gọi một trong hai định nghĩa) và cũng tạo ra một hộp. Dòng đầu tiên giết chết thế hệ sau khi đệ quy đã đi sâu 2000 cấp độ.

Jeremy Ashkenas (của CoffeeScript nổi tiếng) đã triển khai thành công một khối sử dụng context free DSL in Ruby. Bên trong, nó hoạt động bằng cách tạo khóa băm cho mỗi tên 'quy tắc' và lưu trữ các khối cho mỗi định nghĩa của quy tắc đó trong một mảng, được chọn ngẫu nhiên khi quy tắc được gọi.

Các định nghĩa quy tắc Eisenscript trước sẽ dịch để Ruby DSL như vậy:

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 

Tôi là một người dùng mới làm quen Python và vì vậy đã và đang làm một số nghiên cứu về khả năng lập trình chức năng của Python. Có vẻ như lambda quá hạn chế để tạo ra thứ gì đó tương tự như Ruby DSL của Jeremy, và, theo như tôi có thể nói, lambda là lựa chọn duy nhất cho các chức năng ẩn danh?

Pythonista có kinh nghiệm tiếp cận thiết kế như thế nào?

Trả lời

3

Tôi quyết định xây dựng nguyên mẫu ban đầu bằng cách dịch các khối của thư viện Ruby thành các hàm được xác định trước được chuyển vào thể hiện ContextFree, được tăng cường với điều kiện để tránh vòng lặp vô hạn và thêm vào ví dụ. Đây là trạng thái hiện tại. Phê bình được hoan nghênh; đây là một số mã Python đầu tiên của tôi và tôi đã sẵn sàng và sẵn sàng đào tạo lại thành ngữ Ruby của mình cho thành ngữ 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 
+1

Bạn có thể sử dụng 'types.MethodType' thay vì' self.instancemethod'. Ngoài ra, bạn nên sử dụng 'setattr (obj, name, thing)' thay vì sửa đổi '__dict__' trực tiếp. – JBernardo

4

Viết một trình phân tích ngữ pháp cho ngữ cảnh tự do ngữ cảnh là khó. Có lẽ bạn nên sử dụng một số loại thư viện để làm cho mọi thứ trở nên dễ dàng hơn.

Tôi sẽ xem mô-đun PyParsing. Việc tải xuống đi kèm với một số ví dụ, một trong số đó là một trình phân tích cú pháp SQL đơn giản, có thể được khai sáng để xem xét, ít nhất là bước đầu tiên.

+0

Xin chào Wilduck, cảm ơn bạn đã phản hồi! Tôi thực sự muốn thực hiện chức năng này theo cách của một DSL nội bộ, thay vì viết một trình phân tích cú pháp để phân tích cú pháp Eisenscript trực tiếp. Tôi đã đến để thích DSL nội bộ bất cứ khi nào có thể do thực tế là sử dụng một cho một mục đích nhất định cung cấp tất cả sức mạnh của ngôn ngữ máy chủ miễn phí. –

+0

Tôi hiểu ý của bạn là gì. FWIW, pyparsing là một công cụ khá tốt để thực hiện một DSL. Bạn có thể có một số may mắn với bài viết này: http://fmeyer.org/en/writing-a-DSL-with-python.html và thảo luận về nó ở đây: http://news.ycombinator.com/item?id = 808091. – Wilduck

+0

Rất tiếc. Tôi không nhận ra điều đó. Tôi sẽ dành thời gian cho những bài viết đó. Cảm ơn một lần nữa. –

Các vấn đề liên quan