2011-07-01 6 views
5

[Uwaga: Ponowne przeczytanie tego przed przesłaniem zdało mi się, że to Q stało się trochę epickie. Dziękuję za poświęcenie się moim długim wyjaśnieniom rozumowania stojącego za tym pościgiem. Czuję, że gdybym był w stanie pomóc innemu przedsiębiorstwu w podobnym projekcie, byłbym bardziej skłonny wejść na pokład, jeśli wiedziałbym, na czym polega motywacja.]Jak wyrazić gramatykę projektowania wolnego od kontekstu jako wewnętrzne DSL w Pythonie?

Dostaję się do Structure Synth przez Mikaela Hvidtfeldta Christensena ostatnio. Jest to narzędzie do generowania geometrii 3D z (głównie) bezkontekstowej gramatyki o nazwie Eisenscript. Struktura Synth jest inspirowany sztuką bez kontekstu. Gramatyki bezkontekstowe mogą tworzyć oszałamiające wyniki z zaskakująco prostych zestawów reguł.

Mój obecny przepływ pracy w ramach struktury strukturalnej obejmuje eksportowanie pliku OBJ ze struktury Synth, importowanie go do Blendera, konfigurowanie świateł, materiałów itp., A następnie renderowanie za pomocą Luxrender. Niestety, importowanie tych plików OBJ często powoduje, że Blender staje się ostry, ponieważ mogą istnieć tysiące obiektów o dość złożonej geometrii. Mówię "sprawiedliwie", ponieważ struktura Synteza generuje tylko podstawowe kształty, ale kula reprezentowana przez trójkąty nadal ma wiele twarzy.

W ten sposób generowanie struktur bezpośrednio w Blenderze byłoby lepszym rozwiązaniem niż obecny proces (powinno to umożliwić głębokie wsparcie Blendera dla skryptów w języku Python). Inteligentna biblioteka Pythona może wykorzystywać zdolności instancji Blendera do korzystania z jednej siatki do generowania niezliczonych obiektów, a tym samym do oszczędzania pamięci. Plus Blender to w pełni funkcjonalny pakiet 3D, a jego umiejętność interpretacji CFDG zapewniłaby twórcze możliwości znacznie wykraczające poza możliwości oferowane przez Structure Synth.

Moje pytanie brzmi: jak najlepiej przetłumaczyć gramatykę Eisenscript na DSL w języku Python. Oto, co proste Eisenscript wygląda następująco:

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 
} 

Aby wyjaśnić, pierwsze wywołanie R1 (linia 2) losowo powołać się na jedną z dwóch definicji R1. Każda definicja R1 rekursywnie wywołuje R1 (losowo wywołując jedną z dwóch definicji), a także tworzy pole. Pierwsza linia generująca zabójstwa po rekursji osiągnęła 2000 poziomów głębokości.

Jeremy Ashkenas (sławy CoffeeScript) z powodzeniem zaimplementował context free DSL in Ruby używając bloków. Wewnętrznie działa, tworząc klucz skrótu dla każdej "nazwy" reguły i przechowuje bloki dla każdej definicji tej reguły w tablicy, aby były one losowo wybierane podczas wywoływania reguły.

Dotychczasowe definicje reguł Eisenscript przełożyłoby do Ruby DSL tak:

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 

Jestem użytkownikiem początkującym Python i tak robili rozeznanie w możliwościach funkcjonalnych programowania Pythona. Wygląda na to, że lambda jest zbyt ograniczona, aby stworzyć coś podobnego do Ruby DSL Jeremy'ego i, o ile mogę to stwierdzić, lambda jest jedyną opcją dla anonimowych funkcji?

Jak doświadczona Pythonista może podejść do projektu?

Odpowiedz

3

Postanowiłem zbudować wstępny prototyp, tłumacząc bloki biblioteki Ruby na wstępnie zdefiniowane funkcje, które są przekazywane do instancji ContextFree, rozszerzonej o warunek unikania nieskończonych pętli i dodawane do instancji jako metody instancji. Oto aktualny stan. Krytyka jest mile widziana; to jest mój pierwszy kod Pythona i jestem gotów przekuć moje idiomy Rubiego na idiomy Pythona.

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

Możesz użyć 'types.MethodType' zamiast' self.instancemethod'. Powinieneś także użyć 'setattr (obj, name, thing)' zamiast bezpośrednio modyfikować '__dict__'. – JBernardo

4

Tworzenie parsera dla gramatyki bez kontekstu jest trudne. Prawdopodobnie lepiej jest użyć biblioteki, aby ułatwić sobie pracę.

Chciałbym sprawdzić moduł PyParsing. Do pobrania dołączono szereg przykładów, z których jednym jest prosty analizator składni SQL, który może być oświecony, przynajmniej na pierwszy rzut oka.

+0

Hej Wilduck, dzięki za odpowiedź! Właściwie wolałbym zaimplementować tę funkcjonalność za pomocą wewnętrznego DSL, zamiast pisać parser do bezpośredniego analizowania Eisenscript. Doszedłem do preferowania wewnętrznych DSL, gdy tylko jest to możliwe, ponieważ używanie jednego dla danego celu zapewnia pełną moc języka hosta za darmo. –

+0

Rozumiem, co masz na myśli. FWIW, pyparsing jest całkiem dobrym narzędziem do implementacji DSL. Możesz mieć trochę szczęścia z tym artykułem: http://fmeyer.org/en/writing-a-DSL-with-python.html i dyskusja na ten temat tutaj: http://news.ycombinator.com/item?id = 808091. – Wilduck

+0

Ups. Nie zdawałem sobie z tego sprawy. Spędzę trochę czasu nad tymi artykułami. Dzięki jeszcze raz. –

Powiązane problemy