Semantyka i weryfikacja programów/Ćwiczenia 2: 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 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 określenie znaczenia wyrażeń boolowskich i arytmetycznych.
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 \, ::= \,\,
         true  \,\,|\,\,
         l \,\,|\,\,
        false  \,\,|\,\,
         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 \,  ::= \,\,   
         0  \,\,|\,\,  1    \,\,|\,\,  \ldots  \,\,|\,\,
         \,\,|\,\,  
         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 \longrightarrow 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>
b, s \longrightarrow b', s
( (\mathbf{Exp} \, \cup \, \mathbf{BExp} \, \cup \, \mathbf{Stmt}) \times \mathbf{State} ) \, \bigcup \,
\mathbf{Num} \, \cup \, \mathbf{Bool} \, \cup \, \mathbf{State}
</math>
</math>
gdzie <math>s \in State</math>.


Zacznijmy od wyrażeń boolowskich.
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>
true, s \Longrightarrow true
l, s \,\Longrightarrow\, l
\quad \quad
\quad \quad
false, s \Longrightarrow false
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
false \land b_2, s \Longrightarrow false
\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 false, s \Longrightarrow false
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 reguła dla negacji:
Oto reguły dla negacji:


<math>
<math>
\neg true, s \Longrightarrow false, s
\neg \mathbf{true}, s \,\Longrightarrow\, \mathbf{true}, s
\quad \quad \quad
\quad \quad \quad
\neg false, s \Longrightarrow true, s  
\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 false, s  \quad  \mbox{ o ile }  
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}\, false\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_2, s
\mathbf{if}\, \mathbf{true} \,\mathbf{then}\, I_1 \,\mathbf{else}\, I_2, s \,\Longrightarrow\, I_2, s
</math>
</math>


Gorzej jest w przypadku instukcji pętli. Reguła mogłaby wyglądać tak:
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\,^{*} false}
\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> jeden raz:
<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 czytelnikowi.
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}\, I e \, \,\mathbf{times}  \,\,|\,\,
         \,\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ę 101.
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 B,E:StateState 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:

b::=l|e1e2|¬b|b1b2|

l::=𝐭𝐫𝐮𝐞|𝐭𝐫𝐮𝐞

e::=n|x|e1+e2|

n::=0|1|

x::=(identyfikatory)

Niech 𝐁𝐄𝐱𝐩 oznacza zbiór wyrażeń boolowskich, b𝐁𝐄𝐱𝐩. Chcemy, aby tranzycje dla wyrażeń były postaci:

e,se,s

i podobnie dla wyrażeń boolowskich:

b,sb,s

gdzie s𝐒𝐭𝐚𝐭𝐞. Dodatkowo będziemy potrzebować również tranzycji postaci:

e,sn oraz b,sl

gdzie n jest liczbą całkowitą, n𝐍𝐮𝐦, a l𝐁𝐨𝐨𝐥={𝐭𝐫𝐮𝐞,𝐭𝐫𝐮𝐞}. 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, I𝐒𝐭𝐦𝐭.

Zacznijmy od (chyba najprostszych) tranzycji dla stałych boolowskich i liczbowych:

l,sln,sn.

Podobnie jak poprzednio, zakładamy tutaj dla wygody, że 𝐍𝐮𝐦subseteq𝐄𝐱𝐩 oraz 𝐁𝐨𝐨𝐥𝐁𝐄𝐱𝐩. Pozwala nam to nie odróżniać stałych występujących w wyrażeniach a zatem pojawiających się po lewej stonie tranzycji mkrok od wartości im odpowiadających pojawiających się po prawej stronie.

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,sl1l2,sl,s o ile l=l1l2

Możemy zaniechać obliczania b2 jeśli b1 oblicza się do false. Oto odpowiednio zmodyfikowane reguły:

b1,sb'1,sb1b2,sb'1b2,s𝐭𝐫𝐮𝐞b2,s𝐭𝐫𝐮𝐞,s𝐭𝐫𝐮𝐞b2,sb2,s

Analogicznie reguły prawostronne to:

b2,sb'2,sb1b2,sb1b'2,sb1𝐭𝐫𝐮𝐞,s𝐭𝐫𝐮𝐞,sb1𝐭𝐫𝐮𝐞,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 nasze tranzycje nie posiadają teraz 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ły dla negacji:

¬𝐭𝐫𝐮𝐞,s𝐭𝐫𝐮𝐞,s¬𝐭𝐫𝐮𝐞,s𝐭𝐫𝐮𝐞,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,s𝐭𝐫𝐮𝐞,s o ile n1n2n1n2,s𝐭𝐫𝐮𝐞,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 e1 i e2.

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:

e,se,sx:=e,sx:=e,sx:=n,ss[xn]

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:

𝐢𝐟𝐭𝐫𝐮𝐞𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sI1,s𝐢𝐟𝐭𝐫𝐮𝐞𝐭𝐡𝐞𝐧I1𝐞𝐥𝐬𝐞I2,sI2,s

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

𝐰𝐡𝐢𝐥𝐞𝐭𝐫𝐮𝐞𝐝𝐨I,sI;𝐰𝐡𝐢𝐥𝐞?𝐝𝐨I,s

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

b,s*𝐭𝐫𝐮𝐞𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,sI;𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,sb,s*𝐭𝐫𝐮𝐞𝐰𝐡𝐢𝐥𝐞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. Jedno z nich oparte jest na pomyśle, aby rozwinąc pętlę 𝐰𝐡𝐢𝐥𝐞, zanim obliczymy wartość dozoru b. Jedyną reguła dla pętli 𝐰𝐡𝐢𝐥𝐞 byłaby wtedy reguła:

𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I,s𝐢𝐟b𝐭𝐡𝐞𝐧(I;𝐰𝐡𝐢𝐥𝐞b𝐝𝐨I)𝐞𝐥𝐬𝐞𝐬𝐤𝐢𝐩,s.

Dzięki temu obliczany warunek logiczny b 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:

I::=𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b|𝐟𝐨𝐫x:=e1𝐭𝐨e2𝐝𝐨I|𝐝𝐨e𝐭𝐢𝐦𝐞𝐬I|𝐝𝐨I𝐰𝐡𝐢𝐥𝐞b

Napisz semantykę małych kroków dla powyższych konstrukcji.


Rozwiązanie

Instrukcja 𝐫𝐞𝐩𝐞𝐚𝐭

Zacznijmy od pętli 𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b. Przyjrzyjmy się dwóm podejściom, które zastosowaliśmy dla pętli 𝐰𝐡𝐢𝐥𝐞 w poprzednim zadaniu. Po pierwsze rozwinięcie:

𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b,sI;𝐢𝐟b𝐭𝐡𝐞𝐧(𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b)𝐞𝐥𝐬𝐞𝐬𝐤𝐢𝐩,s.

Po drugie, spróbujmy odwołać się do * dla dozoru pętli b:

I,sI,s𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b,s𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b,s
I,ssb,s*𝐭𝐫𝐮𝐞𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b,ssI,ssb,s*𝐭𝐫𝐮𝐞𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥b,s𝐫𝐞𝐩𝐞𝐚𝐭?𝐮𝐧𝐭𝐢𝐥b,s

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 𝐝𝐨e𝐭𝐢𝐦𝐞𝐬I

Pętla 𝐝𝐨e𝐭𝐢𝐦𝐞𝐬I, w stanie s, oznacza wykonianie instrukcji I n razy, gdzie n to wartość, do której oblicza się e w stanie s. Czyli najpierw obliczmy e przy pomocy reguły:

e,se,s𝐝𝐨e𝐭𝐢𝐦𝐞𝐬I,sDe𝐭𝐢𝐦𝐞𝐬I,s

która doprowadza nas do konfiguracji:

𝐝𝐨n𝐭𝐢𝐦𝐞𝐬I,s.

Teraz jest juz łatwo:

𝐝𝐨n𝐭𝐢𝐦𝐞𝐬I,sI;𝐝𝐨n1𝐭𝐢𝐦𝐞𝐬I,s o ile n>0

𝐝𝐨n𝐭𝐢𝐦𝐞𝐬I,ss o ile n=0.


Instrukcja 𝐟𝐨𝐫

W przypadku pętli 𝐟𝐨𝐫 przyjmijmy, że wartości wyrażeń e1 i e2 obliczane są przed pierwszą iteracją pętli. Dodatkowo ustalmy, że e1 będzie obliczone jako pierwsze. Czyli:

e1,se'1,s𝐟𝐨𝐫x=e1𝐭𝐨e2𝐝𝐨I,s𝐟𝐨𝐫x=e'1𝐭𝐨e2𝐝𝐨I,s
e2,se'2,s𝐟𝐨𝐫x=n𝐭𝐨e2𝐝𝐨I,s𝐟𝐨𝐫x=n𝐭𝐨e'2𝐝𝐨I,s

Zatem zakres zmiennej x mamy już obliczony, tzn. jesteśmy w konfiguracji

𝐟𝐨𝐫x=n1𝐭𝐨n2𝐝𝐨I,s.

Dalsze reguły mogą być podobne do reguł dla pętli 𝐝𝐨n𝐭𝐢𝐦𝐞𝐬I:

𝐟𝐨𝐫x=n1𝐭𝐨n2𝐝𝐨I,sx:=n1;I;𝐟𝐨𝐫x=n1+1𝐭𝐨n2𝐝𝐨I,s o ile n1n2
𝐟𝐨𝐫x=n1𝐭𝐨n2𝐝𝐨I,s𝐬𝐤𝐢𝐩,s o ile n1>n2


Zauważmy, wartość zmiennej x po zakończeniu pętli wynosi n2. Ponieważ nie zostało wyspecyfikowane jaka powinna być wartość tej zmiennej, taką semantykę uważamy za poprawną.


Semantykę dla 𝐝𝐨I𝐰𝐡𝐢𝐥𝐞b pozostawiamy Czytelnikowi jako proste ćwiczenie. Oczywiście minimalistyczne rozwiązanie to

𝐝𝐨I𝐰𝐡𝐢𝐥𝐞b,s𝐫𝐞𝐩𝐞𝐚𝐭I𝐮𝐧𝐭𝐢𝐥¬b,s


Kalkulator binarny

Zadanie 1

Rozważmy następujący język wyrażeń (liczby binarne z dodawaniem):

e::=ϵ|e0|e1|e1+e2

ϵ oznacza słowo puste, czyli np. ϵ1011 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:

e10+e20(e1+e2)0
e10+e21(e1+e2)1
e11+e20(e1+e2)1

Ale co zrobić z przeniesieniem?

e11+e21?

Podstawowy pomysł polega na potraktowaniu przeniesienia jak dodatkowego składnika:

e11+e21((e1+e2)+ϵ1)0

Zauważmy, że w składni dopuszcza się dowolne przeplatanie operatora dodawania i bitów 0,1. Tę dowolność wykorzystaliśmy właśnie w regułach powyżej. Gdyby nasz język ograniczyć tylko do składni

e::=b|e1+e2

b::=ϵ|b0|b1

(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ł:

ϵ+e0e0ϵ+e1e1

oraz ich odpowiedników:

e0+ϵe0e1+ϵe1.

Niestety, nie możemy użyć reguły przemienności:

e1+e2e2+e1

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:

e1e'1e1+e2e'1+e2e2e'2e1+e2e1+e'2eee0e0eee1e1


Zadanie 2

Rozszerzmy składnię o jeden symbol p oznaczający przepełnienie:

e::=ϵ|p|e0|e1|e1+e2.

Na przykład p101 oznacza tę samą liczbę co ϵ101, 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. (e1+e2)0, 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 p100). 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:

p+e0e0p+e1e1e0+pe0e1+pe1.

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

ϵ+e0e0ϵ+e1e1e0+ϵe0e1+ϵe1.

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:

p+pp.

Ale co należy zrobić, gdy tylko jeden ze składników odnotował przepełnienie? p+ϵ? 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:

p+ϵϵϵ+pϵ.

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

e11+e21((e1+e2)+p1)0.

Nowy sztuczny składnik p1 zawiera jakby na wszelki wypadek informacje o potencjalnym przepełnieniu. Jeśli którykolwiek z pozostałych składników e1 i e2 ma przynajmniej jedną cyfrę, to p zostanie usunięte. W przeciwnym wypadku symbol p 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 𝐟𝐨𝐫x=e1𝐭𝐨e2doI, w której wyrażenie e2 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

x1:=e1||||xn:=en

polegającego na obliczeniu najpierw wartości wyrażeń e1,,en a następnie na podstawieniu nowych wartości na zmienne x1,,xn.