DSL (domain-specific language)

RobertG

Użytkownik
Dołączył
Styczeń 3, 2007
Posty
391
** teoria **

W tej notce pokażę Wam, jak w prosty sposób napisać prosty język programowania.
Utworzenie od początku do końca języka programowania to sporo roboty (analiza
leksykalna, syntaktyczna, semantyczna, kod pośredni, kod wynikowy lub maszyna
wirtualna), lecz można skorzystać z prostszej drogi (która daje też mniej
możliwości), a mianowicie utworzyć nowy język, który będzie wykorzystywany przez
język już istniejący. Takie coś nazywa się DSL i jest dość popularne.

DSL jest wykorzystywane m.in do budowy języków z których mają korzystać eksperci
w danych dziedzinach, chodzi o to, iż programiści, projektanci nie zgłębiają się
w dane zagadnienie (no np geologię, jakąś dziedzinę fizyki), tylko w konsultacji
z ekspertami tej dziedziny budują język, który oni (tzn eksperci) wykorzystają
do rozwiązywania problemów.

DSL może być też wykorzystywane do tworzenia testów oprogramowania, do tworzenia
zapytań, by wyciągnąć dane, etc.

Najważniejsze decyzje, jakie musimy podjąć, to język, który wykorzystamy oraz
składnia tego nowego języka. Składnia ta powinna być naturalna/poprawna dla
języka, który wykorzystamy, powinna dobrze wyrażać problemy, rzeczy, które
chcemy wyrazić w języku, ludzie, którzy będą ten język wykorzystywać powinni ją
akceptować.

Język, który wykorzystamy powinien być ekspresywny, giętki, jeżeli nie jest na
tyle giętki, na ile nam trzeba trzeba zbudować preprocesor (coś jak w C), który
przeleci pliki wejściowe i je zmodyfikuje, tak by były poprawne. Taki
preprocesor może zajmować się również wstępną walidacją.

Sugerowałbym język dynamicznie typizowany, np. Pythona albo Ruby.

** praktyka **

Przykład będzie banalny, mianowicie implementacja (to się chyba Logo nazywa)
języka, gdzie mamy żółwia i on chodzi po planszy, a my sterujemy jego ruchami.

W przykładzie wykorzystamy Pythona, jeśli ktoś jest zainteresowany Rubym to na
końcu jest link.

Zastanówmy się co powinien mieć język do obsługi takiego żółwika? IMHO:
* deklarowanie, jakie są rozmiary planszy, po której chodzi żółwik, np tak
plansza(100, 50), na zapisanie planszy długiej na 100px i szerokiej na 50px

* deklarowanie, gdzie jest początkowy punkt, w którym jest żółwik, np tak
zaczynam(20, 30), by poinformować, iż punkt początkowy ma wsp (20, 30) licząc
w pikselach

* polecenia poruszania się w górę, dół, prawo i lewo, w takiej formie
nazwa_kierunku(wartość w pikselach), np. lewo(10), prawo(20)

* po skończonej wędrówce powinna być opcja, by zapisać wędrówkę do pliku
graficznego, np taka zapisz("koniec.png")


Zauważmy, że taka składnia jest poprawną składnią Pythona, jeżeli była by
niepoprawna to albo trzeba by ją było zmienić albo napisać jakiś preprocesor
(pierwsze rozwiązanie jest dużo lepsze, lecz nie zawsze możliwe).

Poniżej zamieszczam przykładowy kod, który to realizuje, plik wejściowy i
wygenerowane na jego podstawie wyjście.

kod:

Kod:
#!/usr/bin/env python
from PIL import Image
import sys

class Turtle:
    bg_color =  (200,200,200)
    line_color = (33, 66, 88)
    x = 0
    y = 0

    def compile(self, input_file):
        plansza = lambda x,y: self.plansza(x,y)
        zapisz = lambda name: self.zapisz(name)
        zaczynam = lambda x,y: self.zaczynam(x, y)
        lewo = lambda val: self.lewo(val)
        prawo = lambda val: self.prawo(val)
        gora = lambda val: self.gora(val)
        dol = lambda val: self.dol(val)

        for line in open(input_file, 'r').readlines():
            eval(line)

    def plansza(self, x_max, y_max):
        self.img = Image.new("RGB", (x_max, y_max), self.bg_color)

    def zapisz(self, output):
        self.img.save(output)

    def zaczynam(self, x_start, y_start):
        self.x = x_start
        self.y = y_start

    def prawo(self, val):
        for i in range(self.x, self.x+val):
            self.img.putpixel((i, self.y), self.line_color)
        self.x += val

    def lewo(self, val):
        for i in range(self.x-val, self.x):
            self.img.putpixel((i,self.y), self.line_color)
        self.x -= val

    def dol(self, val):
        for i in range(self.y, self.y+val):
            self.img.putpixel((self.x, i), self.line_color)
        self.y += val

    def gora(self, val):
        for i in range(self.y-val, self.y):
            self.img.putpixel((self.x, i), self.line_color)
        self.y -= val

if __name__ == "__main__":
    input = sys.argv[1]
    t = Turtle()
    t.compile(input)

plik wejściowy (pierwszy.zol):

Kod:
plansza(500, 500)
zaczynam(300, 300)
lewo(100)
lewo(100)
dol(10)
lewo(10)
gora(10)
prawo(22)
zapisz("ala124.png")


uruchomienie:
Kod:
rgawron@foo:/opt/tutorial$ ./zolw.py pierwszy.zol

wynikowy plik:
ala124.png


Najważniejsza jest metoda compile, linijka z eval'em, który wykonuje nasz
skrypt.

Lambdy w procedurze compile używłem
dlatego, iż do metod w obrębie klasy odwołujemy się poprzedzając je self, w
naszym języku nie mamy tego self'a, nie chciałem go dodawać, więc teraz
wywołujemy ten obiekt, któremu przypisana jest lambda a ona wywołuje metodę
klasy.

** podsumowanie **

Języki DSL mają specyficzne zastosowanie, nie są one zamiennikami normalnych
"języków", służą one raczej do utworzenia języka w którym będzie można łatwo
wyrazić dany problem, w którym można go wyrazić bardziej abstrakcyjnie.

Podałem bardzo prosty przykład, nie traktujcie tego, jako wszystko, co języki
klasy DSL potrafią. Na koniec podam linka do swojego (marnego) języka DSL, który
http://rgawron.megiteam.pl/2010/01/22/dsl-ruby-vm/ wykonuje plik prostego asemblera i w ten sposób działa jak maszyna wirtualna. To
jest link do notki na moim blogu. tam kod jest w Rubym (i jest paskudnie
napisany :)), no ale powyższy też nie jest zbyt dobry. Mógłby być bardziej
zwięzły ale przez to trochę bardziej zaciemniony.

Rozwiązanie tego problemu (czyli jak napisać program który z pliku wyjściowego
wygeneruje obrazek) można było na wiele sposobów, poprzez wyrażenia regularne,
maszyny stanów, kodując żywcem drabinkę if-else-for-etc albo pewnie i na tysiąc
innych sposobów. Zaletą tego rozwiązania jest jednak to, iż integruje się ono z
innym (czyli z macierzystym językiem), które jest już całkiem dobrym, wygodnym
językiem (o ile dobrze wybierze się język).

Do języka, który wam pokazałem można dorobić masę nowych rzeczy, np to, by
żółwik mógł chodzić nie zostawiając za sobą śladu. Można by też napisać skrypt,
generujący dane wejściowe na podstawie jakiegoś zdjęcia i żółwik rysował by jego
kontury :)

Jak się wam podobało? Zapraszam do dyskusji!
 

grzonu

Były Moderator
Dołączył
Grudzień 26, 2006
Posty
1390
bardzo przydatne i ciekawe rozwiazanie nad którym się szczerze nigdy dłuzej nie zastanawiałem ;)
Oby wiecej takich postów które mierzą wyżej ;)

//jesli ci to nie przeszkadza to przykleje temat za kilka godzin
 

RobertG

Użytkownik
Dołączył
Styczeń 3, 2007
Posty
391
Heh, dzięki. BTW powoli się wgłębiam w temat i za jakiś czas opiszę budowę kompilatora, który pobiera kod w prostym asemblerze i produkuje binarkę, która śmiga pod Linuksem.
 

grzonu

Były Moderator
Dołączył
Grudzień 26, 2006
Posty
1390
ja takze ;) bedzie chetnie sie dokształce :)
Jak zrobisz pod linuxa to ja chetnie przepisze pod winde :p
 

g3t_d0wn

Użytkownik
Dołączył
Styczeń 9, 2010
Posty
13
jeden z lepszych artykułów jakie czytałem w sieci, bardzo treściwy.
 
Do góry Bottom