Zakładamy że symbolami gramatyki są pojedyńcze znaki. Uzupełnić poniższą gramatykę regułami tak by rozpoznawała zwykłe liczby całkowite w notacji dziesiętnej, liczby całkowite w notacji szesnastkowej, tak jak w C: `0x1ab' i liczby zmiennopozycyjne (te muszą zawierać kropkę dziesiętną, np: `.001', `100.' `3.1415').
liczba : liczba_zmiennopozycyjna | liczba_dziesietna | liczba_szesnastkowa ;Uwaga: Zakładamy że liczby dziesiętne i liczby zmiennopozycyjne nie zawierają zer na początku.
Symbolami rachunku zdań są zmienne (wszystkie zmienne są reprezentowane przez pojedynczy symbol), negacja, iloczyn, suma, implikacja i nawiasy. Negacja ma najwyższy priorytet, następnie iloczyn, potem suma. Implikacja ma najniższy priorytet. Iloczyn i suma są łaczne. Dla uniknięcia nieporozumień przyjmiemy że implikacja jest niełączliwa (tzn. wymaga jawnych nawiasów). Napisać gramatykę akceptującą poprawne składniowo zdania rachunku zdań (nie interesuje nas w tej chwili ich wartość logiczna).
Automat z stosem ma taśmę wejściową na której w danym momencie widzi jeden znak i po której przesuwa się tylko do przodu. Ma też stos na którym zapamiętuje stany i symbole. W danym momencie automat może wykonać jedną z trzech rodzajów operacji, mianowicie przesuniecie, redukcję lub stop. Przesunięcie zapamiętuje bieżący stan i bieżący symbol na stosie i przesuwa się do przodu po taśmie wejsciowej. Redukcje są związane z regułami gramatyki: logicznie redukcja zastępuje prawą stronę reguły przez lewą, tzn. jesli prawa stron redukcji ma długość k to odczytuje się ze stosu k par symboli i stanów. Ostatni odczytany stan staje się bieżącym stanem. Następnie automat przechodzi do nowego stanu na zależnie od bieżącego stanu (przed chwilą odczytanego ze stosu) i symbolu z lewej strony produkcji. Jeśli operacją jest stop to automat zatrzymuje się albo akceptując albo zgłaszając błąd. Poniższy pseudo kod ilustruje dzialanie automatu:
stan = poczatkowy; while (1) { znak = daj_znak(); switch(akcja(stan, znak)) { case przesuniecie: push(stan, znak); stan = przejscie(stan, znak); znak = daj_znak(); break; redukcja_1: for(i=0; i < dlugosc1; i++) { (stan, _) = pop(); } push(stan, nonterminal1); stan = przejscie(stan, nonterminal1); break; redukcja_2: ... stop_błąd: ... stop_akceptuj: goto koniec; } } koniec:Dla wyrażeń arytmetycznych tabelę przejść można zbudować w następujący sposób. Ciąg znaków (symboli) tworzących wyrażenie uzupełniamy dodatkowym symbolam znacznika końca. Stany kodują informację o tym czy ostatnio widziane podwyrażenie jest kompletne i o operatorze. Jeśli na wejściu jest zmienna a stan odpowiada niekompletnemu podwyrażeniu to w tabeli 'akcja' mamy przesunięcie, zaś stan zmienia się na kompletne podwyrażenie (z tym samym operatorem). Jeśli na wejściu jest operator zaś stan odpowiada kompletnemu podwyrażeniu to porównujemy priorytety operatorów, tego ze stanu i tego widzianego na wejściu. Jeśli operator ze stanu ma wyższy lub równy priorytet to wykonujemy redukcję (pobieramy trzy pozycje ze stosu), jeśli niższy to przesunięcie i przechodzimy do stanu odpowiadającego przesuniętemu operatorowi i niekompletnemu wyrażeniu. Jeśli na wejściu jest nawias otwierający, zaś stan odpowiada niekompletnemu podwyrażeniu to przesuwamy, a odpowiadający mu stan traktujemy jako niekompletny i mający niższy priorytet od prawdziwych operatorów. Jeśli na wejściu mamy nawias zamykający zaś stan odpowiada kompletnemu prawdziwemu operatorowi to wykonujemy redukcję (pobieramy trzy pozycje ze stosu). Jeśli zaś stan odpowada "kompletnemu" nawiasowi to wykonujemy przesunięcie i przechodzimy do specjalnego stanu w którym niezależnie od wejścia wykonujemy redukcję. Jeśli na wejściu jest znacznik końca zaś stan to "kompletny" stan początkowy to akceptujemy, jeśli inny "kompletny" stan to wykonujemy redukcję. Redukcje zwykle zachowują stan podniesiony ze stosu, ale jeśli podniesiony stan to stan początkowy (traktujemy go jako niekompletny) to zmieniamy go na kompletny. Podobnie redukcja w stanie po nawiasie zamykającym zmienia niekompletny stan podniesiony ze stosu na kompletny. Nieopisane pozycje tabel albo są niemożliwe albo oznaczają błąd składni w wejściowym wyrażeniu. Zgodnie z powyższym opisem jawnie wypisać tabele 'akcja' i 'przejscie' tak by analizować wyrażenia arytmetyczne z operatorami '+', '*' i nawiasami. Odpowiada to gramatyce:
W0 : W EOF ; W : T | W '+' T ; T : P | T '*' P ; P : 'x' | '(' W ')' ;Gdzie 'x', '+', '*', '(', ')' i EOF są symbolami końcowymi, zaś W0, W, T i P są symbolami pomocniczymi, przy czym W0 jest symbolem początkowym.
Komentarz1: W powyższym automacie zapisywanie na stosie symboli może się wydawać zupełnie niepotrzebne. Ale łatwo zmodyfikować automant tak by produkował (być może spłaszczone) drzewo rozbioru i do tego symbole na stosie są niezbędne. Komentarz2: Metoda analizy syntaktycznej za pomocą automatów jest szeroko stosowana. Detale automatów mogą się różnić, różnią się też sposoby budowy tabel (zwylke tabele są generowane mechanicznie). Komatarz3: Dla wyrażeń metodę można trochę uprościć, bo nietrywialne zmiany stanu zależą tylko od operatorów i nawiasów a nie zależą od zmiennych.
Dla poniższej gramatyki wyznaczyć symbole wyprowadzające słowo puste. Symbolami końcowymi są ARRAY, CONST, ID, LICZBA, OF, TYPE i znaki pojawiające się w gramatyce.
deklaracje : stale typy ; stale : stale ';' stala | /* puste */ ; stala : CONST ID '=' LICZBA ; typy : typy typ | /* puste */ ; typ : TYPE ID '=' typ1 ; typ1 : ID | array '[' LICZBA ']' OF typ1 ;
Wyznaczyć zbiory `FIRST' i 'FOLLOW' dla reguł poniższej gramatyki (symbolem początkowym jest `S').
S : S 'x' | /* puste */Czy ta gramatyka jest LL(1). Dlaczego analizator rekursywny ma problem z tą gramatyką?
Wyznaczyć zbiory `FIRST' i 'FOLLOW' dla reguł poniższej gramatyki (symbolem początkowym jest `S').
S : T 'x' ; T : A | B ; A : 'a' | /* puste */ ; B : 'b' | /* puste */ ;Czy ta gramatyka jest LL(1)?
Zapisać w postaci diagramów syntaktycznych gramatykę zadaną przy pomocy poniższch reguł (symbolem początkowym jest `S').
S : I | B ; B : 'b' I2 'e' ';' ; I2 : /* puste */ | I2 I ';' I : 't' X | 'v' X | 'x' '=' E ';' | 'i' C 'd' I2 'e' | 'i' C 'd' I2 'o' I2 'e' | 'w' C 'd' I2 'e' ; X : 'x' ( ',' 'x') * ';' ; E : 'x' ( '+' E )* ; C : E '=' EUwaga: reguły dla X i E to reguły EBNF. Komentarz: Dla skrócenia rachunków używam tylko jednoznakowych symboli końcowych, lecz można sobie wyobrażać że 'x' to dowolny identyfikator, 'b', 'd', 'e', 'i', 'o', 't', 'v' i 'w' to słowa kluczowe ("begin", "do", "end", "if", "otherwise", "type", "var" i "while" odpowiednio). Przerobienie tego na porządny język programowania pozostawiam Państwa wyobraźni...
Wyznaczyć zbiory `FIRST' i 'FOLLOW' dla gramatyki z zadania 7. Czy jest ona LL(1)?