Opis konkretnego języka z planowanymi funkcjonalnościami i gramatyką wysyłany był wcześniej, dlatego zakładam, że nie jest to miejsce na dokładny opis tutaj, w ramach wstępnej wersji interpretera.
Skrótowo, język Stretch jest językiem imperatywnym, którego składnia zainspirowana jest językiem Rust.
Rozwiązanie wykorzystuje polecany generator frontendu BNFC. Definicja gramatyki
znajduje się w katalogu syntax/Stretch.cf, razem z pomocniczym skryptem
generate-parser
, który odpowiada za wygenerowanie odpowiednich plików .hs i
skopiowanie ich do katalogi src/BNFC, do użycia przez właściwy interpreter.
Interpreter operuje na wygenerowanym przez BNFC abstrakcyjnym drzewie, którego definicja znajduje się w src/BNFC/AbsStretch.hs. Cały interpreter obecnie znajduje się w pliku src/Lib.hs. Plik wykonywalny wołający najpierw parser/lexer i potem interpreter, znajduje się w app/Main.hs.
Głównie zdefiniowane są funkcję eval* (np. evalExp, evalStm...), które operują
na głównym stosie monadycznym, StateT MyEnv IO a
.
Typ MyEnv
obecnie składa się z typów (Env, Store, RecordFields)
(z czego
RecordFields
jest tymczasowe). Type Env
i Store
są zdefiniowane
analogicznie jak na przedmiocie Semantyka i weryfikacja programów - jedno jest
mapą z identyfikatora w pewne położenie, natomaist drugie mapuje z tego
położenia w wartości typu algebraicznego Value
.
Błędy obecnie są propagowane funkcją fail
poprzez monadę IO
.
Są obecnie 2 niejedoznaczności:
- shift/reduce - Wyrażenie
MyStruct { field1: 5 }
imyStruct
oba zaczynają się od identyfikatora, natomiast pierwszy oznacza strukturę, a drugi identyfikator pewnej wartości. Tu, w przypadku gdy po identyfikatorze następuje{
zawsze chodzi nam o wyrażenie struktury i parser robi zawsze to, co chcemy. - reduce/reduce - Te wydają się poważne, natomiast jest to związane z projektem
języka. Tu wszystko stara się być wyrażeniem i tak np. ostatnie wyrażenie w
bloku jest wartością danego wyrażenia, np.
{ let a = 5; 3 }
jest wyrażeniem o wartości3
. Kłóci się to gramatycznie z kontrolą przepływu programu, np. chcemy móc pisać po sobie instrukcjeif true {}
, które w końcu także są wyrażeniami. W związku z tym, gramatyka bloku{ [Stm] Exp }
może napotkać na niejednoznaczność. Tutaj naszą preferencją jest mocniejsze wiązanie "w wyrażenie" - wyrażenie typu{ if true { 1 } else { 0 } - 2 }
będzie interpretowane jako suma dwóch mniejszych wyrażeń, a nieif
a, po którym zwracamy z bloku wartość-2
.
Na obecną chwilę nie ma:
- statycznego systemu typów
- nie wykrywanie złych typów funkcji
- nie są przekazywane argumenty do funkcji w trakcie jej wywołania
- statycznych bindingów
Project był stworzony przy wykorzystaniu narzędzia stack
i budowany oraz
testowany przy jego użyciu, dlatego preferowane jest to narzędzie. Projekt można
budować przy użyciu stack build
oraz stack exec stretchi nazwa_pliku
, gdzie
nazwa_pliku
wskazuje do pliku z kodem źródłowym języka Stretch.
Narzędzie to generuje także pliki do wykorzystania przez narzędzie cabal
- tu
analogicznie powinno działać polecenie cabal build
celem zbudowania i cabal run nazwa_pliku
celem uruchomienia interpretera.