Semantyka i weryfikacja programów/Ćwiczenia 2

From Studia Informatyczne

Spis treści

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 B, E : State \to State 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 \,\,|\,\,         e_1 \leq e_2  \,\,|\,\,         \neg b  \,\,|\,\,         b_1  \land  b_2   \,\,|\,\, \ldots

l \, ::= \,\,         \mathbf{true}    \,\,|\,\,         \mathbf{false}

e \,  ::= \,\,           n  \,\,|\,\,          x  \,\,|\,\,         e_1 + e_2  \,\,|\,\, \ldots

n \, ::= \,\, 0 \,\,|\,\, 1 \,\,|\,\, \ldots

x \, ::= \,\, \ldots \, (identyfikatory) \, \ldots

Niech \mathbf{BExp} oznacza zbiór wyrażeń boolowskich, b \in \mathbf{BExp}.

Chcemy, aby tranzycje dla wyrażeń były postaci:

e, s \,\Longrightarrow\, e', s

i podobnie dla wyrażeń boolowskich:

b, s \,\Longrightarrow\, b', s

gdzie s \in \mathbf{State}.

Dodatkowo będziemy potrzebować również tranzycji postaci:

e, s \,\Longrightarrow\, n \quad \quad \mbox{ oraz } \quad \quad b, s \,\Longrightarrow\, l

gdzie n \in jest liczbą całkowitą, n \in \mathbf{Num}, a l \in \mathbf{Bool} = \{ \mathbf{true}, \mathbf{false} \}. Formalnie, zbiór konfiguracji dla semantyki całego języka Tiny to

( (\mathbf{Exp} \, \cup \, \mathbf{BExp} \, \cup \, \mathbf{Stmt}) \times \mathbf{State} ) \, \bigcup \,  \mathbf{Num} \, \cup \, \mathbf{Bool} \, \cup \, \mathbf{State}

a konfiguracje końcowe to \mathbf{State}, aczkolwiek konfiguracje ze zbioru \mathbf{Num} \, \cup \, \mathbf{Bool} pełnią podobną rolę dla wyrażeń arytmetycznych i boolowskich jako konfiguracje końcowe dla instrukcji.

Przypomnijmy, że \mathbf{Stmt} oznacza zbiór instrukcji, I \in \mathbf{Stmt}.

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

l, s \,\Longrightarrow\, l \quad \quad n, s \,\Longrightarrow\, n.

Podobnie jak poprzednio, zakładamy tutaj dla wygody, że \mathbf{Num} \subseteq \mathbf{Exp} oraz \mathbf{Bool} \subseteq \mathbf{BExp}.

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 b_1 \land b_2.

Ponieważ opisujemy teraz pojedyncze (małe) kroki składające się na wykonanie programu, musimy podać w jakiej kolejności będą się obliczać b_1 i b_2. Zacznijmy od strategii lewostronnej:

\frac{b_1, s \,\Longrightarrow\, b'_1, s} {b_1 \land b_2, s \,\Longrightarrow\, b'_1 \land b_2, s} \quad \quad \frac{b_2, s \,\Longrightarrow\, b'_2, s} {l_1 \land b_2, s \,\Longrightarrow\, l_1 \land b_2, s} \quad \quad l_1 \land l_2, s \,\Longrightarrow\, l, s    \mbox{ o ile } l = l_1 \land l_2

Możemy zaniechać obliczania b_2 jeśli b_1 oblicza się do false.

Oto odpowiednio zmodyfikowane reguły:

\frac{b_1, s \,\Longrightarrow\, b'_1, s} {b_1 \land b_2, s \,\Longrightarrow\, b'_1 \land b_2, s} \quad \quad \mathbf{false} \land b_2, s \,\Longrightarrow\, \mathbf{false}, s \quad \quad \mathbf{true}  \land b_2, s \,\Longrightarrow\, b_2, s.

Analogicznie reguły prawostronne to:

\frac{b_2, s \,\Longrightarrow\, b'_2, s} {b_1 \land b_2, s \,\Longrightarrow\, b_1 \land b'_2, s} \quad \quad b_1 \land \mathbf{false}, s \,\Longrightarrow\, \mathbf{false}, s \quad \quad b_1 \land \mathbf{true}, s \,\Longrightarrow\, b_1, s.

Reguły równoległe otrzymujemy jako sumę reguł lewo- i prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie wyrażeń b_1 i b_2 odbywa się teraz w tzw. "przeplocie": Pojedynczy krok polega na wykonaniu jednego kroku obliczenia b_1 albo jednego kroku obliczenia b_2.

Zwróćmy też uwagę, że nasze tranzycje nie posiadają teraz własności determinizmu: wyrażenie b_1 \land b_2 może wyewoluować w pojedyńczym kroku albo do b'_1 \land b_2 albo do b_1 \land b'_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:

\neg \mathbf{true}, s \,\Longrightarrow\, \mathbf{false}, s \quad \quad \quad \neg \mathbf{false}, s \,\Longrightarrow\, \mathbf{true}, s  \quad \quad \quad \frac{b, s \,\Longrightarrow\, b', s}      {\neg b, s \,\Longrightarrow\, \neg b', s}

Reguły dla e_1 \leq e_2 są następujące:

\frac{e_1, s \,\Longrightarrow\, e'_1, 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}

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.

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.

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:

\frac{e, s \,\Longrightarrow\, e', s}      {x := e, s \,\Longrightarrow\, x := e', s} \quad \quad {x := n, s \,\Longrightarrow\, s[x \mapsto n]}

Rozważmy teraz instrukcję warunkową i instrukcję pętli.

Najpierw obliczamy wartość dozoru:

\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} \quad \quad \frac{b, s \,\Longrightarrow\, b', s} {\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow\, \mathbf{while}\, b' \,\mathbf{do}\, I, s}

a gdy dozór jest już obliczony, podejmujemy decyzję.

W przypadku instrukcji warunkowej reguły są oczywiste:

\mathbf{if}\, \mathbf{true} \,\mathbf{then}\, I_1 \,\mathbf{else}\, I_2, s \,\Longrightarrow\, I_1, s \quad \quad \mathbf{if}\, \mathbf{false} \,\mathbf{then}\, I_1 \,\mathbf{else}\, I_2, s \,\Longrightarrow\, I_2, s

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

\mathbf{while}\, \mathbf{true} \,\mathbf{do}\, I, s \,\Longrightarrow\,  I;\, \mathbf{while}\, ? \,\mathbf{do}\, I, s

ale nie wiemy już, jaki był dozór pętli (widzimy tylko wynik obliczenia tego dozoru w stanie s, czyli \mathbf{true}).

Możemy odwołać się do tranzytywnego domknięcia relacji \,\Longrightarrow\, (czyli w zasadzie do semantyki dużych kroków):

\frac{b, s \,\Longrightarrow\,^{*} \mathbf{true}} {\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow\,   I;\, \mathbf{while}\, b \,\mathbf{do}\, I, s} \quad \quad \frac{b, s \,\Longrightarrow\,^{*} \mathbf{false}} {\mathbf{while}\, b \,\mathbf{do}\, I, s \,\Longrightarrow\, s}

Istnieją inne możliwe rozwiązania w stylu małych kroków.

Jedno z nich oparte jest na pomyśle, aby "rozwinąć" pętlę \mathbf{while}\, zanim obliczymy wartość dozoru b. Jedyną reguła dla pętli \mathbf{while}\, byłaby wtedy reguła:

\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.

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.


Ćwiczenie 2

Rozszerzmy język Tiny o następujące dobrze znane konstrukcje iteracji:

I \, ::= \,\,         \mathbf{repeat}\, I \,\mathbf{until}\, b  \,\,|\,\,         \mathbf{for}\, x := e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I  \,\,|\,\,         \,\mathbf{do}\, e \,\mathbf{times}\, I  \,\,|\,\,         \,\mathbf{do}\, I \,\mathbf{while}\, b

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


Rozwiązanie

Instrukcja \mathbf{repeat}\,


Zacznijmy od pętli \mathbf{repeat}\, I \,\mathbf{until}\, b.

Przyjrzyjmy się dwóm podejściom, które zastosowaliśmy dla pętli \mathbf{while}\, w poprzednim zadaniu. Po pierwsze, rozwinięcie:

\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.

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

\frac{I, s \,\Longrightarrow\, I', s'}      {\mathbf{repeat}\, I \,\mathbf{until}\, b, s \,\Longrightarrow\, \mathbf{repeat}\, I' \,\mathbf{until}\, b, s'}

\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'}

Okazuje się, że jest jeszcze gorzej niż w przypadku pętli \mathbf{while}\,: nie pamiętamy już, jaka była instrukcja wewnętrzna naszej pętli!

Czyli takie podejście jest teraz nieskuteczne.


Instrukcja \,\mathbf{do}\, e \,\mathbf{times}\, I

Pętla \,\mathbf{do}\, e \,\mathbf{times}\, I, w stanie s, oznacza wykonanie 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:

\frac{e, s \,\Longrightarrow\, e', s}      {\,\mathbf{do}\, e \,\mathbf{times}\, I, s \,\Longrightarrow\, D e' \,\mathbf{times}\, I, s}

która doprowadza nas do konfiguracji:

\,\mathbf{do}\, n \,\mathbf{times}\, I, s.

Teraz jest już łatwo:

\,\mathbf{do}\, n \,\mathbf{times}\, I, s \,\Longrightarrow\, I; \,\mathbf{do}\, n{-}1 \,\mathbf{times}\, I, s  \quad \mbox{ o ile } n > 0

\,\mathbf{do}\, n \,\mathbf{times}\, I, s \,\Longrightarrow\, s  \quad \mbox{ o ile } n = 0.


Instrukcja \mathbf{for}\,

W przypadku pętli \mathbf{for}\, przyjmijmy, że wartości wyrażeń e_1 i e_2 obliczane są przed pierwszą iteracją pętli.

Dodatkowo ustalmy, że np. e_1 będzie obliczone jako pierwsze. Następnie podstawiamy wartość e_1 na zmienną x. Czyli:

\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}

\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}

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

\mathbf{for}\, x = n_1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s.

Dalsze reguły mogą być podobne do reguł dla pętli \,\mathbf{do}\, n \,\mathbf{times}\, I:

\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

\mathbf{for}\, x = n_1 \,\mathbf{to}\, n_2 \,\mathbf{do}\, I, s \,\Longrightarrow\, s \quad \mbox{ o ile } n_1 > n_2

Zauważmy, wartość zmiennej x po zakończeniu pętli wynosi albo n_2, albo pozostaje niezmieniona, o ile nie była ona zmieniana wewnątrz instrukcji I.

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

\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. Czy semantyka jest taka sama? (Rozważ sytuację, gdy zmienna x jest zmieniana przez instrukcję I.)

Pytanie: a gdybyśmy jednak zażądali przywrócenia na koniec wartości zmiennej x sprzed pętli?

Jak należałoby zmienić nasze reguły?


Semantykę dla \,\mathbf{do}\, I \, \mathbf{while}\, b pozostawiamy Czytelnikowi jako proste ćwiczenie. Oczywiście minimalistyczne rozwiązanie to

\,\mathbf{do}\, I \,\mathbf{while}\, b, s \,\Longrightarrow\, \mathbf{repeat}\, I \,\mathbf{until}\, \neg b, s.


Kalkulator binarny

Ćwiczenie 3

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

e \, ::= \,\,       \epsilon  \,\,|\,\,       e 0  \,\,|\,\,       e 1  \,\,|\,\,       e_1 + e_2

\epsilon oznacza słowo puste, czyli np. \epsilon 1 0 1 1 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:

e_1 0 + e_2 0 \,\Longrightarrow\, (e_1 + e_2) 0

e_1 0 + e_2 1 \,\Longrightarrow\, (e_1 + e_2) 1

e_1 1 + e_2 0 \,\Longrightarrow\, (e_1 + e_2) 1

Ale co zrobić z przeniesieniem?

e_1 1 + e_2 1 \,\Longrightarrow\, ?

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

e_1 1 + e_2 1 \,\Longrightarrow\, ((e_1 + e_2) + \epsilon 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  \,\,|\,\,       e_1 + e_2

b \, ::= \,\,       \epsilon  \,\,|\,\,       b 0  \,\,|\,\,       b 1

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

\epsilon + \epsilon \,\Longrightarrow\, \epsilon.

Gdy jeden ze składników ma mniej cyfr niż drugi, potrzebujemy reguł:

\epsilon + e 0 \,\Longrightarrow\, e 0 \quad \quad \epsilon + e 1 \,\Longrightarrow\, e 1

oraz ich odpowiedników:

e 0 + \epsilon \,\Longrightarrow\, e 0 \quad \quad e 1 + \epsilon \,\Longrightarrow\, e 1.

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

e_1 + e_2 \,\Longrightarrow\, e_2 + e_1

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:

\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


Ćwiczenie 4

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

e \, ::= \,\,       \epsilon  \,\,|\,\,       p  \,\,|\,\,       e 0  \,\,|\,\,       e 1  \,\,|\,\,       e_1 + e_2 .

Na przykład p 1 0 1 oznacza tę samą liczbę, co \epsilon 1 0 1, 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ń e w składni ograniczonej:

e \, ::= \,\,       b  \,\,|\,\,       e_1 + e_2

b \, ::= \,\,       \epsilon  \,\,|\,\,       b 0  \,\,|\,\,       b 1.

reprezentujących sumę liczb binarnych.


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. (e_1 + e_2) 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 p 1 0 0).

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 + 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.

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

\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.

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 + p \,\Longrightarrow\, p.

Ale co należy zrobić, gdy tylko jeden ze składników odnotował przepełnienie? p + \epsilon \,\Longrightarrow\, ?

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 + \epsilon \,\Longrightarrow\, \epsilon \quad \quad \epsilon + p \,\Longrightarrow\, \epsilon.

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

e_1 1 + e_2 1 \,\Longrightarrow\, ((e_1 + e_2) + p 1) 0.

Nowy sztuczny składnik p 1 zawiera jakby na wszelki wypadek informacje o potencjalnym przepełnieniu.

Jeśli którykolwiek z pozostałych składników e_1 i e_2 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

Ć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 \mathbf{for}\, x = e_1 \,\mathbf{to}\, e_2 \,\mathbf{do}\, I, w której wyrażenie e_2 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:

x_1 := e_1 || \ldots || x_n := e_n

polegającego na obliczeniu najpierw wartości wyrażeń e_1, \ldots, e_n, a następnie na podstawieniu tych wartości na zmienne x_1, \ldots, x_n.