Gramatyka regularna

Gramatyka regularna – gramatyka formalna, za pomocą której można opisać język regularny.

Istnieją dwa rodzaje gramatyk regularnych: gramatyka lewostronna, gramatyka prawostronna. Istnieje ścisły związek gramatyki lewostronnej oraz deterministycznego automatu skończonego, taki że gramatyka generuje dokładnie taki język jaki akceptuje automat. Stąd gramatyki lewostronne generują dokładnie wszystkie języki regularne.

Gramatyka regularna to albo prawo albo lewostronna.

Ważne ograniczenia postaci reguł

  • Po lewej stronie występuje zawsze dokładnie jeden symbol nieterminalny (tak samo jak w gramatykach bezkontekstowych)
  • Po prawej stronie występuje nie więcej niż jeden symbol nieterminalny i dowolny łańcuch symboli terminalnych. W gramatykach prawostronnie regularnych symbol terminalny występuje przed nieterminalnym (jeśli ten się tam znajduje), a w lewostronnie regularnych na odwrót[1].

Przykłady

poprawne reguły

A A {\displaystyle A\to A}
A B {\displaystyle A\to B}
A a A {\displaystyle A\to aA}
A a B {\displaystyle A\to aB}
A ϵ {\displaystyle A\to \epsilon }
A a {\displaystyle A\to a}
A C a {\displaystyle A\to Ca} (reguła z gramatyki lewostronnie regularnej)

reguły niepoprawne

A B c D {\displaystyle AB\to cD} (dwa symbole nieterminalne z lewej strony)
A B C {\displaystyle A\to BC} (dwa symbole nieterminalne z prawej strony)

Przykład gramatyki

Jako przykład z zakresu języków programowania może posłużyć gramatyka opisująca ciągi znaków będące zapisem liczb zmiennoprzecinkowych. Poniżej przedstawiono gramatykę G ze zbiorem reguł N = {S,A,B,C,D,E,F} i alfabetem Σ = {0,1,2,3,4,5,6,7,8,9,+,-,.,e}. Symbol S jest symbolem startowym, zaś przez ε oznaczamy ciąg pusty.

S → +A       A → 0A       B → 0C       C → 0C       D → +E       E → 0F       F → 0F
S → -A A → 1A B → 1C C → 1C D → -E E → 1F F → 1F
S → A A → 2A B → 2C C → 2C D → E E → 2F F → 2F
A → 3A B → 3C C → 3C E → 3F F → 3F
A → 4A B → 4C C → 4C E → 4F F → 4F
A → 5A B → 5C C → 5C E → 5F F → 5F
A → 6A B → 6C C → 6C E → 6F F → 6F
A → 7A B → 7C C → 7C E → 7F F → 7F
A → 8A B → 8C C → 8C E → 8F F → 8F
A → 9A B → 9C C → 9C E → 9F F → 9F
A →.B C → eD F → ε
A → B C → ε

Łączenie gramatyk

Za pomocą łączenia prawo i lewostronnych gramatyk można tworzyć języki, które niekoniecznie będą regularne, z definicji jednak, tego typu gramatyki nie będą już regularne.

Zaostrzanie zasad tworzenia reguł

Reguły te można zaostrzyć bez straty mocy, pozwalając na co najwyżej jeden symbol terminalny z prawej strony. Wprowadza się w tym celu kilka stanów pomocniczych P i , {\displaystyle P_{i},} i każdą regułę postaci:

A c 1 c 2 c 3 c k B {\displaystyle A\to c_{1}c_{2}c_{3}\cdots c_{k}B}

Można przepisać do zestawu reguł:

A c 1 P 1 {\displaystyle A\to c_{1}P_{1}}
P 1 c 2 P 2 {\displaystyle P_{1}\to c_{2}P_{2}}
P 2 c 3 P 3 {\displaystyle P_{2}\to c_{3}P_{3}}
{\displaystyle \vdots }
P k 1 c k B {\displaystyle P_{k-1}\to c_{k}B}

Analogicznie przepisuje się reguły postaci:

A c 1 c 2 c 3 c k {\displaystyle A\to c_{1}c_{2}c_{3}\cdots c_{k}}

Drugim często nakładanym ograniczeniem jest zabranianie reguł które zmieniają stan bez czytania żadnych symboli:

A B {\displaystyle A\to B}

Algorytm pozbywania się ich jest następujący:

  • Wybieramy jedną regułę postaci A B , {\displaystyle A\to B,} której nie oznaczyliśmy jeszcze jako zbędnej
  • Dla każdej reguły postaci B Γ , {\displaystyle B\to \Gamma ,} dodajemy do zbioru reguł regułę postaci A Γ , {\displaystyle A\to \Gamma ,} chyba że taka już istnieje
  • Jeśli B było akceptujące, A natomiast nie było, zaznaczamy A jako akceptujące
  • Zaznaczamy wybraną regułę jako zbędną – każde słowo w którego wyprowadzeniu były użyte reguły A B Γ {\displaystyle A\to B\to \Gamma } możemy teraz wyprowadzić samym A Γ . {\displaystyle A\to \Gamma .} Jeśli natomiast wyprowadzenie słowa kończyło się A B , {\displaystyle A\to B,} gdzie B było akceptujące, możemy to wyprowadzenie zakończyć na A , {\displaystyle A,} które teraz również jest akceptujące.
  • Jeśli wszystkie reguły postaci A B {\displaystyle A\to B} są zaznaczone jako zbędne, usuwamy je i kończymy. Jeśli nie, wykonujemy kolejną iterację.

Zostają wtedy jedynie reguły postaci:

A a {\displaystyle A\to a}
A a B {\displaystyle A\to aB}
A ϵ {\displaystyle A\to \epsilon }

Z takich reguł bardzo łatwo już przejść do niedeterministycznego automatu skończonego.

Łagodzenie zasad budowania reguł

Można też pozwolić na łagodniejsze reguły – kierując się w stronę wyrażeń regularnych. Bez zmiany mocy gramatyk regularnych wolno dodać alternatywę, przy czym na symbole nieterminalne po prawej stronie nakłada się ograniczenie, że muszą one się znajdować na końcu dowolnej ścieżki wyboru alternatyw, np.:

A a ( B | a a ( C | b D ) ) {\displaystyle A\to a(B|aa(C|bD))}

Przejście do tradycyjnych gramatyk regularnych dokonuje się rozbijając każdą alternatywę na parę regułek, aż pozbędziemy się ostatniej:

A a B {\displaystyle A\to aB}
A a a a ( C | b D ) {\displaystyle A\to aaa(C|bD)}
A a a a C {\displaystyle A\to aaaC}
A a a a b D {\displaystyle A\to aaabD}

Można też dodać gwiazdkę, oznaczającą powtórzenie fragmentu dowolnie wiele razy, o ile wewnątrz niej znajdują się tylko symbole terminalne, np.:

A a a ( a b ) b b ( a ) B {\displaystyle A\to aa(ab)^{*}bb(a)^{*}B}

Każdej gwiazdki pozbywa się wprowadzając nieterminalnych symbol pomocniczy P , {\displaystyle P,} i dla regułki postaci A α β γ {\displaystyle A\to \alpha \beta ^{*}\gamma } tworzymy regułki:

A α P {\displaystyle A\to \alpha P}
P β P {\displaystyle P\to \beta P}
P γ {\displaystyle P\to \gamma }

Na przykład:

A a a ( a b ) b b ( a ) B {\displaystyle A\to aa(ab)^{*}bb(a)^{*}B}
A a a P 1 {\displaystyle A\to aaP_{1}}
P 1 a b P 1 {\displaystyle P_{1}\to abP_{1}}
P 1 b b P 2 {\displaystyle P_{1}\to bbP_{2}}
P 2 a P 2 {\displaystyle P_{2}\to aP_{2}}
P 2 B {\displaystyle P_{2}\to B}

Używając (z podanymi wyżej ograniczeniami) gwiazdki i alternatywy możemy mieszać gramatyki regularne i wyrażenia regularne. Można też dla każdej gramatyki regularnej zbudować odpowiadające jej wyrażenie regularne. Konstrukcja jest następująca:

  • Przepisujemy gramatykę z użyciem alternatywy, tak żeby dla każdego symbolu nieterminalnego o regułkach A Γ 1 , {\displaystyle A\to \Gamma _{1},} A Γ 2 , . . . , {\displaystyle A\to \Gamma _{2},...,} A Γ k {\displaystyle A\to \Gamma _{k}} pozostała tylko jedna regułka A Γ 1 | Γ 2 | | Γ k . {\displaystyle A\to \Gamma _{1}|\Gamma _{2}|\cdots |\Gamma _{k}.}
  • Wybieramy jeden symbol nieterminalny, oprócz symbolu startowego:
    • Jeśli w jego definicji znajduje się odwołanie do niego samego, przestawiamy te alternatywy do postaci A ( ( Γ 1 | | Γ k ) A ) | ( Γ k + 1 | | Γ n ) {\displaystyle A\to ((\Gamma _{1}|\cdots |\Gamma _{k})A)|(\Gamma _{k+1}|\cdots |\Gamma _{n})} i zamieniamy jego definicję na A ( Γ 1 | | Γ k ) ( Γ k + 1 | | Γ n ) {\displaystyle A\to (\Gamma _{1}|\cdots |\Gamma _{k})^{*}(\Gamma _{k+1}|\cdots |\Gamma _{n})}
    • Skoro w definicji A nie ma już żadnych odwołań do samego siebie, podstawiamy definicję A w miejsce wszystkich wystąpień A w innych regułkach. Otrzymujemy w ten sposób układ zawierający o jeden symbol mniej.
  • Na końcu zostaje nam tylko symbol startowy. Jeśli zawiera odwołanie do samego siebie, musimy go przekształcić zgodnie z tą samą procedurą.
  • Gramatyka jest postaci S wyrażenie regularne {\displaystyle S\to {\mbox{wyrażenie regularne}}}

Na przykład weźmy gramatykę słów nad alfabetem { a , b } {\displaystyle \{a,b\}} nie zawierających podciągu b a b a . {\displaystyle baba.} Gramatyka taka może mieć postać:

S a S {\displaystyle S\to aS} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło
S b P b {\displaystyle S\to bP_{b}} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło, podejrzany prefiks to b {\displaystyle b}
P b b P b {\displaystyle P_{b}\to bP_{b}} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło, podejrzany prefiks to b {\displaystyle b}
P b a P b a {\displaystyle P_{b}\to aP_{ba}} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło, podejrzany prefiks to b a {\displaystyle ba}
P b a b P b a b {\displaystyle P_{ba}\to bP_{bab}} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło, podejrzany prefiks to b a b {\displaystyle bab}
P b a a S {\displaystyle P_{ba}\to aS} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło
P b a b b P b {\displaystyle P_{bab}\to bP_{b}} – jak dotąd b a b a {\displaystyle baba} nie wystąpiło, podejrzany prefiks to b {\displaystyle b}
S ϵ {\displaystyle S\to \epsilon } – możemy skończyć jeśli nie ma podejrzanego prefiksu
P b ϵ {\displaystyle P_{b}\to \epsilon } – możemy skończyć jeśli podejrzany prefiks to b {\displaystyle b}
P b a ϵ {\displaystyle P_{ba}\to \epsilon } – możemy skończyć jeśli podejrzany prefiks to b a {\displaystyle ba}
P b a b ϵ {\displaystyle P_{bab}\to \epsilon } – możemy skończyć jeśli podejrzany prefiks to b a b {\displaystyle bab}

Można ją przepisać do układu równań gramatycznych:

S a S | b P b | ϵ {\displaystyle S\to aS|bP_{b}|\epsilon }
P b b P b | a P b a | ϵ {\displaystyle P_{b}\to bP_{b}|aP_{ba}|\epsilon }
P b a b P b a b | a S | ϵ {\displaystyle P_{ba}\to bP_{bab}|aS|\epsilon }
P b a b b P b | ϵ {\displaystyle P_{bab}\to bP_{b}|\epsilon }

Łatwo można się pozbyć P b a b : {\displaystyle P_{bab}{:}}

S a S | b P b | ϵ {\displaystyle S\to aS|bP_{b}|\epsilon }
P b b P b | a P b a | ϵ {\displaystyle P_{b}\to bP_{b}|aP_{ba}|\epsilon }
P b a b ( b P b | ϵ ) | a S | ϵ {\displaystyle P_{ba}\to b(bP_{b}|\epsilon )|aS|\epsilon }

I P b a : {\displaystyle P_{ba}{:}}

S a S | b P b | ϵ {\displaystyle S\to aS|bP_{b}|\epsilon }
P b b P b | a ( b ( b P b | ϵ ) | a S | ϵ ) | ϵ {\displaystyle P_{b}\to bP_{b}|a(b(bP_{b}|\epsilon )|aS|\epsilon )|\epsilon }

P b {\displaystyle P_{b}} musiby sprowadzić do wygodnej postaci:

P b ( ( b | a b b ) P b ) | ( a b | a a S | a | ϵ ) {\displaystyle P_{b}\to ((b|abb)P_{b})|(ab|aaS|a|\epsilon )}

I pozbyć się odniesienia do siebie samego:

P b ( b | a b b ) ( a b | a a S | a | ϵ ) {\displaystyle P_{b}\to (b|abb)^{*}(ab|aaS|a|\epsilon )}

Możemy teraz podstawić do S : {\displaystyle S{:}}

S a S | b ( b | a b b ) ( a b | a a S | a | ϵ ) | ϵ {\displaystyle S\to aS|b(b|abb)^{*}(ab|aaS|a|\epsilon )|\epsilon }

Teraz już tylko grupujemy samo-odniesienia:

S ( a | b ( b | a b b ) a a ) S | ( b ( b | a b b ) ( a b | a | ϵ ) | ϵ ) {\displaystyle S\to (a|b(b|abb)^{*}aa)S|(b(b|abb)^{*}(ab|a|\epsilon )|\epsilon )}

I usuwamy je:

S ( a | b ( b | a b b ) a a ) ( b ( b | a b b ) ( a b | a | ϵ ) | ϵ ) {\displaystyle S\to (a|b(b|abb)^{*}aa)^{*}(b(b|abb)^{*}(ab|a|\epsilon )|\epsilon )}

W ten sposób mechanicznie stworzyliśmy wyrażenie regularne, którego ręczne zbudowanie byłoby znacznie trudniejsze.

Przypisy

  1. {title} [online], kompilatory.agh.edu.pl [dostęp 2017-11-23] .
  • p
  • d
  • e
Teoria automatów: języki formalne i gramatyki formalne
Hierarchia Chomsky’ego
  • Typ 0
  • Typ 1
  • Typ 2
  • Typ 3
Gramatyka formalna
  • kombinatoryczna
  • kontekstowa
  • bezkontekstowa
  • deterministyczna bezkontekstowa
  • regularna
Język formalny
Minimalny automat akceptujący