Semantyka i weryfikacja programów/Ćwiczenia 2: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 10: | Linia 10: | ||
== Rozszerzenia języka Tiny == | == Rozszerzenia semantyki języka Tiny == | ||
Linia 19: | Linia 19: | ||
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. | ||
Linia 28: | Linia 28: | ||
<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{true} | |||
</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> | |||
e, s \ | <math> | ||
e, s \,\Longrightarrow\, e', s | |||
</math> | </math> | ||
i podobnie dla wyrażeń boolowskich: | i podobnie dla wyrażeń boolowskich: | ||
<math> | |||
b, s \,\Longrightarrow\, b', s | |||
</math> | |||
gdzie <math> s \in \mathbf{State} </math>. | |||
Dodatkowo będziemy potrzebować również tranzycji postaci: | |||
<math> | |||
e, s \,\Longrightarrow\, n | |||
\quad \quad | |||
\mbox{ oraz } | |||
\quad \quad | |||
b, s \,\Longrightarrow\, l | |||
</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{true} \} </math>. | |||
Formalnie, zbiór konfiguracji dla semantyki całego języka Tiny to | |||
<math> | <math> | ||
( (\mathbf{Exp} \, \cup \, \mathbf{BExp} \, \cup \, \mathbf{Stmt}) \times \mathbf{State} ) \, \bigcup \, | |||
\mathbf{Num} \, \cup \, \mathbf{Bool} \, \cup \, \mathbf{State} | |||
</math> | </math> | ||
Zacznijmy od | 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> | <math> | ||
l, s \,\Longrightarrow\, l | |||
\quad \quad | \quad \quad | ||
n, s \,\Longrightarrow\, n. | |||
</math> | </math> | ||
Przejdźmy do spójników logicznych, powiedzmy <math>b_1 \land b_2</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 <math> mkrok </math> | |||
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>. | |||
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ę | 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: | 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 | ||
\mathbf{true} \land b_2, s \,\Longrightarrow\, \mathbf{true}, 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 | b_1 \land \mathbf{true}, s \,\Longrightarrow\, \mathbf{true}, 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 | prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie | ||
wyrażeń <math>b_1</math> i <math>b_2</math> odbywa się teraz | wyrażeń <math> b_1 </math> i <math> b_2 </math> odbywa się teraz | ||
w twz. ''przeplocie'': Pojedynczy krok polega na wykonaniu | w twz. ''przeplocie'': Pojedynczy krok polega na wykonaniu | ||
jednego kroku obliczenia <math>b_1</math> albo jednego kroku | jednego kroku obliczenia <math> b_1 </math> albo jednego kroku | ||
obliczenia <math>b_2</math>. | obliczenia <math> b_2 </math>. | ||
Zwróćmy też uwagę, że nasze tranzycje nie posiadają teraz | Zwróćmy też uwagę, że nasze tranzycje nie posiadają teraz | ||
własności ''determinizmu'': wyrażenie <math>b_1 \land b_2</math> | własności ''determinizmu'': wyrażenie <math> b_1 \land b_2 </math> | ||
może wyewoluować w pojedyńczym kroku albo do | 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>. | <math> b'_1 \land b_2 </math> albo do <math> b_1 \land b'_2 </math>. | ||
Na szczęście, końcowy wynik, do jakiego oblicza się wyrażenie | Na szczęście, końcowy wynik, do jakiego oblicza się wyrażenie | ||
jest zawsze taki sam, niezależnie od przeplotu. | jest zawsze taki sam, niezależnie od przeplotu. | ||
Oto | Oto reguły dla negacji: | ||
<math> | <math> | ||
\neg true, s \Longrightarrow | \neg \mathbf{true}, s \,\Longrightarrow\, \mathbf{true}, s | ||
\quad \quad \quad | \quad \quad \quad | ||
\neg | \neg \mathbf{true}, 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> | ||
Reguły dla <math>e_1 \leq e_2</math> są następujące: | Reguły dla <math> e_1 \leq e_2 </math> są następujące: | ||
<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} | \frac{e_2, s \,\Longrightarrow\, e'_2, 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 | |||
n_1 \leq n_2, s \Longrightarrow true, s \quad \mbox{ o ile } | n_1 \leq n_2, s \,\Longrightarrow\, \mathbf{true}, s \quad \mbox{ o ile } | ||
n_1 \leq n_2 | n_1 \leq n_2 | ||
\quad \quad | |||
n_1 \leq n_2, s \Longrightarrow | n_1 \leq n_2, s \,\Longrightarrow\, \mathbf{true}, s \quad \mbox{ o ile } | ||
n_1 > n_2 | n_1 > n_2 | ||
</math> | </math> | ||
Linia 144: | Linia 191: | ||
Reguły powyższe zależą od semantyki wyrażen arytmetycznych. | Reguły powyższe zależą od semantyki wyrażen arytmetycznych. | ||
Zauważmy, że ponownie pozostawiliśmy dowolność jeśli chodzi o | Zauważmy, że ponownie pozostawiliśmy dowolność jeśli chodzi o | ||
kolejność obliczania wyrażeń arytmetycznych e_1 i e_2. | 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> | |||
Rozważmy teraz instrukcję warunkową i instrukcję pętli. | Rozważmy teraz instrukcję warunkową i instrukcję pętli. | ||
Najpierw obliczamy wartość dozoru: | Najpierw obliczamy wartość dozoru: | ||
<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 161: | Linia 220: | ||
W przypadku instrukcji warunkowej reguły są oczywiste: | W przypadku instrukcji warunkowej reguły są oczywiste: | ||
<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 | \quad \quad | ||
\mathbf{if}\, | \mathbf{if}\, \mathbf{true} \,\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> | ||
Linia 179: | Linia 238: | ||
<math> \,\Longrightarrow\, </math> (czyli w zadadzie do semantyki dużych kroków): | <math> \,\Longrightarrow\, </math> (czyli w zadadzie do semantyki dużych kroków): | ||
<math> | <math> | ||
\frac{b, s \,\Longrightarrow\,^{*} true} | \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 | \quad \quad | ||
\frac{b, s \,\Longrightarrow\,^{*} | \frac{b, s \,\Longrightarrow\,^{*} \mathbf{true}} | ||
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow s} | {\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow\, s} | ||
</math> | </math> | ||
Linia 192: | Linia 251: | ||
Istnieją inne możliwe rozwiązania w stylu małych kroków. | Istnieją inne możliwe rozwiązania w stylu małych kroków. | ||
Jedno z nich oparte jest na pomyśle, aby ''rozwinąc'' pętlę | Jedno z nich oparte jest na pomyśle, aby ''rozwinąc'' pętlę | ||
<math> \mathbf{while}\, </math> | <math> \mathbf{while}\, </math>, zanim obliczymy wartość dozoru <math> b </math>. | ||
Jedyną reguła dla pętli <math> \mathbf{while}\, </math> byłaby wtedy reguła: | |||
<math> | <math> | ||
Linia 201: | Linia 261: | ||
''jednorazowy''. | ''jednorazowy''. | ||
Znalezienie innych rozwiązań, np. opartych na rozszerzeniu składni, | Znalezienie innych rozwiązań, np. opartych na rozszerzeniu składni, | ||
pozostawiamy dociekliwemu | pozostawiamy dociekliwemu Czytelnikowi. | ||
Reguły dla operacji arytmetycznych pozostawiamy do napisania Czytelnikowi. | Reguły dla operacji arytmetycznych również pozostawiamy do napisania Czytelnikowi. | ||
Linia 214: | Linia 274: | ||
\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{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> | ||
Linia 224: | Linia 284: | ||
==== Rozwiązanie ==== | ==== Rozwiązanie ==== | ||
===== Instrukcja <math> \mathbf{repeat}\, </math> ===== | |||
Zacznijmy od pętli <math> \mathbf{repeat}\, I \,\mathbf{until}\, b </math>. | Zacznijmy od pętli <math> \mathbf{repeat}\, I \,\mathbf{until}\, b </math>. | ||
Linia 234: | Linia 296: | ||
</math> | </math> | ||
Po drugie, spróbujmy odwołać się do <math> \,\Longrightarrow\,^{*} </math> dla dozoru pętli | Po drugie, spróbujmy odwołać się do <math> \,\Longrightarrow\,^{*} </math> dla dozoru pętli <math> b </math>: | ||
<math> b </math>: | |||
<math> | <math> | ||
Linia 252: | Linia 313: | ||
Okazuje się, że jest jeszcze gorzej niż w przypadku pętli <math> \mathbf{while}\, </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! | nie pamiętamy już, jaka była instrukcja wewnętrzna naszej pętli! | ||
Czyli takie podejście jest teraz nieskuteczne. | |||
===== Instrukcja <math> \,\mathbf{do}\, e \,\mathbf{times}\, I </math> ===== | |||
Pętla <math> \,\mathbf{do}\, e \,\mathbf{times}\, I </math>, w stanie <math> s </math>, oznacza wykonianie 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 juz łatwo: | |||
Semantykę dla <math> \,\mathbf{do}\, I \mathbf{while}\, b </math> pozostawiamy Czytelnikowi jako | <math> | ||
ćwiczenie. | \,\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> | |||
\,\mathbf{do}\, n \,\mathbf{times}\, I, s \,\Longrightarrow\, s | |||
\quad \mbox{ o ile } n = 0. | |||
</math> | |||
===== Instrukcja <math> \mathbf{for}\, </math> ===== | |||
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 <math> e_1 </math> będzie obliczone jako pierwsze. | |||
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\, x:= n_1; I; \mathbf{for}\, x = n_1+1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s | |||
\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\, \mathbf{skip}, s | |||
\quad \mbox{ o ile } n_1 > n_2 | |||
</math> | |||
Zauważmy, wartość zmiennej <math> x </math> po zakończeniu pętli | |||
wynosi <math> n_2 </math>. Ponieważ nie zostało wyspecyfikowane jaka | |||
powinna być wartość tej zmiennej, taką semantykę uważamy | |||
za poprawną. | |||
Semantykę dla <math> \,\mathbf{do}\, I \, \mathbf{while}\, b </math> pozostawiamy Czytelnikowi jako | |||
proste ćwiczenie. | |||
Oczywiście minimalistyczne rozwiązanie to | Oczywiście minimalistyczne rozwiązanie to | ||
Linia 284: | Linia 416: | ||
</math> | </math> | ||
<math> \epsilon </math> oznacza słowo puste, czyli np. <math> \epsilon 1 0 1 </math> | <math> \epsilon </math> oznacza słowo puste, czyli np. <math> \epsilon 1 0 1 1 </math> | ||
oznacza binarną liczbę | oznacza binarną liczbę 1011. | ||
Napisz semantykę operacyjną obliczającą wartość wyrażeń. | Napisz semantykę operacyjną obliczającą wartość wyrażeń. | ||
Linia 397: | Linia 529: | ||
Rozszerzmy składnię o jeden symbol <math> p </math> oznaczający | Rozszerzmy składnię o jeden symbol <math> p </math> oznaczający | ||
''przepełnienie'': | ''przepełnienie'': | ||
<math> | <math> | ||
e \, ::= \,\, | e \, ::= \,\, | ||
Linia 403: | Linia 536: | ||
e 0 \,\,|\,\, | e 0 \,\,|\,\, | ||
e 1 \,\,|\,\, | e 1 \,\,|\,\, | ||
e_1 + e_2 | e_1 + e_2 . | ||
</math> | </math> | ||
Na przykład <math> p 1 0 1 </math> oznacza tę samą liczbę co <math> \epsilon 1 0 1 | 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 | </math>, ale z dodatkową informacją, że podczas jej obliczania nastąpiło | ||
Linia 516: | Linia 650: | ||
==== Zadanie 2 ==== | ==== Zadanie 2 ==== | ||
Rozważ inną semantykę pętli <math> \mathbf{for}\, x = e_1 \,\mathbf{to}\, e_2 do I </math>, | |||
w której wyrażenie <math> e_2 </math> jest obliczane na nowo | |||
przed każdą iteracją pętli. | |||
Napisz reguły semantyczne dla tej instrukcji, nie odwołując | |||
się do innych istrukcji pętli. | |||
==== Zadanie 3 ==== | |||
Dodaj do wyrażeń binarnych operację odejmowania. | Dodaj do wyrażeń binarnych operację odejmowania. | ||
==== Zadanie 4 ==== | |||
Zaproponuj semantykę przypisania równoległego | |||
<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 | |||
nowych wartości na zmienne <math> x_1, \ldots, x_n </math>. |
Wersja z 08:20, 7 sie 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 semantyki języka Tiny
Zadanie 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
Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych:
Niech oznacza zbiór wyrażeń boolowskich, . Chcemy, aby tranzycje dla wyrażeń były postaci:
i podobnie dla wyrażeń boolowskich:
gdzie . Dodatkowo będziemy potrzebować również tranzycji postaci:
gdzie jest liczbą całkowitą, , a . Formalnie, zbiór konfiguracji dla semantyki całego języka Tiny to
a konfiguracje końcowe to ; aczkolwiek konfiguracje ze zbioru pełnią podobną rolę dla wyrażeń arytmetycznych i boolowskich jako konfiguracje końcowe dla instrukcji. Przypomnijmy, że oznacza zbiór instrukcji, .
Zacznijmy od (chyba najprostszych) tranzycji dla stałych boolowskich i liczbowych:
Podobnie jak poprzednio, zakładamy tutaj dla wygody, że oraz . 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 . 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ły 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 i .
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:
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 instrukcji 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. Jedno z nich oparte jest na pomyśle, aby rozwinąc pętlę , zanim obliczymy wartość dozoru . Jedyną reguła dla pętli byłaby wtedy reguła:
Dzięki temu obliczany warunek logiczny 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.
Zadanie 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
Instrukcja
Zacznijmy od pętli . Przyjrzyjmy się dwóm podejściom, które zastosowaliśmy dla pętli w poprzednim zadaniu. Po pierwsze rozwinięcie:
Po drugie, spróbujmy odwołać się do dla dozoru pętli :
Okazuje się, że jest jeszcze gorzej niż w przypadku pętli : nie pamiętamy już, jaka była instrukcja wewnętrzna naszej pętli! Czyli takie podejście jest teraz nieskuteczne.
Instrukcja
Pętla , w stanie , oznacza wykonianie instrukcji razy, gdzie to wartość, do której oblicza się w stanie . Czyli najpierw obliczmy przy pomocy reguły:
która doprowadza nas do konfiguracji:
Teraz jest juz łatwo:
Instrukcja
W przypadku pętli przyjmijmy, że wartości wyrażeń i obliczane są przed pierwszą iteracją pętli. Dodatkowo ustalmy, że będzie obliczone jako pierwsze. Czyli:
Zatem zakres zmiennej mamy już obliczony, tzn. jesteśmy w konfiguracji
Dalsze reguły mogą być podobne do reguł dla pętli :
Zauważmy, wartość zmiennej po zakończeniu pętli
wynosi . Ponieważ nie zostało wyspecyfikowane jaka
powinna być wartość tej zmiennej, taką semantykę uważamy
za poprawną.
Semantykę dla pozostawiamy Czytelnikowi jako
proste ćwiczenie.
Oczywiście minimalistyczne rozwiązanie to
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ę 1011. 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 obydwa składniki mają tyle samo cyfr, do zakończenia dodawania potrzebujemy reguły:
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 obliczenia.
Na koniec dodajemy typowe reguły opisujące jak krok podwyrażenia indukuje krok całego 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 dla 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
Rozważ inną semantykę pętli , w której wyrażenie jest obliczane na nowo przed każdą iteracją pętli. Napisz reguły semantyczne dla tej instrukcji, nie odwołując się do innych istrukcji pętli.
Zadanie 3
Dodaj do wyrażeń binarnych operację odejmowania.
Zadanie 4
Zaproponuj semantykę przypisania równoległego
polegającego na obliczeniu najpierw wartości wyrażeń a następnie na podstawieniu nowych wartości na zmienne .