Maszyna 2-stosowa

advertisement
Maszyna 2-stosowa
Wstęp
Należy wykonać intepreter prostej maszyny stosowej, w której kod programu zapisany jest jako
zmodyfikowana ONP (odwrotna notacja polska), rozszerzona o pętle i dodatkowe operacje takie
jak operacje wejścia/wyjścia konsoli, operacje min/max i duplikację.
Przykładowo, wyrażenie:
(2 + 3*4)*5
Możemy przekształcić do ONP:
234*+5*
Która może być interpretowana jako ciąg instrukcji maszyny stosowej, tj. liczba oznacza
operację odłożenia jej wartości na stos, natomiast operacja (jeśli dwuargumentowa) zdjęcie
dwóch ostatnich liczb ze stosu, wykonanie na nich działania i odłożenie wyniku na stos.
Przykładowo zapis "2 3 4 * +" interpretujemy jako odłożenie 2, 3 i 4 na stos, następnie zdjęcie ze
stosu 4 i 3 oraz odłożenie wyniku mnożenia, czyli 12 na stos (na stosie znajduje się w tym
momencie 2 i 12), następnie zdjęcie ze stosu 12 i 2 oraz odłożenie wyniku dodawania, czyli 14
na stos.
Do zapisu wprowadzimy również pętle, używając klamerek (jako instrukcji), przykładowo:
(2+3)*(2+3)*(2+3)
Możemy zapisać jako ciąg instrukcji (liczba poprzedzająca klamerkę to licznik pętli):
3{23+}**
Aby zinterpretować taki zapis będziemy potrzebować dwóch stosów, jeden nazwiemy stosem
obliczeniowym, drugi stosem liczników pętli.
Wszystkie liczby będziemy odkładać na stos obliczeniowy. Jeśli pojawi się początek pętli,
zdejmiemy ze stosu obliczeniowego liczbę i przepiszemy do stosu liczników pętli, następnie po
dotarciu do końca pętli, będziemy zmniejszać ostatni licznik na stosie i wykonywać skok do
początku pętli, a jeśli dojdzie do zera, zdejmiemy go ze stosu i przejdziemy do dalszych
instrukcji.
Do naszej maszyny stosowej dodamy jeszcze znacznik końca ciągu instrukcji "." oraz instrukcję
czytania wejścia & i instrukcję wypisywania wyjścia %, przykładowo:
&2+%.
10
W powyższym programie instrukcja & zczyta z konsoli pierwszą liczba po kropce (czyli 10), doda
do niej 2 i wypisze wynik (ze stosu obliczeń), czyli 12.
Specyfikacja instrukcji rozszerzonej maszyny stosowej
liczba (z przedziału -10000 .. 10000) - odłożenie liczby całkowitej na stos obliczeń
. - kropka, czyli koniec ciągu instrukcji
$ - duplikacja, czyli odczytanie ostatniego elementu na stosie obliczeń i ponowne odłożenie go
& - odczytanie liczby całkowitej z konsoli (scanf) i odłożenie jej na stos obliczeń
% - zdjęcie ostatniej liczby ze stosu obliczeń i wypisanie jej na konsolę (printf)
< - zdjęcie dwóch ostatnich liczb ze stosu obliczeń, wyznaczenie minimum i odłożenie wyniku na
ten stos
> - zdjęcie dwóch ostatnich liczb ze stosu obliczeń, wyznaczenie maksimum i odłożenie wyniku
na ten stos
+ - zdjęcie dwóch ostatnich liczb ze stosu obliczeń, odłożenie wyniku dodawania na ten stos
* - zdjęcie dwóch ostatnich liczb ze stosu obliczeń, odłożenie wyniku mnożenia na ten stos
{ - zdjęcie ze stosu obliczeń ostatniej liczby i odłożenie jej na stos liczników pętli (można
założyć, że licznik będzie zawsze większy od zera, czyli pętla wykona się przynajmniej 1 raz),
} - zmniejszenie ostatniej liczby na stosie liczników pętli o 1, a następnie jeśli licznik ten jest
równy zero - zdjęcie tej liczby ze stosu liczników pętli, w innym wypadku - skok do pierwszej
instrukcji po klamerce (zapamiętanej podczas wczytywania instrukcji),
Dla ułatwienia wszystkie operacje dwuargumentowe są przemienne (nie ma operacji
odejmowania i dzielenia).
Input
Na wejściu pierwsza liczba to liczba testów T.
Dla każdego testu w kolejnych liniach:
Instrukcje maszyny w rozszerzonej notacji ONP zakończone "." (maksymalnie 1000 instrukcji)
Ciąg liczb podanych na wejście programu maszyny lub pusta linia, jeśli brak wejścia
Rozmiar stosów i liczba instrukcji nie przekroczy 1000 elementów (można używać statycznych
tablic)
Output
Dla każdego testu w kolejnych liniach:
Ciąg liczb wyjściowych programu (wypisywanych komendą %)
Wskazówka (ważne!)
Instrukcje możemy pamiętać w tablicy instrukcji, której elementami będą struktury: kod operacji i
argument (liczba).
Kod operacji w formie znaku tj. } * + $ itp.., oraz specjalna operacja na odłożenie liczby, np. 'p' od
push.
Sposób wczytywania tablicy instrukcji (atoi = zamiana tekstu na liczbę):
char buf[100];
while(1) {
scanf("%s",buf);
if (buf[0]=='.') {
...zapisz instrukcje konca do tablicy instrukcji
break;
}
if (buf[0]=='*') zapisz operacje do tablicy instrukcji...
.... zapisz inne operacje
else {
zapisz operację odłożenia na stos liczby = atoi(buf);
}
}
Dla operacji "}" można przechowywać w argumencie początek pętli (indeks pierwszej instrukcji
w tablicy instrukcji). Podczas wczytywania klamerek można skorzystać z dodatkowego stosu, tj.
po odczytaniu z wejścia "{" musimy dodać tą instrukcję do tablicy instrukcji, odłożyć na
dodatkowy stos klamerek aktualną ilość instrukcji i po odczytaniu z wejścia "}" zdjąć z tego stosu
początek pętli i zapisać w argumencie ("}" stanie się instrukcją skoku).
Polecam implementowanie stosów jako zwykłe tablice z indeksami wierzchołka stosu, czyli
ilością odłożonych elementów.
Example
Input:
2
0&{&> }%.
3 10 30 20
0 & { $ 1 + } $ -1 + { * } % .
5
Output:
30
120
Dodatkowe przykłady:
Program obliczania maksimum N liczb naturalnych (wprowadzamy ilość liczb i kolejne liczby z konsoli):
0&{&> }%.
Program obliczania sumy kwadratów N liczb całkowitych:
0&{&$*+}%.
Program obliczania silnii liczby naturalnej (większej od 1):
0 & { $ 1 + } $ -1 + { * } % .
Program obliczania sumy silnii N liczb naturalnych (wiekszych od 1):
0 & { 0 & { $ 1 + } $ -1 + { * } + + } % .
Download