Skip to content

Commit b795843

Browse files
author
Dawid Sygocki
committed
Merge branch 'etap/5-interpreter' into 'main'
Etap 5 See merge request TKOM_22L_WW/Dawid_Sygocki/not-javascript!7
2 parents e436c39 + 0d823e5 commit b795843

File tree

88 files changed

+2771
-1076
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+2771
-1076
lines changed

README.md

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@ Przeważająca większość konstruktów językowych to wyrażenia, co ma uczyni
1212
Obejmuje to m.in.:
1313
* bloki kodu, które zwracają wartość ostatniego wyrażenia w nich zawartego (lub `null`, jeśli są puste),
1414
* pętle `for` i `while` zwracające po zakończeniu wartość odpowiednio licznika oraz sprawdzanego warunku,
15-
* przypisania wartości do zmiennych.
15+
* przypisania wartości do zmiennych (nie chodzi tu o deklaracje).
1616

1717
Język implementuje funkcje anonimowe i pozwala na przypisywanie ich do zmiennych.
1818

19-
Podstawowe typy danych będą przekazywane do funkcji przez kopię, natomiast łańcuchy znaków (których zawartość jest niezmienna) poprzez referencję.
19+
Argumenty są przekazywane do funkcji przez kopię.
2020

2121

2222
## Formalna specyfikacja i składnia
2323

2424
Gramatyka realizowanego języka opisana jest w pliku [gramatyka.md](docs/gramatyka.md). Reguły dotyczące operatorów są zgodne z tabelami z pliku [operatory.md](docs/operatory.md).
2525

26-
Nie przewiduje się na razie konfiguracji zachowania interpretera poprzez specjalne pliki.
26+
Nie przewiduje się konfiguracji zachowania interpretera poprzez specjalne pliki.
2727

28-
Możliwe jest importowanie zawartości innych skryptów za pomocą instrukcji `pull`. Rolę biblioteki standardowej pełni przestrzeń nazw `std`. Zdefiniowane w "dociąganym" skrypcie elementy są wprowadzanie do przestrzeni nazw skryptu głównego, a w wypadku konfliktu nazw można odwołać się do nich z użyciem pełnej ścieżki (np. `std.io.print`).
28+
Planowane było umożliwienie użytkownikowi importowania zawartości innych skryptów za pomocą instrukcji `pull`. Rolę biblioteki standardowej miała pełnić przestrzeń nazw `std`. Zdefiniowane w "dociąganym" skrypcie elementy miały być wprowadzane do przestrzeni nazw skryptu głównego, a w wypadku konfliktu nazw możliwe miało być odwołanie się do nich z użyciem pełnej ścieżki (np. `std.io.print`).
29+
30+
Ze względu na obecny brak wsparcia (w klasie interpretera) dla instrukcji `pull`, funkcje wbudowane `print` oraz `quit` zlokalizowane są bezpośrednio w środowisku użytkownika.
2931

3032
## Wymaganie funkcjonalne
3133
1. typy
@@ -42,6 +44,7 @@ Możliwe jest importowanie zawartości innych skryptów za pomocą instrukcji `p
4244
* obsługa literałów całkowitoliczbowych w formie dziesiętnej (np. `3424`), szesnastkowej (np. `0xaf`), ósemkowej (np. `0x644`) oraz dwójkowej (`0b101011`)
4345
* obsługa literałów zmiennoprzecinkowych z opcjonalną częścią ułamkową, ale nie całkowitą (np. `25.`, ale nie `.1234`) oraz wsparciem dla notacji naukowej bez znormalizowanej mantysy (np. `12.34e15`)
4446
* operatory: znaku (`+`, `-`), dodawania (`+`), odejmowania (`-`), mnożenia (`*`), dzielenia (`/`), reszty z dzielenia (`%`), potęgowania (`^`)
47+
* przepełnienie podczas operacji na liczbach całkowitych nie jest zgłaszane
4548
3. obsługa operacji znakowych
4649
* typ `string`
4750
* wieloliniowe literały ograniczone cudzysłowami wspierające sekwencje ucieczki z wykorzystanie znaku `\`
@@ -60,17 +63,19 @@ Możliwe jest importowanie zawartości innych skryptów za pomocą instrukcji `p
6063
* instrukcja warunkowa `if`
6164
* opcjonalne części `elif` (wiele wystąpień) oraz `else` (jedno wystąpienie)
6265
* dla `if` oraz `elif` wymagane jest zdefiniowanie warunku w nawiasach
66+
* warunek jest spełniony, jeśli jego wartość rzutowana do typu `bool` wynosi `true`
6367
7. instrukcje pętli
6468
* instrukcja pętli zakresowej `for`
6569
* pozwala na zadeklarowanie niemutowalnego licznika (o wybranej nazwie) inkrementowanego wedle specyfikacji zakresu; deklaracja ta jest opcjonalna (pętla wykona się poprawną ilość razy bez konieczności deklaracji)
6670
* specyfikacja zakresu umieszczona jest w nawiasach po słowie kluczowym `for`, działa analogicznie do konstrukcji `range` z języka Python: możliwe jest określenie tylko górnej granicy (np. `5`), wartości startowej i górnej granicy (np. `0:5`) lub wartości startowej, górnej granicy i kroku inkrementacji (np. `0:5:2`)
6771
* instrukcja pętli warunkowej `while`
6872
* "klasyczna" postać - wymaga podania w nawiasach po słowie kluczowym `while` jakiegoś warunku ewaluowanego przed każdą iteracją
73+
* warunek jest spełniony, jeśli jego wartość rzutowana do typu `bool` wynosi `true`
6974
* przerwanie wykonania
7075
* słowo kluczowe `break` pozwala na bezwarunkowe przerwanie wykonania obu typów pętli
7176
* słowo kluczowe `break_if` pozwala na warunkowe przerwanie wykonania obu typów pętli - warunek należy podać w nawiasach
7277
8. funkcje
73-
* defiowanie funkcji anonimowych z użyciem słowa kluczowego `functi`, po którym następuje lista parametrów i ciało funkcji (blok)
78+
* defiowanie funkcji anonimowych z użyciem słowa kluczowego `functi`, po którym następuje lista parametrów i ciało funkcji
7479
* funkcje anonimowe mogą być przypisane do zmiennej/stałej
7580
* funkcje anonimowe mogą przechwytywać zmienne (mechanizm domknięć), ale nie mogą ich modyfikować
7681
* wywołanie funkcji możliwe jest z użyciem nawiasów, w których podane są argumenty, możliwe rekursywne wywołania
@@ -79,15 +84,13 @@ Możliwe jest importowanie zawartości innych skryptów za pomocą instrukcji `p
7984
* parametry funkcji można określić słowem kluczowym `const` - nie będzie się dało wtedy modyfikować ich wartości (domyślnie są mutowalne)
8085
* przerwanie wykonania funkcji można wymusić z użyciem słowa kluczowe `return`, po którym następić może wyrażenie stanowiące wartość zwrotną
8186
9. obsługa błędów
82-
* z użyciem domyślnej implementacji: wypisywanie błędów w określonym formacie (zawierającym pozycję, oznaczenie błędu i dodatkowe dane) na standardowy strumień błędów `stderr` na każdym etapie działania aplikacji (błędy znakowe, składniowe, semantyczne, czasu uruchomienia)
83-
* przykładowe błędy:
84-
* znakowe - nieoczekiwany znak (w szczególności ETX)
85-
* składniowe - nieprawidłowa instrukcja, nieoczekiwany token (w szczególności ETX), przekroczenie zakresu literału
86-
* semantyczne - przypisanie wartości do zdefiniowanej wcześniej stałej, nieznany identyfikator, brak przypisania przy definicji stałej
87-
* czasu uruchomienia - dzielenie przez zero, `null` podany w miejsce parametru nieopcjonalnego, przekroczenie zakresu zmiennej, brak wymaganej przestrzeni nazw
88-
* wystąpienie błędu oznacza przerwanie działania bieżącego programu (w przypadku trybu REPL nie powinno to kończyć działania interperetera)
89-
* w przypadku błędów składniowych należy pominąć wszelkie tokeny aż do rozpoczęcia następnej instrukcji i kontynuować sprawdzanie, by użytkownik mógł zapoznać się z możliwie pełną liczbą błędów od razu
90-
* wygodny byłby mechanizm wyjątków z użyciem słów kluczowych `try` i `catch` z możliwością definiowania własnych klas błędów, nie jest to jednak obecnie zaplanowane (wiązałoby się z potencjalnym wprowadzeniem mechanizmu dziedziczenia, wsparcia dla OOP, itd.)
87+
* z użyciem domyślnej implementacji: wypisywanie błędów w określonym formacie (zawierającym pozycję, oznaczenie błędu i dodatkowe dane) na standardowy strumień błędów `stderr` (lub inny wybrany) na każdym etapie działania aplikacji (błędy leksykalne, składniowe, czasu uruchomienia)
88+
* zaimplementowane błędy:
89+
* leksykalne - nieoczekiwany znak (w szczególności koniec strumienia), przekroczenie maksymalnej długości tokena (komentarza, łańcucha znaków lub liczby), nieprawidłowy przedrostek liczbowy, brakująca część liczby (wykładnik, po przedrostku), nieznany token
90+
* składniowe - nieoczekiwany token (w szczególności ETX), wyrażenie lub instrukcja (w miejscu innego tokena, wyrażenia, instrukcji), przekroczenie zakresu liczby całkowitej, niepoprawne użycie gałęzi `default` w dopasowaniu wzorca (podwojenie, nieumieszczenie na ostatniej pozycji), brak wartości początkowej dla zmiennej niemutowalnej, użycie tej samej nazwy parametru więcej niż raz
91+
* czasu uruchomienia - przypisanie wartości do zdefiniowanej wcześniej stałej, nieznany identyfikator, dzielenie przez zero, `null` podany w miejsce parametru nieopcjonalnego, ponowna inicjalizacja zmiennej, nieprawidłowe rzutowanie, użycie instrukcji `return` poza funkcją, użycie instrukcji `break` poza pętlą, wywołanie funkcji z nieprawidłową liczba argumentów, wyrażenie przyjmujące wartość `null` w definicji zakresu pętli `for`, nieprawidłowa l-wartość w przypisaniu
92+
* wystąpienie błędu czasu uruchomienia oznacza przerwanie działania interpretera i ograniczenie się do sparsowania reszty tekstu (nie dotyczy to trybu REPL, gdzie błędy czasu uruchomienia są ignorowane)
93+
* w przypadku niemożliwych do rozwiązania błędów składniowych pomijane są wszelkie tokeny aż do rozpoczęcia następnej instrukcji i kontynuować sprawdzanie, by użytkownik mógł zapoznać się z możliwie pełną liczbą błędów od razu
9194
10. obsługa operacji logicznych
9295
* typ `bool`
9396
* obsługa literałów: `true`, `false`
@@ -99,7 +102,7 @@ Możliwe jest importowanie zawartości innych skryptów za pomocą instrukcji `p
99102
* sprawdzenie typu (operatory `is`, `is not`)
100103
* porównanie z literałem (operatory `==`, `!=`, `<`, `<=`, `>`, `>=`)
101104
* spełnienie predykatu (poprzez podanie nazwy jednoargumentowej funkcji)
102-
* warunki można grupować za pomocą nawaisów oraz słów kluczowych: `and`, `or`
105+
* warunki można grupować za pomocą nawiasów oraz słów kluczowych: `and`, `or`
103106
12. obsługa opcjonalności
104107
* każda zmienna może przyjąć wartość `null` (`null` jest osobnego typu)
105108
* najprostszą operacją możliwą do przeprowadzenia na wartości opcjonalnej jest dostarczenie w wyrażeniu wartości "awaryjnej" na wypadek wystąpienia nulla za pomocą operatora binarnego `??`
@@ -108,26 +111,39 @@ Możliwe jest importowanie zawartości innych skryptów za pomocą instrukcji `p
108111
## Wymagania niefunkcjonalne
109112

110113
1. Lekser i parser powinny działać na tyle szybko i sprawnie, by możliwe było wyświetlanie informacji np. o błędach składniowych na żywo w trakcie pisania programu.
111-
2. Interpreter powinien być odporny na błędy - nie zawieszać się, nie przerywać nagle działania.
114+
2. Interpreter w trybie REPL powinien być odporny na błędy - nie zawieszać się, nie przerywać nagle działania.
112115

113116
## Sposób uruchomienia
114117

115-
Interpreter będzie dostarczony w postaci programu konsolowego. Uruchomiony bez argumentów, będzie pobierał dane ze standardowego strumienia wejściowego. Jako argumenty podać można natomiast pliki skryptowe, które zostaną wczytane i wykonane. W takim przypadku standardowe wejście pozostanie nieczynne.
118+
Interpreter jest dostarczony w postaci programu konsolowego. Uruchomiony bez argumentów, pobiera dane ze standardowego strumienia wejściowego. Jako argumenty podać można natomiast plik skryptowy, który zostanie wczytany i wykonany. W takim przypadku standardowe wejście pozostanie nieczynne.
119+
120+
Do zbudowania i uruchomienia programu wymagane jest środowisko .NET w wersji >=6.
121+
Po wejściu do katalogu Toffee należy uruchomić polecenie `dotnet run`.
116122

117123
## Architektura
118124

119-
System składał się będzie z następujących warstw:
125+
System składa się z następujących warstw:
120126
* skaner znaków (leniwa generacja znaków) - śledzenie pozycji, unifikacja znaków nowej linii,
121127
* analizator leksykalny (leniwa generacja tokenów),
122-
* analizator składniowy - parser typu RD generujący hierarchię obiektów,
128+
* analizator składniowy - parser typu RD generujący abstrakcyjne drzewo składniowe (leniwa generacja instrukcji),
123129
* interpreter wygenerowanego drzewa.
124130

125-
Dodatkowo zaimplementowane będą różne klasy do obsługi błędów, logowania, itp.
131+
Dodatkowo zaimplementowane są różne klasy pomocnicze:
132+
* katalog CommandLine - nadzorowanie uruchomienia interpretera (tryb REPL lub nie),
133+
* katalog ErrorHandling - obsługa błędów,
134+
* katalog Running/Operations - operacje czasu uruchomienia (np. arytmetyczne czy rzutowanie),
135+
* katalog Running/Functions - interfejs funkcji i funkcje natywne,
136+
* Running/EnvironmentStack.cs - klasy związane z zarządzaniem zakresem zmiennych i samymi zmiennymi,
137+
* SyntacticAnalysis/CommentSkippingLexer.cs - klasa opakowująca interfejs leksera w celu pominięcia komentarzy,
138+
* Running/AstPrinter.cs - drukarz drzewa składniowego,
139+
* klasy mapujące sekwencje znaków na tokeny, tokeny na operatory, literały, czy typy
126140

127141
## Testowanie
128142

129143
Analizator leksykalny oraz składniowy pracują na zasadzie konsumpcji kolejnych znaków (lub tokenów) i generowania rezultatu, który powinien być deterministyczny, a więc i możliwy do sprawdzenia pod kątem poprawności (np. poprzez proste porównanie sekwencji).
130144

131-
Komponenty systemu testowane będą jednostkowo, niezależnie dzięki wykorzystaniu w implementacji podejścia obiektowego (możliwe będzie podstawienie obiektu np. leksera z użyciem atrapy).
145+
Komponenty systemu (obecnie skaner, lekser i parser) testowane są jednostkowo, niezależnie dzięki wykorzystaniu w implementacji podejścia obiektowego (możliwe będzie podstawienie obiektu np. leksera z użyciem atrapy).
146+
147+
Poza przykładami mającymi zadziałać poprawnie testowane są przypadki brzegowe, np. nagłe przerwanie strumienia wejściowego, nieprawidłowa sekwencja znaków/tokenów.
132148

133-
Oczywiście testowane będą, poza przykładami mającymi zadziałać poprawnie, przypadki brzegowe, np. nagłe przerwanie strumienia wejściowego, dzielenie przez zero i wszelkie błędy, zaproponowane wyżej lub nie.
149+
W testu interpretera zastosowane są testy integracyjne, podające na wejście tekst instrukcji i oczekujące określonego wyjścia.

0 commit comments

Comments
 (0)