Semantyka i weryfikacja programów/Ćwiczenia 2: Różnice pomiędzy wersjami
Nie podano opisu zmian |
m Zastępowanie tekstu – „.↵</math>” na „</math>” |
||
(Nie pokazano 15 wersji utworzonych przez 3 użytkowników) | |||
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 semantyki języka Tiny == | |||
{{cwiczenie|1|cw1| | |||
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> | ||
B, E : State \to State | B, E : State \to State | ||
</math> | </math> | ||
dla | dla określenia znaczenia wyrażeń boolowskich i arytmetycznych. | ||
Zdefiniuj znaczenie wyrażeń za pomocą semantyki operacyjnej, | Zdefiniuj znaczenie wyrażeń za pomocą semantyki operacyjnej, w stylu małych kroków. | ||
w stylu małych kroków. | }} | ||
==== Rozwiązanie == | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span> | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych: | Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych: | ||
<math> | <math> | ||
b \, ::= \,\, | b \, ::= \,\, | ||
l \,\,|\,\, | |||
e_1 \leq e_2 \,\,|\,\, | e_1 \leq e_2 \,\,|\,\, | ||
\neg b \,\,|\,\, | \neg b \,\,|\,\, | ||
b_1 \land b_2 \,\,|\,\, \ldots | b_1 \land b_2 \,\,|\,\, \ldots | ||
</math> | |||
<math> | |||
l \, ::= \,\, | |||
\mathbf{true} \,\,|\,\, | |||
\mathbf{false} | |||
</math> | </math> | ||
<math> | <math> | ||
e \, ::= \,\, | e \, ::= \,\, | ||
n \,\,|\,\, | |||
x \,\,|\,\, | x \,\,|\,\, | ||
e_1 + e_2 \,\,|\,\, \ldots | e_1 + e_2 \,\,|\,\, \ldots | ||
</math> | </math> | ||
<math> | |||
n \, ::= \,\, 0 \,\,|\,\, 1 \,\,|\,\, \ldots | |||
</math> | |||
<math> | |||
x \, ::= \,\, \ldots \, (identyfikatory) \, \ldots | |||
</math> | |||
Niech <math>\mathbf{BExp}</math> oznacza zbiór wyrażeń boolowskich, <math>b \in \mathbf{BExp}</math>. | |||
Chcemy, aby tranzycje dla wyrażeń były postaci: | Chcemy, aby tranzycje dla wyrażeń były postaci: | ||
<math> | <math> | ||
e, s \ | e, s \,\Longrightarrow, e', s | ||
</math> | </math> | ||
i podobnie dla wyrażeń boolowskich: | i podobnie dla wyrażeń boolowskich: | ||
<math> | <math> | ||
b, s \ | b, s \,\Longrightarrow, b', s | ||
</math> | </math> | ||
gdzie <math>s \in \mathbf{State}</math>. | |||
Dodatkowo będziemy potrzebować również tranzycji postaci: | |||
<math> | <math> | ||
e, s \,\Longrightarrow, n | |||
\quad \quad | |||
\mbox{ oraz } | |||
\quad \quad | \quad \quad | ||
b, s \,\Longrightarrow, l | |||
</math> | </math> | ||
gdzie <math>n \in</math> jest liczbą całkowitą, <math>n \in \mathbf{Num}</math>, a <math>l \in \mathbf{Bool} = \{ \mathbf{true}, \mathbf{false} \}</math>. | |||
Formalnie, zbiór konfiguracji dla semantyki całego języka Tiny to | |||
<math> | |||
( (\mathbf{Exp} \, \cup \, \mathbf{BExp} \, \cup \, \mathbf{Stmt}) \times \mathbf{State} ) \, \bigcup \, | |||
\mathbf{Num} \, \cup \, \mathbf{Bool} \, \cup \, \mathbf{State} | |||
</math> | |||
a konfiguracje końcowe to <math>\mathbf{State}</math>, aczkolwiek konfiguracje ze zbioru <math>\mathbf{Num} \, \cup \, \mathbf{Bool}</math> pełnią podobną rolę dla wyrażeń arytmetycznych i boolowskich jako konfiguracje końcowe dla instrukcji. | |||
Przypomnijmy, że <math>\mathbf{Stmt}</math> oznacza zbiór instrukcji, <math>I \in \mathbf{Stmt}</math>. | |||
Zacznijmy od (chyba najprostszych) tranzycji dla stałych boolowskich i liczbowych: | |||
<math> | |||
l, s \,\Longrightarrow, l | |||
\quad \quad | |||
n, s \,\Longrightarrow, n</math> | |||
Podobnie jak poprzednio, zakładamy tutaj dla wygody, że <math>\mathbf{Num} \subseteq \mathbf{Exp}</math> oraz <math>\mathbf{Bool} \subseteq \mathbf{BExp}</math>. | |||
Pozwala nam to nie odróżniać stałych występujących w wyrażeniach, a zatem pojawiających się po lewej stonie tranzycji od wartości im odpowiadających, pojawiających się po prawej stronie. | |||
Przejdźmy do spójników logicznych, powiedzmy <math>b_1 \land b_2</math>. | Przejdźmy do spójników logicznych, powiedzmy <math>b_1 \land b_2</math>. | ||
Ponieważ opisujemy teraz pojedyncze (małe) kroki składające się na | Ponieważ opisujemy teraz pojedyncze (małe) kroki składające się na wykonanie programu, musimy podać w jakiej kolejności będą się obliczać <math>b_1</math> i <math>b_2</math>. Zacznijmy od strategii lewostronnej: | ||
wykonanie programu, musimy podać w jakiej kolejności będą się | |||
obliczać <math>b_1</math> i <math>b_2</math>. Zacznijmy od strategii lewostronnej: | |||
<math> | <math> | ||
\frac{b_1, s \Longrightarrow b'_1, s} | \frac{b_1, s \,\Longrightarrow, b'_1, s} | ||
{b_1 \land b_2, s \Longrightarrow b'_1 \land b_2, s} | {b_1 \land b_2, s \,\Longrightarrow, b'_1 \land b_2, s} | ||
\quad \quad | \quad \quad | ||
\frac{b_2, s \Longrightarrow b'_2, s} | \frac{b_2, s \,\Longrightarrow, b'_2, s} | ||
{l_1 \land b_2, s \Longrightarrow l_1 \land b_2, s} | {l_1 \land b_2, s \,\Longrightarrow, l_1 \land b_2, s} | ||
\quad \quad | \quad \quad | ||
l_1 \land l_2 \Longrightarrow l, | l_1 \land l_2, s \,\Longrightarrow, l, s | ||
\mbox{ o ile } l = l_1 \land l_2 | \mbox{ o ile } l = l_1 \land l_2 | ||
</math> | </math> | ||
Możemy zaniechać obliczania | Możemy zaniechać obliczania <math>b_2</math> jeśli <math>b_1</math> oblicza się do false. | ||
<math>b_2</math> jeśli <math>b_1</math> oblicza się do false. | |||
Oto odpowiednio zmodyfikowane reguły: | Oto odpowiednio zmodyfikowane reguły: | ||
<math> | <math> | ||
\frac{b_1, s \Longrightarrow b'_1, s} | \frac{b_1, s \,\Longrightarrow, b'_1, s} | ||
{b_1 \land b_2, s \Longrightarrow b'_1 \land b_2, s} | {b_1 \land b_2, s \,\Longrightarrow, b'_1 \land b_2, s} | ||
\quad \quad | \quad \quad | ||
false \land b_2, s \Longrightarrow false | \mathbf{false} \land b_2, s \,\Longrightarrow, \mathbf{false}, s | ||
\quad \quad | \quad \quad | ||
true \land b_2, s \Longrightarrow b_2, s | \mathbf{true} \land b_2, s \,\Longrightarrow, b_2, s</math> | ||
</math> | |||
Analogicznie reguły prawostronne to: | Analogicznie reguły prawostronne to: | ||
<math> | <math> | ||
\frac{b_2, s \Longrightarrow b'_2, s} | \frac{b_2, s \,\Longrightarrow, b'_2, s} | ||
{b_1 \land b_2, s \Longrightarrow b_1 \land b'_2, s} | {b_1 \land b_2, s \,\Longrightarrow, b_1 \land b'_2, s} | ||
\quad \quad | \quad \quad | ||
b_1 \land false, s \Longrightarrow false | b_1 \land \mathbf{false}, s \,\Longrightarrow, \mathbf{false}, s | ||
\quad \quad | \quad \quad | ||
b_1 \land true, s \Longrightarrow b_1, s | b_1 \land \mathbf{true}, s \,\Longrightarrow, b_1, s</math> | ||
</math> | |||
Reguły ''równoległe'' otrzymujemy jako sumę reguł lewo- i | Reguły ''równoległe'' otrzymujemy jako sumę reguł lewo- i prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie wyrażeń <math>b_1</math> i <math>b_2</math> odbywa się teraz w tzw. "przeplocie": Pojedynczy krok polega na wykonaniu jednego kroku obliczenia <math>b_1</math> albo jednego kroku obliczenia <math>b_2</math>. | ||
prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie | Zwróćmy też uwagę, że nasze tranzycje nie posiadają teraz własności ''determinizmu'': wyrażenie <math>b_1 \land b_2</math> może wyewoluować w pojedyńczym kroku albo do <math>b'_1 \land b_2</math> albo do <math>b_1 \land b'_2</math>. | ||
wyrażeń <math>b_1</math> i <math>b_2</math> odbywa się teraz | Na szczęście końcowy wynik, do jakiego oblicza się wyrażenie, jest zawsze taki sam, niezależnie od przeplotu. | ||
w | |||
jednego kroku obliczenia <math>b_1</math> albo jednego kroku | |||
obliczenia <math>b_2</math>. | |||
Zwróćmy też uwagę, że nasze tranzycje nie posiadają teraz | |||
własności ''determinizmu'': wyrażenie <math>b_1 \land b_2</math> | |||
może wyewoluować w pojedyńczym kroku albo do | |||
<math>b'_1 \land b_2</math> albo do <math>b_1 \land b'_2</math>. | |||
Na szczęście | |||
jest zawsze taki sam, niezależnie od przeplotu. | |||
Oto | Oto reguły dla negacji: | ||
<math> | <math> | ||
\neg true, s \Longrightarrow false, s | \neg \mathbf{true}, s \,\Longrightarrow, \mathbf{false}, s | ||
\quad \quad \quad | \quad \quad \quad | ||
\neg false, s \Longrightarrow true, s | \neg \mathbf{false}, s \,\Longrightarrow, \mathbf{true}, s | ||
\quad \quad \quad | \quad \quad \quad | ||
\frac{b, s \Longrightarrow b', s} | \frac{b, s \,\Longrightarrow, b', s} | ||
{\neg b, s \Longrightarrow \neg b', s} | {\neg b, s \,\Longrightarrow, \neg b', s} | ||
</math> | </math> | ||
Linia 121: | Linia 154: | ||
<math> | <math> | ||
\frac{e_1, s \Longrightarrow e'_1, s} | \frac{e_1, s \,\Longrightarrow, e'_1, s} | ||
{e_1 \leq e_2, s \Longrightarrow e'_1 \leq e_2, s} | {e_1 \leq e_2, s \,\Longrightarrow, e'_1 \leq e_2, s} | ||
\quad \quad | |||
\frac{e_2, s \,\Longrightarrow, e'_2, s} | |||
{e_1 \leq e_2, s \,\Longrightarrow, e_1 \leq e'_2, s} | |||
</math> | |||
\ | <math> | ||
n_1 \leq n_2, s \,\Longrightarrow, \mathbf{true}, s \quad \mbox{ o ile } n_1 \leq n_2 | |||
\quad \quad | |||
n_1 \leq n_2, s \,\Longrightarrow, \mathbf{false}, s \quad \mbox{ o ile } n_1 > n_2</math> | |||
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 <math>e_1</math> i <math>e_2</math>. | |||
Jako pierwszą z instrukcji rozważmy przypisanie. | |||
Najpierw obliczamy wyrażenie po prawej stronie przypisania, a gdy wyrażenie to wyewoluuje do stałej (obliczy się), modyfikujemy stan: | |||
<math> | |||
\frac{e, s \,\Longrightarrow, e', s} | |||
{x := e, s \,\Longrightarrow, x := e', s} | |||
\quad \quad | |||
{x := n, s \,\Longrightarrow, s[x \mapsto n]} | |||
</math> | </math> | ||
Rozważmy teraz instrukcję warunkową i instrukcję pętli. | Rozważmy teraz instrukcję warunkową i instrukcję pętli. | ||
Linia 142: | Linia 183: | ||
<math> | <math> | ||
\frac{b, s \Longrightarrow b', s} | \frac{b, s \,\Longrightarrow, b', s} | ||
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow | {\mathbf{if}\, b \,\mathbf{then}\, I_1 \,\mathbf{else}\, I_2, s \,\Longrightarrow, | ||
\mathbf{if}\, b'\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s} | \mathbf{if}\, b' \,\mathbf{then}\, I_1 \,\mathbf{else}\, I_2, s} | ||
\quad \quad | \quad \quad | ||
\frac{b, s \Longrightarrow b', s} | \frac{b, s \,\Longrightarrow, b', s} | ||
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow \mathbf{while}\, b'\, \mathbf{do}\, I, s} | {\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow, \mathbf{while}\, b' \,\mathbf{do}\, I, s} | ||
</math> | </math> | ||
Linia 154: | Linia 195: | ||
<math> | <math> | ||
\mathbf{if}\, true\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_1, s | \mathbf{if}\, \mathbf{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}\, \mathbf{false} \,\mathbf{then}\, I_1 \,\mathbf{else}\, I_2, s \,\Longrightarrow, I_2, s | ||
</math> | </math> | ||
Gorzej jest w przypadku | Gorzej jest w przypadku instrukcji pętli. Reguła mogłaby wyglądać tak: | ||
<math> | <math> | ||
\mathbf{while}\, true\, \mathbf{do}\, I, s \Longrightarrow | \mathbf{while}\, \mathbf{true} \,\mathbf{do}\, I, s \,\Longrightarrow, | ||
I;\, \mathbf{while}\, ?\, \mathbf{do}\, I, s | I;\, \mathbf{while}\, ? \,\mathbf{do}\, I, s | ||
</math> | </math> | ||
ale nie wiemy już, jaki był dozór pętli (widzimy tylko wynik | ale nie wiemy już, jaki był dozór pętli (widzimy tylko wynik obliczenia tego dozoru w stanie s, czyli <math>\mathbf{true}</math>). | ||
obliczenia tego dozoru w stanie s, czyli <math> \mathbf{true} </math>). | Możemy odwołać się do tranzytywnego domknięcia relacji <math>\,\Longrightarrow</math>, (czyli w zasadzie do semantyki dużych kroków): | ||
Możemy odwołać się do tranzytywnego domknięcia relacji | |||
<math> \,\Longrightarrow | |||
<math> | <math> | ||
\frac{b, s \,\Longrightarrow | \frac{b, s \,\Longrightarrow,^{*} \mathbf{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 | |||
\frac{b, s \,\Longrightarrow | \frac{b, s \,\Longrightarrow,^{*} \mathbf{false}} | ||
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow s} | {\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow, s} | ||
</math> | </math> | ||
Istnieją inne możliwe rozwiązania w stylu małych kroków. | |||
Jedno z nich oparte jest na pomyśle, aby "rozwinąć" pętlę <math>\mathbf{while}\ </math>, zanim obliczymy wartość dozoru <math>b</math>. | |||
Istnieją inne możliwe rozwiązania | Jedyną reguła dla pętli <math>\mathbf{while}\ </math>, byłaby wtedy reguła: | ||
Reguły dla operacji arytmetycznych pozostawiamy do napisania Czytelnikowi. | <math> | ||
\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow, \mathbf{if}\, b \,\mathbf{then}\, (I; \mathbf{while}\, b \,\mathbf{do}\, I) \,\mathbf{else}\, \mathbf{skip}, s</math> | |||
Dzięki temu obliczany warunek logiczny <math>b</math> jest zawsze ''jednorazowy''. | |||
Znalezienie innych rozwiązań, np. opartych na rozszerzeniu składni, pozostawiamy dociekliwemu Czytelnikowi. | |||
Reguły dla operacji arytmetycznych również pozostawiamy do napisania Czytelnikowi. | |||
</div></div> | |||
{{cwiczenie|2|cw2| | |||
Rozszerzmy język Tiny o następujące dobrze znane konstrukcje iteracji: | Rozszerzmy język Tiny o następujące dobrze znane konstrukcje iteracji: | ||
Linia 196: | Linia 242: | ||
I \, ::= \,\, | I \, ::= \,\, | ||
\mathbf{repeat}\, I \,\mathbf{until}\, b \,\,|\,\, | \mathbf{repeat}\, I \,\mathbf{until}\, b \,\,|\,\, | ||
\mathbf{for}\, x := e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I \,\,|\,\, | |||
\,\mathbf{do}\, | \,\mathbf{do}\, e \,\mathbf{times}\, I \,\,|\,\, | ||
\,\mathbf{do}\, I \mathbf{while}\, b | \,\mathbf{do}\, I \,\mathbf{while}\, b | ||
</math> | </math> | ||
Napisz semantykę małych kroków dla powyższych konstrukcji. | Napisz semantykę małych kroków dla powyższych konstrukcji. | ||
}} | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span> | |||
<div class="mw-collapsible-content" style="display:none"> | |||
'''Instrukcja <math>\mathbf{repeat}\ </math>,''' | |||
<br> | |||
Zacznijmy od pętli <math>\mathbf{repeat}\, I \,\mathbf{until}\, b</math>. | |||
Przyjrzyjmy się dwóm podejściom, które zastosowaliśmy dla pętli <math>\mathbf{while}\ </math>, w poprzednim zadaniu. | |||
Po pierwsze, rozwinięcie: | |||
<math> | <math> | ||
\,\mathbf{ | \mathbf{repeat}\, I \,\mathbf{until}\, b, s \,\Longrightarrow, I; \mathbf{if}\, b \,\mathbf{then}\, (\mathbf{repeat}\, I \,\mathbf{until}\, b) \,\mathbf{else}\, \mathbf{skip}, s</math> | ||
Po drugie, spróbujmy odwołać się do <math>\,\Longrightarrow,^{*}</math> dla dozoru pętli <math>b</math>: | |||
<math> | |||
\frac{I, s \,\Longrightarrow, I', s'} | |||
{\mathbf{repeat}\, I \,\mathbf{until}\, b, s \,\Longrightarrow, \mathbf{repeat}\, I' \,\mathbf{until}\, b, s'} | |||
</math> | </math> | ||
<math> | |||
\frac{I, s \,\Longrightarrow, s' \quad b, s \,\Longrightarrow,^{*} \mathbf{false}} | |||
{\mathbf{repeat}\, I \,\mathbf{until}\, b, s \,\Longrightarrow, s'} | |||
\quad \quad | |||
\frac{I, s \,\Longrightarrow, s' \quad b, s \,\Longrightarrow,^{*} \mathbf{true}} | |||
{\mathbf{repeat}\, I \,\mathbf{until}\, b, s \,\Longrightarrow, \mathbf{repeat}\, ? \,\mathbf{until}\, b, s'} | |||
</math> | |||
Okazuje się, że jest jeszcze gorzej niż w przypadku pętli <math>\mathbf{while}\ </math>,: nie pamiętamy już, jaka była instrukcja wewnętrzna naszej pętli! | |||
Czyli takie podejście jest teraz nieskuteczne. | |||
<br> | |||
'''Instrukcja <math>\,\mathbf{do}\, e \,\mathbf{times}\, I</math>''' | |||
<br> | |||
Pętla <math>\,\mathbf{do}\, e \,\mathbf{times}\, I</math>, w stanie <math>s</math>, oznacza wykonanie instrukcji <math>I</math> <math>n</math> razy, gdzie <math>n</math> to wartość, do której oblicza się <math>e</math> w stanie <math>s</math>. | |||
Czyli najpierw obliczmy <math>e</math> przy pomocy reguły: | |||
<math> | |||
\frac{e, s \,\Longrightarrow, e', s} | |||
{\,\mathbf{do}\, e \,\mathbf{times}\, I, s \,\Longrightarrow, D e' \,\mathbf{times}\, I, s} | |||
</math> | |||
która doprowadza nas do konfiguracji: | |||
<math> | |||
\,\mathbf{do}\, n \,\mathbf{times}\, I, s</math> | |||
Teraz jest już łatwo: | |||
<math> | |||
\,\mathbf{do}\, n \,\mathbf{times}\, I, s \,\Longrightarrow, I; \,\mathbf{do}\, n{-}1 \,\mathbf{times}\, I, s | |||
\quad \mbox{ o ile } n > 0 | |||
</math> | |||
<math> | <math> | ||
I \, ::= \,\, | \,\mathbf{do}\, n \,\mathbf{times}\, I, s \,\Longrightarrow, s | ||
\quad \mbox{ o ile } n = 0</math> | |||
<br> | |||
'''Instrukcja <math>\mathbf{for}\ </math>,''' | |||
<br> | |||
W przypadku pętli <math>\mathbf{for}\ </math>, przyjmijmy, że wartości wyrażeń <math>e_1</math> i <math>e_2</math> obliczane są przed pierwszą iteracją pętli. | |||
Dodatkowo ustalmy, że np. <math>e_1</math> będzie obliczone jako pierwsze. | |||
Następnie podstawiamy wartość <math>e_1</math> na zmienną <math>x</math>. | |||
Czyli: | |||
<math> | |||
\frac{e_1, s \,\Longrightarrow, e'_1, s} | |||
{\mathbf{for}\, x = e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I,s \,\Longrightarrow, \mathbf{for}\, x = e'_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I, s} | |||
</math> | |||
<math> | |||
\frac{e_2, s \,\Longrightarrow, e'_2, s} | |||
{\mathbf{for}\, x = n \,\mathbf{to}\, e_2 \,\mathbf{do}\, I,s \,\Longrightarrow, \mathbf{for}\, x = n \,\mathbf{to}\, e'_2 \,\mathbf{do}\, I, s} | |||
</math> | |||
Zatem zakres zmiennej <math>x</math> mamy już obliczony, tzn. jesteśmy w konfiguracji | |||
<math> | |||
\mathbf{for}\, x = n_1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s</math> | |||
Dalsze reguły mogą być podobne do reguł dla pętli <math>\,\mathbf{do}\, n \,\mathbf{times}\, I</math>: | |||
<math> | |||
\mathbf{for}\, x = n_1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s \,\Longrightarrow, I; \mathbf{for}\, x = n_1+1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s[x \mapsto n_1] | |||
\quad \mbox{ o ile } n_1 \leq n_2 | |||
</math> | |||
<math> | |||
\mathbf{for}\, x = n_1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s \,\Longrightarrow, s | |||
\quad \mbox{ o ile } n_1 > n_2 | |||
</math> | |||
Zauważmy, wartość zmiennej <math>x</math> po zakończeniu pętli wynosi albo <math>n_2</math>, albo pozostaje niezmieniona, o ile nie była ona zmieniana wewnątrz instrukcji <math>I</math>. | |||
Ponieważ nie zostało wyspecyfikowane, jaka powinna być wartość tej zmiennej, możemy taką semantykę uznać za poprawną. | |||
'''Pytanie:''' oto inna wersja jednej z powyższych reguł: | |||
<math> | |||
\mathbf{for}\, x = n_1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s \,\Longrightarrow, I; \mathbf{for}\, x = n_1+1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s[x \mapsto s(x)+1] | |||
\quad \mbox{ o ile } s(x) \leq n_2</math> | |||
Czy semantyka jest taka sama? | |||
(Rozważ sytuację, gdy zmienna <math>x</math> jest zmieniana przez instrukcję <math>I</math>.) | |||
'''Pytanie:''' a gdybyśmy jednak zażądali przywrócenia na koniec wartości zmiennej <math>x</math> sprzed pętli? | |||
Jak należałoby zmienić nasze reguły? | |||
Semantykę dla <math>\,\mathbf{do}\, I \, \mathbf{while}\, b</math> pozostawiamy Czytelnikowi jako proste ćwiczenie. | |||
Oczywiście minimalistyczne rozwiązanie to | |||
<math> | |||
\,\mathbf{do}\, I \,\mathbf{while}\, b, s \,\Longrightarrow, \mathbf{repeat}\, I \,\mathbf{until}\, \neg b, s</math> | |||
</div></div> | |||
== Kalkulator binarny == | |||
{{cwiczenie|3|cw3| | |||
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>\epsilon</math> oznacza słowo puste, czyli np. <math>\epsilon 1 0 1 1</math> oznacza binarną liczbę 1011. | |||
Napisz semantykę operacyjną obliczającą wartość wyrażeń. | |||
}} | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span> | |||
<div class="mw-collapsible-content" style="display:none"> | |||
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 obydwa składniki mają tyle samo cyfr, do zakończenia dodawania potrzebujemy reguły: | |||
<math> | |||
\epsilon + \epsilon \,\Longrightarrow, \epsilon</math> | |||
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 obliczenia. | |||
Na koniec dodajemy typowe reguły opisujące jak krok podwyrażenia indukuje krok całego wyrażenia: | |||
<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> | |||
</div></div> | |||
{{cwiczenie|4|cw4| | |||
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ą wartość wyrażenia wraz z informacja o ewentualnym przepełnieniu. | |||
Wynik powinien byc poprawny przynajmniej dla wyrażeń <math>e</math> w składni ograniczonej: | |||
<math> | |||
e \, ::= \,\, | |||
b \,\,|\,\, | |||
e_1 + e_2 | |||
</math> | </math> | ||
<math> | <math> | ||
b \, ::= \,\, | |||
\epsilon \,\,|\,\, | |||
b 0 \,\,|\,\, | |||
b 1</math> | |||
reprezentujących sumę liczb binarnych. | |||
}} | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span> | |||
<div class="mw-collapsible-content" style="display:none"> | |||
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 dodawania 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. | |||
</div></div> | |||
== Zadania domowe == | == Zadania domowe == | ||
{{cwiczenie|1|cw1.dom| | |||
Podaj przykład wyrażenia boolowskiego, które nie policzy się ani przy użyciu strategii lewo-, ani prawostronnej, a policzy się przy strategii równoległej. | |||
}} | |||
{{cwiczenie|2|cw2.dom| | |||
Zmodyfikuj semantykę wyrażeń następująco: dla każdego podwyrażenia niedeterministycznie wybierana jest strategia lewo- albo prawostronna, ale niedozwolony jest "przeplot". | |||
}} | |||
{{cwiczenie|3|cw3.dom| | |||
Rozważ inną semantykę pętli <math>\mathbf{for}\, x = e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I</math>, w której wyrażenie <math>e_2</math> jest obliczane na nowo przed każdą iteracją pętli. | |||
}} | |||
{{cwiczenie|4|cw4.dom| | |||
Dodaj do wyrażeń binarnych operację odejmowania. | |||
}} | |||
{{cwiczenie|5|cw15.dom| | |||
Zaproponuj semantykę przypisania równoległego w języku TINY: | |||
<math> | |||
x_1 := e_1 || \ldots || x_n := e_n | |||
</math> | |||
polegającego na obliczeniu najpierw wartości wyrażeń <math>e_1, \ldots, e_n</math>, a następnie na podstawieniu tych wartości na zmienne <math>x_1, \ldots, x_n</math>. | |||
}} |
Aktualna wersja na dzień 21:29, 11 wrz 2023
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 semantyki języka Tiny
Ćwiczenie 1
Semantyka języka Tiny z wykładu używała funkcji semantycznych dla określenia znaczenia wyrażeń boolowskich i arytmetycznych. Zdefiniuj znaczenie wyrażeń za pomocą semantyki operacyjnej, w stylu małych kroków.
Rozwiązanie
Ćwiczenie 2
Rozszerzmy język Tiny o następujące dobrze znane konstrukcje iteracji:
Napisz semantykę małych kroków dla powyższych konstrukcji.
Rozwiązanie
Kalkulator binarny
Ćwiczenie 3
Rozważmy następujący język wyrażeń (liczby binarne z dodawaniem):
oznacza słowo puste, czyli np. oznacza binarną liczbę 1011. Napisz semantykę operacyjną obliczającą wartość wyrażeń.
Rozwiązanie
Ćwiczenie 4
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ą wartość wyrażenia wraz z informacja o ewentualnym przepełnieniu. Wynik powinien byc poprawny przynajmniej dla wyrażeń w składni ograniczonej:
reprezentujących sumę liczb binarnych.
Rozwiązanie
Zadania domowe
Ćwiczenie 1
Podaj przykład wyrażenia boolowskiego, które nie policzy się ani przy użyciu strategii lewo-, ani prawostronnej, a policzy się przy strategii równoległej.
Ćwiczenie 2
Zmodyfikuj semantykę wyrażeń następująco: dla każdego podwyrażenia niedeterministycznie wybierana jest strategia lewo- albo prawostronna, ale niedozwolony jest "przeplot".
Ćwiczenie 3
Rozważ inną semantykę pętli , w której wyrażenie jest obliczane na nowo przed każdą iteracją pętli.
Ćwiczenie 4
Dodaj do wyrażeń binarnych operację odejmowania.
Ćwiczenie 5
Zaproponuj semantykę przypisania równoległego w języku TINY:
polegającego na obliczeniu najpierw wartości wyrażeń , a następnie na podstawieniu tych wartości na zmienne .