Używając BISON-a i LEX-a napisz kompilator prostego języka imperatywnego do kodu maszyny rejestrowej. Specyfikacja języka i maszyny jest zamieszczona poniżej. Kompilator powinien sygnalizować miejsce i rodzaj błędu (np. druga deklaracja zmiennej, użycie niezadeklarowanej zmiennej, ...), a w przypadku braku błędów zwracać kod na maszynę rejestrową. Kod wynikowy powinien wykorzystywać jak najmniej rejestrów i wykonywać się jak najszybciej (w miarę optymalnie, zobacz przykład na końcu strony).
program → LET declarations IN commands END declarations → declarations identifier | declarations identifier = num | ε commands → commands command | ε command → identifier := expression; | IF condition THEN commands ELSE commands END | WHILE condition DO commands END | READ identifier; | PRINT identifier; expression → identifier | identifier + identifier | identifier - identifier | identifier * identifier | identifier / identifier | identifier % identifier condition → identifier = identifier | identifier != identifier | identifier < identifier | identifier > identifier | identifier <= identifier | identifier >= identifier
Język powinien być zgodny z powyższą gramatyką i spełniać następujące warunki:
Maszyna rejestrowa składa się ze specjalnego rejestru a, licznika rozkazów k oraz zbioru rejestrów ri, dla i=0,1,2,.... Maszyna pracuje na liczbach naturalnych (wynikiem odejmowanie większej liczby od mniejszej jest 0). Program maszyny składa się z ciągu rozkazów, który niejawnie numerujemy od zera. W kolejnych krokach wykonujemy zawsze rozkaz o numerze k aż napotkamy instrukcję HALT. Początkowa zawartość rejestrów jest nieokreślona. Poniżej jest lista rozkazów wraz z ich interpretacją:
READ i | pobiera liczbę naturalną i zapisuje w rejestrze ri | k ← k+1 | ||
PRINT i | wyświetla zawartość rejestru ri | k ← k+1 | ||
FIX i | a ← i | k ← k+1 | ||
LOAD i | a ← ri | k ← k+1 | ||
STORE i | ri ← a | k ← k+1 | ||
ADD i | a ← a+ri | k ← k+1 | ||
SUB i | a ← a-ri | k ← k+1 | ||
JUMP i | k ← i | |||
JZ i | jeśli a=0 to k ← i, w p.p. k ← k+1 | |||
JGE i | jeśli a>0 to k ← i, w p.p. k ← k+1 | |||
HALT | zatrzymaj program |
Przejście do nieistniejącego rozkazu jest traktowane jako błąd.
Prosty interpreter kodu MR w C++.
LET a b c d=0 IN READ a; READ b; IF a<b THEN c:=a; a:=b; b:=c; ELSE END WHILE b>d DO c:=a%b; a:=b; b:=c; END PRINT a; END
FIX 0 STORE 6 READ 3 READ 4 LOAD 4 SUB 3 JZ 14 LOAD 3 STORE 5 LOAD 4 STORE 3 LOAD 5 STORE 4 LOAD 4 SUB 6 JZ 33 LOAD 4 JZ 27 FIX 1 ADD 3 STORE 0 SUB 4 JGE 20 FIX 1 STORE 1 LOAD 0 SUB 1 STORE 5 LOAD 4 STORE 3 LOAD 5 STORE 4 JUMP 13 PRINT 3 HALT
Dla następującego programu
LET n m=4 reszta potega dzielnik=2 zero=0 jeden=1 IN READ n; WHILE n>=m DO potega:=zero; reszta:=n%dzielnik; WHILE reszta=zero DO n:=n/dzielnik; potega:=potega+jeden; reszta:=n%dzielnik; END IF potega>zero THEN PRINT dzielnik; PRINT potega; ELSE dzielnik:=dzielnik+jeden; m:=dzielnik*dzielnik; END END IF n!=jeden THEN PRINT n; PRINT jeden; ELSE END END
kod wynikowy powinien działać w czasie porównywalnym do poniższych wyników
Uruchamianie programu. ? 1234567890 2 1 3 2 5 1 3607 1 3803 1 Skończono program (wykonano kroków: 3867104, wykorzystano rejestrów: 12).
Uruchamianie programu. ? 12345678901 857 1 14405693 1 Skończono program (wykonano kroków: 4942416, wykorzystano rejestrów: 12).
Uruchamianie programu. ? 12345678903 3 1 4115226301 1 Skończono program (wykonano kroków: 112938143, wykorzystano rejestrów: 12).
Maciej.Gebala@pwr.edu.pl