Semantyka i weryfikacja programów/Ćwiczenia 2: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 1: | Linia 1: | ||
== | == Zawartość == | ||
== | Ćwiczymy dalej semantykę małych kroków. | ||
Uzupełnimy semantykę języka Tiny o semantykę operacyjną | |||
wyrażeń boolowskich i arytmetycznych. | |||
Następnie rozszerzymy nasz język o róznorodne konstrukcje iteracji. | |||
Na koniec zdefiniujemy operacje arytmetyczne liczb binarnych. | |||
== Rozszerzenia języka Tiny == | |||
==== Zadanie 1 ==== | ==== 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 155: | Linia 163: | ||
<math> | <math> | ||
\mathbf{if}\, true\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_1, s | \mathbf{if}\, true\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_1, s | ||
\quad \quad | |||
\mathbf{if}\, false\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_2, s | \mathbf{if}\, false\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_2, s | ||
</math> | </math> | ||
Linia 175: | Linia 183: | ||
{\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 | |||
\frac{b, s \,\Longrightarrow\,^{*} false} | \frac{b, s \,\Longrightarrow\,^{*} false} | ||
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow s} | {\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow s} | ||
Linia 196: | Linia 204: | ||
I \, ::= \,\, | I \, ::= \,\, | ||
\mathbf{repeat}\, I \,\mathbf{until}\, b \,\,|\,\, | \mathbf{repeat}\, I \,\mathbf{until}\, b \,\,|\,\, | ||
FOR x := e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I \,\,|\,\, | \mathbf{FOR}\, x := e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I \,\,|\,\, | ||
\,\mathbf{do}\, I e \,\mathbf{times} \,\,|\,\, | \,\mathbf{do}\, I e \,\mathbf{times} \,\,|\,\, | ||
\,\mathbf{do}\, I \mathbf{while}\, b | \,\mathbf{do}\, I \,\mathbf{while}\, b | ||
</math> | </math> | ||
Linia 206: | Linia 214: | ||
<math> | <math> | ||
\,\mathbf{do}\, I \mathbf{while}\, b \,\Longrightarrow\, \mathbf{repeat}\, I \,\mathbf{until}\, \neg b | \,\mathbf{do}\, I \,\mathbf{while}\, b \,\Longrightarrow\, \mathbf{repeat}\, I \,\mathbf{until}\, \neg b | ||
</math> | |||
==== Rozwiązanie ==== | |||
..... | |||
== Kalkulator binarny == | |||
==== Zadanie 1 ==== | |||
Rozważmy następujący język wyrażeń (liczby binarne z dodawaniem): | |||
<math> | |||
e \, ::= \,\, | |||
\epsilon \,\,|\,\, | |||
e 0 \,\,|\,\, | |||
e 1 \,\,|\,\, | |||
e_1 + e_2 | |||
</math> | </math> | ||
<math> \epsilon </math> oznacza słowo puste, czyli np. <math> \epsilon 1 0 1 </math> | |||
oznacza binarną liczbę 101. | |||
Napisz semantykę operacyjną obliczającą wartość wyrażeń. | |||
==== Rozwiązanie ==== | ==== Rozwiązanie ==== | ||
Składnia wyrażeń pozwala na wygodny dostęp do najmniej znaczącego | |||
bitu liczby. Spróbujmy zatem zastosować metodę dodawania | |||
pisemnego: | |||
<math> | |||
e_1 0 + e_2 0 \,\Longrightarrow\, (e_1 + e_2) 0 | |||
</math> | |||
<math> | |||
e_1 0 + e_2 1 \,\Longrightarrow\, (e_1 + e_2) 1 | |||
</math> | |||
<math> | |||
e_1 1 + e_2 0 \,\Longrightarrow\, (e_1 + e_2) 1 | |||
</math> | |||
Ale co zrobić z przeniesieniem? | |||
<math> | |||
e_1 1 + e_2 1 \,\Longrightarrow\, ? | |||
</math> | |||
Podstawowy pomysł polega na potraktowaniu przeniesienia jak dodatkowego składnika: | |||
<math> | |||
e_1 1 + e_2 1 \,\Longrightarrow\, ((e_1 + e_2) + \epsilon 1) 0 | |||
</math> | |||
Zauważmy, że w składni dopuszcza się dowolne przeplatanie operatora dodawania | |||
i bitów <math> 0, 1 </math>. Tę dowolność wykorzystaliśmy właśnie w regułach | |||
powyżej. Gdyby nasz język ograniczyć tylko do składni | |||
<math> | |||
e \, ::= \,\, | |||
b \,\,|\,\, | |||
e_1 + e_2 | |||
</math> | |||
<math> | |||
b \, ::= \,\, | |||
\epsilon \,\,|\,\, | |||
b 0 \,\,|\,\, | |||
b 1 | |||
</math> | |||
(nazwijmy ją ''składnią ograniczoną'') to powyższe reguły byłyby niepoprawne. | |||
Zanim dopiszemy pozostałe reguły, określmy zbiór konfiguracji jako | |||
zbiór wyrażeń. Konfiguracje końcowe to wyrażenia bez operatora dodawania | |||
(liczby binarne). Nasz pomysł jest taki, że tranzycje stopniowo przesuwają | |||
operator dodawania w lewo, aż się go ostatecznie ''pozbędą''. | |||
Gdy jeden ze składników ma mniej cyfr niż drugi, potrzebujemy reguł: | |||
<math> | |||
\epsilon + e 0 \,\Longrightarrow\, e 0 | |||
\quad \quad | |||
\epsilon + e 1 \,\Longrightarrow\, e 1 | |||
</math> | |||
oraz ich odpowiedników: | |||
<math> | |||
e 0 + \epsilon \,\Longrightarrow\, e 0 | |||
\quad \quad | |||
e 1 + \epsilon \,\Longrightarrow\, e 1. | |||
</math> | |||
Niestety, nie możemy użyć reguły przemienności: | |||
<math> | |||
e_1 + e_2 \,\Longrightarrow\, e_2 + e_1 | |||
</math> | |||
gdyż spowodowałaby ona możliwość ''pętlenia się'', a zatem braku wyniku obliczania wyrażenie. | |||
Na koniec dodajemy typowe reguły opisujące jak krok podwyrażenia daje | |||
krok całęgo wyrażenia: | |||
<math> | <math> | ||
\frac{e_1 \,\Longrightarrow\, e'_1} | |||
{e_1 + e_2 \,\Longrightarrow\, e'_1 + e_2} | |||
\quad \quad | |||
\frac{e_2 \,\Longrightarrow\, e'_2} | |||
{e_1 + e_2 \,\Longrightarrow\, e_1 + e'_2} | |||
\quad \quad | |||
\frac{e \,\Longrightarrow\, e'} | |||
{e 0 \,\Longrightarrow\, e' 0} | |||
\quad \quad | |||
\frac{e \,\Longrightarrow\, e'} | |||
{e 1 \,\Longrightarrow\, e' 1} | |||
\quad \quad | |||
</math> | </math> | ||
==== Zadanie 2 ==== | |||
Rozszerzmy składnię o jeden symbol <math> p </math> oznaczający | |||
''przepełnienie'': | |||
<math> | |||
e \, ::= \,\, | |||
\epsilon \,\,|\,\, | |||
p \,\,|\,\, | |||
e 0 \,\,|\,\, | |||
e 1 \,\,|\,\, | |||
e_1 + e_2 | |||
</math> | |||
Na przykład <math> p 1 0 1 </math> oznacza tę samą liczbę co <math> \epsilon 1 0 1 | |||
</math>, ale z dodatkową informacją, że podczas jej obliczania nastąpiło | |||
''przepełnienie''. | |||
Rozumiemy przez to sytuację, gdy wynik ma więcej cyfr niż każdy z | |||
argumentów. Cyfry zero z lewej strony (najbardziej znaczące) również | |||
uważamy za pełnoprawne cyfry, nie należy ich usuwać ani dodawać nowych. | |||
Napisz semantykę operacyjną obliczającą wyrażenie wraz z | |||
informacja o ewentualnym przepełnieniu. | |||
Wynik powinien byc poprawny przynajmniej dal wyrażeń w składni | |||
ograniczonej. | |||
==== Rozwiązanie ==== | ==== Rozwiązanie ==== | ||
Zadanie dotyczy w zasadzie składni ograniczonej, ale jako konfiguracji | |||
pośrednich będziemy zapewne potrzebować wyrażeń wykraczających poza | |||
tę składnię, np. <math> (e_1 + e_2) 0 </math>, podobnie jak w poprzednim | |||
zadaniu. Zatem mamy tu do czynienia z typowym zabiegiem rozszerzania | |||
składni na użytek semantyki operacyjnej (z tym, że rozszerzenie jest | |||
dane z góry i nie musimy go wymyślać :) | |||
Przyjmijmy, że konfiguracjami są dowolne wyrażenia, a konfiguracjami | |||
końcowymi wyrażenia bez operatora dodawania (ale teraz mogą to być | |||
np. wyrażenia postaci <math> p 1 0 0 </math>). | |||
Spróbujmy pozostawić wszystkie reguły z poprzedniego zadania. | |||
Dodamy tylko kilka nowych reguł, odpowiedzialnych za przepełnienie. | |||
Zacznijmy od najprostszej sytuacji, gdy jeden ze składników ma mniej | |||
cyfr, i to ten właśnie składnik odnotował przepełnienie: | |||
<math> | |||
p + e 0 \,\Longrightarrow\, e 0 | |||
\quad \quad | |||
p + e 1 \,\Longrightarrow\, e 1 | |||
\quad \quad | |||
e 0 + p \,\Longrightarrow\, e 0 | |||
\quad \quad | |||
e 1 + p \,\Longrightarrow\, e 1 | |||
</math> | |||
W takiej sytuacji oczywiście informacja o przepełnieniu zostaje | |||
wymazana. | |||
Jeśli przepełnienie zostało odnotowane w składniku o większej liczbie | |||
cyfr, to reguły | |||
<math> | |||
\epsilon + e 0 \,\Longrightarrow\, e 0 | |||
\quad \quad | |||
\epsilon + e 1 \,\Longrightarrow\, e 1 | |||
\quad \quad | |||
e 0 + \epsilon \,\Longrightarrow\, e 0 | |||
\quad \quad | |||
e 1 + \epsilon \,\Longrightarrow\, e 1. | |||
</math> | |||
z poprzedniego zadania są wystarczające. | |||
Rozważmy teraz przypadek, gdy obydwa składniki mają tę samą liczbę | |||
cyfr. | |||
Jeśli obydwa odnotowały przepełnienie, to oczywiście informacja ta | |||
powinna zostać zachowana: | |||
<math> | |||
p + p \,\Longrightarrow\, p | |||
</math> | |||
Ale co należy zrobić, gdy tylko jeden ze składników odnotował | |||
przepełnienie? <math> p + \epsilon \,\Longrightarrow\, ? </math> | |||
Oczywiście, w tej sytuacji żadnego przepełnienia nie ma, ponieważ | |||
drugi składnik ma wystarczająco dużo cyfr by je przesłonić. | |||
Oto odpowiednie reguły: | |||
<math> | |||
p + \epsilon \,\Longrightarrow\, \epsilon | |||
\quad \quad | |||
\epsilon + p \,\Longrightarrow\, \epsilon | |||
</math> | |||
Na koniec zostało najważniejsze: kiedy powinniśmy generować sygnał o | |||
przepełnieniu? | |||
Przypomnijmy sobie reguły dla dadawania pisemnego w poprzednim | |||
zadaniu. | |||
Jeśli nie ma przeniesienia, to przepełnienie nie może powstać. | |||
Natomiast jeśli jest przeniesienie, to stanowi ono ''potencjalne | |||
przepełnienie''. | |||
Odpowiednia reguła to | |||
<math> | |||
e_1 1 + e_2 1 \,\Longrightarrow\, ((e_1 + e_2) + p 1) 0. | |||
</math> | |||
Nowy sztuczny składnik <math> p 1 </math> zawiera jakby na wszelki wypadek | |||
informacje o potencjalnym przepełnieniu. | |||
Jeśli którykolwiek z pozostałych składników <math> e_1 </math> i <math> e_2 </math> | |||
ma przynajmniej jedną cyfrę, | |||
to <math> p </math> zostanie usunięte. W przeciwnym wypadku symbol <math> p </math> | |||
i przetrwa i będzie poprawnie informował o sytuacji przepełnienia. | |||
== Zadania domowe == | == Zadania domowe == | ||
==== Zadanie 1 ==== | |||
Podaj przykład wyrażenia, które nie policzy się | |||
ani przy użyciu strategii lewo- ani prawostronnej, a policzy się przy strategii | |||
równoległej. | |||
==== Zadanie 2 ==== | |||
Dodaj do wyrażeń binarnych operację odejmowania. |
Wersja z 17:02, 27 lip 2006
Zawartość
Ćwiczymy dalej semantykę małych kroków. Uzupełnimy semantykę języka Tiny o semantykę operacyjną wyrażeń boolowskich i arytmetycznych. Następnie rozszerzymy nasz język o róznorodne konstrukcje iteracji. Na koniec zdefiniujemy operacje arytmetyczne liczb binarnych.
Rozszerzenia języka Tiny
Zadanie 1
Semantyka języka Tiny z wykładu używała funkcji semantycznych dla określenie znaczenia wyrażeń boolowskich i arytmetycznych. Zdefiniuj znaczenie wyrażeń za pomocą semantyki operacyjnej, w stylu małych kroków.
Rozwiązanie
Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych:
Chcemy, aby tranzycje dla wyrażeń były postaci: i podobnie dla wyrażeń boolowskich: gdzie .
Zacznijmy od wyrażeń boolowskich.
Przejdźmy do spójników logicznych, powiedzmy . Ponieważ opisujemy teraz pojedyncze (małe) kroki składające się na wykonanie programu, musimy podać w jakiej kolejności będą się obliczać i . Zacznijmy od strategii lewostronnej:
Możemy zaniechać obliczania jeśli oblicza się do false. Oto odpowiednio zmodyfikowane reguły:
Analogicznie reguły prawostronne to:
Reguły równoległe otrzymujemy jako sumę reguł lewo- i prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie wyrażeń i odbywa się teraz w twz. przeplocie: Pojedynczy krok polega na wykonaniu jednego kroku obliczenia albo jednego kroku obliczenia . Zwróćmy też uwagę, że nasze tranzycje nie posiadają teraz własności determinizmu: wyrażenie może wyewoluować w pojedyńczym kroku albo do albo do . 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:
Reguły dla są następujące:
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:
a gdy dozór jest już obliczony, podejmujemy decyzję. W przypadku instrukcji warunkowej reguły są oczywiste:
Gorzej jest w przypadku instukcji pętli. Reguła mogłaby wyglądać tak:
ale nie wiemy już, jaki był dozór pętli (widzimy tylko wynik obliczenia tego dozoru w stanie s, czyli ). Możemy odwołać się do tranzytywnego domknięcia relacji (czyli w zadadzie do semantyki dużych kroków):
Takie rozwiązanie nie jest zatem czystą semantyką małych kroków. Istnieją inne możliwe rozwiązania, w stylu małych kroków, np. przy użyciu rozszerzonej składni. Znalezienie takiego rozwiązania pozostawiamy dociekliwemu czytelnikowi.
Reguły dla operacji arytmetycznych pozostawiamy do napisania Czytelnikowi.
Zadanie 2
Rozszerzmy język Tiny o następujące dobrze znane konstrukcje iteracji:
Napisz semantykę małych kroków dla powyższych konstrukcji. Niedozwolone jest korzystanie z jakiejś konstrukcji w semantyce innej, np.
Rozwiązanie
.....
Kalkulator binarny
Zadanie 1
Rozważmy następujący język wyrażeń (liczby binarne z dodawaniem):
oznacza słowo puste, czyli np. oznacza binarną liczbę 101. Napisz semantykę operacyjną obliczającą wartość wyrażeń.
Rozwiązanie
Składnia wyrażeń pozwala na wygodny dostęp do najmniej znaczącego bitu liczby. Spróbujmy zatem zastosować metodę dodawania pisemnego:
Ale co zrobić z przeniesieniem?
Podstawowy pomysł polega na potraktowaniu przeniesienia jak dodatkowego składnika:
Zauważmy, że w składni dopuszcza się dowolne przeplatanie operatora dodawania i bitów . Tę dowolność wykorzystaliśmy właśnie w regułach powyżej. Gdyby nasz język ograniczyć tylko do składni
(nazwijmy ją składnią ograniczoną) to powyższe reguły byłyby niepoprawne.
Zanim dopiszemy pozostałe reguły, określmy zbiór konfiguracji jako zbiór wyrażeń. Konfiguracje końcowe to wyrażenia bez operatora dodawania (liczby binarne). Nasz pomysł jest taki, że tranzycje stopniowo przesuwają operator dodawania w lewo, aż się go ostatecznie pozbędą.
Gdy jeden ze składników ma mniej cyfr niż drugi, potrzebujemy reguł:
oraz ich odpowiedników:
Niestety, nie możemy użyć reguły przemienności:
gdyż spowodowałaby ona możliwość pętlenia się, a zatem braku wyniku obliczania wyrażenie.
Na koniec dodajemy typowe reguły opisujące jak krok podwyrażenia daje krok całęgo wyrażenia:
Zadanie 2
Rozszerzmy składnię o jeden symbol oznaczający przepełnienie: Na przykład oznacza tę samą liczbę co , ale z dodatkową informacją, że podczas jej obliczania nastąpiło przepełnienie. Rozumiemy przez to sytuację, gdy wynik ma więcej cyfr niż każdy z argumentów. Cyfry zero z lewej strony (najbardziej znaczące) również uważamy za pełnoprawne cyfry, nie należy ich usuwać ani dodawać nowych.
Napisz semantykę operacyjną obliczającą wyrażenie wraz z informacja o ewentualnym przepełnieniu. Wynik powinien byc poprawny przynajmniej dal wyrażeń w składni ograniczonej.
Rozwiązanie
Zadanie dotyczy w zasadzie składni ograniczonej, ale jako konfiguracji pośrednich będziemy zapewne potrzebować wyrażeń wykraczających poza tę składnię, np. , podobnie jak w poprzednim zadaniu. Zatem mamy tu do czynienia z typowym zabiegiem rozszerzania składni na użytek semantyki operacyjnej (z tym, że rozszerzenie jest dane z góry i nie musimy go wymyślać :)
Przyjmijmy, że konfiguracjami są dowolne wyrażenia, a konfiguracjami końcowymi wyrażenia bez operatora dodawania (ale teraz mogą to być np. wyrażenia postaci ). Spróbujmy pozostawić wszystkie reguły z poprzedniego zadania. Dodamy tylko kilka nowych reguł, odpowiedzialnych za przepełnienie.
Zacznijmy od najprostszej sytuacji, gdy jeden ze składników ma mniej cyfr, i to ten właśnie składnik odnotował przepełnienie:
W takiej sytuacji oczywiście informacja o przepełnieniu zostaje wymazana. Jeśli przepełnienie zostało odnotowane w składniku o większej liczbie cyfr, to reguły
z poprzedniego zadania są wystarczające.
Rozważmy teraz przypadek, gdy obydwa składniki mają tę samą liczbę cyfr. Jeśli obydwa odnotowały przepełnienie, to oczywiście informacja ta powinna zostać zachowana:
Ale co należy zrobić, gdy tylko jeden ze składników odnotował przepełnienie? Oczywiście, w tej sytuacji żadnego przepełnienia nie ma, ponieważ drugi składnik ma wystarczająco dużo cyfr by je przesłonić. Oto odpowiednie reguły:
Na koniec zostało najważniejsze: kiedy powinniśmy generować sygnał o przepełnieniu? Przypomnijmy sobie reguły dla dadawania pisemnego w poprzednim zadaniu. Jeśli nie ma przeniesienia, to przepełnienie nie może powstać. Natomiast jeśli jest przeniesienie, to stanowi ono potencjalne przepełnienie. Odpowiednia reguła to
Nowy sztuczny składnik zawiera jakby na wszelki wypadek informacje o potencjalnym przepełnieniu. Jeśli którykolwiek z pozostałych składników i ma przynajmniej jedną cyfrę, to zostanie usunięte. W przeciwnym wypadku symbol i przetrwa i będzie poprawnie informował o sytuacji przepełnienia.
Zadania domowe
Zadanie 1
Podaj przykład wyrażenia, które nie policzy się ani przy użyciu strategii lewo- ani prawostronnej, a policzy się przy strategii równoległej.
Zadanie 2
Dodaj do wyrażeń binarnych operację odejmowania.