Anliz leksykln: prolem dopsowywni wzorc, udownie lekserów Wyszukiwnie wzorc W prktycznych zstosownich teorii języków formlnych nie sposó nie wspomnieć o prolemie wyszukiwni wzorc. Zjmiemy się njprostszą formą, w jkiej możn sformułowć ten prolem jego wersją dl tekstów. Jest on szczególnie wżn w erze rozwoju Internetu, wydjne implementcje lgorytmów rozwiązujących ten prolem wykorzystywne są powszechnie. Codziennie przeszukujemy zwrtość sieci, nszego dysku, skrzynki pocztowej czy edytownego pliku tekstowego z pomocą nrzędzi dostrcznych przez serwisy internetowe, systemy opercyjne, klientów pocztowych czy edytory dokumentów tekstowych. Definicj Wzorzec x = 1 2 m orz tekst y = 1 2 n to dowolne skończone ciągi znków nd ustlonym lfetem. Zzwyczj zkłd się, że m<<n (m jest istotnie mniejsze niż n). Prolem (wyszukiwni wzorc x w tekście y): Znleźć wszystkie i, tkie że i i+m-1 = 1 m. Przykłd W tekście nigdy nie ufj komputerowi, którego nie możesz wyrzucić przez okno (Steve Woznik) wzorzec ni występuje n pozycjch 1, 7, 37 orz 78, zś w tekście wzorzec występuje n pozycjch 3, 11, 16, 24, 32, 37,45, 50, 58, 66 orz 71, ntomist wzorzec n pozycjch 5, 18, 26, 39, 52 orz 60. Spróujmy spojrzeć n rozwiązni prolemu wyszukiwni wzorc z punktu widzeni teorii utomtów. Dl kżdego wzorc x rdzo łtwo skonstruowć niedeterministyczny utomt skończony, który ędzie kceptowł wszystkie prefiksy tekstu y które kończą się wystąpieniem wzorc x. Konstrukcj przeieg jk nstępuje. Ziór stnów m moc m+1, stn początkowy i po jednym stnie dl kżdego znku wzorc, stnem kceptującym jest m. Przejści między stnmi etykietowne są kolejnymi litermi wzorc, to jest (q i-1, i )=q i. Pondto dodjemy przejści prowdzące ze stnu q 0 do stnu q 0 orz ze stnu q m do stnu q m dl kżdej litery lfetu. Zuwżmy, że rdzo podone utomty rozwżliśmy w wykłdzie 3. 1 q 0 q 1 2 q 3 2 m q m Zproponowny utomt nie jest deterministyczny, przez co nie ndje się do ezpośredniej implementcji jko procedur służąc do wyszukiwni wzorców. Z wcześniejszych
wykłdów znmy jednk metodę determinizcji utomtów. Jej główną wdą jest możliwość wykłdniczego wzrostu liczy stnów. Okzuje się jednk, że w przypdku utomtów tego typu, podczs determinizcji licz stnów nie zmieni się. Zdnie: Udowodnij ten fkt przy złożeniu, że i j dl ij. Przykłd: Skonstruujmy utomt niedeterministyczny dl wzorc, przyjmując, lfet ={,}. ={,} q 0 q 1 q 2 q 3 q 4 q 5 orz jego deterministyczną wersję: q 0 q 1 q 2 q 3 q 4 q 5 Dodtkową zletą tkiego podejści jest możliwość prostego rozszerzeni go n wyszukiwnie wielu wzorców jednocześnie. Konstruujemy wówczs osony utomt dl kżdego wzorc i utożsmimy ze soą stny początkowe wszystkich powstłych utomtów. W tkim przypdku mmy do czynieni z podoną włsnością, jk w przypdku wzorc dl pojedynczego wzorc. Tym rzem jednk, licz stnów wynikowego utomtu deterministycznego może ulec zmniejszeniu. Z prktycznego punktu widzeni njwżniejsze jest, że nie m możliwości y uległ zwiększeniu. Ćwiczenie: Skonstruuj zgodnie z podnym przepisem utomt niedeterministyczny rozpoznjący jednocześnie wzorce orz orz zdeterminizuj go. Ćwiczenie: Zproponuj metodę udowni utomtu rozwiązującego prolem wyszukiwni wzorc x= 1 m, w którym: - dl stnu początkowego mmy przejści (q 0,x)=q 0 dl x 1, orz (q 0, 1 )=q 1 - dl pozostłych stnów mmy dokłdnie po jednym przejściu postci (q i,x)=q j orz (q i,)=q k, gdzie x. Opis jkiego klsycznego lgorytmu uzyskłeś w ten sposó?
Wyszukiwnie pojedynczego wzorc, nwet jednego ze skończonego (i niewielkiego) zioru wzorców, yw niewystrczjące. Wyorźmy soie sytucję, gdy chcieliyśmy wyszukć wszystkie komentrze zwierjące słowo int w oszernym kodzie progrmu npisnego w C. Poszukujemy wówczs npisów postci /*y*/, gdzie w miejsce y jest tekstem zwierjącym słowo int. Wiemy już, że język tkich npisów łtwo zpisć w postci wyrżeni regulrnego, do opisnego zdni możn wykorzystć progrm powłoki unixowej grep. Zmist tego mogliyśmy wykorzystć opisny wcześniej schemt wyszukiwni wzorc i użyć utomtu rozpoznjącego ten język regulrny. Dokłdnie tk dził grep. Uwg: Wyrżeni regulrne powłoki UNIX (wykorzystywne między innymi przez progrm grep) w sposó istotny rozszerzją znne Ci wyrżeni regulrne. Szczegóły sprwdź w podręczniku systemowym (strony mn) dl poleceni grep. Nie ędziemy przytczć dokłdnej skłdni wyrżeń regulrnych, któr jest dostępn w systemie UNIX. Podmy tylko prostą trnslcję znnego Ci z jednego z pierwszych wykłdów sposou zpisu wyrżeń regulrnych: język pusty - nie podje się go wprost język jednoelementowy symol odpowiedni dl dnego znku, uwg: znki specjlne muszą yć poprzedzne `\`, znki specjlne to nwisy, iłe znki czy symole relcji przykłd: ozncz język {}, zś \. język {.} nwisy ustljące kolejność dziłń oznczny dokłdnie tk smo, wykorzystywne symole to `(` orz `)` język złożony ze słow pustego - nie podje się go wprost, jest ntomist możliwość zpisu język r, służy do tego opertor? przykłd:? ozncz {}{} sum dwóch języków znkiem sumy jest ` ` uwg: sumy języków jednoelementowych możn zpisć wymienijąc znki w nwisch kwdrtowych, w przypdku ciągu znków o kolejnych kodch ASCII możemy korzystjąc ze znku `-` użyć zkresu przykłd: język {,,c} opisny wrżeniem regulrnym c możn zpisć c lu [c] lu [-c] konktencj dwóch języków oznczmy dokłdnie tk smo pisząc dw języki jeden z drugim domknięcie Kleene ego oznczne znkiem `*` Innym, wżnym zdniem, które możn powierzyć nrzędziom korzystjącym z teorii utomtów jest nliz leksykln. Zleży nm wówczs nie tyle n odnlezieniu konkretnych frgmentów w podnym tekście, ile n pocięciu go n frgmenty zgodnie z ustlonymi zsdmi. Sytucj tk m miejsce w przypdku zodnowych plików tekstowy, jk /etc/psswd. Dne dotyczące kżdego użytkownik systemu zpisne są tm w osonych wierszch i rozdzielone znkmi `:`. W rozwiązniu tego typu prostych prolemów nlizy leksyklnej możemy się posiłkowć innym progrmem powłoki UNIX cut, czy też klsą StringTokenizer stndrdowej ilioteki język Jv.
Często struktur pliku nie jest tk regulrn, mimo to chcieliyśmy podzielić zwrty w nim tekst n frgmenty o określonej skłdni. Tką prcę wykonuje kżdy kompiltor, który musi zrozumieć kod progrmu, to jest w pierwszym etpie podzielić go n elementrne skłdniki zwne leksemmi. Pozwl to określić, które frgmenty kodu są nzwmi zmiennych, które słowmi kluczowymi, które typmi dnych. W kolejnym etpie kompiltor sprwdz, czy tk wydzielone leksemy ułożone są zgodnie z zsdmi oowiązującymi w dnym języku progrmowni. Ale tym zjmiemy w czsie jednego z kolejnych wykłdów. Wróćmy do podziłu tekstu n leksemy w nszym przykłdzie kodu progrmu. Kżdy leksem opisny yć może z pomocą wyrżeni regulrnego. Słow kluczowe język są listą zstrzeżonych ciągów znków. Nzwy zmiennych to ciągi znków lfnumerycznych rozpoczynjący się literą (zzwyczj może też zwierć, nwet zczynć się znkiem `_`) i nie ędących słowmi kluczowymi. W podony sposó opisć możn inne elementy z których skłdją się kody progrmów npisne w zdnym języku progrmowni. W dlszej części tego wykłdu omówimy progrm generujący kod źródłowy skner w języku C lu C++, który umożliwi podzielenie tekstu n leksemy zgodnie z opisem korzystjącym z wyrżeń regulrnych. Progrmem tym jest lex (lexicl nlyzer genertor). Znim przenlizujemy prostą specyfikcję skner, powiemy kilk słów o jej strukturze i idei dziłni. Kżd specyfikcj skłd się z trzech części rozdzielonych wierszmi %%: deklrcji i definicji reguł rozpoznwni procedur W części pierwszej umieszczmy ujęte w nwisy %{ orz %} wszystkie glolne deklrcje konieczne przy kompilcji nszego wynikowego progrmu, w szczególności dyrektywy dołączjące kod iliotek (include). Poz tym definiujemy tm wyrżeni regulrne, z których ędziemy później udowć opisy leksemów. Wykorzystując zdefiniowne wcześniej wyrżeni otczmy je nwismi klmrowymi. W części drugiej reguły określjące, co m zostć zroione z odnlezionymi leksemmi. Reguły te są postci: wzorzec kod w C/C++ W osttniej, trzeciej części, zwiermy definicje procedur z których chcemy skorzystć w sknerze. W szczególności możemy tm umieścić funkcję min (domyślnie wykonywny ędzie tylko kod umieszczony w regułch, niedopsowne znki są drukowne n stndrdowym wyjściu). Zwsze dopsowywny jest njdłuższy psujący leksem, jeśli psuje on do kilku wzorców, wyrny ędzie zdefiniowny njwcześniej. Więcej szczegółów (n przykłd tryy lex) znleźć możn w podręcznikch systemów progrmu lex i licznych pordnikch dostępnych w sieci. Kod przykłdowego pliku lekser.l wygląd nstępująco:
1. %{ 2. #include <iostrem> 3. using nmespce std; 4. int licz; 5. %} 6. 7. digit [0-9] 8. digits {digit}+ 9. frctionl "."{digits} 10. sign_opt ("+" "-")? 11. exp_opt ((e E){sign_opt}{digits})? 12. numer {sign_opt}({digits}{frctionl} {digits}"."? {frctionl}){exp_opt} 13. 14. %% 15. 16. {numer} { cout << tof(yytext) << ", "; licz++; } 17. 18.. \n { /* nothing */ } 19. 20. %% 21. 22. int min() { 23. licz = 0; 24. cout << "Pocztek sknowni..." << endl; 25. yylex(); 26. cout << endl << "Koniec sknowni, zesknowno licz: " << licz << endl; 27. return 0; 28. } Anliz specyfikcji skner: Wiersze 14 i 20 (%%) rozgrniczją logiczne części specyfikcji skner. W wierszch 1-5 umieszczone są deklrcje glolne, dołączon jest iliotek iostrem, ustwion zostje stndrdow przestrzeń nzw orz zdeklrown zmienn gloln typu cłkowitego. Wiersze 7-12 zwierją specyfikcję wykorzystywnych w regułch języków regulrnych. Zuwż, że wykorzystno tylko wyrżenie zdefiniowne jko numer (wiersz 16), pozostłe posłużyły do jego definicji. W wierszu 18 zdefiniowno (jko puste) zchownie w przypdku dopsowni pojedynczego znku, pozwoli to pominąć cłą zwrtość pliku tekstowego nie psującą do zdefiniownego wzorc zpoiegjąc wydrukowniu jej n stndrdowym wyjściu. Zuwż, że oprócz znku `.` dopsowującego się do dowolnego symolu wyrżenie zwier znk specjlny nowego wiersz. Przypomnij soie dziłnie progrmu grep. Bd on tekst wiersz po wierszu drukując tylko te wiersze, których frgment psuje do wzorc. Wyrżeni regulrne w systemie UNIX nie dopsowują znku nowego wiersz jko zwykłego znku. Do osługi początku/końc wiersz mmy symole specjlne \n (symol nowego wiersz) orz ^ i $, odpowiednio początek i koniec wiersz. Wiersze 22-28 to implementcj funkcji min. Domyślnie (gdyyśmy nie podli włsnej implementcji funkcji min) lex wygenerowły wiersze 22, 25, 27 orz 28. Kompilcj progrmów korzystjących ze specyfikcji skner przeieg dwuetpowo. Njpierw, korzystjąc z progrmu lex (lu flex) generujemy kod progrmu w języku C lu C++. Podjemy jko jedyny rgument progrmu lex nzwę pliku ze specyfikcją skner.
Wygenerowny plik ędzie domyślnie nosił nzwę lex.yy.c. Kompilujemy go z użyciem progrmu gcc lu g++ do pliku wykonywlnego z opcją -lfl, domyślnie wynik zpisny zostnie w pliku.out. Kompilcj pliku lekser.l n mszynie ultr60: flex lekser.l g++ lex.yy.c lfl Ćwiczenie: Zmodyfikuj specyfikcję skner lekser.l tk, y drukown ył sum wszystkich zesknownych licz zmist ich ilości. Lex/Flex widomości podstwowe Przypomnijmy, że nlizą leksyklną nzywmy podził dnych wejściowych skłdjących się z ciągu znków (tekstu, zwrtości pliku, itp.) n ciąg określonych symoli leksyklnych nzywnych tokenmi. Tokeny opisywne są zzwyczj z pomocą wyrżeń regulrnych. Kżde wyrżenie regulrne opisuje pewien język ziór ciągów znków djących się do niego dopsowć. Progrmy wykonujące nlizę leksyklną nzywmy leksermi. Progrm tki może zostć zimplementowny ezpośrednio w dowolnym języku progrmowni. Możn również wykorzystć w tym celu nrzędzi umożliwijące generownie kodu źródłowego nliztor leksyklnego n podstwie przygotownej specyfikcji dziłni. Przykłdem tego typu nrzędzi jest progrm lex lu odpowidjący mu w systemie GNU progrm flex. Przygotownie specyfikcji nliztor leksyklnego Progrm lex służy do tworzeni nliztorów leksyklnych n podstwie przygotownej specyfikcji. Powinn on określć strukturę poszukiwnych tokenów orz reguły zchowni w przypdku pozytywnego dopsowni. Specyfikcj dl progrmu lex m postć pliku tekstowego o nstępującym formcie: %{ %} %% %% deklrcje użytkownik definicje wyrżeń regulrnych reguły dopsowni definicje funkcji i procedur Część pierwsz zwierjąc deklrcje użytkownik jest kopiown do wynikowego źródł lekser ez zmin. W tym miejscu powinny yć umieszczone deklrcje dołączeni specyficznych plików ngłówkowych, deklrcje zmiennych glolnych, funkcji i procedur użytkownik, itp. W części drugiej umieszczne są definicje wyrżeń regulrnych wykorzystywnych do rozpoznwni symoli leksyklnych. Do konstrukcji wyrżeń możemy używć nstępujących opercji:
sum dwóch wyrżeń regulrnych [-m] opertor zkresu () grupownie * domknięcie Kleene go (zero lu więcej wystąpień) + co njmniej jedno wystąpienie? co njwyżej jedno wystąpienie. dowolny znk (poz znkiem nowego wiersz) ^ lewy kontekst psuje do do początku wiersz $ prwy kontekst psuje do końc wiersz... dopsownie dosłowne Zdefiniowne proste wyrżeni mogą służyć do udowni rdziej skomplikownych wyrżeń. Do wcześniej zdefiniownego wyrżeni odwołujemy się umieszczjąc je w nwisch { orz }. {wyrzenie} Część trzeci zwier reguły określjące zchownie progrmu po pozytywnym dopsowniu określonych wyrżeń regulrnych. Reguły te są postci wzorzec { kod język C } Zchownie lekser po dopsowniu wzorc określnie jest z pomocą frgmentów kodu w języku C. Możemy więc korzystć ze zmiennych i funkcji zdefiniownych części deklrcji. Pondto, możemy używć między innymi poniższych zmiennych specjlnych: yytext osttnio dopisny wzorzec (chr *) yyleng długość osttnio dopsownego wzorc (int) yyin wejście progrmu (FILE *) yyout wyjście progrmu (FILE *) W części czwrtej umieszczne są definicje funkcji i procedur. W szczególności możemy umieścić w tym miejscu definicję funkcji min. Anliz leksykln jest wykonywn przez funkcję yylex() wygenerowną przez progrm lex. W przypdku nie dostrczeni implementcji funkcji min, lex wygeneruje ją z domyślną zwrtością skłdjącą się z pojedynczego wywołni funkcji yylex(). Kompilcj i uruchomienie Kompilcj plików źródłowych opisujących dziłnie nliztor leksyklnego przeieg dwuetpowo. W pierwszym etpie n podstwie przygotownej specyfikcji progrm lex generuje kod źródłowy nliztor leksyklnego. lex specyfikcj.l Wygenerowny przez powyższe polecenie kod źródłowy umieszczny jest w pliku o nzwie lex.yy.c. W drugim etpie jest on kompilowny z pomocą kompiltor język C. Ay kompilcj zkończył się sukcesem wymgne jest dołączenie ilioteki fl.
gcc lex.yy.c lfl Wykonnie powyższego poleceni spowoduje utworzenie pliku wykonywlnego pod domyślną nzwą.out. N etpie kompilcji źródł lekser możliwe jest używnie większości opcji używnego kompiltor język C. W szczególności możliwe jest uzysknie innej nzwy pliku z pomocą opcji o nzw_pliku. Podsumowując schemt kompilcji może yć w uproszczeniu przedstwiony nstępująco: input.l lex lex.yy.c gcc lex.yy.c -lfl.out Schemt dziłni nliztor leksyklnego Anliztor leksyklny wygenerowny przy pomocy progrmu lex przetwrz dne wejściowe próując dopsowć czytne ciągi znków do wyrżeń regulrnych zdefiniownych w specyfikcji. Po pozytywnym dopsowni wzorc, wykonywne są określone dl niego reguły. W przypdku niejednoznczności specyfikcji, jeśli więcej niż jedn reguł psuje do przetwrznego ciągu znków, lekser wyier njdłuższe możliwe dopsownie. W przypdku dopsowni kilku tokenów tej smej długości wyierny jest ten, który w pliku źródłowym zostł zdefiniowny njwcześniej. Zdnie 1 Zprojektuj nliztor leksyklny rozpoznjący poprwnie zpisne wyrżeni rytmetyczne. Przygotowny nliztor powinien rozpoznwć liczy cłkowite w zpisie dziesiętnym i szesnstkowym (dodtnie i ujemne), liczy rzeczywiste w zpisie trdycyjnym i wykłdniczym, nwisy zmykjące i otwierjące orz podstwowe opertory: +, -, *, /. Przyjmij, że zpis rozpoznwnych licz jest zgodny z konwencją język C. Po zkończeniu dziłni progrm powinien wydrukowć n stndrdowym wyjściu podsumownie zwierjące liczę użytych opertorów, liczę i łączną sumę znlezionych licz cłkowitych orz liczę i łączną sumę znlezionych licz rzeczywistych. Rozwiąznie Nszym zdniem jest zprojektownie nliztor leksyklnego rozpoznjącego w czytnym pliku liczy cłkowite i rzeczywiste orz opertory rytmetyczne i nwisy. Pondto, progrm powinien zliczć wystąpieni kżdego typu rozpoznnych oiektów. Rozpoczniemy od przygotowni deklrcji dołączeni plików ngłówkowych ilioteki stndrdowej język C orz deklrcji zmiennych niezędnych do zliczni rozpoznnych oiektów.
%{ #include <stdio.h> #include <stdli.h> %} int numopertors = 0; int numints = 0; int numdoules = 0; int numbrckets = 0; int intvlue = 0; doule doulevlue = 0.0; Nstępnie musimy zdefiniowć wyrżeni regulrne opisujące strukturę poszukiwnych tokenów. Pojedyncze cyfry dziesiętne i szesnstkowe, tkże ich niepuste ciągi opisujemy definiując nstępujące wyrżeni regulrne digit [0-9] hex_digit {digit} [-f] [A-F] digits {digit}+ hex_digits {hex_digit}+ Zuwżmy, że odwołując się do etykiety wcześniej zdefiniownego wyrżeni regulrnego, umieszczmy ją w nwisch klmrowych { orz }. Liczy w zpisie szesnstkowym (według konwencji język C) zpisujemy z przedrostkiem 0x lu OX. Musimy ztem dodć wyrżenie regulrne pozwljące dopsowć ten przedrostek hex_pref "0x" "0X" Liczy w dowolnym z rozwżnych przez ns zpisów mogą yć ujemne. Potrzene jest więc wyrżenie regulrne pozwljące dopsowć opcjonlny jednorgumentowy opertor. sign_opt ("-")? Ay poprwnie dopsowć zpis licz rzeczywistych musimy yć w stnie dopsowć kropkę ułmkową orz opcjonlny sufiks notcji wykłdniczej. Dodjemy więc wyrżeni regulrne frctionl exp_opt "."{digits ((e E){sign_opt}{digits})? Używjąc powyższych wyrżeń regulrnych jko skłdników możemy zdefiniowć wyrżeni opisujące dowolne liczy cłkowite w zpisie dziesiętnym i szesnstkowym orz dowolne liczy rzeczywiste w zpisie stndrdowym i wykłdniczym. Przypomnijmy, że prwidłowymi reprezentcjmi licz rzeczywistych w języku C są 3.6, 5. orz.8 orz opcjonlnie sufiks notcji wykłdniczej. Przygotowne przez ns wyrżeni regulrne powinny prwidłowo dopsowywć liczy rzeczywiste w kżdym z opisnych przypdków. dec_num hex_num doule_num doule_exp_num {digits} {hex_pref}{hex_digits} ({digits}{frctionl} {digits}"."? {frctionl}) ({digits}{frctionl} {digits}"."? {frctionl}){exp_opt} Dl zdefiniownych powyżej wyrżeń regulrnych, w kolejnej części specyfikcji umieszczmy reguły rozpoznwni określjące zchownie progrmu po dopsowniu wzorc do określonego wyrżeni regulrnego.
37. {dec_num} { printf("licz dziesietn (%s)\n",yytext); 38. ++numints; 39. intvlue += strtol(yytext,null,10); } Po dopsowniu wyrżeni regulrnego opisującego liczę cłkowitą w zpisie dziesiętnym, drukujemy odpowiednią informcję n ekrn, orz uktulnimy wrtości odpowiednich zmiennych. Zuwżmy, że znlezione tokeny są zwrcne w zmiennej yytext w postci npisów (typu chr *). Jeśli więc chcemy użyć ich wrtości liczowej (cłkowitej lu rzeczywistej) nleży dokonć odpowiedniej konwersji, np. z pomocą funkcji z ilioteki stndrdowej język C: toi, tof, strtol, strtod, itp. Używjąc wyrżeń regulrnych opisujących liczy cłkowite w zpisie szesnstkowym, liczy rzeczywiste w zpisie stndrdowym i wykłdniczym orz opertory i nwisy w podony sposó definiujemy reguły zchowni progrmu po ich dopsowniu. Pozostło jeszcze tylko przygotowć implementcję funkcji min zwierjącej wywołnie funkcji yylex() relizującej nlizę leksyklną orz drukownie oczekiwnego podsumowni. Kompletn specyfikcj lekser znjduje się n końcu tych ćwiczeń. Uwg: Przygotowny przez ns nliztor leksyklny rozpoznje prwidłowo sformtowne pojedyncze skłdniki wyrżeń rytmetycznych. Nie jest jednk w stnie zweryfikowć poprwności zpisu tych wyrżeń. Ay wykonć to zdnie potrzeujemy nliztor skłdniowego (prser). Projektowniem tego typu progrmów zjmiemy się w czsie jednych z kolejnych zjęć. Zdnie 2 Zprojektuj nliztor leksyklny rozpoznjący elementy skłdni prostego pseudossemler. Zkłdmy, że dl progrmu w pseudo-ssemlerze dostępny jest pojedynczy rejestr, w którym wykonywne są wszystkie oliczeni, orz pmięć ustlonego rozmiru (np. 1024B) dresown liczmi cłkowitymi. Lekser powinien rozpoznwć co njmniej nstępujące słow kluczowe: INPUT wczytnie zwrtości rejestru ze stndrdowego wejści OUTPUT wydrukownie zwrtości rejestru n stndrdowym wyjściu LOAD x wczytnie do rejestru wrtości z pmięci pod dresem x STORE x zpisnie wrtości z rejestru w pmięci pod dresem x ADD x dodnie do zwrtości rejestru zwrtości pmięci pod dresem x SUBT x odjęcie od zwrtości rejestru zwrtości pmięci pod dresem x VAR x y umieszczenie wrtości y w pmięci pod dresem x CLEAR wyzerownie rejestru HALT ztrzymnie dziłni progrmu. Zkłdmy, że progrmy npisne w rozwżnym pseudo-ssemlerze ędą przetwrzć liczy cłkowite. Pondto, dresy w pmięci mogą yć podwne ezpośrednio z pomocą licz cłkowitych orz w postci zmiennych. Nzw zmiennej może yć dowolnym ciągiem znków lfnumerycznych rozpoczynjących się literą, ntomist ezpośredni dres rejestru może yć dowolną liczą cłkowitą w zpisie dziesiętnym.
Rozwiąznie Nszym zdniem jest zprojektownie nliztor leksyklnego rozpoznjącego w czytnym pliku elementy skłdni prostego pseudo-ssemler. Jednym z elementów rozwżnego język są komentrze jednowierszowe w stylu C++. Zuwżmy, że zwrtość komentrzy powinn yć ignorown. Ztem od rozpoznnego początkowego znku komentrz // do końc wiersz nsz nliztor nie powinien rozpoznwć żdnych elementów język. Rozpoczniemy od przygotowni deklrcji dołączeni pliku ngłówkowego ilioteki stndrdowej język C orz deklrcji zmiennej wykorzystywnej do zpmiętni tryu ignorowni wnętrz komerzy. %{ #include <stdio.h> int ignore = 0; %} Adresy pmięci w postci licz cłkowitych orz nzwy zmiennych w postci dowolnych ciągów znków lfnumerycznych rozpoczynjących się od litery ędą dopsowne z pocą wyrżeni regulrnych numer [1-9][0-9]* id [A-Z-z][A-Z-z0-9]* Słow kluczowe rozwżnego pseudo-ssemler dopsujemy ntomist dokłdnie do ich nzwy. comment "//" input "INPUT" output "OUTPUT" dd "ADD" sut "SUBT" lod "LOAD" store "STORE" cler "CLEAR" hlt "HALT" vr "VAR" Dl zdefiniownych powyżej wyrżeń regulrnych, w kolejnej części specyfikcji umieszczmy reguły rozpoznwni określjące zchownie progrmu po dopsowniu wzorc. Ay prwidłowo przetwrzć komentrze, po dopsowniu odpowiedniego wyrżeni regulrnego nliztor musi zpmiętć, że jest w tryie przetwrzni komentrze i wszystkie dopsowne elementy skłdni powinny yć ignorowne. {comment} { if(!ignore) printf("komentrz\n"); ignore = 1; } Po dopsowniu końc wiersz nliztor powinien opuścić try komentrz i powrócić do rozpoznwni elementów skłdni. \n { if(ignore) ignore = 0; } Dl kżdego dopsownego słow kluczowego lekser powinien wyświetlić komunikt, pod wrunkiem że dopsowny element nie znjduje się wewnątrz komentrz. {input} { if(!ignore) printf("czytnie z rejestru wejściowego\n"); }
Używjąc wyrżeń regulrnych opisujących pozostłe słow kluczowe w podony sposó definiujemy reguły zchowni progrmu po ich dopsowniu. Pozostło jeszcze tylko przygotowć implementcję funkcji min zwierjącej wywołnie funkcji yylex() relizującej nlizę leksyklną. Kompletn specyfikcj lekser znjduje się n końcu tych ćwiczeń. Zprojektowny powyżej nliztor rozpoznje tylko kilk podstwowych słów kluczowych. Możesz rozszerzyć listę rozpoznwnych instrukcji o mnożenie, dzielenie, instrukcje wrunkowe, instrukcje skoku, pętle itp. Uwg: Podonie jk w rozwiązniu zdni 1, przygotowny lekser rozpoznje elementy skłdni rozwżnego pseudo-ssemler. Nie jest niestety w stnie zweryfikowć, czy przetwrzny progrm jest zgodny ze specyfikcją nszego prostego język. W przyszłości rozszerzymy możliwości nszego progrmu o nlizę skłdniową i symulcję dziłni progrmu. Zdnie domowe Przygotuj nliztor leksyklny dl plików źródłowych progrmów w języku Pscl. Przygotowny lekser powinien prwidłowo rozpoznwć słow kluczowe, nzwy zmiennych, stłe numeryczne, znkowe, itp. Uwg: Rozwżny powyżej mterił oejmuje wyłącznie niezędne podstwy używni progrmu lex/flex do tworzeni nliztorów leksyklnych. Więcej szczegółów możesz znleźć w podręczniku systemowym progrmu lex/flex orz w sieci Internet.
Pełne rozwiązni zdń W rozwiąznich zdń omówiliśmy njwżniejsze szczegóły implementcji pomijjąc mniej istotne frgmenty. Poniżej zmieszczmy kompletne kody progrmów. 1. Specyfikcj lekser z zdni 1. 1. %{ 2. 3. #include <stdio.h> 4. #include <stdli.h> 5. 6. int numopertors = 0; 7. int numints = 0; 8. int numdoules = 0; 9. int numbrckets = 0; 10. int intvlue = 0; 11. doule doulevlue = 0.0; 12. 13. %} 14. 15. digit [0-9] 16. hex_digit {digit} [-f] [A-F] 17. digits {digit}+ 18. hex_digits {hex_digit}+ 19. hex_pref "0x" "0X" 20. frctionl "."{digits} 21. sign_opt ("-")? 22. exp_opt ((e E){sign_opt}{digits})? 34. plus_op "+" 24. minus_op "-" 25. mult_op "*" 26. div_op "/" 27. opertor {plus_op} {minus_op} {mult_op} {div_op} 28. op_rcket "(" 29. cl_rcket ")" 30. dec_num {digits} 31. hex_num {hex_pref}{hex_digits} 32. doule_num ({digits}{frctionl} {digits}"."? {frctionl}) 33. doule_exp_num ({digits}{frctionl} {digits}"."? {frctionl}){exp_opt} 34. 35. %% 36. 37. {dec_num} { printf("licz dziesietn (%s)\n",yytext); 38. ++numints; 39. intvlue += strtol(yytext,null,10); } 40. 41. {hex_num} { printf("licz szesnstkow (%s)\n",yytext); 42. ++numints; 43. intvlue += strtol(yytext,null,16); } 44. 45. {doule_num} { printf("licz rzeczywist (stndrdow) (%s)\n",yytext); 46. ++numdoules; 47. doulevlue += strtod(yytext,null); } 48. 49. {doule_exp_num} { printf("licz rzeczywist (wykłdnicz) (%s)\n)",yytext); 50. ++numdoules; 51. doulevlue += strtod(yytext,null); } 52. 53. {opertor} { printf("opertor (%c)\n",yytext[0]); 54. ++numopertors; } 55. 56. {op_rcket} { printf("nwis otwierjcy\n"); 57. ++numbrckets; } 58. 59. {cl_rcket} { printf("nwis zmykjcy\n"); 60. ++numbrckets; } 61. 62.. \n { /* pomijj niedopsowne znki */ } 63. 64.%% 65. 66. int min() { 67. printf("pocztek sknowni...\n"); 68. yylex(); 69. printf("\nkoniec sknowni\n"); 70. printf("\tprzeczytno %d licz cłkowitych o łcznej wrtosci %d\n", 71. numints,intvlue); 72. printf("\tprzeczytno %d licz rzeczywistych o łcznej wrtosci %f\n", 73. numdoules,doulevlue); 74. printf("\tuzyto %d nwisow orz %d opertorow\n\n",numbrckets,numopertors); 75. 76. return 0; 77.}
2. Specyfikcj lekser z zdni 2. 1. %{ 2. 3. #include <stdio.h> 4. 5. int ignore = 0; 6. 7. %} 8. 9. numer [1-9][0-9]* 10. id [A-Z-z][A-Z-z0-9]* 11. comment "//" 12. input "INPUT" 13. output "OUTPUT" 14. dd "ADD" 15. sut "SUBT" 16. lod "LOAD" 17. store "STORE" 18. cler "CLEAR" 19. hlt "HALT" 20. vr "VAR" 21. 22. %% 23. 24. {input} { if(!ignore) printf("czytnie z rejestru wejściowego\n"); } 25. {output} { if(!ignore) printf("pisnie do rejestru wyjściowego\n"); } 26. {dd} { if(!ignore) printf("dodwnie do kumultor\n"); } 27. {sut} { if(!ignore) printf("odejmownie od kumultor\n"); } 28. {lod} { if(!ignore) printf("wczytnie z pmieci\n"); } 29. {store} { if(!ignore) printf("zpisnie do pmieci\n"); } 30. {cler} { if(!ignore) printf("zerownie kumultor\n"); } 31. {hlt} { if(!ignore) printf("ztrzymnie progrmu\n"); } 32. {vr} { if(!ignore) printf("deklrcj zmiennej\n"); } 33. {id} { if(!ignore) printf("zmienn: %s\n",yytext); } 34. {numer} { if(!ignore) printf("dres: %s\n",yytext); } 35. {comment} { if(!ignore) printf("komentrz\n"); ignore = 1; } 36. \n { if(ignore) ignore = 0; } 37.. { /* pomijj wszystkie niedopsowne znki */ } 38. 39. %% 40. 41. int min() { 42. printf("pocztek sknowni...\n"); 43. yylex(); 44. printf("\nkoniec sknowni\n"); 45. 46. return 0; 47. }