Semantyka i weryfikacja programów/Ćwiczenia 1
Ćwiczenia 1: semantyka operacyjna wyrażeń (małe kroki)
Zadanie 1 (przygotowawcze)
Rozważmy prosty język wyrażeń, którego składnia opisana jest następującą gramatyką:
Wynikiem wyrażenienia warunkowego jest wartość wyrażenia , o ile wyrażenie oblicza się do wartości różnej od zera; w przeciwnym przypadku wynikiem jest wartość wyrażenia .
Zaproponuj semantykę operacyjną (małe kroki) dla tego języka.
Rozwiązanie
Zacznijmy od ustalenia notacji i dziedzin syntaktycznych. Niech oznacza zbiór stałych liczbowych, . Podobnie, niech oznacza zbiór identyfikatorów, które mogą być nazwami zmiennych; . Wreszcie, niech oznacza zbiór wyrażeń; . Dla ułatwienia zapisywania reguł zakładamy, ze stałe liczbowe sa wyrażeniami, czyli .
Będziemy potrzebować zbioru stanów, opisujących wartości przypisane zmiennym. Najprostszym rozwiązaniem jest przyjąc, że stan to funkcja z do . Oznaczmy przez zbiór wszystkich takich funkcji; stany oznaczac będziemy przez .
W naszej semantyce będziemy potrzebowac tranzycji dwóch postaci. Po pierwsze, tranzycja
oznaczająca mały krok w trakcie obliczania wyrażenia w stanie , w wyniku którego wyewoluowało do . Stan nie ulega zmiania podczas obliczania wyrażenia, więc to samo figuruje po lewej i prawej stronie strzałki.
Po drugie, tranzycja
będzie oznaczaczać, że wyrażenie jest już policzone, a jego wartością jest .
Zatem przyjmijmy, że zbiór konfiguracji to
a konfiguracje końcowe to .
Uwaga: tranzycje pierwszej postaci mogłyby również wyglądać następująco: wtedy zbiorem konfiguracji byłby zbiór a konfiguracje końcowe pozostałyby bez zmian (koniec uwagi).
Najprostsze są tranzycje prowadzące do konfiguracji końcowej:
Zmienna oblicza się do swojej wartości w bieżącym stanie:
Teraz zajmiemy się dodawaniem . Ponieważ semantyka jest w stylu małych kroków, musimy zdecydować się czy najpierw obliczyć pierwszy (lewy) składnik Parser nie mógł rozpoznać (błąd składni): {\displaystyle e_1 ] czy drugi? Jeśli wybierzemy lewy, otrzymamy regułę: <math> \frac{e_1, s \,\Longrightarrow\, e'_1, s} {e_1 + e_2, s \,\Longrightarrow\, e'_1 + e_2}. }
Czyli mały krok w stanowi też mały krok w . Po zakończeniu obliczania przechodzimy do :
A na końcu dodajemy:
Zauważmy tutaj pewną subtelność, dotyczącą dwóch wystąpień symbolu +: pierwsze wystąpienie oznacza jedną z konstrukcji składniowych języka, a drugie oznacza operację dodawania w zbiorze . Pozwalamy sobie na taką kolizję oznaczeń, gdyż nie powinna ona prowadzić do niejednoznaczności. Pamiętajmy, że składnia języka jest składnią abstrajkcyjną, więc zamiast moglibyśmy równie dobrze pisać np. .
Inna możliwą strategią obliczania jest strategia prawostronna, którą otrzymujemy zastępując pierwsze dwie z trzech powyższych reguł przez:
Ponadto, jeśli przyjmiemy wszystkie pięc reguł, otrzymamy strategię równoległą, polegającą na obliczaniu jednocześnie i . Bardziej precyzyjnie mówiąc, małe kroki obliczające obydwa podwyrażenia przeplatają się, i to w dowolny sposób. Ta dowolność prowadzi do niedeterminizmu, czyli do sytuacji, gdy kolejna (następna) konfiguracja nie jest wyznaczona jednoznacznie. Jest tak, gdyż jednocześnie możemy mieć dwie tranzycje
Zauważmy natomiast, że kolejność przeplatania się małych kroków obliczających i nie wpływa w tym przypadku na końcową wartość całego wyrażenia.
Na koniec reguły dla wyrażenia warunkowego.
Zadanie 2
Rozszerzmy język wyrażeń z poprzedniego zadania o jedną konstrukcję
Wyrażenie zawiera w sobie deklarację , która stanowi jedyny mechannizm wiązania identyfikatorów w naszym języku. Wartość wyrażenia obliczamy następująco: najpierw oblicza się wartość , podstawia ją na zmienna , a następnie oblicza wyrażenie . Zakresem zmiennej jest wyrażenie , ale jeśli w występuje podwyrażenie to odwołania do wewnątrz odnoszą się do najbliższej (najbardziej zagnieżdzonej) deklaracji zmiennej . Taki mechanizm wiązania identyfikatorów nazywamy wiązaniem statycznym.
Zakładamy, że na początku wartości wszystkich zmiennych są nieokreślone, czyli zmienne są niezainicjowane, a odwołanie do niezainicjowanej zmiennej jest uważane za niepoprawne.
Przykłady
Parser nie mógł rozpoznać (błąd składni): {\displaystyle \mathbf{let}\, z = 5 \,\mathbf{in}\, x+z \quad \quad \mbox{brak wyniku; odwołanie do niezainicjowanej zmiennej}\, x }
Rozwiązanie
Podobnie jak poprzednio, stan powinien opisywać wartości przypisane zmiennym, ale powinniśmy też uwzględnić niezainicjowane zmienne, czyli zmienne bez żadnej wartości. Przyjmijmy zatem, że stan to skończona funkcja częściowa z do . Oznaczmy przez zbiór wszystkich takich funkcji. Naturalnym stanem początkowym jest stan pusty, tzn. pusta funkcja częściowa, który będziemy oznaczać . A wartość wyrażenia w stanie początkowym wynosi o ile zachodzi:
Będziemy potrzebowac tranzycji dwóch postaci, podobnie jak poprzednio, ale pierwsza postać będzie trochę ogólniejsza:
Tranzycja ta oznacza mały krok w trankcie obliczania wyrażenia w stanie , w wyniku którego wyewoluowało do a nowym stanem jest . Stan może się teraz zmienić na skutek deklaracji zmiennych.
Ponieważ stan jest funkcją częściową, musimy zmienić niektóre reguły, np.
Zadanie 3
Zmodyfikuj semantykę z poprzedniego zadania tak, aby uzyskać leniwą ewaluację wyrażeń, zgodnie z dyrektywą: nie obliczaj wyrażenia o ile jego wynik nie jest potrzebny (albo: obliczaj wartość wyrażenia dopiero wtedy, gdy jego wynik jest naprawdę potrzebny). Spojrzmy na przykład:
Według semantyki z poprzedniego zadania wyrażnie to nie ma wartości, bo w deklaracji jest odwołanie do niezainicjowanej zmiennej. Natomiast w semantyce leniwej wyrażenie to obliczy się do wartości , ponieważ wyrażenie nie będzie wogóle obliczane. Będzie tak dlatego, że w wyrażeniu nie ma odwołań do zmiennej .
Rozwiązanie
Zadanie 4
Dotychczas wystąpienie błędu podczas obliczania wyrażenia, np. odwołanie do niezainicjowanej zmiennej, powodowało, że wyrażenie nie posiadało wartości (nie było ciągu tranzycji prowadzących do konfiguracji końcowej). Zmodyfikuj którąś z semantyk z poprzednich zadań tak, aby błąd był komunikowany jako jedna z konfiguracji końcowych. To znaczy: jeśli obliczenie wyrażenia w stanie jest niemożliwe bo wystąpił błąd, to