Semantyka i weryfikacja programów/Ćwiczenia 1: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Sl (dyskusja | edycje)
Nie podano opisu zmian
Sl (dyskusja | edycje)
Nie podano opisu zmian
Linia 1: Linia 1:


== Ćwiczenia 1: Semantyka operacyjna wyrażeń ==
== Ćwiczenia 1: Semantyka operacyjna wyrażeń ==




==== Zadanie: ====
==== Zadanie 1 ====
Semantyka języka Tiny z wykładu używała funkcji semantycznych  
Semantyka języka Tiny z wykładu używała funkcji semantycznych  
<math>
<math>
Linia 13: Linia 12:
w stylu dużych kroków (semantyka naturalna) i małych kroków.
w stylu dużych kroków (semantyka naturalna) i małych kroków.


==== Rozwiązanie: ====
==== Rozwiązanie ====


Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych:
Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych:
Linia 52: Linia 51:
<math>s \in State</math>, <math>n</math> jest liczbą całkowitą,  
<math>s \in State</math>, <math>n</math> jest liczbą całkowitą,  
<math>n \in Int</math>, a <math>l \in Bool = \{ true, false \}</math>.
<math>n \in Int</math>, a <math>l \in Bool = \{ true, false \}</math>.
Tranzycja taka oznacza, że wyrażenie <math>e</math> w stanie <math>s<math> wylicza się do
Tranzycja taka oznacza, że wyrażenie <math>e</math> w stanie <math>s</math> wylicza się do
wartości <math>n</math>, oraz że wyrażenie logiczne <math>b</math> w
wartości <math>n</math>, oraz że wyrażenie logiczne <math>b</math> w
stanie <math>s</math> wylicza się do <math>l</math>.
stanie <math>s</math> wylicza się do <math>l</math>.
Linia 340: Linia 339:
<math>
<math>
\frac{b, s \longrightarrow true}
\frac{b, s \longrightarrow true}
{\mathbf{while}\, b\, \mathbf{do}\' I, s \Longrightarrow  
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow  
  I;\, \mathbf{while}\, b\, \mathbf{do}\, I, s}
  I;\, \mathbf{while}\, b\, \mathbf{do}\, I, s}
\quad \quad \quad
\quad \quad \quad
Linia 369: Linia 368:
Niektóre konfiguracje nie były wogóle przez nas używane. Które?
Niektóre konfiguracje nie były wogóle przez nas używane. Które?


==== Zadanie: ====
==== Zadanie 2 ====
Rozważ dodatkowo operację dzielenia:
Rozważ dodatkowo operację dzielenia:
<math>
<math>
Linia 381: Linia 380:
jeśli błąd wystąpił.
jeśli błąd wystąpił.


==== Rozwiązanie (szkic): ====
==== Rozwiązanie (szkic) ====


Dopóki nie wystąpi błąd dzielenia przez zero, semantyka programu
Dopóki nie wystąpi błąd dzielenia przez zero, semantyka programu
Linia 431: Linia 430:


<math>
<math>
\frac{e_1, s \dkrok Blad}
\frac{e_1, s \longrightarrow Blad}
{e_1 \leq e_2, s \dkrok Blad}
{e_1 \leq e_2, s \longrightarrow Blad}
\quad \quad \quad
\quad \quad \quad
\frac{e_2, s \dkrok Blad}
\frac{e_2, s \longrightarrow Blad}
{e_1 \leq e_2, s \dkrok Blad}
{e_1 \leq e_2, s \longrightarrow Blad}
</math>
</math>


Linia 442: Linia 441:


<math>
<math>
\frac{b \dkrok Blad}
\frac{b \longrightarrow Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \dkrok Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \longrightarrow Blad}
\quad \quad \quad
\quad \quad \quad
\frac{b \dkrok Blad}
\frac{b \longrightarrow Blad}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \dkrok Blad}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \longrightarrow Blad}
</math>
</math>


Linia 452: Linia 451:


<math>
<math>
\frac{I_1, s \dkrok Blad}
\frac{I_1, s \longrightarrow Blad}
{I_1;\, I_2, s \dkrok Blad}
{I_1;\, I_2, s \longrightarrow Blad}
\quad \quad \quad
\quad \quad \quad
\frac{I_1, s \dkrok Blad}
\frac{I_1, s \longrightarrow Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \dkrok Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \longrightarrow Blad}
\quad \quad \quad
\quad \quad \quad
\frac{I_2, s \dkrok Blad}
\frac{I_2, s \longrightarrow Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \dkrok Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \longrightarrow Blad}
\quad \quad \quad
\quad \quad \quad
\frac{I, s \dkrok Blad}
\frac{I, s \longrightarrow Blad}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \dkrok Blad}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \longrightarrow Blad}
</math>
</math>


Linia 470: Linia 469:


(Powyżej traktowaliśmy tranzycję  
(Powyżej traktowaliśmy tranzycję  
<math> I, s \dkrok Blad </math> jak ''duży krok''.
<math> I, s \longrightarrow Blad </math> jak ''duży krok''.
Ale tak naprawdę pojawienie się błędu powinno spowodować
Ale tak naprawdę pojawienie się błędu powinno spowodować
''natychmiastowe'' zatrzymanie programu, więc może tranzycję taką
''natychmiastowe'' zatrzymanie programu, więc może tranzycję taką
można również traktować jak ''mały krok'',  
można również traktować jak ''mały krok'',  
<math> I, s \mkrok Blad </math>?)
<math> I, s \Longrightarrow Blad </math>?)

Wersja z 07:10, 24 lip 2006

Ćwiczenia 1: Semantyka operacyjna wyrażeń

Zadanie 1

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::=true|false|e1e2|¬b|b1b2|b1b2

e::=0|1||x|e1+e2|e1*e2|e1e2|


Zacznijmy od dużych kroków.

Semantyka naturalna

Chcemy, aby tranzycje wyrażen wyglądały następująco:

e,snb,sl,

gdzie sState, n jest liczbą całkowitą, nInt, a lBool={true,false}. Tranzycja taka oznacza, że wyrażenie e w stanie s wylicza się do wartości n, oraz że 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×State)(Expr×State)(BExpr×State)StateIntBool

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. State=VarInt. Konfiguracje końcowe pozostają bez zmian (State). 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:

b,strueI;𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,ss𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,ss

b,sfalse𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,ss

Podobnie dla instrukcji warunkowej. Teraz zajmiemy się tranzycjami dla wyrażeń. Zacznijmy od stalych arytmetycznych:

n,sn,dla nInt

Zauważmy, iż celowo nie odróżniamy liczby nInt 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:

true,struefalse,sfalse

Analogicznie, czynimy tu założenie, że Bool jest podbiorem wyrażen boolowskich.

Operatory arytmetyczne definiujemy następująco:

e1,sn1e2,sn2n=n1+n2e1+e2,sn

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 umożliwiał efekty uboczne podzas obliczania wyrażeń.

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

b1,sl1b2,sl2l=l1l2b1b2,sl

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 (nazwijmy je regułami lewo-stronnymi, ponieważ rozpoczynamy od lewego koniunktu):

b1,sfalseb1b2,sfalseb1,strueb2,slb1b2,sl

Wybraliśmy następującą kolejność obliczania wyrażeń: najpierw b_1, potem b_2. 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):

b1,sfalseb1b2,sfalseb2,sfalseb1b2,sfalse

Czyli jeśli którekolwiek z podwyrażeń daje wynik false, to taki wynik zyskuje całe wyrażenie. Dodatkowo potrzebujemy jeszcze reguły:

b1,strueb2,strueb1b2,strue

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 b_1 i b_2 do momentu, gdy jedno z nich obliczy się do wynikiem false, albo aż obydwa się zakończą z wynikiem true.

W naszym prostym języku wszystkie cztery 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: e,se,s i podobnie dla wyrażeń boolowskich: b,sb,s gdzie sState. Przyjmijmy na razie takie same konfiguracje i konfiguracje końcowe jak dla semantyki naturalnej.


Zacznijmy od wyrażeń boolowskich.

true,struefalse,sfalse

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

b1,sb'1,sb1b2,sb'1b2,sb2,sb'2,sl1b2,sl1b2,sl1l2l, o ile l=l1l2

Podobnie jak poprzednio, możemy zaniechać obliczania b2 jeśli b1 oblicza się do false. Oto odpowiednio zmodyfikowane reguły:

b1,sb'1,sb1b2,sb'1b2,sfalseb2,sfalsetrueb2,sb2,s

Analogicznie reguły prawostronne to:

b2,sb'2,sb1b2,sb1b'2,sb1false,sfalseb1true,sb1,s

Reguły równoległe otrzymujemy jako sumę reguł lewo- i prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie wyrażeń b1 i b2 odbywa się teraz w twz. przeplocie: Pojedynczy krok polega na wykonaniu jednego kroku obliczenia b1 albo jednego kroku obliczenia b2. Zwróćmy też uwagę, że po raz pierwszy nasze tranzycje nie posiadają własności determinizmu: wyrażenie b1b2 może wyewoluować w pojedyńczym kroku albo do b'1b2 albo do b1b'2. Na szczęście, końcowy wynik, do jakiego oblicza się wyrażenie jest zawsze taki sam, niezależnie od przeplotu.

Oto reguła dla negacji:

¬true,sfalse,s¬false,strue,sb,sb,s¬b,s¬b,s

Reguły dla e1e2 są następujące:

e1,se'1,se1e2,se'1e2,se2,se'2,se1e2,se1e'2,sn1n2,strue,s o ile n1n2n1n2,sfalse,s o ile n1>n2

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 e_1 i e_2.

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

b,sb,s𝐢𝐟b𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,s𝐢𝐟b𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sb,sb,s𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,s𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,s

a gdy dozór jest już obliczony, podejmujemy decyzję. W przypadku instrukcji warunkowej reguły są oczywiste:

𝐢𝐟true𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sI1,s𝐢𝐟false𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sI2,s

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

𝐰𝐡𝐢𝐥𝐞true𝐝𝐨I,sI;𝐰𝐡𝐢𝐥𝐞?𝐝𝐨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ę do tranzycji dużych kroków:

b,strue𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,sI;𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,sb,sfalse𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,ss

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ą:

e1,se'1,se1+e2,se'1+e2,se2,se'2,sn+e2,sn+e'2,sn1+n2,sn,s o ile n=n1+n2

Niektóre konfiguracje nie były wogóle przez nas używane. Które?

Zadanie 2

Rozważ dodatkowo operację dzielenia: e::=|e1/e2 i rozszerz semantyki z poprzedniego zadania. Dzielenie przez zero jest błądem i kończy natychmiast wykonanie programu. Oprócz stanu wynikiem programu powinna byc informacja o błędzie, jeśli błąd wystąpił.

Rozwiązanie (szkic)

Dopóki nie wystąpi błąd dzielenia przez zero, semantyka programu powinna być identyczna jak w poprzednim zadaniu. Zatem pozostawiamy wszystkie reguły z poprzedniego zadania, tak w semantyce naturalnej jak i w semantyce małych kroków. Dodatkowo potrzebujemy reguł, które opiszą

    • kiedy powstaje błąd oraz
    • jak zachowuje się program po wystąpieniu błędu

Zaczynamy od pierwszego punktu. W tym celu dodajemy do konfiguracji jedną konfigurację końcową Blad. Reguła opisująca powstanie błędu może wyglądać np. tak w semantyce naturalnej:

e2,s0e1/e2,sBlad

a tak w semantyce małych kroków:

e1/0,sBlad

Pomijamy tutaj reguły dla przypadku, gdy e2 oblicza się do wartości różnej od zera. Ponadto dla czytelności przyjęliśmy, że wynikiem tranzycji jest wyłącznie informacja o błędzie, a stan jest zapominany. Bez trudu możnaby wszystkie reguły (zarówno te powyżej jak i te poniżej) zmodyfikować tak, by wraz z informacją o błędzie zwracany był też stan, w którym błąd się pojawił. Np. ostatnia reguła wyglądałaby następująco:

e1/0,sBlad,s

I zamiast pojedyńczej konfiguracji końcowej Blad, potrzebowalibyśmy oczywiście całego zbioru {Blad}×State.

Przejdźmy teraz do drugiego punktu. Potrzebujemy dodatkowych reguł, które zagwarantują, że błąd, raz pojawiwszy się, propaguje się przez wszystkie konstrukcje składniowe, a normalne obliczenie wyrażenia jest wstrzymame. Zacznijmy od sementyki naturalnej:

e1,sBlade1e2,sBlade2,sBlade1e2,sBlad

Następnie, błąd w wyrażeniu powinien wstrzymać normalne wykonanie instrukcji:

bBlad𝐢𝐟b𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sBladbBlad𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,sBlad

I wreszcie błąd powinien propagować się do kolejnych instrukcji:

I1,sBladI1;I2,sBladI1,sBlad𝐢𝐟b𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sBladI2,sBlad𝐢𝐟b𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sBladI,sBlad𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,sBlad

I tak dalej.

Pytanie dla Czytelnika: jak napisać tę semantykę w podejściu mało-krokowym?

(Powyżej traktowaliśmy tranzycję I,sBlad jak duży krok. Ale tak naprawdę pojawienie się błędu powinno spowodować natychmiastowe zatrzymanie programu, więc może tranzycję taką można również traktować jak mały krok, I,sBlad?)