Dobra, projekt konkursowy czas zacząć. Na początku wypadałoby poglądawo opisać planowane kroki działania DSLExecutora i przedstawić plan pracy nad ich implementacją.
Schemat działania
Koncepcja jest prosta: użytkownik podaje kod w danej składni DSL, DSLExecutor go parsuje i wykonuje (patrz: założenia i przykład w poprzednim poście). Bardziej szczegółowo, wygląda to tak:
- Wynikiem parsowania jest drzewo wyrażeń zbudowane na podstawie funkcji i wartości zawartych w kodzie. Drzewo to jest obiektem umieszczanym w pamięci i przekazywanym do komponentu wykonującego wyrażenia (ExpressionExecutor).
- Wykonanie wyrażenia to przejście po drzewie i wykonanie kolejnych jego węzłów (które też są wyrażeniami).
- Istnieje kilka rodzajów wyrażeń (zostaną one dokładniej zdefiniowane w kolejnych postach), w tym:
- wyrażenie reprezentujące stałą wartość – jego wykonanie to po prostu zwrócenie zawartej w nim wartości,
- wyrażenie reprezentujące wykonanie funkcji biblioteki standardowej – wykonanie takiego wyrażenia to uzyskanie wartości argumentów funkcji (tzn. wykonanie wyrażeń reprezentujących te argumenty), wykonanie samej funkcji i zwrócenie jej wyniku.
- Wykonywaniem funkcji zajmuje się dedykowany komponent (FunctionExecutor).
Opisany wyżej tok przetwarzania przedstawia diagram, obrazujący przykład podany w poprzednim poście (przyjęta składania DSL to odwrotna notacja polska):
Obraz mówi więcej niż tysiąc słow, no nie? Jeśli nie, to wyjaśnienie:
- DSLExecutor przyjmuje zapis operacji (1 2 + 3 -).
- Parser przekształca go w drzewo wyrażeń.
- ExpressionExecutor wykonuje korzeń drzewa wyrażenia – funkcję Sub (realizującej odejmowanie). W tym celu przekazuje opis funkcji do FunctionExecutora.
- FunctionExecutor chce uzyskać wartość pierwszego argumentu funkcji Sub. W tym celu przekazuje jego wyrażenie ExpressionExecutorowi.
- ExpressionExecutor wykonuje to wyrażenie, tzn. zleca FunctionExecutorowi wykonanie funkcji Add.
- Jako że argumenty Add to stałe, FunctionExecutor nie musi ich przekazywać do ExpressionExecutora. Wykonuje funkcję Add i zwraca jej wynik (3).
- ExpressionExecutor zwraca otrzymany wynik.
- FunctionExecutor ma już wartość pierwszego argumentu Sub. Drugi argument to stała, więc FunctionExecutor może wykonać funkcję – robi to i zwraca wynik (0).
- FunctionExecutor chce uzyskać wartość pierwszego argumentu funkcji Sub. W tym celu przekazuje jego wyrażenie ExpressionExecutorowi.
- ExpressionExecutor zwraca otrzymany wynik.
Jak widać, wykonanie zadanych operacji sprowadza się do rekurencyjnego odbijania piłeczki pomiędzy komponentami ExpressionExecutor i FunctionExecutor. W rzeczywistości będzie to nieco bardziej zagmatwane, ale na razie taki poziom szczegółowości wystarczy.
Harmonogram prac
Wydaje się, że najbardziej interesującą częścią projektu będzie opracowanie sensownej składni i zaimplementowanie parsera dla przykładowego języka DSL. Spodziewam się jednak, że to zadanie zajmie sporo czasu, a chciałbym jak najszybciej stworzyć MVP. Poza tym, składnia DSL ma być wymienna, a najważniejszym komponentem DSLExecutora jest silnik wykonywania wyrażeń. Dlatego docelowym DSLem zamierzam zająć się pod koniec projektu.
Jak więc przedstawia się mój plan prac? Oto on:
- Definicja dziedziny, w skład której wejdą dostępne typy wyrażeń i koncepcja funkcji.
- Jak najprostsza implementacja “środowiska wykonania” wyrażeń – komponentów ExpressionExecutor i FunctionExecutor.
- Jak najprostszy DSL i jego parser.
- Opracowanie przykładowej biblioteki standardowej.
- Na tym etapie DSLExecutor będzie już działał – zakres funkcjonalności będzie minimalny, ale wystarczający do wykonywania operacji.
- Skoro DSLExecutorem będzie się już można bawić, postaram się sklecić dla niego jakiś interfejs użytkownika (pewnie coś na wzór executify).
- Rozbudowa środowiska wykonania – zakładam, że do momentu implementacji tego zadania przyjdą mi do głowy pomysły na nowe funkcjonalności (a może to Czytelnicy je zaproponują?).
- Jak się rzekło, na koniec postaram się opracować sensowny (tzn. nie-najprostszy) język DSL i jego parser.
Każdy z tych etapów będę opisywał na niniejszym blogu. Oprócz suchego przedstawiania rozwiązań, postaram się, aby potencjalni Czytelnicy mogli wynieść z moich postów coś dla siebie. Od czasu do czasu zamierzam pisać o rzeczach niezwiązanych bezpośrednio z funkcjonalnościami DSLExecutora – np. o testach jednostkowych, czy moim stylu pisania kodu.
Tyle na dzisiaj. DSLExecutor nabiera kształtu, w związku z czym zapraszam do śledzenia procesu jego powstawania!