Semantyka i weryfikacja programów/Ćwiczenia 1

Z Studia Informatyczne
Wersja z dnia 07:29, 15 lip 2006 autorstwa Sl (dyskusja | edycje)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)
Przejdź do nawigacjiPrzejdź do wyszukiwania

Ćwiczenia 1: Semantyka operacyjna wyrażeń

Zadanie:

Semantyka języka Tiny z wykładu używała funkcji semantycznych B,E:StateState dla określenie znaczenia wyrażeń boolowskich i arytmetycznych. Zdefiniuj znaczenie wyrażeń za pomocą semantyki operacyjnej, w stylu dużych kroków (semantyka naturalna) i małych kroków.

Rozwiązanie:

Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych:

b::=truefalsee1e<sub>1</sub>e<sub>2</sub>¬bb<sub>1</sub>b<sub>2</sub>b<sub>1</sub>b<sub>2</sub>

e::=01xe<sub>1</sub>+e<sub>2</sub>e<sub>1</sub>*e<sub>2</sub>e<sub>1</sub>e<sub>2</sub>


Zacznijmy od dużych kroków.

Semantyka naturalna

Chcemy, aby tranzycje wyrażen wyglądały następująco: Parser nie mógł rozpoznać (nieznana funkcja „\dkrok”): {\displaystyle e, s \dkrok n b, s \dkrok l } gdzie s \in State, n jest liczbą całkowitą, n \in Int, a l \in Bool = \{ true, false \}. Tranzycja taka oznacza, że wyrażenie e w stanie s wylicza się do wartości n (wyrażenie logiczne b w stanie s wylicza się do l). Zauważmy, że zakładamy tu, iż obliczenie wyrażenia nie zmienia stanu (nie ma efektów ubocznych).

W tym celu rozszerzamy zbiór konfiguracji Γ następująco: Γ=(Instr×Stan)(Expr×Stan)(BExpr×Stan)StanIntBool gdzie Instr oznacza zbiór instrukcji (jedna z kategorii syntaktycznych języka Tiny), Expr zbiór wyrażeń arytmetycznych a BExpr zbiór wyrażeń boolowskich. Stan = Var \to Int. Konfiguracje końcowe pozostają bez zmian (Stan). Tranzycje dla instrukcji pozostają zasadniczo bez zmian, z tym, że odwołania do funkcji semantycznych dla wyrazżen zastępujemy przez odpowiednie tranzycje. Np. dla instrukcji pętli będziemy mieć następujące reguły:

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b, s \dkrok true I; while b do I, s \dkrok s'} {while b do I, s \dkrok s'} }

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b, s \dkrok false}{while b do I, s \dkrok s} }

Podobnie dla instrukcji warunkowej. Teraz zajmiemy się tranzycjami dla wyrażeń. Zacznijmy od stalych arytmetycznych: Parser nie mógł rozpoznać (nieznana funkcja „\dkrok”): {\displaystyle n, s \dkrok n, dla n \in Int } Zauważmy, iż celowo nie odróżniamy liczby n \in Int od stałej reprezentującej tę liczbę, która może pojawić się w wyrażeniach zgodnie z przyjętą przez nas składnią. Czyli zakładamy, że Int jest podzbiorem zbioru wyrażeń. W powyższej tranzycji, n po lewej stronie to stała reprezentująca liczbę, która widnieje po prawej stronie.

Analogiczne tranzycje dla stałych boolowskich to: Parser nie mógł rozpoznać (nieznana funkcja „\dkrok”): {\displaystyle true, s \dkrok true false, s \dkrok false } Czynimy tu analogiczne założenie, że Bool jest podbiorem wyrażen boolowskich.

Operatory arytmetyczne definiujemy następująco: Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{e<sub>1</sub>, s \dkrok n<sub>1</sub> e<sub>2</sub>, s \dkrok n<sub>2</sub> n = n<sub>1</sub> + n<sub>2</sub> } {e<sub>1</sub> + e<sub>2</sub>,s \dkrok n} } Czyli aby obliczyć sumę e1 + e2 w stanie s, trzeba najpierw obliczyć e1 i e2 w stanie s, a następnie dodać obliczone wartości. Zauważmy, że nie specyfikujemy kolejności, w jakiej mają się obliczać e1 i e2. I choć tutaj nie ma to żadnego znaczenia, w przyszłości będzie inaczej, gdy jezyk będzie dopuszczał efekty uboczne wyrażeń.

Podobne reguły można napisać dla pozostałych operacji arytmnetycznych, oraz dla spójników logicznych:

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b<sub>1</sub>, s \dkrok l<sub>1</sub> b<sub>2</sub>, s \dkrok l<sub>2</sub> l = l<sub>1</sub> \land l<sub>2</sub>} {b<sub>1</sub> \land b<sub>2</sub>, s \dkrok l} } Oczywiście jeśli b1 oblicza się do false, wartość całego wyrażenia jest false niezależnie od wartości wyrażenia b2.

Czyli jeśli zaczniemy od obliczenia b1 i wynikiem będzie false, to nie ma wogóle potrzeby obliczania b2. Oto odpowiednie reguły (lewo-stronne):

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b<sub>1</sub>, s \dkrok false} {b<sub>1</sub> \land b<sub>2</sub>,s \dkrok false} \regula{b<sub>1</sub>, s \dkrok true b<sub>2</sub>, s \dkrok l} {b<sub>1</sub> + b<sub>2</sub>,s \dkrok l} }

Wybraliśmy następującą kolejność obliczania wyrażeń: najpierw b1, potem b2. Pozostawiamy Czytelnikowi napisanie analogicznych reguł dla kolejności odwrotnej (reguły prawo-stronne).

Rozważmy też następującą kombinację obydwu semantyk (reguły równoległe): Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b<sub>1</sub>, s \dkrok false} {b<sub>1</sub> \land b<sub>2</sub>,s \dkrok false} \regula{b<sub>2</sub>, s \dkrok false} {b<sub>1</sub> \land b<sub>2</sub>,s \dkrok false} } Czyli jeśli którekolwiek z podwyrażeń daje wynik false, to taki wynik zyskuje całe wyrażenie. Dodatkowo potrzebujemy jeszcze reguły: Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b<sub>1</sub>, s \dkrok true b<sub>2</sub>, s \dkrok true} {b<sub>1</sub> \land b<sub>2</sub>,s \dkrok true} } Zauważmy, że powyższych reguł nie da sie zaimplementować sekwencyjnie: nie wiadomo czy najpierw obliczać b1 czy b2. Reguły te odpowiadają raczej strategii ,,równoległej: obliczaj ,,jednocześnie b1 i b2 albo do pierwszego false, albo aż obydwa się zakończą z wynikiem true.

W naszym prostym języku wszystkie czterech warianty są równoważne. Różnice pomiędzy nimi zobaczymy jednak już w następnym zadaniu, w którym pojawi się prosta odmiana efektów ubocznych (błąd wykonania).


Reguły dla pozostałych spójników logicznych oraz dla negacji pozostawiamy jako ćwiczenie.

A teraz małe kroki.

Strukturalna semantyka operacyjna (małe kroki)

Chcemy, aby tranzycje dla wyrażeń były postaci: Parser nie mógł rozpoznać (nieznana funkcja „\dkro”): {\displaystyle e, s \dkro e', s } i podobnie dla wyrażeń boolowskich: Parser nie mógł rozpoznać (nieznana funkcja „\dkrok”): {\displaystyle b, s \dkrok b', s } gdzie s \in State. Przyjmijmy na razie takie same konfiguracje i konfiguracje końcowe jak dla semantyki naturalnej.

Zacznijmy od wyrażeń boolowskich.

Parser nie mógł rozpoznać (nieznana funkcja „\mkrok”): {\displaystyle true, s \mkrok true false, s \mkrok false }

Przejdźmy do spójników logicznych, powiedzmy b1 \land b2. Ponieważ opisujemy teraz pojedyncze (małe) kroki składające się na wykonanie programu, musimy podać w jakiej kolejności będą się wykonywać. Zacznijmy od strategii lewostronnej:

Parser nie mógł rozpoznać (nieznana funkcja „\regu”): {\displaystyle \reguła{b<sub>1</sub>, s \mkrok b'<sub>1</sub>, s} {b<sub>1</sub> \land b<sub>2</sub>, s \mkrok b'<sub>1</sub> \land b<sub>2</sub>, s} \reguła{b<sub>2</sub>, s \mkrok b'<sub>2</sub>, s} {l<sub>1</sub> \land b<sub>2</sub>, s \mkrok l<sub>1</sub> \land b<sub>2</sub>, s} l<sub>1</sub> \land l<sub>2</sub> \mkrok l, jeśli l = l<sub>1</sub> \land l<sub>2</sub> }

Podobnie jak poprzednio, możemy zaniechać obliczania b2 jeśli b1 oblicza się do false.

Parser nie mógł rozpoznać (nieznana funkcja „\regu”): {\displaystyle \reguła{b<sub>1</sub>, s \mkrok b'<sub>1</sub>, s} {b<sub>1</sub> \land b<sub>2</sub>, s \mkrok b'<sub>1</sub> \land b<sub>2</sub>, s} false \land b<sub>2</sub>, s \mkrok false true \land b<sub>2</sub>, s \mkrok b<sub>2</sub>,s }

Analogicznie reguły prawostronne to:

Parser nie mógł rozpoznać (nieznana funkcja „\regu”): {\displaystyle \reguła{b<sub>2</sub>, s \mkrok b'<sub>2</sub>, s} {b<sub>1</sub> \land b<sub>2</sub>, s \mkrok b<sub>1</sub> \land b'<sub>2</sub>, s} b<sub>1</sub> \land false, s \mkrok false b<sub>1</sub> \land true, s \mkrok b<sub>1</sub>, s }

Reguły równoległe otrzymujemy jako sumę reguł lewo- i prawostronnych (w sumie 6 reguł).

Oto reguła dla negacji: Parser nie mógł rozpoznać (nieznana funkcja „\mkrok”): {\displaystyle \neg true, s \mkrok false, s \neg false, s \mkrok true, s }

Reguły dla e1 \leq e2 są następujące:

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{e<sub>1</sub>, s \mkrok e'<sub>1</sub>, s} {e<sub>1</sub> \leq e<sub>2</sub>, s \mkrok e'<sub>1</sub> \leq e<sub>2</sub>, s} \regula{e<sub>2</sub>, s \mkrok e'<sub>2</sub>, s} {e<sub>1</sub> \leq e<sub>2</sub>, s \mkrok e<sub>1</sub> \leq e'<sub>2</sub>, s} n<sub>1</sub> \leq n<sub>2</sub>, s \mkrok true, s o ile n<sub>1</sub> \leq n<sub>2</sub> n<sub>1</sub> \leq n<sub>2</sub>, s \mkrok false, s o ile n<sub>1</sub> > n<sub>2</sub> }

Reguły powyższe zależą od semantyki wyrażen arytmetycznych. Zauważmy, że ponownie pozostawiliśmy dowolność jeśli chodzi o kolejność obliczania wyrażeń arytmetycznych e1 i e2.

Rozważmy teraz instrukcję warunkową i instrukcję pętli. Najpierw obliczamy wartość dozoru:

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b, s \mkrok b', s} {if b then I<sub>1</sub> else I<sub>2</sub>, s \mkrok if b' then I<sub>1</sub> else I<sub>2</sub>, s} \regula{b, s \mkrok b', s} {while b do I, s \mkrok while b' do I, s} <math> a gdy dozór jest już obliczony, podejmujemy decyzję. W przypadku instrukcji warunkowej reguły są oczywiste: <math> if true then I<sub>1</sub> else I<sub>2</sub>, s \mkrok I<sub>1</sub>, s if false then I<sub>1</sub> else I<sub>2</sub>, s \mkrok I<sub>2</sub>, s }

Gorzej jest w przypadku instukcji pętli. Reguła mogłaby wyglądać tak:

Parser nie mógł rozpoznać (nieznana funkcja „\mkrok”): {\displaystyle while true do I, s \mkrok I; while ? do I, s }

ale nie wiemy już, jaki był dozór pętli (widzimy tylko wynik obliczenia tego dozoru w stanie s, true). Możemy odwołać się więc do tranzycji dużych kroków:

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{b, s \dkrok true} {while b do I, s \mkrok I; while b do I, s} \regula{b, s \dkrok false} {while b do I, s \mkrok s} }

Takie rozwiązanie nie jest zatem ,,czystą semantyką małych kroków. Istnieją inne możliwe rozwiązania, w stylu małych kroków, których znalezienie pozostawiamy dociekliwemu czytelnikowi.

Na koniec podajemy reguły dla operacji arytmetycznych, na przykładzie dodawania. Przyjmijmy, dla przykładu, strategię lewostronną:

Parser nie mógł rozpoznać (nieznana funkcja „\regula”): {\displaystyle \regula{e<sub>1</sub>, s \mkrok e'<sub>1</sub>, s} {e<sub>1</sub> + e<sub>2</sub>, s \mkrok e'<sub>1</sub> + e<sub>2</sub>, s} \regula{e<sub>2</sub>, s \mkrok e'<sub>2</sub>, s} {n + e<sub>2</sub>, s \mkrok n + e'<sub>2</sub>, s} n<sub>1</sub> + n<sub>2</sub>, s \mkrok n, s o ile n = n<sub>1</sub> + n<sub>2</sub> }


Zadanie:

Rozważ dodatkowo operację dzielenia: e::=e<sub>1</sub>/e<sub>2</sub> i rozszerz semantyki z poprzedniego zadania tak, by dzielenie przez zero kończyło program. Oprócz stanu wynikiem programu powinna byc informacja o błędzie.

Rozwiązanie:

....