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 + { * } + + } % .