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
<
&lt;
'
&apos;
>
&gt;
”
&quote;
&
&amp;
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