DSLExecutor: schemat działania i harmonogram prac

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):

DSLExecutor_diagram

Obraz mówi więcej niż tysiąc słow, no nie? Jeśli nie, to wyjaśnienie:

  1. DSLExecutor przyjmuje zapis operacji (1 2 + 3 -).
  2. Parser przekształca go w drzewo wyrażeń.
  3. ExpressionExecutor wykonuje korzeń drzewa wyrażenia – funkcję Sub (realizującej odejmowanie). W tym celu przekazuje opis funkcji do FunctionExecutora.
    1. FunctionExecutor chce uzyskać wartość pierwszego argumentu funkcji Sub. W tym celu przekazuje jego wyrażenie ExpressionExecutorowi.
      1. ExpressionExecutor wykonuje to wyrażenie, tzn. zleca FunctionExecutorowi wykonanie funkcji Add.
      2. Jako że argumenty Add to stałe, FunctionExecutor nie musi ich przekazywać do ExpressionExecutora. Wykonuje funkcję Add i zwraca jej wynik (3).
      3. ExpressionExecutor zwraca otrzymany wynik.
    2. 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).
  4. 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:

  1. Definicja dziedziny, w skład której wejdą dostępne typy wyrażeń i koncepcja funkcji.
  2. Jak najprostsza implementacja “środowiska wykonania” wyrażeń – komponentów ExpressionExecutor i FunctionExecutor.
  3. Jak najprostszy DSL i jego parser.
  4. 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.
  5. Skoro DSLExecutorem będzie się już można bawić, postaram się sklecić dla niego jakiś interfejs użytkownika (pewnie coś na wzór executify).
  6. 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ą?).
  7. 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!