Zajęcia 6
Transkrypt
Zajęcia 6
Pr. 5 (indywidualny). Analizator języków znacznikowych (Flex + Bison/Linux) 1. Cel ćwiczeń Celem ćwiczeń jest stworzenie analizatora składniowego z elementami analizy semantycznej dla języków znacznikowych opartych o standard XML. W czasie ćwiczenia rozważana będzie uproszczona wersja metajęzyka bazująca na Canonical XML v.1.0. Cytowane w opisie reguły gramatyki (oraz ich numeracja) pochodzą ze specyfikacji XML (http://www.w3.org/TR/2006/REC-xml-20060816). UWAGA! Elementów wyszarzonych w regułach produkcji nie należy implementować! 3. Analiza leksykalna UWAGA! Dla każdego wykrywanego tokena zwracany jest jego typ (zadeklarowany w pliku xml.y) oraz wartość poprzez zmienną yylval. Jeżeli typ zmiennej yylval jest unią z polem tekstowym s, instrukcje przypisywania wartości mogą mieć postać: strcpy( yylval.s, yytext + i); gdzie i jest liczbą naturalną umożliwiającą pominięcie początkowych i znaków w buforze yytext. Należy zwrócić uwagę, aby typ tokena zwracany był dopiero po napotkaniu sekwencji znaków kończących (np. „>”). Wartość tokena w zmiennej yylval może być ustalana we wcześniejszych wyrażeniach! A) Definicja klas znaków oraz tokenów XML Klasa znaków Oznaczenie Definicja [a-zA-Z_:] znaki rozpoczynające alphaXML nazwę XML [a-zA-Z0-9_:.-] znaki dozwolone w alphanumXML nazwie XML identXML {alphaXML}{alphanumXML}* identyfikator XML S [ \r\n\t]+ białe znaki [^<>&'"\n] znaki ASCII bez charXML znaków specjalnych B) Eliminacja komentarzy z wykorzystaniem mechanizmu stanów (ST_COMMENT). C) Wykrywanie błędów niezamkniętego komentarza w funkcji yywrap(). D) Wykrywanie błędów nieoczekiwanego zamknięcia komentarza. Komentarze mogą występować w dowolnym miejscu dokumentu XML poza znacznikami. Zaczynają się sekwencją „<!--” i kończą sekwencją znaków „-->”. Komentarze mogą zawierać dowolną liczbę dowolnych znaków, jednak bezpośrednio przed zakończeniem nie może znajdować się "-". [15] <Comment> ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->' E) Wykrywanie otwarcia i zamknięcia instrukcji przetwarzania (PI_TAG_BEG i PI_TEG_END) z wykorzystaniem mechanizmu stanów (ST_PI). UWAGA! Pomijamy zawartość instrukcji przetwarzania, w związku z czym przyjmuje ona uproszczoną postać (Name = identXML): [!!] PROC_INSTR ::= '<?' Name {dowolna, pomijana zawartość} '?>' Każda instrukcja przetwarzania ograniczona jest specjalnymi znacznikami „<?” oraz „?>”. Bezpośrednio po znaczniku rozpoczynającym musi wystąpić oznaczenie rodzaju instrukcji przetwarzania. Po niej występuje zawartość, której format zależy od rodzaju instrukcji sterującej. Dla wielu rodzajów instrukcji sterujących, zawartością jest lista parametrów postaci <identyfikator> Eq "wartość". F) Wykrywanie otwarcia znacznika początkowego (STAG_BEG) i pustego (ETAG_BEG) oraz ich zamknięcia (TAG_END) z wykorzystaniem mechanizmu stanów (ST_STAG). UWAGA! Pomijamy atrybuty znaczników, w związku z czym znaczniki przyjmują uproszczoną postać: [40] START_TAG ::= '<' Name {pomijane atrybuty: (S Attribute)* S?} '>' [44] EMPTY_TAG ::= '<' Name {pomijane atrybuty: (S Attribute)* S?} '/>' G) Wykrywanie otwarcia i zamknięcia znacznika końcowego (ETAG_BEG i EMPTY_TAG_END) z wykorzystaniem mechanizmu stanów (ST_ETAG). Znacznik końcowy posiada następującą postać: [42] END_TAG ::= '</' Name S? '>' Znaczniki początkowe oraz puste rozpoczynają się symbolem „<”, a znacznik końcowy rozpoczyna się sekwencją „</”. Następnie, musi bezpośrednio wystąpić nazwa znacznika (Name). Po nazwie może wystąpić lista atrybutów. Znacznik początkowy i końcowy kończą się znakiem „>”, podczas gdy znacznik pusty zakończony jest sekwencją „/>”. H) Wykrywanie w funkcji yywrap() błędów niezamkniętych znaczników 3. Analiza składniowa hierarchicznej struktury dokumentu XML (bez uwzględniania atrybutów znaczników) A) Globalna struktura dokumentu (fragmenty nieobowiązkowe zostały wyszarzone) Dokument w językach XML składa się z zalecanego wstępu (PROLOG), elementu głównego (ELEMENT) i opcjonalnych dodatków (MISC): [1] document ::= prolog? element Misc* B) Wstęp – należy wypisać informację o każdej znalezionej instrukcji przetwarzania (bez zawartości, np. „<?xml?>”) Wstęp zawiera listę (XML_PI_LIST) instrukcji przetwarzania (XML_PI), z których pierwsza (powinna zawsze wystąpić) musi zawierać oznaczenie rodzaju dokumentu oraz numeru jego wersji (np. <?xml version="1.0" encoding="utf-8"?>). [22] prolog ::= <XMLDecl>? <Misc>* (doctypedecl Misc*)? [27] Misc ::= Comment | PI | S C) Element główny - należy wypisać informację o każdym znalezionym znaczniku (bez atrybutów, np. „<element1>”, „<element2/>” lub „</element1”) Dokument XML musi zawierać przynajmniej jeden element (ELEM), który może być korzeniem pewnej hierarchicznej struktury drzewiastej. Element może występować w postaci pary znaczników (TAG_PAIR) z pewną zawartością (CONTENT) pomiędzy nimi. Element może być również pojedynczym, pustym znacznikiem (EMPTY_TAG), z którym nie jest związana żadna zawartość. [39] ELEMENT ::= EMPTY_TAG | TAG_PAIR D) Treść (CONTENT) Pomiędzy znacznikiem początkowym i końcowym pary może wystąpić treść (CONTENT) zawierająca listę wystąpień: tekstu, elementów (ELEMENT) niższego poziomu (rekurencja), instrukcji przetwarzania (PROC_INSTR) oraz innych elementów. [43] CONTENT CharData?)* ::= CharData? ((ELEMENT | CDSect | PI | Comment) 4. Analiza semantyczna i przetwarzanie dokumentu A) Analiza zgodności znaczników w parach znaczników początkowych i końcowych B) Wypisywanie znaczników dokumentu z wcięciami odzwierciedlającymi stopień zagnieżdżenia tekstu i znaczników. Znaczniki końcowe powinny rozpoczynać się w tej samej kolumnie co początkowe. 5. Zadania dodatkowe A) Wykrywanie tekstu pomiędzy znacznikami i jego wypisywanie. Tekst powinien być wypisywany z wcięciami odpowiadającymi poziomowi zagłębienia znaczników. Powinien zaczynać się od nowego wiersza. Nie powinien wychodzić poza 78. kolumnę tekstu – należy wtedy resztę tekstu umieścić w nowym wierszu. B) Poprawne wypisywanie znaków specjalnych zgodnie z poniższą tabelą: znak kod znak kod < < ' ' > > ” "e; & & C) Wykrywanie (an.leks.) oraz interpretowanie (an. skł.) atrybutów znaczników D) Analiza składniowa instrukcji przetwarzania definiującej rodzaj i wersję dokumentu. [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' [24] VersionInfo ::= S 'version' Eq (''1.0'' | '"1.0"') [25] Eq ::= S? '=' S? E) Wykrywanie znaczników specjalnych „<!” Name .... „>”. 7. Przykładowe pliki XML Wejściowy plik XML <!—- Program testowy 1 --> <?xml version="1.0" encoding="utf-8"?> <Root> <!—- element główny --> <Elem1>A</Elem1> <!—- element 1 --> <Elem2/> <!—- element 2 --> <Elem3>B</Elem3> <!—- element 2 --> </Root> <!—- Program testowy 2 --> <?xml version="1.0" encoding="utf-8"?> <Root> <!—- element główny --> <Elem1>A<?spec test?>Aprim</Elem1> <Elem2/> <!—- element 2 --> <Elem3>B<Elem4>C<elem5/>D</Elem4>E </Elem3> <!—- element 2 --> </Root> <?xml version = "1.0" encoding ="UTF-8" standalone = "no"?> <!DOCTYPE PROCENTRIES SYSTEM "proc.dtd"> <?xml-stylesheet type="text/xsl" href="1.xsl"?> <PROCENTRIES> <!-- Hardware / CDROM --> <PROCENTRY> <MENU>Hardware</MENU> <SUBMENU>CDROM</SUBMENU> <TAB>CDROM settings</TAB> <WIDGETTEXT>Close door on mount</WIDGETTEXT> <CONFIGNAME>dev/cdrom/autoclose</CONFIGNAME> <TYPE>Checkbox</TYPE> Wyjście analizatora <?xml?> <Root> <Elem1>A </Elem1> <Elem2/> <Elem3>B </Elem3> </Root> <?xml?> <Root> <Elem1>A <?spec?> Aprim </Elem1> <Elem2/> <Elem3>B <Elem4>C <Elem5/>D </Elem4>E </Elem3> </Root> <?xml?> <!DOCTYPE> <?xml?> <PROCENTRIES> <PROCENTRY> <MENU>Hardware </MENU> <SUBMENU>CDROM </SUBMENU> <TAB>CDROM settings </TAB> <WIDGETTEXT>Close door on mount </WIDGETTEXT> <CONFIGNAME>dev/cdrom/autoclose <ELEMENT>0</ELEMENT> <FILE>/proc/sys/dev/cdrom/autoclose</FILE> <DESCRIPTION>Close on mount: Setting this option will cause the kernel to close the door of the CDROM-drive ... drive.</DESCRIPTION> </PROCENTRY> </PROCENTRIES> </CONFIGNAME> <TYPE>Checkbox </TYPE> <ELEMENT>0 </ELEMENT> <FILE>/proc/sys/dev/cdrom /autoclose </FILE> <DESCRIPTION>Close on mount: Setting this option will cause the kernel to close the door of the CDROM-drive ... drive. </DESCRIPTION> </PROCENTRY> </PROCENTRIES> 8. Uwagi odnośnie realizacji zadania 7 A) Największe kłopoty sprawia dopasowanie znaczników początkowych i końcowych. Typowe błędy można wykryć np. usuwając ostatni znacznik końcowy lub dodając jego kopię. Istnieją dwa podstawowe sposoby uzyskania dopasowania znaczników. Pierwszy sposób polega na wprowadzeniu zmiennej składniowej odpowiadającej elementowi i sprawdzaniu, czy wartość zwracana przez znacznik początkowy jest taka sama, jak zwracana przez znacznik końcowy. W tym rozwiązaniu dopasowanie odpowiednich znaczników następuje automatycznie – jest sterowane składnią. Inny, trudniejszy sposób polega na utworzeniu jawnego stosu identyfikatorów znaczników. Każdy identyfikator znacznika otwierającego dany element jest odkładany na stos i zdejmowany oraz porównywany z identyfikatorem znacznika końcowego. B) Tekst pomiędzy znacznikami nie musi być ciągiem identyfikatorów. Ponieważ identyfikator znacznika nie jest dowolnym tekstem, można wykorzystać stany analizatora leksykalnego do wykrywania kontekstu wewnątrz znacznika (wykrywamy identyfikatory) lub poza znacznikiem (wykrywamy wolny tekst). Po pojawieniu się ciągu znaków (w szczególności jednego znaku) rozpoczynającego znacznik przechodzimy do stanu wykrywania identyfikatora znacznika. Po wykryciu identyfikatora przechodzimy do rozpoznawania dowolnego tekstu, który jednak nie może zawierać znaków końca znacznika. Po wykryciu końca znacznika przechodzimy do stanu początkowego. Wykrywanie niedopasowanych cudzysłowów też najłatwiej dokonać z użyciem dodatkowych stanów, ale można to też zrobić dopasowując wzorce poprawnego i niepoprawnego napisu. C) Wcięcia można dokonywać na poziomie analizy leksykalnej (wykrywając przejście do nowego wiersza i dodając wcięcia) lub na poziomie analizy składniowej (preferowany sposób). Do obsługi wcięć potrzebna jest zmienna określająca poziom zagnieżdżenia. D) W systemach Linux mnóstwo przykładów plików w języku XML można znaleźć wydając polecenie locate *xml lub, jeśli baza danych polecenia locate nie jest aktualizowana, za pomocą polecenia find /etc -type f -name \*.xml print 2>/dev/null. Powtórzenie informacji o stanach: Deklaracja stanów (w pierwszej sekcji): %x stan1 stan2 ... Rozpatrywanie wyrażenia wyłącznie w jednym stanie: <stan1>wyr_reg ... Rozpatrywanie wyrażenia w 2 stanach: <stan1,stan2>wyr_reg ... Rozpatrywanie wyrażenia we wszystkich stanach: <stan1,stan2>wyr_reg ... Przejście do innego stanu: <stan1>wyr_reg {akcja; BEGIN stan2; } Stan początkowy: INITIAL (nie trzeba go deklarować) Zmienna zawierająca aktualny stan: YYSTATE