Kompilator wyrażeń arytmetycznych
-- Sebastian Pawlak, 2003.
Program dla wyrażeń arytmetycznych
(opisanych zadaną gramatyką) generuje optymalny (minimalna ilość
wykorzystanej pamięci) kod "asemblera".
Mój program działa według następującego, ogólnego schematu:
1. Wczytaj wyrażenie.
2. Skonwertuj wyrażenie do Odwrotnej Notacji Polskiej.
3. Rozbij wyrażenie na wyrażenia cząstkowe (np. x0=a*b, x0=3+x0).
4. Stwórz tabelę pomocną przy optymalizacji.
5. Optymalizuj wyrażenia na podstawie tabeli.
6. Generuj kod w "asemblerze" (dostępne mnemoniki: MOV, ADD, MUL).
Warta uwagi jest funkcja konwertująca wyrażenie w notacji nawiasowej do
wyrażenia w Odwrotnej Notacji Polskiej. Oto algorytm konwersji wykorzystujący
stos. Uwaga: "znacznik" to spacja.
Krok 1 - Jeżeli nie ma więcej znaków w ciągu wejściowym, to realizuj krok 6;
w przeciwnym razie przeczytaj następny element (nawias, operator lub
argument) i nazwij go x.
Krok 2 - Jeżeli x jest argumentem, to zapisz go w ciągu wyjściowym, umieść po
nim znacznik i przejdź do realizacji kroku 1.
Krok 3 - Jeżeli x jest nawiasem otwierającym, wpisz go na stos i realizuj krok 1.
Jeżeli x jest nawiasem zamykającym, przejdź do realizacji kroku 4,
a w przeciwnym razie - kroku 5.
Krok 4 - Jeżeli na wierzchu stosu nie ma nawiasu otwierającego, to wycofaj ten
obiekt ze stosu wpisując go do ciągu wyjściowego, a zaraz za nim wpisz
znacznik i przejdź do wykonywania kroku 4. Jeżeli na wierzchołku stosu
jest nawias otwierający, to wycofaj go ze stosu i przejdź do realizacji
kroku 1.
Krok 5 - W przypadku gdy priorytet elementu wierzchołkowego stosu jest większy
lub równy priorytetowi x, wycofaj element z wierzchołka stosu, wpisz go
do ciągu wyjściowego, a potem wpisz za nim znacznik i powtórz krok 5.
W przeciwnym razie połóż x na wierzchołek stosu i wróć do wykonywania
kroku 1.
Krok 6 - Opróżnij stos po jednym elemencie wpisując każdy z nich do ciągu
wyjściowego i wstawiając między nimi znaczniki i to już koniec.
Założenia:
1. Wyrażenie jest postaci i:=wyrażenie. "i" to identyfikator (jedna wielka lub
mała litera). "wyrażenie" to wyrażenie artymetyczne bez spacji, np.
a*(b+23). Zmiennych, używanych w wyrażeniu, tyczą się te same zasady co
identyfikatorów.
2. Zakładam, że wejściowe wyrażenie jest poprawne.
3. Dostępne są operatory "*" (iloczyn) i "+" (suma). Iloczyn ma wyższy
priorytet niż suma.
Po uruchomieniu programu należy wpisać wyrażenie arytmetyczne.
Można także wywołać program przekierowując na niego strumień:
echo "i:=a+b" | ./a.out
Kod programu w plikach main.c, stos.c i stos.h
Oto przykładowy wynik pracy z programem:
Podaj wyrazenie arytmetyczne bez spacji (np. i:=a+b*(2+c)): Zmienne moga miec dlugosc jednego znaku (litera duza lub mala). i:=a+a+2*(a+b)+b*b Wynik konwersji do Odwrotnej Notacji Polskiej: a a + 2 a b + * + b b * + Wynik rozbicia na wyrazenia czastkowe: x0 = a + a x1 = b + a x2 = x1 * 2 x3 = x2 + x0 x4 = b * b x5 = x4 + x3 Tabela optymalizacji: x0 x1 x2 x3 x4 x5 0 - - - - - - 1 v - - - - - 2 v v - - - - 3 v - v - - - 4 - - - v - - 5 - - - v v - -- - - - - - v Wynik optymalizacji: x0 = a + a x1 = b + a x1 = x1 * 2 x0 = x1 + x0 x1 = b * b x0 = x1 + x0 Generowanie kodu (MNEMONIK arg1, arg2 <=> arg2 op= arg1): MOV a, x0 ADD a, x0 MOV b, x1 ADD a, x1 MUL 2, x1 ADD x1, x0 MOV b, x1 MUL b, x1 ADD x1, x0 MOV x0, i
Kod źródłowy pliku "stos.h":
/* Implementacja stosu. */ #ifndef STOS_H #define STOS_H void push(unsigned long int); unsigned long int pop(void); int ileElementow(void); #endif
Kod źródłowy pliku "stos.c":
/* Implementacja stosu. */ #include <stdio.h> #include <malloc.h> #include <string.h> #include "stos.h" #define MAX_STOS 1024 /* maksymalny rozmiar stosu */ unsigned long int stos[MAX_STOS]; int sp = 0; /* wskaznik kolejnego wolnego miejsca na stosie */ /* Wstawianie elementu na stos. */ void push(unsigned long int element) { if (sp < MAX_STOS) stos[sp++] = element; else printf("push: Przepelnienie stosu!\n"); } /* Pobieranie elementu ze stosu. */ unsigned long int pop(void) { if (sp > 0) return stos[--sp]; return 0; } /* Zwraca liczbe elementow na stosie */ int ileElementow(void) { return sp; }
Kod źródłowy pliku "main.c":
/************************************* * Kompilator wyrazen arytmetycznych * * z optymalizacja. * * Sebastian Pawlak, s1222 * *************************************/ /* Identyfikator i wszystkie zmienne moga miec dlugosc jednego * znaku (litery duze lub male). * Wartosci liczbowe musza byc naturalne. * Rozroznia sie operatory: * iloczyn, + suma * Dwa najstarsze bity liczby sa zarezerwowane na flagi - tak * wiec max wartosc liczby wynosi 2^(8*sizeof(unsigned long)-2). */ #include <stdio.h> #include <string.h> /* dla strlen() */ #include <stdlib.h> /* dla atol() */ #include <ctype.h> /* dla isalnum() */ #include "stos.h" #define MAX 1024 /* maksymalna dlugosc wyrazenia (OdwNotPl) w znakach */ void konwerterONP(char *, char *); int wyrazeniaCzastkowe(char *); void optymalizacja(int); void generowanieKodu(int, char); int main(void) { char wyrazenie[MAX], wyrazenieONP[MAX], *wsk; int liczbaWyrazen; printf("Podaj wyrazenie arytmetyczne bez spacji (np. i:=a+b*(2+c)):\n"); printf("Zmienne moga miec dlugosc jednego znaku (litera duza lub mala).\n"); fscanf(stdin, "%s", wyrazenie); if (!(wsk = strstr(wyrazenie, ":="))) { fprintf(stderr, "main: Blad - brak :=\n"); exit(-1); } konwerterONP(wyrazenieONP, wsk + 2 /* omin := */); printf("\nWynik konwersji do Odwrotnej Notacji Polskiej: \n%s\n\n", wyrazenieONP); printf("Wynik rozbicia na wyrazenia czastkowe:\n"); liczbaWyrazen = wyrazeniaCzastkowe(wyrazenieONP); if (ileElementow()) { printf("\nTabela optymalizacji:\n"); optymalizacja(liczbaWyrazen); } printf("\nGenerowanie kodu (MNEMONIK arg1, arg2 <=> arg2 op= arg1):\n"); generowanieKodu(liczbaWyrazen, wyrazenie[0]); return 0; } /* Funkcja przeksztalca wyrazenie zapisane w notacji nawiasowej * do wyrazenia w Odwrotnej Notacji Polskiej. * * Obslugiwane sa operatory: * + */ void konwerterONP(char *wynik, char *s) { char x, y; while (x = *s++) { /* krok 1 */ if (isalnum(x)) { /* krok 2 */ /* kopiowanie calej liczby z wejscia na wyjscie */ do *wynik++ = x; while (x = *s, isalnum(x) && s++); *wynik++ = ' '; } else if (x == '(') /* krok 3 */ push((double)'('); else if (x == ')') /* krok 4 */ while ((y = (char)pop()) != '(') *wynik++ = y, *wynik++ = ' '; else { /* krok 5 */ while (y = (char)pop(), y && (y == '*' || (y == '+' && x != '*'))) *wynik++ = y, *wynik++ = ' '; push((double)y); push((double)x); } } while(x = (char)pop()) /* krok 6 */ *wynik++ = x, *wynik++ = ' '; *wynik = '\0'; /* zakonczenie wyjsciowego lancucha znakow zerem */ } /* maski bitowe */ #define ZMIENNA (1 << (8 * sizeof (unsigned long int) - 2)) #define ZMIENNA2 (2 << (8 * sizeof (unsigned long int) - 2)) #define LICZBA (3 << (8 * sizeof (unsigned long int) - 2)) #define MASKA (3 << (8 * sizeof (unsigned long int) - 2)) struct { unsigned long int arg1, arg2; int nr; char op; } wyrazenia[MAX]; /* Funkcja wypisuje argument na wyjscie. * Rozroznia czy argument jest liczba, zmienna zywa czy zmienna tymczasowa */ void wypisz(unsigned long int l) { if ((l & MASKA) == LICZBA) /* liczba */ printf("%ld", l & ~LICZBA); else if ((l & MASKA) == ZMIENNA) /* zmienna zywa */ printf("%c", (char)(l & ~ZMIENNA)); else if ((l & MASKA) == ZMIENNA2) /* zmienna tymczasowa */ printf("x%d", (char)(l & ~ZMIENNA2)); else fprintf(stderr, "wypisz: Blad!\n"); } /* Funkcja rozbija podane wyrazenia na wyrazenia czastkowe * postaci xN = arg1 op arg2. */ int wyrazeniaCzastkowe(char *s) { char element[MAX]; int nrZmiennej = 0; unsigned long int arg1, arg2; while (sscanf(s, "%s", element) != -1) { s += strlen(element) + 1; if (isdigit(element[0])) /* cyfra */ push(LICZBA | atol(element)); else if (isalpha(element[0])) /* litera */ push(ZMIENNA | element[0]); else if (element[0] == '*') { /* iloczyn */ /* wypisanie wyrazenia na ekran */ printf("x%d = ", nrZmiennej); wypisz(arg1 = pop()); printf(" * "); wypisz(arg2 = pop()); printf("\n"); /* zapamietanie wyrazenia w tablicy */ wyrazenia[nrZmiennej].arg1 = arg1; wyrazenia[nrZmiennej].arg2 = arg2; wyrazenia[nrZmiennej].op = '*'; push(ZMIENNA2 | nrZmiennej++); } else if (element[0] == '+') { /* suma */ /* wypisanie wyrazenia na ekran */ printf("x%d = ", nrZmiennej); wypisz(arg1 = pop()); printf(" + "); wypisz(arg2 = pop()); printf("\n"); /* zapamietanie wyrazenia w tablicy */ wyrazenia[nrZmiennej].arg1 = arg1; wyrazenia[nrZmiennej].arg2 = arg2; wyrazenia[nrZmiennej].op = '+'; push(ZMIENNA2 | nrZmiennej++); } else { fprintf(stderr, "wyrazeniaCzastkowe: Blad - nieznany znak!\n"); exit(-1); } } /* sprawdzanie czy nie podano prostego wyrazenia typu i:=2 - bez operatorow */ if (((arg1 = pop()) & MASKA) == ZMIENNA || (arg1 & MASKA) == LICZBA) { printf("x%d = ", nrZmiennej); wypisz(arg1); printf("\n"); wyrazenia[nrZmiennej++].arg1 = arg1; } else push(arg1); return nrZmiennej; /* liczba wyrazen czastkowych */ } /* Funkcja tworzy tabele optymalizacji i generuja zoptymalizowane * wyrazenia czastkowe. */ void optymalizacja(int liczbaWyrazen) { char tab[MAX][MAX] = { "\0" }; int i, j, k; tab[liczbaWyrazen][liczbaWyrazen - 1] = 1; /* wynikowa zmienna tmp zywa */ for (i = liczbaWyrazen - 1; i >= 0; i--) { for (j = 0; j < liczbaWyrazen - 1; j++) /* przepisz wiersz do gory tabeli */ tab[i][j] = tab[i + 1][j]; tab[i][i] = 0; /* zabij aktualna zmienna tmp uzywana po lewej stronie */ if ((wyrazenia[i].arg1 & MASKA) == ZMIENNA2) /* ozyw uzyw. po praw. str. zmienne tmp */ tab[i][wyrazenia[i].arg1 & ~ZMIENNA2] = 1; if ((wyrazenia[i].arg2 & MASKA) == ZMIENNA2) tab[i][wyrazenia[i].arg2 & ~ZMIENNA2] = 1; } /* wypisz tabele optymalizacji na wyjscie */ printf(" "); for (i = 0; i < liczbaWyrazen; i++) /* wypisz nazwy zmiennych w naglowku tabeli */ printf("x%-4d", i); printf("\n"); for (i = 0; i <= liczbaWyrazen; i++) { if (i < liczbaWyrazen) /* numer wyrazenia - lewa pionowa kolumna tabeli */ printf("%2d ", i); else printf("-- "); for (j = 0; j < liczbaWyrazen; j++) /* "v" zywe, "-" nie zywe */ printf(" %c ", tab[i][j] ? 'v' : '-'); printf("\n"); } /* tworzenie optymalnych wyrazen na podstawie tabeli */ wyrazenia[0].nr = 0; for (i = 1; i < liczbaWyrazen; i++) { for (j = 0; j < i; j++) { for (k = i - 1; k >= 0; k--) if (wyrazenia[k].nr == j) break; if (tab[i][k] == 0 || (tab[i][k] == 1 && tab[i + 1][k] == 0)) /* wolna zm. tmp */ break; } if (j < i) { wyrazenia[i].nr = j; /* z powodu zmiany zmiennej xN na inna xK (z powodu wolnego miejsca) * nalezy wszystkie wystapienia zmiennej xN pozamieniac na xK */ for (k = i; k < liczbaWyrazen; k++) { if ((wyrazenia[k].arg1 & MASKA) == ZMIENNA2 && (wyrazenia[k].arg1 & ~ZMIENNA2) == i) wyrazenia[k].arg1 = ZMIENNA2 | j; if ((wyrazenia[k].arg2 & MASKA) == ZMIENNA2 && (wyrazenia[k].arg2 & ~ZMIENNA2) == i) wyrazenia[k].arg2 = ZMIENNA2 | j; } } else wyrazenia[i].nr = i; } printf("\nWynik optymalizacji:\n"); for (i = 0; i < liczbaWyrazen; i++) { printf("x%d = ", wyrazenia[i].nr); wypisz(wyrazenia[i].arg1); printf(" %c ", wyrazenia[i].op); wypisz(wyrazenia[i].arg2); printf("\n"); } } /* Funkcja na podstawie optymalnych wyrazen czastkowych generuje kod * w pseudo asemblerze. * Drugi argument menemoniku stanowi wynik danej operacji: * ADD arg1, arg2 <=> arg2 += arg1 */ void generowanieKodu(int liczbaWyrazen, char e) { int i, k; /* jesli mamy do czynienia z prostym wyrazeniem typu i:=2 - bez operatorow */ if (!ileElementow()) { printf("MOV "); wypisz(wyrazenia[liczbaWyrazen - 1].arg1); printf(", %c\n", e); return; } for (i = 0; i < liczbaWyrazen; i++) { if ((wyrazenia[i].arg1 & MASKA) == ZMIENNA2 && (wyrazenia[i].arg1 & ~ZMIENNA2) == wyrazenia[i].nr) k = 1; /* wyrazenie typu xN = xN op b <=> xN op= b */ else if ((wyrazenia[i].arg2 & MASKA) == ZMIENNA2 && (wyrazenia[i].arg2 & ~ZMIENNA2) == wyrazenia[i].nr) k = 2; /* wyrazenie typu xN = b op xN <=> xN op= b */ else k = 0; /* wyrazenie typi xN = xK op b lub xN = b op xK lub * xN = xK op xL lub xN = a op b */ if (k) { /* jesli wyrazenie typu k == 1 lub k == 2 */ printf("%s ", wyrazenia[i].op == '*' ? "MUL" : "ADD"); wypisz(k == 1 ? wyrazenia[i].arg2 : wyrazenia[i].arg1); printf(", x%d\n", wyrazenia[i].nr); } else { /* jesli wyrazenie typu k == 0 */ printf("MOV "); wypisz(wyrazenia[i].arg1); printf(", x%d\n", wyrazenia[i].nr); printf("%s ", wyrazenia[i].op == '*' ? "MUL" : "ADD"); wypisz(wyrazenia[i].arg2); printf(", x%d\n", wyrazenia[i].nr); } } /* przepisanie wartosc ze zmiennej tmp ostatniego wyrazenia do id */ printf("MOV x%d, %c\n", wyrazenia[liczbaWyrazen - 1].nr, e); }