Program rysujący schemat układu logicznego opisanego w zaprojektowanym przez siebie języku
Program ten został napisany przeze mnie na VI semestrze studiów na laboratorium z przedmiotu o nazwie "Języki formalne i kompilatory". Do realizacji projektu wykorzystałem język Java i środowisko NetBeans.
Tutaj znajduje się plik jar z programem (prezentowana tutaj wersja różni się od akademickiej tym, że w tamtej wersji kod był wczytywany z pliku, zaś w tej wersji, aby ułatwić obsługę, kod jest wprowadzane przez powstałe podczas tworzenia tej strony gui).
Należało napisać program rysujący schemat układu logicznego opisanego w zaprojektowanym przez siebie języku. Język miał obsługiwać zdefiniowane elementy takie jak bramki OR, AND, NOT, XOR, wejście-wyjście oraz połączenia pomiędzy nimi. Język powinien umożliwiać tworzenie tzw. węzłów, czyli własnych komponentów składających się z wielu elementów (przykładem może być multiplekser).
Aplikacja powinna zostać napisana w języku Java, mogła być wyposażona w GUI do prezentacji wynikowego schematu. Analiza leksykalna i składniowa zrobiona powinna być zrobiona przy pomocy własnych klas o strukturze zgodnej ze strukturą kompilatorów LL(1) przedstawioną na wykładzie. Aplikacje powinny być zaprojektowane przy wykorzystaniu diagramów w języku UML oraz zaimplementowane przy wykorzystaniu projektowania obiektowego.
Ogólne założenie języka jest takie, że pierwszy leksem w wierszu określa jakiego rodzaju element definiujemy (bramka, wezel, wejście, wyjście). Drugim elementem jest określenie identyfikatora urządzenia (nie mogą się powtarzać i muszą być mniejsze od 40), trzeci element określa, ile będzie wejść do urządzenia. Kolejne elementy to numery wejść do urządzenia. Należy je zakończyć średnikiem, co oznacza koniec wejść. W przypadku różnicy pomiędzy liczbą zadeklarowanych wejść a rzeczywistą ich liczbą program poinformuje nas o błędzie i kod nie będzie skompilowany. W przypadku definiowania węzłów obowiązują trochę inne zasady.
Przykłady użycia (2-wejściowa bramka or):
wejscie u1
wejscie u2
or u3 we2 1 2 ;
wyjscie u4 3
W przypadku tworzenia węzłów obowiązują następujące reguły:
- tworzenie węzła: ostatni element węzła jest jego wyjściem, nazwy urządzeń użyte w produkcji węzła będą zmienione
- wejścia w poleceniu wezel to te , które podczas tworzenia w tworzwezel zaczynały się od litery z
- numerowanie elementów w węzłach zaczyna się od numeru 40 (kolejność występowania w kodzie) więc istnieje możliwość podłączenia
wyjść lub kolejnych bramek pod elementy zawarte w węzłach
wejscie u1
wejscie u2
tworzwezel_{
or u1 we2 z1 z2 ;
and u2 we3 1 z1 z2 ;
} u3
wezel u3 we2 1 2
wyjscie u4 42
Ʃ = {'T_OR', 'T_AND', 'T_NOT', 'T_XOR', 'T_NUMERURZADZENIA', 'T_LICZBAWEJSC', 'T_NUMERWEJSCIA', 'T_KONIECPROGRAMU', 'T_WEZEL', 'T_KONIECWEZLA', 'T_WEJSCIE', 'T_WYJSCIE', 'T_KONIECWEJSC', 'T_NUMERWEJSCIAZEWNETRZNEGO', 'T_TWORZWEZEL'}
Γ = {'URZADZENIE', 'NAZWAURZADZENIA', 'WEJSCIA', 'WEZEL', 'NOWYWEZEL', 'CIAŁO'}
S = 'CIALO'
P = {
CIALO → URZADZENIE CIALO
CIALO → NOWYWEZEL CIALO
CIALO → T_KONIECPROGRAMU
URZADZENIE → NAZWAURZADZENIA T_NUMERURZADZENIA
T_LICZBAWEJSC WEJSCIA
URZADZENIE → T_WEJSCIE T_NUMERURZADZENIA
URZADZENIE → T_WYJSCIE T_NUMERURZADZENIA T_NUMERWEJSCIA
NAZWAURZADZENIA → T_OR
NAZWAURZADZENIA → T_AND
NAZWAURZADZENIA → T_NOT
NAZWAURZADZENIA → T_XOR
NAZWAURZADZENIA → T_WEZEL
NOWYWEZEL → T_TWORZWEZEL WEZEL
WEZEL → URZADZENIE WEZEL
WEZEL → T_KONIECWEZLA T_NUMERURZADZENIA
WEJSCIA → T_NUMERWEJSCIA WEJSCIA
WEJSCIA → T_NUMERWEJSCIAZEWNETRZNEGO WEJSCIA
WEJSCIA → T_KONIECWEJSC
}
lookahead1(CIALO) = {'T_OR'}
lookahead1(CIALO) = {'T_AND'} lookahead1(CIALO) = {'T_NOT'} lookahead1(CIALO) = {'T_XOR'} lookahead1(CIALO) = {'T_WEZEL'} lookahead1(CIALO) = {'T_WEJSCIE'} lookahead1(CIALO) = {'T_WYJSCIE'} lookahead1(CIALO) = {'T_TWORZWEZEL'} lookahead1(CIALO) = {'T_KONIECPROGRAMU'} zbiory rozłączne |
lookahead1(URZADZENIE) = {'T_OR'}
lookahead1(URZADZENIE) = {'T_AND'} lookahead1(URZADZENIE) = {'T_NOT'} lookahead1(URZADZENIE) = {'T_XOR'} lookahead1(URZADZENIE) = {'T_WEZEL'} lookahead1(URZADZENIE) = {'T_WEJSCIE'} lookahead1(URZADZENIE) = {'T_WYJSCIE'} zbiory rozłączne |
lookahead1(NAZWAURZADZENIA) = {'T_OR'}
lookahead1(NAZWAURZADZENIA) = {'T_AND'} lookahead1(NAZWAURZADZENIA) = {'T_NOT'} lookahead1(NAZWAURZADZENIA) = {'T_XOR'} lookahead1(NAZWAURZADZENIA) = {'T_WEZEL'} zbiory rozłączne |
lookahead1(WEZEL) = {'T_OR'}
lookahead1(WEZEL) = {'T_AND'} lookahead1(WEZEL) = {'T_NOT'} lookahead1(WEZEL) = {'T_XOR'} lookahead1(WEZEL) = {'T_WEZEL'} lookahead1(WEZEL) = {'T_TWORZWEZEL'} lookahead1(WEZEL) = {'T_KON'} zbiory rozłączne |
lookahead1(WEJSCIA) = {'T_NUMERWEJSCIA'}
lookahead1(WEJSCIA) = {'T_KONIECWEJSC'} lookahead1(WEJSCIA) = {'T_NUMERWEJSCIAZEWNETRZNEGO'} zbiory rozłączne |
T_OR | or |
T_AND | and |
T_NOT | not |
T_NUMERURZADZENIA | u[0-9]{1,3} |
T_LICZBAWEJSC | we[0-9]{1,3} |
T_NUMERWEJSCIA | [0-9]{1,3} |
T_WEZEL | wezel |
T_KONIECWEZLA | \\} |
T_WEJSCIE | wejscie |
T_WYJSCIE | wyjscie |
T_KONIECWEJSC | ; |
T_NUMERWEJSCIAZEWNETRZNEGO | Z[0-9]{1,3} |
T_TWORZWEZEL | tworzwezel_\\{ |
T_KONIECPROGRAMU | EOF |
Klasa Rozruch odpowiedzialna jest za uruchamianie kolejnych komponentów programu. Używamy flagi poprawny_kod aby wiedzieć, czy poprzednie etapy sprawdzania kodu dały pozytywny rezultat. Jeśli nie, dalsze działania nie są wykonywane. Pierwszą czynnością jest wczytanie do tablicy kodu wysłanego przez użytkownika. Następnie uruchamiamy analizator leksykalny. W przypadku poprawnych leksemów przechodzimy do analizatora składniowego. Gdy ten także nie zmieni flagi na 0, tworzymy drzewo składni i tworzymy elementy z węzłów. Wszystkie komponenty zapisujemy do nowej tablicy. Następnie ustalamy, który element w jakim bloku zostanie wyświetlony, drukujemy drzewo składni i inicjujemy grafikę.
Funkcja Lookahead w analizatorze składniowym sprawdza, czy dany leksem ma prawo znajdować się w określonym miejscu kodu. Iteracyjnie sprawdzamy wszystkie leksemy, które mogą wystąpić dla danego symbolu nieterminalnego. Dzięki zastosowaniu gramatyki LL(1) wystarczy sprawdzenie tylko jednego leksemu. W przypadku uznania, że dany leksem jest w odpowiednim miejscu, zwracamy liczbę odpowiadającą numerowi produkcji, która została tutaj wykonana. W innym przypadku funkcja zwróci wartość -1, co zostanie zinterpretowane jako błąd i spowoduje wyświetlenie odpowiedniego błędu.