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 → VAR declarations START commands END declarations → declarations identifier | ε commands → commands command | ε command → identifier := expression; | IF condition THEN commands ELSE commands END | WHILE condition DO commands END | READ identifier; | PRINT value; expression → value | value + value | value - value | value * value | value / value | value % value condition → value = value | value != value | value < value | value > value | value <= value | value >= value value → identifier | num
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 | ||
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 | ||
ADDC i | a ← a+i | k ← k+1 | ||
SUBC i | a ← a-i | k ← k+1 | ||
ZERO | a ← 0 | 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++.
VAR a b c START READ a; READ b; IF a<b THEN c:=a; a:=b; b:=c; ELSE END WHILE b>0 DO c:=a%b; a:=b; b:=c; END PRINT a; END
READ 7 READ 8 LOAD 8 SUB 7 JZ 12 LOAD 7 STORE 9 LOAD 8 STORE 7 LOAD 9 STORE 8 JUMP 12 LOAD 8 SUBC 0 JZ 48 LOAD 8 JZ 42 STORE 1 LOAD 7 STORE 0 ZERO ADDC 1 ADD 0 SUB 1 JZ 41 LOAD 1 STORE 2 ZERO ADDC 1 ADD 0 SUB 2 SUB 2 JZ 37 LOAD 2 ADD 2 STORE 2 JUMP 27 LOAD 0 SUB 2 STORE 0 JUMP 20 LOAD 0 STORE 9 LOAD 8 STORE 7 LOAD 9 STORE 8 JUMP 12 PRINT 7 HALT
Dla następującego programu
VAR n m reszta potega dzielnik START READ n; dzielnik:=2; m:=4; WHILE n>=m DO potega:=0; reszta:=n%dzielnik; WHILE reszta=0 DO n:=n/dzielnik; potega:=potega+1; reszta:=n%dzielnik; END IF potega>0 THEN PRINT dzielnik; PRINT potega; ELSE dzielnik:=dzielnik+1; m:=dzielnik*dzielnik; END END IF n!=1 THEN PRINT n; PRINT 1; 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: 4261288, wykorzystano rejestrów: 11).
Uruchamianie programu. ? 12345678901 857 1 14405693 1 Skończono program (wykonano kroków: 5454974, wykorzystano rejestrów: 11).
Uruchamianie programu. ? 12345678903 3 1 4115226301 1 Skończono program (wykonano kroków: 124295771, wykorzystano rejestrów: 11).
Maciej.Gebala@pwr.edu.pl