Jak pisałem wcześniej, tworzone przeze mnie narzędzie będzie przetwarzało wyrażenia i funkcje. Zanim zajmę się szczegółami tego przetwarzania, skupię się na zdefiniowaniu przetwarzanych bytów – wyrażeń i funkcji właśnie. Byty te razem tworzą dziedzinę, w której DSLExecutor będzie się poruszał.
Wszystko jest wyrażeniem
Zacznijmy od podstawowych stwierdzeń dotyczących sposobu, w jaki będę traktował tekstowy zapis operacji (czyli wejściowy DSL):
- Cały taki zapis jest wyrażeniem.
- Każde wyrażenie ma przypisany typ wartości zwracanej.
- Wyrażenie może zawierać podwyrażenia – które też są wyrażeniami.
- Zbiór wyrażeń to też wyrażenie.
- Zbiór wyrażeń zawiera jedno wyrażenie wyróżnione – to, którego wartość zwracana stanie się wartością zwracaną całego zbioru.
Takie podejście pozwoli mi traktować wszystkie elementy DSLa w jednolity sposób. Spójrzmy na przykłady. Za składnię DSL przyjmijmy pseudokod przypominający C#: (typ wartości zwracanej każdego z poniższych wyrażeń to <tt>int</tt>.)
- Literał jest wyrażeniem:
-
1
-
- Wywołanie funkcji jest wyrażeniem:
-
GetActiveUsersNumber()
-
- Wywołanie funkcji parametrycznej jest wyrażeniem zawierającym podwyrażenia reprezentujące argumenty tej funkcji – w tym przypadku dwa (zaczerpnięte z przykładów powyżej):
-
Add(GetActiveUsersNumber(), 1)
-
- Zbiór wyrażeń jest wyrażeniem zawierającym podwyrażenia – w tym przypadku dwa wywołania funkcji. Przyjmijmy, że wyróżnionym (zwracającym) wyrażeniem jest wyrażenie ostatnie w zbiorze:
-
RegisterUser("manisero") Add(GetActiveUsersNumber(), 1)
-
Ameryki nie odkryłem – zbliżone podejście obowiązuje w chyba wszystkich znanych mi językach obiektowych, funkcyjnych i skryptowych. W następnym poście pokażę, jak te założenia przekładają się na implementację klas należących do dziedziny DSLExecutora – zapraszam!