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:
plik wejściowy (pierwszy.zol):
uruchomienie:
wynikowy plik:
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!
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:
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!