NAZWA¶
flex - szybki generator analizatora leksykalnego
SKŁADNIA¶
flex [-bcdfhilnpstvwBFILTV78+? -C[aefFmr] -ooutput -Pprefix
-Sskeleton] [--help --version] [filename ...]
WPROWADZENIE¶
Uwaga! To tłumaczenie może być nieaktualne!
Podręcznik ten opisuje narzędzie
flex. Jest ono przeznaczone do
generowania programów, dokonywujących dopasowywania wzorców na
tekście. Podręcznik zawiera zarówno sekcje przewodnikowe jak i
informacyjne.
Opis
krótki przegląd możliwości narzędzia
Proste Przykłady
Format Pliku Wejściowego
Wzorce
rozszerzone wyrażenia regularne używane przez flex
Sposób Dopasowywania Wejścia
reguły określania, co dopasowano
Akcje
jak podawać, co robić po dopasowaniu wzorca
Generowany Skaner
szczegóły o skanerze, tworzonym przez fleksa; jak kontrolować źródło
wejściowe
Warunki Startowe
wprowadzanie do skanerów kontekstu i obsługa "mini-skanerów"
Wielokrotne Bufory Wejściowe
jak obsługiwać wiele źródeł wejściowych; jak skanować z łańcuchów
zamiast z plików
Reguły Końca Pliku
specjalne reguły dopasowywane do końca wejścia
Różne Makra
ogół makr dostępnych z poziomu akcji
Wartości Dostępne Użytkownikowi
ogół wartości dostępnych z poziomu akcji
Łączenie z Yacc
łączenie skanerów flex z analizatorami yacc
Opcje
opcje linii poleceń fleksa i dyrektywa "%option"
Kwestie wydajnościowe
jak przyspieszać skanery
Generowanie Skanerów C++
eksperymentalna właściwość generowania klas skanerów C++
Niezgodności z Lex i POSIX
czym flex różni się od standardów AT&T lex i POSIX lex
Diagnostyka
objaśnienie komunikatów o błędach, generowanych przez flex (lub
skanery)
Pliki
pliki używane przez flex
Niedostatki / Błędy
znane problemy fleksa
Zobacz Także
pozostała dokumentacja i związane z fleksem narzędzia
Autor
informacja kontaktu z autorem
OPIS¶
flex jest narzędziem przeznaczonym do generowania
skanerów: programów, rozpoznających wzorce leksykalne
tekstu.
flex odczytuje podane pliki wejściowe (lub stdin gdy nie
są podane) i pobiera z nich opis generowanego skanera. Opis składa
się z par wyrażeń regularnych i kodu C. Pary te nazywane
są
regułami.
flex jako wyjście generuje plik
źródłowy C o nazwie
lex.yy.c. Definiuje on funkcję
yylex(). Plik ten musi kompilowany i konsolidowany z biblioteką
-lfl. Po uruchomieniu pliku wykonywalnego, program analizuje
wejście w poszukiwaniu wyrażeń regularnych. Gdy tylko takie
się znajdzie, wykonywany jest odpowiedni fragment kodu C.
PROSTE PRZYKŁADY¶
Przedstawmy teraz trochę prostych przykładów aby obyć
się z używaniem
flex. Następujący plik
wejściowy
flex określa skaner, który za każdym
razem gdy napotka łańcuch "username", podmieni go
nazwą użytkownika:
%%
username printf( "%s", getlogin() );
Domyślnie tekst, którego
flex nie może dopasować jest
kopiowany na wyjście. Skaner będzie więc kopiował
swój plik wejściowy na wyjście, podmieniając wszelkie
pojawienia "username". W tym przykładzie wejścia mamy
tylko jedną regułę. Wzorcem jest "username", a
akcją jest "printf". Znaki "%%" oznaczają
początek reguł.
Oto kolejny prosty przykład:
int num_lines = 0, num_chars = 0;
%%
\n ++num_lines; ++num_chars;
. ++num_chars;
%%
main()
{
yylex();
printf( "# of lines = %d, # of chars = %d\n",
num_lines, num_chars );
}
Ten skaner zlicza liczbę znaków i liczbę linijek swojego
wejścia (nie daje żadnego wyjścia, nie licząc
końcowego raportu). Pierwsza linia deklaruje dwie zmienne globalne,
"num_lines" i "num_chars", które są
dostępne wewnątrz funkcji
yylex() i
main(),
zadeklarowanej po drugim "%%". Mamy tu dwie reguły: pierwsza
dopasowuje się do nowej linii ("\n") i inkrementuje licznik
linii oraz znaków; druga dopasowuje się do dowolnego znaku innego
niż nowa linia (wyrażenie regularne ".") i zwiększa
licznik liczby znaków.
A oto trochę bardziej skomplikowany przykład:
/* skaner dla zabawkowego Pascalo-podobnego języka */
%{
/* potrzebujemy tego do wywołania atof() */
#include <math.h>
%}
DIGIT [0-9]
ID [a-z][a-z0-9]*
%%
{DIGIT}+ {
printf( "Liczba całkowita: %s (%d)\n", yytext,
atoi( yytext ) );
}
{DIGIT}+"."{DIGIT}* {
printf( "Liczba zmiennoprzecinkowa: %s (%g)\n", yytext,
atof( yytext ) );
}
if|then|begin|end|procedure|function {
printf( "Słowo kluczowe: %s\n", yytext );
}
{ID} printf( "Identyfikator: %s\n", yytext );
"+"|"-"|"*"|"/" printf( "Operator: %s\n", yytext );
"{"[^}\n]*"}" /* zjedz jednolinijkowe komentarze */
[ \t\n]+ /* zjedz białe spacje */
. printf( "Nierozpoznany znak: %s\n", yytext );
%%
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* pomiń nazwę programu */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
yylex();
}
Są to początki prostego skanera dla języka podobnego do Pascala.
Rozróżnia poszczególne rodzaje
tokenów i informuje
co zobaczył.
Szczegóły tego przykładu zostaną wyjaśnione w
następnych sekcjach.
Plik wejściowy
fleksa składa się z trzech sekcji,
rozdzielanych liniami z łańcuchem
%%:
definicje
%%
reguły
%%
kod użytkownika
Sekcja
definicji zawiera definicje prostych
nazw,
upraszczających później specyfikację skanera. Zawiera
też deklaracje
warunków początkowych, które
objaśniono w dalszej sekcji.
Definicje nazw mają postać:
nazwa definicja
gdzie "nazwa" jest słowem, rozpoczynającym się od
litery lub podkreślenia ('_'). Pozostałe znaki mogą być
literami, cyframi, podkreśleniami lub myślnikami. Definicja jest
pobierana od momentu pojawienia się pierwszego znaku, który nie jest
spacją i który znajduje się za nazwą. Definicja
rozciąga się do końca linii. Do takiej definicji można
się następnie odwoływać przy użyciu konwencji
"{nazwa}", która jest automatycznie rozwijana w
"(definicję)". Na przykład
DIGIT [0-9]
ID [a-z][a-z0-9]*
definiuje "DIGIT" jako wyrażenie regularne, pasujące do
pojedynczej cyfry, a "ID" jako wyrażenie regularne
odpowiadające literze z doklejonymi ewentualnymi literami lub cyframi.
Późniejsze odniesienie do
{DIGIT}+"."{DIGIT}*
jest równoważne
([0-9])+"."([0-9])*
i dopasowuje jedną lub więcej cyfr, po których występuje
kropka i ewentualnie następne cyfry.
Sekcja
reguł wejścia
fleksa zawiera szereg reguł w
postaci:
wzorzec akcja
Przed wzorcem nie może wystąpić wcięcie, a akcja musi
rozpoczynać się w tej samej linii.
Dla dalszego opisu akcji patrz dalej.
W końcu, sekcja kodu użytkownika jest zwyczajnie kopiowana do
lex.yy.c (bez dokonywania w niej zmian). Jest to używane do
funkcji pomocniczych, które wołają lub są wołane
przez skaner. Obecność tej sekcji jest opcjonalna; jeśli nie
istnieje, to ostatni
%% pliku wejściowego może być
pominięty.
Jeśli w sekcjach definicji lub reguł znajduje się jakiś
wcięty (indentowany) tekst lub tekst ujęty w
%{ i
%}, to jest on kopiowany dosłownie na wyjście (po
usunięciu %{}). Znaki %{} muszą pojawić się samodzielnie w
liniach bez wcięć.
W sekcji reguł, tekst wcięty lub tekst %{}, znajdujący się
przed pierwszą regułą może służyć
deklarowaniu zmiennych lokalnych dla procedury skanującej oraz (po
deklaracjach) kodu, który ma być wywoływany za każdym
uruchomieniem procedury skanującej. Pozostałe przypadki
wciętego tekstu lub tekstu %{} sekcji reguł są nadal kopiowane
na wyjście, lecz ich znaczenie nie jest dokładnie zdefiniowane i
mogą spowodować błędy kompilacji
(właściwość ta jest obecna dla zgodności z
POSIX; zobacz niżej inne tego typu właściwości).
W sekcji definicji na wyjście kopiowane są również
nie-wcięte bloki komentarza, ujęte między znaki "/*"
i "*/".
WZORCE¶
Wzorce wejściowe są pisane z użyciem rozszerzonego zestawu
wyrażeń regularnych. Są to:
x dopasowuje znak 'x'
. dowolny znak poza nową linią
[xyz] "klasa znaków"; w tym przypadku wzorzec odpowiada
zarówno 'x', 'y' jak i 'z'
[abj-oZ] "klasa znaków" z zakresem; odpowiada ona
'a', 'b', dowolnej literze od 'j' do 'o' oraz 'Z'
[^A-Z] zanegowana "klasa znaków" tj. dowolny znak poza
wymienionymi w klasie. W tym wypadku dowolny znak oprócz
dużych liter
[^A-Z\n] dowolny znak oprócz dużych liter lub nowej linii
r* zero lub więcej r'ów, gdzie r jest wyrażeniem regularnym
r+ jeden lub więcej r'ów
r? zero lub jeden r (tj. "opcjonalny r")
r{2,5} od dwu do pięciu r
r{2,} dwa lub więcej r
r{4} dokładnie 4 r
{nazwa} rozwinięcie definicji "nazwa" (patrz wyżej)
"[xyz]\"foo"
łańcuch literalny: [xyz]"foo
\X Jeśli X to 'a', 'b', 'f', 'n', 'r', 't' lub 'v',
to następuje interpretacja ANSI-C \x. W przeciwnym
wypadku używany jest literalny 'X' (używane do cytowania
operatorów--np. '*').
\0 znak NUL (kod ASCII 0)
\123 znak o wartości ósemkowej 123
\x2a znak o wartości szesnastkowej 2a
(r) dopasuj r; nawiasy są używane do przeciążania priorytetów
(patrz niżej)
rs wyrażenie regularne r, za którym następuje wyrażenie
regularne s; nazywa się to "łączeniem"
r|s r lub s
r/s r, lecz tylko jeśli za nim następuje s. Tekst dopasowywany
przez s jest załączany do określania czy ta reguła miała
"najdłuższe dopasowanie", lecz potem jest zwracany do
wejścia przed wykonaniem akcji. Tak więc akcja widzi tylko
tekst dopasowany przez r. Ten rodzaj wzorca jest nazywany
"doklejonym kontekstem". (Istnieją pewne kombinacje r/s,
których flex nie potrafi właściwie dopasować; zobacz uwagi
w dalszej sekcji Niedostatki / Błędy w okolicach
"niebezpiecznego kontekstu doklejonego".)
^r r, lecz tylko na początku linii (tj. zaraz po rozpoczęciu
skanowania, lub po wyskanowaniu nowej linii).
r$ r, lecz tylko na końcu linii (tj. tuż przed nową linią).
Równoważne "r/\n".
Zauważ, że notacja nowej linii fleksa jest dokładnie tym,
co było używane jako '\n' przez kompilator C, użyty do
kompilacji fleksa; w praktyce na niektórych systemach DOS
musisz wyfiltrować \r lub jawnie używać r/\r\n zamiast
"r$".
<s>r r, lecz tylko dla warunku początkowego s (zobacz niżej
dyskusję o warunkach początkowych)
<s1,s2,s3>r
to samo, lecz jeśli dowolny z warunków początkowych s1,
s2 lub s3
<*>r r w dowolnym warunku początkowym, nawet wykluczającym
<<EOF>> koniec pliku
<s1,s2><<EOF>>
koniec pliku w warunkach początkowych s1 lub s2
Zauważ, że w obrębie klasy znaków wszystkie operatory
wyrażeń regularnych tracą swoje znaczenie specjalne (nie
licząc cytowania '\', znaków klasy '-', ']' oraz '^' na
początku klasy).
Wymienione wyżej wyrażenia regularne są pogrupowane zgodnie z
priorytetami, licząc od najwyższego do najniższego (z góry
na dół). Te, które zgrupowano razem mają jednakowy
priorytet. Na przykład,
foo|bar*
jest równoważne
(foo)|(ba(r*))
ponieważ operator '*' ma wyższy priorytet niż łączenie,
a łączenie ma wyższy priorytet niż alternatywa ('|').
Wzorzec ten pasuje więc
albo do łańcucha "foo"
albo do "ba", po którym może nastąpić
zero lub więcej r. W celu dopasowania "foo" lub zero lub
więcej "bar"'ów, użyj:
foo|(bar)*
a żeby dopasować zero lub więcej
"foo"-lub-"bar"'ów:
(foo|bar)*
Poza znakami i zakresami znaków, klasy znaków mogą też
zawierać specjalne
wyrażenia. Wyrażenia te są
ujmowane w ograniczniki
[: i
:] (które muszą dodatkowo
pojawiać się wewnątrz '[' i ']' klasy znaków; inne
elementy w klasie znaków też mogą się pojawić).
Prawidłowymi wyrażeniami są:
[:alnum:] [:alpha:] [:blank:]
[:cntrl:] [:digit:] [:graph:]
[:lower:] [:print:] [:punct:]
[:space:] [:upper:] [:xdigit:]
Wyrażenia te oznaczają zestaw znaków, odpowiadający
równoważnemu standardowi funkcji
isXXX języka C.
Przykładowo
[:alnum:] oznacza wszystkie znaki, dla których
isalnum(3) zwraca prawdę - tj. wszelkie znaki alfabetyczne lub
numeryczne. Niektóre systemy nie udostępniają
isblank(3). Flex definiuje
[:blank:] jako spację lub
tabulację.
Na przykład następujące klasy są sobie równoważne:
[[:alnum:]]
[[:alpha:][:digit:]
[[:alpha:]0-9]
[a-zA-Z0-9]
Jeśli twój skaner jest niewrażliwy na wielkość
znaków (flaga (flaga
-i), to
[:upper:] i
[:lower:]
są równoważne
[:alpha:].
Trochę uwag o wzorcach:
- -
- Zanegowana klasa znaków, taka jak wyżej
wymienione przykładowe "[^A-Z]" będzie pasować
do nowej linii, chyba że "\n" (lub równoważna
sekwencja specjalna) jest jednym z jawnie obecnych w klasie znaków
(np. "[^A-Z\n]"). Odbiega to od sposobu traktowania zanegowanych
klas znaków przez inne narzędzia operujące na
wyrażeniach regularnych, lecz niestety niespójność
jest ugruntowana historycznie. Dopasowywanie nowej linii oznacza, że
wzorzec w rodzaju [^"]* może dopasować się do
całego wejścia, chyba że istnieje w nim drugi
cudzysłów.
- -
- Reguła może mieć najwyżej jedną
instancję dowiązanego kontekstu (operatory '/' lub '$'). Wzorce
warunku początkowego '^' oraz "<<EOF>>"
mogą pojawić się tylko na początku wzorca i dodatkowo,
podobnie jak '/' i '$', nie mogą być grupowane w nawiasy. Znak
'^', który nie pojawia się na początku reguły, lub
'$', nie znajdujący się na końcu traci swoje specjalne
znaczenie.
- Następujące wzorce są niedozwolone:
foo/bar$
<sc1>foo<sc2>bar
Zauważ, że pierwszy z nich może być zapisany jako
"foo/bar\n".
- Następujące wzorce powodują, że '$' lub
'^' są traktowane jak zwykłe znaki:
foo|(bar$)
foo|^bar
Jeśli oczekiwaną wartością jest "foo" lub
"bar-z-nową-linią", to użyć można
następującego wzorca (akcja specjalna | jest wyjaśniona
niżej):
foo |
bar$ /* tu rozpoczyna się akcja */
Podobna sztuczka powinna zadziałać dla dopasowywania foo lub
bar-na-początku-linii.
JAK DOPASOWYWANE JEST WEJŚCIE¶
Po uruchomieniu skanera, analizuje on swoje wejście w poszukiwaniu
łańcuchów odpowiadających któremuś z jego
wzorców. Jeśli znajdzie więcej niż jeden pasujący
wzorzec, wybiera ten, który pasuje do największej ilości tekstu
(w regułach z dowiązanym kontekstem oznacza to też
długość części dowiązanej, mimo faktu, że
zostanie ona zwrócona na wejście. Jeśli znajdzie dwa lub
więcej dopasowań o tej samej długości, to wybierana jest
pierwsza reguła.
Po określeniu dopasowania, tekst dopasowania (zwany dalej
tokenem)
jest udostępniany we wskaźnikowej zmiennej globalnej
yytext,
a jego długość w globalnej zmiennej całkowitej
yyleng. Wykonywana jest też odpowiadająca wzorcowi
akcja (szczegółowy opis akcji jest dalej), a następnie
pozostała część wejścia jest dopasowywana do
kolejnego wzorca.
Jeśli dopasowanie nie zostanie znalezione, wykonana zostanie
reguła
domyślna: następny znak wejścia jest uważany za
dopasowany i kopiowany na stdout. Tak więc najprostszym poprawnym plikiem
wejściowym
fleksa jest:
%%
Generuje to skaner, który po prostu kopiuje swoje wejście (jeden znak
naraz) na wyjście.
Zauważ, że
yytext może być definiowane na dwa
sposoby: jako
wskaźnik do znaków lub jako
tablica
znaków. Używanie konkretnej definicji można kontrolować,
włączając do pliku wejściowego w pierwszej sekcji
specjalne dyrektywy
%pointer lub
%array. Domyślnie
używana jest dyrektywa
%pointer, chyba że używa się
opcji
-l zgodności z leksem i wtedy
yytext staje się
tablicą. Korzyścią z używania
%pointer jest
zwiększenie szybkości skanowania i zlikwidowanie
przepełnień bufora przy dopasowywaniu dużych tokenów
(chyba że zabraknie pamięci dynamicznej). Wadą jest
ograniczenie sposobu modyfikowania przez akcje zmiennej
yytext (zobacz
następną sekcję) i to, że wywołania funkcji
unput() niszczą aktualną zawartość
yytext,
co może przyprawiać o ból głowy podczas portowania
skanerów między różnymi wersjami
lex.
Zaletą
%array jest możliwość modyfikowania
yytext i to, że wołanie
unput() nie niszczy
yytext. Poza tym, istniejące programy
lex czasami
zewnętrznie zaglądają do
yytext przy użyciu
deklaracji w postaci:
extern char yytext[];
Definicja ta jest błędna przy użyciu z
%pointer, lecz
prawidłowa dla
%array.
%array definiuje
yytext jako tablicę
YYLMAX
znaków, co domyślnie jest dość dużą
wartością. Możesz zmieniać rozmiar przez proste
#definiowanie
YYLMAX na inną wartość w pierwszej sekcji
wejściowego pliku
fleksa. Jak wspomniano wyżej, dla
%pointer yytext wzrasta dynamicznie, by przechowywać duże
tokeny. Chociaż oznacza to, że skaner
%pointer może
zbierać duże tokeny (jak np. całe bloki komentarzy), to zakop
sobie w pamięci, że za każdym razem gdy skaner zmienia rozmiar
yytext to musi również reskanować cały token od
początku, więc może się to okazać powolne.
yytext w chwili obecnej
nie zwiększa dynamicznie rozmiaru
jeśli wywołanie
unput() powoduje wepchnięcie z powrotem
zbyt dużego bloku tekstu. Zamiast tego pojawia się błąd
wykonania.
Zauważ też, że postaci
%array nie można
używać z klasami skanerów C++ (zobacz opcję
c++
poniżej).
AKCJE¶
Każdy wzorzec reguły ma odpowiadającą mu akcję,
która może być dowolną instrukcją języka C.
Wzorzec kończy się na pierwszym niecytowanym znaku białej
spacji; reszta linijki jest akcją. Jeśli akcja jest pusta, to token
wejściowy jest zwyczajnie odrzucany. Na przykład oto program,
kasujący wszystkie pojawienia łańcucha "wytnij mnie":
%%
"wytnij mnie"
(Wszystkie pozostałe znaki wejścia zostaną skopiowane na
wyjście, gdyż dopasują się do reguły domyślnej.)
Oto program, który kompresuje wielokrotne spacje i tabulacje do pojedynczej
spacji. Program wycina też wszystkie białe spacje z końca
linii:
%%
[ \t]+ putchar( ' ' );
[ \t]+$ /* ignoruj ten token */
Jeśli akcja zawiera znak '{', to rozciąga się ona aż do
zamykającego '}', nawet na przestrzeni wielu linii.
flex ma pewne
wiadomości o łańcuchach C i komentarzach, więc nie
zostanie ogłupione przez klamry, które mogą się w nich
znajdować. Poza tym dozwolone są też akcje, które
zaczynają się od
%{ i zawierają tekst akcji aż do
następnego
%} (niezależnie od zwyczajnych klamer
wewnątrz akcji).
Akcja składająca się wyłącznie z pionowej kreski ('|')
oznacza "taka sama, jak akcja następnej reguły". Dla
zobrazowania patrz niżej.
Akcje mogą zawierać kod C, włączając w to instrukcje
return, przeznaczone do zwracania wartości do procedury,
która wywołała
yylex(). Przy każdym wywołaniu
yylex() kontynuuje przetwarzanie tokenów od miejsca, w którym
ostatnio przerwał aż do osiągnięcia końca pliku lub
wywołania return.
Akcje mogą spokojnie modyfikować zmienną
yytext; nie
mogą jej jednak wydłużać (dodawanie znaków do jej
końca nadpisze dalsze znaki strumienia wejściowego). Odmiennie jest
natomiast przy używaniu
%array (patrz wyżej); wtedy
yytext można spokojnie modyfikować w dowolny sposób.
Podobnie do powyższej zmiennej, można spokojnie modyfikować
yyleng, lecz należy uważać by nie robić tego
jeśli akcja używa
yymore() (patrz niżej).
Istnieje wiele dyrektyw specjalnych, które można zawrzeć w akcji:
- -
- ECHO kopiuje wejście yytext na wyjście
skanera.
- -
- BEGIN z doklejoną nazwą warunku
początkowego umieszcza skaner w odpowiednim warunku początkowym
(patrz niżej).
- -
- REJECT Kieruje skaner na działanie w
"drugiej najlepszej" regule, która została dopasowana
do wzorca wejściowego (lub prefiksu wejścia). Reguła jest
wybierana według zasad opisanych w "Jak dopasowywane jest
wejście", po czym następuje odpowiednie ustawienie
yytext oraz yyleng. Może to być albo ta
reguła, która dopasowała się do takiej samej
ilości tekstu, jak poprzednia, lecz wystąpiła
później w pliku wejściowym fleksa, albo taka, która
dopasowała się do mniejszej ilości tekstu. Na
przykład, następujący przykład będzie liczył
słowa wejściowe i wołał funkcję special() dla
każdego "frob":
int word_count = 0;
%%
frob special(); REJECT;
[^ \t\n]+ ++word_count;
Bez dyrektywy REJECT, słowa "frob" wejścia nie
byłyby zliczane jako słowa, gdyż skaner normalnie wykonuje
tylko jedną akcję na token. Dozwolonych jest wiele komend
REJECT, z których każda wyszukuje najbardziej
pasującego następcę. Na przykład poniższy skaner
skanując token "abcd" zapisze na wyjściu
"abcdabcaba":
%%
a |
ab |
abc |
abcd ECHO; REJECT;
.|\n /* zjedz nietrafione znaki */
(Pierwsze trzy reguły mają wspólną akcję z
czwartą, gdyż używają akcji specjalnej '|'.)
REJECT jest dość kosztowną
właściwością jeśli chodzi o wydajność
skanera; jeśli jest używane w którejś z akcji skanera,
to spowolni wszystkie dopasowania skanera. Co więcej,
REJECT nie może być używany z opcjami -Cf i
-CF (zobacz niżej).
- Zauważ też, że, w przeciwieństwie do
innych akcji specjalnych, REJECT jest
odgałęzieniem; kod akcji występujący
bezpośrednio po nim nie zostanie wykonany.
- -
- yymore() mówi skanerowi, że przy
następnym dopasowaniu reguły, odpowiadający token powinien
być doklejony do bieżącej wartości
yytext. Na przykład, przy wejściu
"mega-kludge", poniższy przykład na wyjściu
wypisze "mega-mega-kludge":
%%
mega- ECHO; yymore();
kludge ECHO;
Pierwsze "mega-" jest dopasowane i wydrukowane na wyjście.
Następnie dopasowane jest "kludge", lecz poprzednie
"mega-" wciąż znajduje się na początku
yytext i komenda ECHO dla "kludge" wydrukuje w
rzeczywistości "mega-kludge".
Dwie uwagi na temat
yymore(). Po pierwsze,
yymore() zależy od
wartości
yyleng, odzwierciedlającej rozmiar
bieżącego tokenu. Zatem jeśli używasz
yymore(), nie
modyfikuj tej zmiennej. Po drugie, obecność
yymore() w akcji
skanera wpływa na pewne pogorszenie wydajności w szybkości
dokonywania przez skaner dopasowań.
- -
- yyless(n) zwraca wszystkie poza pierwszymi n
znakami bieżącego tokenu z powrotem do strumienia
wejściowego, skąd zostaną one powtórnie przeskanowane
przy dopasowywaniu następnego wzorca. yytext i yyleng
są odpowiednio dostrajane (tj. yyleng będzie teraz
równe n). Na przykład, przy wejściu
"foobar", następujący kod wypisze
"foobarbar":
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
Podanie yyless argumentu zerowego powoduje reskanowanie całego
obecnego łańcucha wejściowego. O ile nie zmienisz sposobu
kolejnego przetwarzania przez skaner wejścia (przy użyciu np.
BEGIN), spowoduje to nieskończoną pętlę.
Zwróć uwagę, że
yyless jest makrem i może
być używane tylko z pliku wejściowego fleksa, a nie z innych
plików źródłowych.
- -
- unput(c) wstawia znak c z powrotem do
strumienia wejściowego. Będzie to następny skanowany znak.
Poniższa akcja pobierze bieżący token i spowoduje, że
zostanie reskanowany po ujęciu w nawiasy.
{
int i;
/* Kopiuj yytext, gdyż unput() niszczy jego zawartość */
char *yycopy = strdup( yytext );
unput( ')' );
for ( i = yyleng - 1; i >= 0; --i )
unput( yycopy[i] );
unput( '(' );
free( yycopy );
}
Zwróć uwagę, że skoro każdy unput() wstawia
dany znak na początek strumienia, to wstawianie znaków
musi odbywać się tyłem-na-przód.
Ważnym potencjalnym problemem używania
unput() jest fakt,
że jeśli używasz dyrektywy
%pointer (domyślne),
wywołanie
unput() niszczy zawartość
yytext, poczynając od znaku najbardziej z prawej, idąc w lewo
za każdym wywołaniem. Jeśli potrzebujesz zachować
wartość yytext po użyciu tej funkcji, (jak w powyższym
przykładzie), musisz skopiować jej zawartość gdzie indziej
lub zbudować skaner z użyciem
%array.
Na koniec, zauważ też, że nie możesz wstawiać tak
znaków
EOF. Nie można tą metodą zaznaczać
końca pliku w strumieniu.
- -
- input() odczytuje następny znak ze strumienia
wejściowego. Na przykład, poniższe jest jednym ze
sposobów pożerania komentarzy języka C:
%%
"/*" {
register int c;
for ( ; ; )
{
while ( (c = input()) != '*' &&
c != EOF )
; /* zeżryj tekst komentarza */
if ( c == '*' )
{
while ( (c = input()) == '*' )
;
if ( c == '/' )
break; /* znalazłem koniec */
}
if ( c == EOF )
{
error( "EOF w komentarzu" );
break;
}
}
}
(Zauważ, że jeśli skaner jest skompilowany z użyciem
C++, to input() nazywa się yyinput(). Jest tak w
celu zapobieżenia zderzeniu nazwy ze strumieniem C++ poprzez
nazwę input.)
- -
- YY_FLUSH_BUFFER wypróżnia wewnętrzny
bufor skanera. Przy następnym razie gdy skaner będzie
dopasowywał się do tokenu, najpierw napełni na nowo bufor z
użyciem YY_INPUT (zobacz niżej Generowany Skaner). Akcja
ta jest szczególnym przypadkiem bardziej ogólnej funkcji
yy_flush_buffer(), opisanej niżej w sekcji Wielokrotne Bufory
Wejściowe.
- -
- yyterminate() może być używane
zamiast instrukcji return akcji. Kończy działanie skanera i
zwraca 0 do wywołującego skaner, wskazując, że
"wszystko zrobione". Domyślnie, yyterminate() jest
wywoływane również po napotkaniu końca pliku. Jest to
makro i może być redefiniowane.
GENEROWANY SKANER¶
Wynikiem działania fleksa jest plik
lex.yy.c, zawierający
procedurę skanującą
yylex() oraz zestaw tablic,
używanych przez niego do dopasowywania tokenów i parę procedur
i makr. Domyślnie
yylex() jest deklarowany jako
int yylex()
{
... tu różne definicje i akcje ...
}
(Jeśli twoje środowisko obsługuje prototypy funkcji, to
będzie to "int yylex( void )".) Definicję tę
można zmienić definiując makro "YY_DECL". Na
przykład
#define YY_DECL float lexscan( a, b ) float a, b;
informuje fleksa, by nadać procedurze skanującej nazwę
lexscan i że procedura ta ma zwracać typ float i
pobierać dwa argumenty (też typu float). Zwróć uwagę,
że jeśli podajesz argumenty procedurze skanującej,
używając deklaracji w niezaprototypowanym stylu K&R, musisz
zakończyć definicję średnikiem (;).
Przy każdym wywołaniu
yylex(), następuje skanowanie
tokenów z globalnego pliku wejściowego
yyin (który
domyślnie wskazuje na stdin). Wczytywanie trwa aż do
osiągnięcia końca pliku, lub aż do napotkania w
którejś z akcji instrukcji
return.
Jeśli skaner osiąga koniec pliku, to kolejne wywołania są
niezdefiniowane. Sposobem na skorygowanie tego jest przekierowanie
yyin
na nowy plik wejściowy (w tym wypadku skanowanie następuje z nowego
pliku) lub wywołanie
yyrestart().
yyrestart() pobiera jeden
argument: wskaźnik
FILE * (który może być nil,
jeśli ustawiłeś
YY_INPUT na skanowanie ze
źródła innego niż
yyin), i inicjalizuje
yyin
na początek tego pliku. W zasadzie nie ma różnicy między
zwykłym przypisaniem
yyin do nowego pliku i użyciem
yyrestart(); Procedura ta jest dostępna z uwagi na
kompatybilność z poprzednimi wersjami
flex, a także
dlatego, że może być używana do przełączania
plików wejściowych w środku skanowania. Może być
też używana do porzucania bieżącego bufora
wejściowego poprzez wywołanie z argumentem
yyin; lepszym
rozwiązaniem jest jednak użycie
YY_FLUSH_BUFFER (patrz
wyżej). Zauważ, że
yyrestart() nie resetuje
warunku początkowego na
INITIAL (zobacz niżej Warunki
Początkowe).
Jeśli
yylex() kończy skanowanie z powodu wywołania
instrukcji
return w jednej z akcji, skaner może być
wołany ponownie i wznowi działanie tam, gdzie skończył.
Domyślnie (i dla celów wydajności) skaner zamiast pojedynczych
getc() wykonuje odczyty blokowe z
yyin. Sposób pobierania
wejścia może być kontrolowany przez definiowanie makra
YY_INPUT. Sekwencja wywołująca YY_INPUT to
"YY_INPUT(buf,wynik,max_rozmiar)". Jej wynikiem jest umieszczenie co
najwyżej
max_rozmiar znaków w tablicy znakowej
buf i
zwrócenie w zmiennej całkowitej
wynik albo liczby wczytanych
znaków albo stałej YY_NULL (0 w systemach uniksowych),
określającej EOF. Domyślnie, YY_INPUT czyta z globalnego
wskaźnika "yyin".
Przykładowa definicja YY_INPUT (w sekcji definicji pliku wejściowego):
%{
#define YY_INPUT(buf,wynik,max_rozmiar) \
{ \
int c = getchar(); \
wynik = (c == EOF) ? YY_NULL : (buf[0] = c, 1); \
}
%}
Definicja ta zmieni przetwarzanie wejścia tak, by naraz pojawiał
się tylko jeden znak.
W momencie, gdy skaner uzyska od YY_INPUT warunek końca pliku, to woła
funkcję
yywrap(). Jeśli
yywrap() zwróci zero, to
zakłada, że funkcja poszła dalej i skonfigurowała
yyin do wskazywania na nowy plik, a skanowanie trwa dalej. Jeśli
zwróci wartość niezerową, skaner kończy
działanie, zwracając 0 do funkcji wywołującej.
Zauważ, że w każdym przypadku warunek początkowy pozostaje
niezmieniony;
nie przechodzi on w
INITIAL.
Jeśli nie chcesz podawać własnej wersji
yywrap(), to
musisz albo użyć opcji
%option noyywrap (wtedy skaner
zachowuje się, jakby
yywrap() zwracało 1), albo
konsolidować z
-lfl, uzyskując tak domyślną
wersję funkcji, zawsze zwracającej 1.
Do skanowania z buforów pamięciowych (a nie z plików)
przeznaczone są trzy procedury:
yy_scan_string(),
yy_scan_bytes() oraz
yy_scan_buffer(). Zobacz niżej
dyskusję w sekcji Wielokrotne Bufory Wejściowe.
Swoje wyjście
ECHO skaner zapisuje do globalnego strumienia
yyout (domyślnie stdout), który można
przedefiniować dzięki zwykłemu przypisaniu tej zmiennej do
innego wskaźnika
FILE.
WARUNKI POCZĄTKOWE¶
flex daje mechanizm warunkowej aktywacji reguł. Reguły
rozpoczynające się od "<sc>" włączą
się tylko jeśli skaner znajduje się w warunku początkowym
"sc". Na przykład,
<STRING>[^"]* { /* zjedz ciało łańcucha ... */
...
}
będzie aktywne tylko jeśli skaner jest w warunku początkowym
"STRING", a
<INITIAL,STRING,QUOTE>\. { /* obsłuż cytowanie ... */
...
}
będzie aktywne tylko jeśli obecnym warunkiem początkowym jest
albo "INITIAL", albo "STRING" albo "QUOTE".
Warunki początkowe są deklarowane w sekcji definicji wejścia przy
użyciu niewciętych linii, zaczynających się od
%s
lub
%x, za którymi następuje lista nazw. Pierwsza postać
deklaruje
włączające warunki początkowe, a druga
wykluczające. Warunek początkowy włącza się
przy użyciu akcji
BEGIN. Reguły używające danego
warunku początkowego będą aktywne aż do wywołania
następnej akcji
BEGIN. Jeśli warunek początkowy jest
włączający , to reguły bez warunków
początkowych będą również aktywne. Jeśli jest
wykluczający, to wykonywane będą
tylko
reguły odpowiadające warunkowi początkowemu. Zestaw reguł
opierających się na tym samym wykluczającym warunku
początkowym, opisuje skaner, który jest niezależny od wszelkich
innych reguł wejścia fleksa. Z uwagi na to, warunki
wykluczające ułatwiają tworzenie
"mini-skanerów", które skanują części
wejścia, odmienne syntaktycznie od reszty (np. komentarze).
W rozróżnieniu warunków włączających i
wykluczających istnieje wciąż pewna niejasność: oto
przykład, ilustrujący ich powiązanie. Zestaw reguł:
%s przyklad
%%
<przyklad>foo rob_cos();
bar cos_innego();
jest równoważny
%x przyklad
%%
<przyklad>foo rob_cos();
<INITIAL,przyklad>bar cos_innego();
Bez użycia kwalifikatora
<INITIAL,przyklad>, wzorzec
bar w drugim przykładzie nie byłby aktywny (tj. nie
dopasowałby się) w warunku początkowym
przyklad.
Jeśli użylibyśmy do kwalifikowania
bar tylko
<przyklad>, to byłoby aktywny tylko w warunku
początkowym
przyklad, ale nie w
INITIAL, podczas gdy w
pierwszym przykładzie jest aktywny w obydwu, gdyż warunek
początkowy
przyklad jest w nim
włączający
(%s).
Zauważ też, że specjalny specyfikator
<*> pasuje do
dowolnego warunku początkowego. Tak więc, powyższe można
zapisać również następująco:
%x przyklad
%%
<przyklad>foo rob_cos();
<*>bar cos_innego();
Reguła domyślna (wykonywania
ECHO na każdym niedopasowanym
znaku) pozostaje aktywna w warunkach początkowych. Jest to w sumie
równoważne:
<*>.|\n ECHO;
BEGIN(0) zwraca do stanu oryginalnego, w którym aktywne są
tylko reguły bez warunku początkowego. Stan ten jest oznaczany jako
warunek początkowy "INITIAL", więc można go
ustawić również poprzez
BEGIN(INITIAL). (Nawiasy
wokół nazwy warunku początkowego nie są wymagane, lecz
są w dobrym tonie.)
Akcje
BEGIN mogą być podawane jako kod wcięty na
początku sekcji reguł. Na przykład, następujący kod
spowoduje, że skaner wejdzie w warunek początkowy
"SPECIAL" za każdym razem, gdy wywołane zostanie
yylex() a zmienna globalna
enter_special będzie ustawiona
na prawdę:
int enter_special;
%x SPECIAL
%%
if ( enter_special )
BEGIN(SPECIAL);
<SPECIAL>blahblahblah
...i kolejne ruguły...
Dla zilustrowania wykorzystania warunków początkowych, oto skaner,
który daje dwie różne interpretacje łańcucha
"123.456". Domyślnie będzie traktował go jako 3
elementy, liczbę całkowitą 123, kropkę i liczbę
całkowitą "456". Jeśli jednak łańcuch
zostanie poprzedzony linią z napisem "expect-floats", to
będzie go traktował jako pojedynczy element zmiennoprzecinkowy
(123.456).
%{
#include <math.h>
%}
%s expect
%%
expect-floats BEGIN(expect);
<expect>[0-9]+"."[0-9]+ {
printf( "znalazłem zmiennoprzecinkową, = %f\n",
atof( yytext ) );
}
<expect>\n {
/* jest to koniec linii, więc
* potrzebujemy kolejnego "expect-number"
* przed rozpoznawaniem dalszych liczb
*/
BEGIN(INITIAL);
}
[0-9]+ {
printf( "znalazłem całkowitą, = %d\n",
atoi( yytext ) );
}
"." printf( "znalazłem kropkę\n" );
Oto skaner, który rozpoznaje komentarze C podczas zliczania linii.
%x comment
%%
int line_num = 1;
"/*" BEGIN(comment);
<comment>[^*\n]* /* zjedz wszystko, co nie jest '*' */
<comment>"*"+[^*/\n]* /* zjedz '*'-ki, po których nie ma '/' */
<comment>\n ++line_num;
<comment>"*"+"/" BEGIN(INITIAL);
Skaner ten może mieć problemy z dopasowaniem maksymalnej ilości
tekstu w każdej z reguł. Ogólnie, przy pisaniu szybkich
skanerów, próbuj dopasowywać w każdej regule tyle, ile
się da.
Zauważ, że nazwy warunków początkowych są tak
naprawdę wartościami całkowitymi i mogą być tak
przechowywane. Tak więc powyższe można rozwinąć w
następującym stylu:
%x comment foo
%%
int line_num = 1;
int comment_caller;
"/*" {
comment_caller = INITIAL;
BEGIN(comment);
}
...
<foo>"/*" {
comment_caller = foo;
BEGIN(comment);
}
<comment>[^*\n]* /* zjedz wszystko co nie jest '*' */
<comment>"*"+[^*/\n]* /* zjedz '*', po których nie ma '/' */
<comment>\n ++line_num;
<comment>"*"+"/" BEGIN(comment_caller);
Co więcej, możesz mieć dostęp do bieżącego warunku
początkowego poprzez makro
YY_START (o wartości
całkowitej). Na przykład, powyższe przypisania do
comment_caller można by zapisać jako
comment_caller = YY_START;
Flex jako alias do
YY_START daje
YYSTATE (gdyż jest to nazwa,
używana przez AT&T
lex).
Zauważ, że warunki początkowe nie mają własnej
przestrzeni nazw; %s i %x-y deklarują nazwy podobnie jak #define.
Na deser, oto przykład dopasowywania cytowanych w stylu C napisów przy
użyciu wykluczających warunków początkowych,
włącznie z rozwijanymi sekwencjami specjalnymi (lecz bez sprawdzania
czy łańcuch nie jest za długi):
%x str
%%
char string_buf[MAX_STR_CONST];
char *string_buf_ptr;
\" string_buf_ptr = string_buf; BEGIN(str);
<str>\" { /* zobaczyłem zamykający cytat - gotowe */
BEGIN(INITIAL);
*string_buf_ptr = '\0';
/* zwróć typ i wartość tokenu stałej łańcuchowej do
* analizatora
*/
}
<str>\n {
/* błąd - niezakończona stała łańcuchowa */
/* generuj komunikat o błędzie */
}
<str>\\[0-7]{1,3} {
/* ósemkowa sekwencja specjalna */
int result;
(void) sscanf( yytext + 1, "%o", &result );
if ( result > 0xff )
/* błąd, stała poza zakresem */
*string_buf_ptr++ = result;
}
<str>\\[0-9]+ {
/* generuj błąd - zła sekwencja specjalna; coś jak
* '\48' lub '\0777777'
*/
}
<str>\\n *string_buf_ptr++ = '\n';
<str>\\t *string_buf_ptr++ = '\t';
<str>\\r *string_buf_ptr++ = '\r';
<str>\\b *string_buf_ptr++ = '\b';
<str>\\f *string_buf_ptr++ = '\f';
<str>\\(.|\n) *string_buf_ptr++ = yytext[1];
<str>[^\\\n\"]+ {
char *yptr = yytext;
while ( *yptr )
*string_buf_ptr++ = *yptr++;
}
Często, np. w niektórych przykładach powyżej można
skończyć pisząc grupę reguł, rozpoczynających
się od tych samych warunków początkowych. Flex ułatwia
całość wprowadzając pojęcie
zakresu warunku
początkowego. Zakres rozpoczyna się od:
<SCs>{
gdzie
SCs jest listą jednego lub więcej warunków
początkowych. Wewnątrz zakresu warunku początkowego każda
reguła dostaje automatycznie przedrostek
<SCs> aż do
napotkania
'}', który odpowiada startowemu
'{'. W ten
sposób na przykład
<ESC>{
"\\n" return '\n';
"\\r" return '\r';
"\\f" return '\f';
"\\0" return '\0';
}
jest równoważne:
<ESC>"\\n" return '\n';
<ESC>"\\r" return '\r';
<ESC>"\\f" return '\f';
<ESC>"\\0" return '\0';
Zakresy warunków początkowych mogą być
zagnieżdżane.
Do obsługi stosów warunków początkowych są przeznaczone
trzy procedury:
- void yy_push_state(int new_state)
- wrzuca bieżący warunek początkowy na stos
warunków początkowych i przełącza się w stan
new_state, zupełnie jak po użyciu BEGIN new_state
(pamiętaj, że nazwy warunków początkowych są
również liczbami całkowitymi).
- void yy_pop_state()
- zdejmuje wartość ze stosu i przełącza
się na nią przez BEGIN.
- int yy_top_state()
- zwraca wierzchołek stosu bez zmiany zawartości
stosu.
Stos warunków początkowych rośnie dynamicznie i nie ma
żadnych wbudowanych ograniczeń. Po wyczerpaniu pamięci,
wykonywanie programu jest przerywane.
Aby korzystać ze stosów warunków początkowych, skaner musi
zawierać dyrektywę
%option stack (zobacz niżej
rozdział Opcje).
WIELOKROTNE BUFORY WEJŚCIOWE¶
Niektóre skanery (te, obsługujące pliki dołączane
"include") wymagają odczytu z wielu strumieni wejściowych.
Ponieważ skanery
flex wykonują sporo buforowania, nie
można jednoznacznie zdecydować skąd będzie wykonywany
następny odczyt przez proste napisanie
YY_INPUT, które jest
wrażliwe na kontekst skanowania.
YY_INPUT wywoływane jest
tylko gdy skaner osiąga koniec swojego bufora, który może
być daleko po wyskanowaniu instrukcji takiej jak "include",
wymagającej przełączenia źródła wejścia.
Aby załatwić niektóre z tych problemów,
flex daje
mechanizm tworzenia i przełączania między wielokrotnymi
buforami wejściowymi. Bufor wejściowy jest tworzony z użyciem
funkcji
YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
która pobiera wskaźnik
FILE i rozmiar size, a następnie
tworzy bufor związany z danym plikiem, którego wielkość (w
znakach) jest określona parametrem rozmiaru. (w razie
wątpliwości użyj
YY_BUF_SIZE jako rozmiaru). Funkcja
zwraca uchwyt
YY_BUFFER_STATE, który może być potem
przekazywany do innych procedur (zobacz niżej). Typ
YY_BUFFER_STATE jest wskaźnikiem do struktury
struct
yy_buffer_state więc można bezpiecznie inicjalizować
zmienne YY_BUFFER_STATE na
((YY_BUFFER_STATE) 0) i odnosić
się do struktury w celu poprawnego zadeklarowania buforów
wejściowych w plikach źródłowych innych niż ten od
twojego skanera. Zauważ, że wskaźnik
FILE w
wywołaniu
yy_create_buffer jest używany tylko jako
wartość
yyin widzianego przez
YY_INPUT; jeśli
redefiniujesz
YY_INPUT tak, żeby nie używało
yyin, to możesz spokojnie przekazać tu zerowy wskaźnik
FILE. Zadany bufor do skanowania wybiera się za pomocą:
void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )
co przełącza bufor wejściowy skanera tak, że kolejne tokeny
będą pochodziły z bufora
new_buffer. Zauważ,
że
yy_switch_to_buffer() może być używane przez
yywrap() do zestawiania różnych rzeczy we wznowionym skanowaniu
zamiast otwierania nowego pliku i ustawiania na nim
yyin. Zauważ
też, że przełączanie źródeł
wejściowych przez
yy_switch_to_buffer() lub
yywrap()
nie zmienia warunku początkowego.
void yy_delete_buffer( YY_BUFFER_STATE buffer )
używane jest do odzyskania miejsca związanego z buforem (
buffer może być wartością nil, ale wtedy funkcja ta
nic nie robi.) Można też czyścić bieżącą
zawartość bufora, stosując:
void yy_flush_buffer( YY_BUFFER_STATE buffer )
Funkcja ta niszczy zawartość bufora, więc przy następnej
próbie dopasowania tokenu z bufora, skaner najpierw wypełni bufor na
nowo używając
YY_INPUT.
yy_new_buffer() jest synonimem
yy_create_buffer(),
udostępnionym dla zgodności z C++ narzędziami
new i
delete, służącymi do tworzenia i niszczenia
obiektów dynamicznych.
Na koniec makro
YY_CURRENT_BUFFER zwraca uchwyt
YY_BUFFER_STATE do
bieżącego bufora.
A oto przykład używania tych właściwości w skanerze,
rozwijającym pliki załączane (właściwość
<<EOF>> jest opisywana niżej):
/* stan "incl" jest używany do wybierania nazwy załączanego pliku
*/
%x incl
%{
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}
%%
include BEGIN(incl);
[a-z]+ ECHO;
[^a-z\n]*\n? ECHO;
<incl>[ \t]* /* zjedz białą spację */
<incl>[^ \t\n]+ { /* mam nazwę pliku załącznika */
if ( include_stack_ptr >= MAX_INCLUDE_DEPTH )
{
fprintf( stderr, "Zbyt zagnieżdżone załączniki" );
exit( 1 );
}
include_stack[include_stack_ptr++] =
YY_CURRENT_BUFFER;
yyin = fopen( yytext, "r" );
if ( ! yyin )
error( ... );
yy_switch_to_buffer(
yy_create_buffer( yyin, YY_BUF_SIZE ) );
BEGIN(INITIAL);
}
<<EOF>> {
if ( --include_stack_ptr < 0 )
{
yyterminate();
}
else
{
yy_delete_buffer( YY_CURRENT_BUFFER );
yy_switch_to_buffer(
include_stack[include_stack_ptr] );
}
}
Do zestawiania buforów wejściowych dla skanowania
łańcuchów z pamięci zamiast plików istnieją trzy
procedury. Każda z nich tworzy nowy bufor wejściowy do skanowania
łańcucha i zwraca odpowiadający uchwyt
YY_BUFFER_STATE
(który powinieneś skasować stosując
yy_delete_buffer() po zakończeniu działania).
Przełączają one też przetwarzanie na nowy bufor przy
użyciu
yy_switch_to_buffer(), więc następne
wywołanie
yylex() rozpocznie skanowanie łańcucha.
- yy_scan_string(const char *str)
- skanuje łańcuch zakończony zerem.
- yy_scan_bytes(const char *bytes, int len)
- skanuje len bajtów (dopuszczalne zera w
środku) począwszy od pozycji bytes.
Zauważ, że obydwie funkcje tworzą i skanują
kopie
oryginalnych danych. (Jest to pożądane, gdyż
yylex()
modyfikuje zawartość skanowanego bufora.) Kopiowania można
uniknąć, stosując:
- yy_scan_buffer(char *base, yy_size_t size)
- które skanuje bufor na miejscu, zaczynając od
base, a w długości size bajtów, z
których dwa bajty muszą być znakami
YY_END_OF_BUFFER_CHAR (ASCII NUL). Ostatnie dwa bajty nie są
skanowane; tak więc skanowanie przebiega od base[0] do
base[size-2] włącznie.
- Jeśli nie ustawisz odpowiednio base to
yy_scan_buffer() zwraca wskaźnik nil zamiast tworzyć nowy
bufor wejściowy.
- Typ yy_size_t jest typem całkowitym, na
który rzutuje się wyrażenie całkowite,
określające rozmiar bufora.
REGUŁY END-OF-FILE¶
Specjalna reguła "<<EOF>>" określa akcje,
które należy wykonać po osiągnięciu końca pliku
i gdy yywrap() zwraca zero (tj. wskazuje brak dalszych plików do
przetworzenia). Akcja musi się zakończyć zrobieniem jednej z
czterech rzeczy:
- -
- przypisaniem yyin do nowego pliku wejściowego
(w poprzednich wersjach fleksa po dokonaniu przypisania należało
wywołać specjalną akcję YY_NEW_FILE; nie jest
to już wymagane);
- -
- wywołaniem instrukcji return;
- -
- wywołaniem specjalnej akcji yyterminate();
- -
- przełączeniem na nowy bufor za pomocą
yy_switch_to_buffer().
Reguły <<EOF>> nie mogą być używane z innymi
wzorcami; mogą one być kwalifikowane jedynie listą
warunków początkowych. Jeśli podana jest niekwalifikowana
reguła <<EOF>>, to dotyczy ona
wszystkich
warunków początkowych, które nie mają jeszcze akcji
<<EOF>>. Aby podać regułę <<EOF>> tylko
dla początkowego warunku początkowego użyj
<INITIAL><<EOF>>
Te reguły przydatne są do łapania rzeczy takich, jak
niezamknięte cytaty. Przykład:
%x quote
%%
...inne reguły cytatowe...
<quote><<EOF>> {
error( "nie zamknięty cytat" );
yyterminate();
}
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}
RÓŻNE MAKRA¶
Można zdefiniować makro
YY_USER_ACTION, które
służy do podania akcji wykonywanej zawsze przed akcją
dopasowanej reguły. Na przykład może być #definiowane do
wywoływania procedury konwertującej yytext na małe litery. Gdy
wywoływane jest
YY_USER_ACTION, zmienna
yy_act określa
numer dopasowanej reguły (reguły są numerowane od 1).
Załóżmy, że chcesz wyprofilować jak często jest
używana każda z reguł. Rozwiązaniem jest
następujący kawałek kodu:
#define YY_USER_ACTION ++ctr[yy_act]
gdzie
ctr jest tablicą przechowującą zawartość
różnych reguł. Zauważ, że makro
YY_NUM_RULES
daje ogólną liczbę reguł (łącznie z
regułą domyślną, nawet jeśli używasz
-s),
więc poprawną deklaracją
ctr jest:
int ctr[YY_NUM_RULES];
Makro
YY_USER_INIT służy do podania akcji, która
będzie wykonywana zawsze przed pierwszym skanem (i przed
wewnętrznymi inicjalizacjami skanera). Na przykład można to
wykorzystać do wołania procedury czytającej tablice danych lub
otwierającej plik raportowy.
Makro
yy_set_interactive(is_interactive) może być używane
do sterowania czy bieżący bufor jest uważany za
interaktywny. Bufor interaktywny jest przetwarzany wolniej, lecz musi
być używany gdy wejście rzeczywiście jest interaktywne.
Zapobiega to problemom związanym z oczekiwaniem na wypełnienie
buforów (zobacz niżej dyskusję flagi
-I).
Wartość niezerowa w wywołaniu makra zaznacza bufor jako
interaktywny, a zero to wyłącza. Zauważ, że użycie
tego makra przesłania
%option always-interactiv lub
%option
never-interactive (zobacz niżej Opcje). Przed rozpoczęciem
skanowania bufora, który jest (lub nie jest) interaktywny, należy
wywołać funkcję
yy_set_interactive().
Makro
yy_set_bol(at_bol) może być wykorzystywane do sterowania
czy bieżący kontekst skanujący bufora dla następnego
dopasowania tokena jest dokonywany jak gdyby od początku linii. Niezerowa
wartość argumentu powoduje, że reguły zakotwiczone w '^'
stają się aktywne, a wartość zerowa je dezaktywuje.
Makro
YY_AT_BOL() zwraca prawdę jeśli następny token
skanowany z bieżącego bufora będzie miał aktywne
reguły '^'. W przeciwnym wypadku zwraca fałsz.
W niektórych generowanych skanerach akcje są zebrane wszystkie w
jedną wielką instrukcję switch i są rozdzielone makrem
YY_BREAK, które można redefiniować. Domyślnie jest
to po prostu "break". Redefiniowanie
YY_BREAK umożliwia
użytkownikom C++ zadeklarowanie, by makro nie robiło niczego
(uważając przy tym szczególnie, by każda reguła
kończyła się instrukcją "break" lub
"return"!). Można tak zapobiec cierpieniom spowodowanym
ostrzeżeniami o tym, że przez zakończenie akcji reguły
instrukcją return,
YY_BREAK jest nieosiągalne.
WARTOŚCI DOSTĘPNE DLA UŻYTKOWNIKA¶
Sekcja ta zestawia różne wartości dostępne dla
użytkownika w akcjach regułowych.
- -
- char *yytext zawiera bieżący tekst tokenu.
Może być modyfikowany, lecz nie może być
wydłużany (nie można doklejać dodatkowych znaków
na końcu).
- Jeśli w pierwszej sekcji opisu skanera pojawi się
dyrektywa specjalna %array to yytext zostanie zadeklarowane
jako charyytext[YYLMAX], gdzie YYLMAX jest
makrodefinicją, którą można przedefiniować w
pierwszej sekcji (wartość domyślna to ogólnie 8KB).
Używanie %array daje wolniejsze skanery, lecz
wartość yytext staje się odporna na wywołania
input() i unput(), które potencjalnie niszczą jego
wartość kiedy yytext jest wskaźnikiem znakowym.
Przeciwną dyrektywą do %array jest %pointer,
która jest dyrektywą domyślną.
- Dyrektywy %array nie można używać do
generowania klas skanera C++ (flaga -+).
- -
- int yyleng przechowuje długość
bieżącego tokenu.
- -
- FILE *yyin jest plikiem, z którego flex
domyślnie odczytuje wejście. Może być redefiniowany,
lecz taki zabieg ma sens tylko nim rozpocznie się skanowanie lub po
napotkaniu EOF. Zmienianie tej wartości w środku skanowania
może dać nieoczekiwane rezultaty spowodowane buforowaniem
wejścia. Zamiast tego użyj wtedy yyrestart(). Po
zakończeniu skanowania przez napotkanie końca pliku, można
przypisać wartość yyin do nowego pliku
wejściowego i wywołać ponownie skaner by
dokończył skanowanie.
- -
- void yyrestart( FILE *new_file ) może być
wołane do wskazywania yyin na nowy plik wejściowy.
Przełączenie na nowy plik jest natychmiastowe (wszelkie
poprzednio buforowane wejście jest tracone). Zauważ, że
wołanie yyrestart() z argumentem yyin porzuca
bieżący bufor wejściowy i kontynuuje skanowanie tego samego
pliku wejściowego.
- -
- FILE *yyout jest plikiem, do którego kierowane
jest wyjście akcji ECHO. Użytkownik może mu
przypisać inną wartość.
- -
- YY_CURRENT_BUFFER zwraca uchwyt
YY_BUFFER_STATE do bieżącego bufora.
- -
- YY_START zwraca wartość
całkowitą, odpowiadającą bieżącemu warunkowi
początkowemu. Wartości tej można używać dalej z
BEGIN do powrotu do tego warunku.
ŁĄCZENIE Z YACC¶
Jednym z podstawowych zastosowań
fleksa jest
współtowarzyszenie generatorowi analizatorów
yacc.
Analizatory składni
yacc oczekują wywołania procedury o
nazwie
yylex() celem znalezienia kolejnego tokenu wejściowego.
Procedura powinna zwrócić typ następnego tokenu oraz
wstawić związaną z nim wartość do globalnej zmiennej
yylval. Aby używać
fleksa z
yaccem, należy
yaccowi przekazać opcję
-d, co każe mu generować
plik
y.tab.h zawierający definicje wszystkich
%tokenów(%tokens) pojawiających się w wejściu
yacc. Plik ten jest następnie załączany do skanera
fleksowego. Na przykład jeśli jednym z tokenów jest
"TOK_NUMBER", to część skanera może
wyglądać tak:
%{
#include "y.tab.h"
%}
%%
[0-9]+ yylval = atoi( yytext ); return TOK_NUMBER;
OPCJE¶
flex ma następujące opcje:
- -b
- Generuje informacje zapasowe do lex.backup. Oto
lista stanów skanera, które wymagają kopii zapasowych oraz
znaki wejściowe dla których to zachodzi. Dodając
reguły można usunąć stany zapasowe. Jeśli
wyeliminowane zostaną wszystkie stany zapasowe, a użyte
będzie -Cf lub -CF, wygenerowany skaner będzie
działał szybciej (zobacz flagę -p). Opcją to
powinni się martwić jedynie użytkownicy wyciskający
ostatnie poty ze swoich skanerów. (Zobacz sekcję o
Rozważaniach nad Wydajnością.)
- -c
- nieużywana i niezalecana opcja dla zgodności z
POSIX-em.
- -d
- powoduje, że generowany skaner działa w trybie
debug. Za każdym razem po rozpoznaniu wzorca, gdy globalna
zmienna yy_flex_debug jest niezerowa (co jest domyślne),
skaner zapisze na stderr linię w postaci:
--accepting rule at line 53 ("dopasowany tekst")
Numer linii odnosi się do położenia reguły w pliku
definiującym skaner (tj. w pliku, potraktowanym fleksem). Komunikaty
są również generowane gdy skaner robi kopie zapasowe,
przyjmuje domyślną regułę, dochodzi do końca
bufora (lub napotyka NUL; w tym momencie obydwa [zdarzenia]
wyglądają jednakowo z punktu widzenia skanera) lub osiąga
koniec pliku.
- -f
- określa szybki skaner. Nie dokonywana jest
kompresja tabel i pomijane jest stdio. W efekcie kod jest duży, lecz
szybki. Opcja ta jest równoważna -Cfr (zobacz
niżej).
- -h
- generuje zestawienie "pomocy" opcji fleksa na
stdout i kończy działanie. -? i --help są
równoważnikami -h.
- -i
- nakazuje fleksowi generowania skanera niewrażliwego na
wielkość znaków. Wielkość liter we wzorcach
zostanie zignorowany, a tokeny wejścia będą dopasowywane
niezależnie od wielkości. Dopasowany tekst znajdujący
się w yytext będzie miał zachowaną
oryginalną wielkość liter.
- -l
- włącza maksymalną zgodność z
oryginalną implementacją leksa z AT&T. Zauważ,
że nie oznacza to pełnej zgodności. Użycie tej opcji
kosztuje sporo wydajności i eliminuje z użycia opcje
-+,-f,-F,-Cf lub -CF. Dla
szczegółów o zapewnianej zgodności, zobacz niżej
sekcję o niezgodnościach między Leksem i POSIX-em. Opcja ta
powoduje też z#definiowanie nazwy YY_FLEX_LEX_COMPAT w
generowanym skanerze.
- -n
- kolejna ignorowana opcja dodana dla zgodności z
POSIX-em.
- -p
- generuje raport o wydajności na stderr. Raport
składa się z komentarzy o właściwościach pliku
wejściowego fleksa, więc powoduje znaczną
utratę wydajności skanera. Jeśli podasz tę flagę
dwukrotnie, uzyskasz też komentarze o właściwościach,
które doprowadziły do drugorzędnych utrat
wydajności.
- Zauważ, że użycie REJECT, %option
yylineno, i zmiennego wiszącego kontekstu (variable trailing
context) (zobacz niżej sekcję o Niedostatkach /
Błędach) powoduje znaczną utratę wydajności;
używanie yymore(), operatora ^ i flagi -I
powoduje pomniejsze utraty wydajności.
- -s
- powoduje, że domyślna reguła
(powodująca echo niedopasowanego wejścia skanera na stdout) nie
jest wykonywana. Jeśli skaner napotka wejście, którego nie
może dopasować do reguł, przerywa działanie z
błędem. Opcja ta jest przydatna do znajdowania dziur w zbiorze
reguł skanera.
- -t
- nakazuje fleksowi zapisanie wygenerowanego skanera
na standardowe wyjście zamiast do pliku lex.yy.c.
- -v
- nakazuje fleksowi pisanie na stderr
zestawienia statystyk dotyczących generowanego skanera.
Większość statystyk jest pozbawiona znaczenia dla typowego
użytkownika, lecz pierwsza z linijek wskazuje wersję fleksa (to
samo co zgłasza opcja -V), a następna linia flagi użyte do
generowania skanera, z domyślnymi włącznie.
- -w
- powstrzymuje komunikaty o ostrzeżeniach.
- -B
- nakazuje fleksowi generowanie skanera wsadowego,
czyli odwrotność skanerów interaktywnych,
generowanych przez -I (zobacz niżej). Ogólnie, opcji
-B używa się mając pewność, że
skaner nigdy nie będzie używany interaktywnie i chcąc
wycisnąć jeszcze troszeczkę więcej
wydajności. Jeśli chcesz zyskać więcej
wydajności, powinieneś użyć opcji -Cf lub
-CF (opisanych niżej), które włączają
-B i tak automatycznie.
- -F
- mówi, że należy użyć reprezentacji
tablicy szybkiego skanera (i stdio ma być pominięte).
Reprezentacja ta jest mniej więcej tak szybka jak reprezentacja
pełnej tablicy (-f), i dla niektórych zestawów
wzorców będzie znacznie mniejsza (a dla innych większa).
Ogólnie, jeśli wzorzec zawiera zarówno "słowa
kluczowe" jak i łapiącą-wszystko regułę
"identyfikatora", tak jak poniższy zestaw:
"case" return TOK_CASE;
"switch" return TOK_SWITCH;
...
"default" return TOK_DEFAULT;
[a-z]+ return TOK_ID;
to lepiej użyć reprezentacji pełnej tablicy. Jeśli
obecna jest tylko reguła "identyfikatora" i używasz
potem hasza lub podobnej rzeczy do wykrywania słów kluczowych,
to lepiej użyć opcji -F.
- Opcja ta odpowiada -CFr (zobacz niżej). Nie
można jej używać z -+.
- -I
- nakazuje fleksowi generowanie skanera
interaktywnego. Skaner interaktywny patrzy naprzód do wyboru
dopasowania jedynie jeśli musi. Okazuje się, że patrzenie o
jeden dodatkowy znak dalej, nawet jeśli skaner ma już
dość do dopasowania tokenu jest trochę szybsze niż
wersja minimalna. Lecz skanery patrzące naprzód dają
dziadowską wydajność interaktywną; na przykład
gdy użytkownik wpisze nową linię, to nie jest ona
rozpoznawana jako token nowej linii dopóki nie wprowadzony zostanie
następny token, co oznacza często wpisanie całej
kolejnej linii.
- Skanery fleksa są domyślnie
interaktywne, chyba że użyjesz opcji kompresji tablicy
-Cf lub -CF (zobacz niżej). Jest tak dlatego, że
jeśli oczekujesz wysokiej wydajności, to powinieneś
użyć jednej z tych opcji, a jeśli tego nie
zrobiłeś, flex zakłada, że jesteś
gotów poświęcić trochę wydajności na rzecz
intuicyjnego zachowania interaktywnego. Zauważ też, że
nie możesz użyć -I w połączeniu z
-Cf lub -CF. Z tej przyczyny opcja ta nie jest w
rzeczywistości wymagana; jest domyślnie włączona dla
tych przypadków, dla których jest dopuszczalna.
- Opcją -B możesz wymusić by skaner
nie był interaktywny (zobacz powyżej).
- -L
- nakazuje fleksowi nie generować dyrektyw
#line. Bez tej opcji flex przyprawia generowany skaner
dyrektywami #line, więc komunikaty o błędach w akcjach
będą poprawnie położone względem oryginalnego
pliku wejściowego fleksa (jeśli błędy
wynikają z kodu w pliku wejściowym) lub [względem]
lex.yy.c (jeśli błędy są winą
fleksa -- powinieneś zgłosić takie błędy
pod adres e-mail podany poniżej.)
- -T
- powoduje, że flex działa w trybie
śledzenia. Będzie generował na stderr wiele
komunikatów o postaci wejścia i wynikających zeń
niedeterministycznych i deterministycznych automatach skończonych.
Opcja ta jest używana zwykle w opiece nad fleksem.
- -V
- drukuje numer wersji na stdout i kończy
działanie. --version jest synonimem -V.
- -7
- nakazuje fleksowi generowanie skanera 7-bitowego,
tj. takiego który może rozpoznawać w swoim wejściu
tylko znaki 7-bitowe. Zaletą używania -7 jest to, że
tablice skanera będą o połowę mniejsze niż
wygenerowane opcją -8 (zobacz niżej). Wadą jest to,
że skanery takie często się zawieszają lub
załamują jeśli na ich wejściu znajdzie się znak
8-bitowy.
- Zauważ jednak, że jeśli generujesz skaner z
użyciem opcji kompresji tablic -Cf lub -CF, to
użycie -7 zachowa jedynie niewielki rozmiar przestrzeni
tablic, a spowoduje, że skaner będzie znacząco mniej
przenośny. Domyślnym zachowaniem fleksa jest generowanie
skanerów 8-bitowych, chyba że użyto opcji -Cf lub
-CF, i wtedy flex generuje domyślnie skaner 7-bitowy,
chyba że twoja maszyna zawsze była skonfigurowana na generowanie
skanerów 8-bitowych (co często się zdarza poza USA). To,
czy flex wygenerował skaner 7 czy 8 bitowy, można
określić, sprawdzając zestawienie flag w wyjściu
-v, co opisano wyżej.
- Zauważ, że jeśli używasz -Cfe
lub -CFe, flex wciąż domyślnie generuje skaner
8-bitowy, gdyż po kompresji pełne tablice 8-bitowe nie są
wiele większe od 7-bitowych.
- -8
- nakazuje fleksowi generowanie skanera 8-bitowego,
tj. takiego, który rozpoznaje znaki 8-bitowe. Flaga ta jest wymagana
jedynie dla skanerów wygenerowanych z użyciem -Cf lub
-CF, gdyż w innych wypadkach jest ona przyjmowana jako
domyślna.
- -+
- określa, że chcesz by fleks wygenerował
klasę skanera w C++. Zobacz sekcję o generowaniu skanerów
C++.
- -C[aefFmr]
- steruje poziomem kompresji tablic, balansując
między małymi a szybkimi skanerami.
- -Ca ("wyrównaj") nakazuje fleksowi
poświęcić rozmiar tablic w wygenerowanych skanerach na
rzecz szybkości, gdyż elementy tablic mogą być lepiej
wyrównane pod kątem dostępu do pamięci i
obliczeń. Na niektórych architekturach RISC pobieranie i
operowanie na długich słowach jest efektywniejsze niż na
mniejszych jednostkach, takich jak krótkie słowa. Opcja ta
może podwoić rozmiar tablic używanych przez twój
skaner.
- -Ce Nakazuje fleksowi budowanie klas
równoważności, tj. zestawów znaków o
identycznych właściwościach leksykalnych (np. jeśli
jedynym wystąpieniem cyfr w pliku wejściowym fleksa jest klasa
znaków "[0-9]", to cyfry z przedziały od 0 do 9
zostaną wstawione do tej samej klasy równoważności.
Klasy takie zwykle znacznie redukują ostateczne rozmiary
tablic/obiektów (zwykle 2-5 razy) i są całkiem tanie od
strony wydajnościowej (jedno podglądnięcie w tablicy na
skanowany znak).
- -Cf określa, że należy generować
pełne tablice skanera - flex nie ma ich
kompresować poprzez branie korzyści z podobnych funkcji
przejść dla różnych stanów.
- -CF określa, że należy użyć
alternatywnej, szybkiej reprezentacji skanera (opisanej pod flagą
-F). Opcja ta nie może być używana z -+.
- -Cm nakazuje fleksowi budowanie klas
meta-równoważności, które są zbiorami klas
równoważności (lub znaków, jeśli klasy
równoważności nie są używane), które są
często używane wspólnie. Klasy takie są często
dobrą rzeczą podczas używania skompresowanych tablic, lecz
mają one już umiarkowany wpływ na wydajność (dwa
lub jeden test "if" i jedno podglądnięcie tablicy na
skanowany znak).
- -Cr powoduje, że generowany skaner omija
użycie standardowej biblioteki I/O dla wejścia. Zamiast
wołać fread() lub getc(), skaner będzie
używać wywołania systemowego read(), zyskując
tak trochę na wydajności (w skali zależnej od systemu). W
rzeczywistości jest to bez znaczenia, chyba że używasz
też -Cf lub -CF. Wykorzystanie -Cr może
też spowodować dziwne zachowanie jeśli np. odczytasz z
yyin z pomocą stdio przed wywołaniem skanera (skaner
pominie tekst pozostawiony przez twoje odczyty w buforze wejściowym
stdio).
- -Cr nie działa jeśli zdefiniujesz
YY_INPUT (zobacz wyżej Generowany Skaner).
- Samotne -C określa, że tablice skanera
powinny być kompresowane, lecz nie należy używać klas
równoważności i klas metarównoważności.
- Opcje -Cf lub -CF i -Cm nie mają
sensu razem - nie ma sytuacji dla klas metarównoważności
jeśli tablica nie jest kompresowana. Poza tym opcje można
swobodnie łączyć.
- Domyślnym ustawieniem jest -Cem, które
określa, że flex powinien generować klasy
równoważności i metarównoważności.
Ustawienie to daje najwyższy stopień kompresji tablic. Kosztem
większych tablic można uzyskać szybciej wykonujące
się skanery. Następujące zestawienie jest mniej więcej
prawdziwe:
najwolniejsze i najmniejsze
-Cem
-Cm
-Ce
-C
-C{f,F}e
-C{f,F}
-C{f,F}a
najszybsze i największe
Zauważ, że skanery z najmniejszymi tablicami są zwykle
najszybciej generowane i kompilowane, więc podczas prac rozwojowych
prawdopodobnie najchętniej użyjesz domyślnej, maksymalnej
kompresji.
- -Cfe jest często dobrym kompromisem między
szybkością a rozmiarem dla skanerów gotowych do
wdrożenia (production scanners).
- -ooutput
- nakazuje fleksowi zapisanie skanera do pliku output
zamiast do lex.yy.c. Jeśli połączysz -o z
opcją -t, to skaner jest zapisywany na stdout, lecz jego
dyrektywy #line (zobacz wyżej opcję -L),
odnoszą się do pliku output.
- -Pprefiks
- zmienia domyślny przedrostek yy używany
przez fleksa dla wszystkich zmiennych i funkcji globalnych na
prefiks. Na przykład -Pfoo zmienia nazwę
yytext na footext. Zmienia to też nazwę
domyślnego pliku wyjściowego z lex.yy.c na
lex.foo.c. A oto wszystkie nazwy, których dotyczy takie
zachowanie:
yy_create_buffer
yy_delete_buffer
yy_flex_debug
yy_init_buffer
yy_flush_buffer
yy_load_buffer_state
yy_switch_to_buffer
yyin
yyleng
yylex
yylineno
yyout
yyrestart
yytext
yywrap
(Jeśli używasz skanera C++, to dotyczyć to będzie tylko
yywrap i yyFlexLexer.) Wewnątrz samego skanera
można wciąż używać jednej i drugiej konwencji
nazywania; jednak z zewnątrz dozwolone są tylko nazwy
zmodyfikowane.
- Opcja ta umożliwia łatwe łączenie w
całość różnych programów fleksa w
jeden plik wykonywalny. Zauważ jednak, że używanie tej
opcji zmienia też nazwę yywrap(), więc musisz
teraz albo udostępnić własną wersję tej procedury
dla swojego skanera, albo użyć %option noyywrap,
gdyż konsolidacja z -lfl nie daje już funkcji
domyślnej.
- -Sskeleton_file
- przesłania domyślny plik szkieletowy, na
podstawie którego flex buduje swoje skanery. Nie będziesz
używać tej opcji, chyba że zajmujesz się rozwojem
fleksa.
flex daje też mechanizm kontrolowania opcji z samej specyfikacji
skanera, zamiast linii poleceń. Działa to przez włączanie
dyrektyw
%option w pierwszej sekcji specyfikacji skanera. W jednej
dyrektywie
%option można podawać wiele opcji, a w samej
pierwszej sekcji pliku wejściowego fleksa można używać
wielu dyrektyw.
Większość opcji jest podawana po prostu jako nazwy, poprzedzone
opcjonalnie słowem "no" (bez białych spacji w
środku), które neguje ich znaczenie. Część jest
równoważna flagom fleksa lub ich negacjom:
7bit -7
8bit -8
align -Ca
backup -b
batch -B
c++ -+
caseful lub
case-sensitive przeciwne do -i (domyślne)
case-insensitive lub
caseless -i
debug -d
default przeciwne do -s
ecs -Ce
fast -F
full -f
interactive -I
lex-compat -l
meta-ecs -Cm
perf-report -p
read -Cr
stdout -t
verbose -v
warn przeciwne do -w
(dla -w użyj "%option nowarn")
array równoważne "%array"
pointer równoważne "%pointer" (domyślne)
Niektóre
%opcje dają właściwości
niedostępne gdzie indziej:
- always-interactive
- nakazuje fleksowi generowanie skanera, który zawsze
uważa swoje wejście za "interaktywne". Normalnie przy
każdym pliku wejściowym skaner woła isatty() do
określenia czy wejście skanera jest interaktywne i powinno
być czytane po znaku. Po użyciu tej opcji wywołanie takie
nie jest robione.
- main
- nakazuje fleksowi udostępnić domyślny
program main() dla skanera, który po prostu woła
yylex(). Opcja ta implikuje noyywrap (zobacz
niżej).
- never-interactive
- nakazuje fleksowi generowanie skanera, który zawsze
uważa swoje wejście za "nieinteraktywne" (znów,
nie jest wołane isatty()). Opcja ta jest przeciwna do
always-interactive.
- stack
- włącza używanie stosów warunków
początkowych (zobacz wyżej Warunki Początkowe).
- stdinit
- jeśli jest ustawione (np. %option stdinit) to
zachodzi inicjalizacja yyin i yyout na stdin i
stdout, zamiast domyślnych nil. Niektóre
istniejące programy lex zależą od tego zachowania,
nawet jeśli nie jest ono zgodne z ANSI C, które nie
wymagają stałych czasu kompilacji stdin i
stdout.
- yylineno
- nakazuje fleksowi generowanie skanera, który
przechowuje liczbę obecnie odczytanych linii w zmiennej globalnej
yylineno. Opcja ta jest wymuszana przez %option
lex-compat.
- yywrap
- jeśli nie jest ustawione (np. %option
noyywrap), to skaner nie woła yywrap() na końcu
pliku, lecz po prostu przyjmuje, że nie ma już plików do
skanowania (dopóki użytkownik nie wskaże yyin na nowy plik
i nie wywoła yylex() ponownie).
flex skanuje akcje reguł w celu określenia czy używasz
właściwości
REJECT lub
yymore(). Opcje
reject i
yymore mogą przesłonić jego decyzję
na taką, jaką ustawisz przy użyciu opcji, zarówno
ustawiając je (np.
%option reject) do wskazania, że
właściwość jest rzeczywiście używana, lub
wyłączając je, wskazując, że
właściwość nie jest używana (np.
%option
noyymore).
Trzy opcje pobierają wartości łańcuchowe, offsetowane
znakiem '=':
%option outfile="ABC"
jest równoważne
-oABC, a
%option prefix="XYZ"
jest równoważne
-PXYZ. Poza tym,
%option yyclass="foo"
dotyczy tylko skanerów C++ (opcja
-+). Mówi to fleksowi,
że
foo jest wyprowadzone jako podklasa
yyFlexLexer,
więc
flex będzie umieszczał twoje akcje w funkcji
składowej
foo::yylex() zamiast w
yyFlexLexer::yylex().
Powoduje to też generowanie funkcji składowej
yyFlexLexer::yylex(), emitującej po wywołaniu błąd
działania (przez wywołanie
yyFlexLexer::LexerError()). Dla
dalszych informacji zobacz też niżej Generowanie Skanerów C++.
Istnieją opcje dla purystów, nie chcących widzieć w swoich
skanerach niepotrzebnych procedur. Każda z następujących opcji
(np. (np.,
%option nounput), powoduje, że dana procedura nie
pojawia się w wygenerowanym skanerze:
input, unput
yy_push_state, yy_pop_state, yy_top_state
yy_scan_buffer, yy_scan_bytes, yy_scan_string
(chociaż
yy_push_state() i podobne i tak nie pojawią się
dopóki nie użyjesz
%optionstack)
.
ROZWAŻANIA NAD WYDAJNOŚCIĄ¶
Podstawowym zadaniem przy projektowaniu
fleksa było zapewnienie,
że będzie generował wydajne skanery. Został
zoptymalizowany do dobrej współpracy z wielkimi zestawami
reguł. Poza omawianymi już wpływami opcji kompresji
-C,
istnieje jeszcze kilka akcji/opcji wpływających na
wydajność. Są to, od najkosztowniejszej do najmniej kosztownej:
REJECT
%option yylineno
arbitralny wiszący kontekst
zestawy wzorców, wymagające cofania
%array
%option interactive
%option always-interactive
'^' operator rozpoczęcia linii
yymore()
z których pierwsze trzy są bardzo kosztowne, a ostatnie dwa w
miarę tanie. Zauważ też, że
unput() jest
implementowane jako wywołanie procedurowe, które prawdopodobnie
wykonuje sporo pracy, podczas gdy
yyless() jest tanim makrem; więc
jeśli wstawiasz z powrotem nadmiarowy wyskanowany tekst, użyj
yyless().
REJECT powinno być unikane za wszelką cenę z punktu
widzenia wydajności. Jest to szczególnie kosztowna opcja.
Pozbycie się cofania jest trudne i może często prowadzić do
błędów w skomplikowanych skanerach. W praktyce zaczyna się
od użycia flagi
-b do wygenerowania pliku
lex.backup. Na
przykład dla wejścia
%%
foo return TOK_KEYWORD;
foobar return TOK_KEYWORD;
plik ten wygląda tak:
State #6 is non-accepting -
associated rule line numbers:
2 3
out-transitions: [ o ]
jam-transitions: EOF [ \001-n p-\177 ]
State #8 is non-accepting -
associated rule line numbers:
3
out-transitions: [ a ]
jam-transitions: EOF [ \001-` b-\177 ]
State #9 is non-accepting -
associated rule line numbers:
3
out-transitions: [ r ]
jam-transitions: EOF [ \001-q s-\177 ]
Compressed tables always back up.
Pierwszych kilka linii mówi, że istnieje stan skanera, w którym
może on przyjąć 'o', lecz nie może przyjąć
innego znaku i że w tym stanie aktualnie skanowany tekst nie pasuje do
żadnej reguły. Stan ten pojawia się podczas próby
dopasowania reguł z linijek 2 i 3 pliku wejściowego. Jeśli
skaner jest w tym stanie i odczyta cokolwiek innego niż 'o', to
będzie musiał się cofnąć i określić,
która reguła pasuje. Po chwili skrobania się w głowę
można zauważyć, że musi to być stan, gdy skaner
zobaczył "fo". W tej sytuacji otrzymanie czegokolwiek innego
niż 'o' spowoduje cofnięcie do prostego dopasowania 'f' (reguła
domyślna).
Komentarz odnośnie stanu #8 mówi, że istnieje problem przy
skanowaniu "foob". Rzeczywiście, jeśli pojawi się
dowolny znak inny niż 'a', to skaner będzie musiał się
cofnąć do przyjmowania "foo". Podobnie sprawa ma się
ze stanem #9, mówiącym o "fooba", po którym nie
następuje 'r'.
Ostatni komentarz przypomina nam, że usuwanie cofania nie ma sensu
jeśli nie używamy
-Cf lub
-CF, gdyż nie daje to
żadnego zysku wydajności na skanerach kompresowanych.
Sposobem usuwania cofania jest dodawanie reguł dla
"błędów":
%%
foo return TOK_KEYWORD;
foobar return TOK_KEYWORD;
fooba |
foob |
fo {
/* fałszywy alarm, nie jest to słowo kluczowe */
return TOK_ID;
}
Eliminowanie cofania można przeprowadzić również przy
użyciu reguły "łap-wszystko":
%%
foo return TOK_KEYWORD;
foobar return TOK_KEYWORD;
[a-z]+ return TOK_ID;
Jest to, tam gdzie można je zastosować, najlepsze rozwiązanie.
Komunikaty cofania często układają się w kaskady. W
skomplikowanych zbiorach reguł można dostać setki
komunikatów. Mimo to, jeśli można je zdeszyfrować, to ich
usuwanie wymaga tylko tuzina reguł (łatwo się jednak
pomylić i spowodować, że reguła obsługi
błędu będzie pasować do prawidłowego tokena.
Możliwe, że przyszłe implementacje fleksa będą
automatycznie zajmowały się usuwaniem cofania).
Ważne jest pamiętanie, że korzyści z eliminacji tego
problemu zyskujesz dopiero po zlikwidowaniu
każdej instancji
cofania. Pozostawienie choć jednej oznacza, że nie zyskujesz
niczego.
Zmienny wiszący kontekst (gdzie zarówno prowadząca jak i
kończąca część nie mają ustalonej
długości) wprowadza utratę wydajności zbliżoną
do
REJECT (tzn. znaczną). Dlatego gdy tylko można, to zapisz
taką regułę:
%%
mouse|rat/(cat|dog) run();
jako:
%%
mouse/cat|dog run();
rat/cat|dog run();
lub jako
%%
mouse|rat/cat run();
mouse|rat/dog run();
zwróć uwagę, że specjalna akcja '|'
nie powoduje
żadnych oszczędności, a wręcz może pogorszyć
sprawę (zobacz niżej Niedostatki / Błędy).
Innym obszarem, gdzie użytkownik może zwiększać
wydajność skanera jest to, że im dłuższe są
dopasowywane tokeny, tym szybciej działa skaner. Jest tak dlatego,
że przetwarzanie długich tokenów większości
znaków wejściowych zachodzi w wewnętrznej (krótkiej)
pętli skanującej i rzadko musi przechodzić przez dodatkową
pracę związaną z ustawianiem środowiska skanującego
(np.
yytext) dla akcji. Przypomnij sobie skaner komentarzy C:
%x comment
%%
int line_num = 1;
"/*" BEGIN(comment);
<comment>[^*\n]*
<comment>"*"+[^*/\n]*
<comment>\n ++line_num;
<comment>"*"+"/" BEGIN(INITIAL);
Można to przyspieszyć następująco:
%x comment
%%
int line_num = 1;
"/*" BEGIN(comment);
<comment>[^*\n]*
<comment>[^*\n]*\n ++line_num;
<comment>"*"+[^*/\n]*
<comment>"*"+[^*/\n]*\n ++line_num;
<comment>"*"+"/" BEGIN(INITIAL);
Teraz zamiast sytuacji, gdzie nowa linia wymaga przetwarzania następnej
akcji, rozpoznawanie nowych linii jest "rozrzucone" na inne
reguły. Umożliwia to zachowanie jak najdłuższego
dopasowania. Zauważ, że
dodawanie reguł
nie
spowalnia skanera! Jego szybkość jest niezależna od liczby
reguł i (w porównaniu do rozważań z początku sekcji)
ich stopnia skomplikowania (z zastrzeżeniem do operatorów takich jak
'*' i '|').
Ostateczny przykład przyspieszania skanera: załóżmy, że
chcesz skanować plik zawierający identyfikatory i słowa
kluczowe w liczbie jednego na linię, bez żadnych obcych znaków
i chcesz rozpoznawać wszystkie słowa kluczowe. Naturalnym odruchem
początkowym jest:
%%
asm |
auto |
break |
... etc ...
volatile |
while /* to jest słowo kluczowe */
.|\n /* a to nie... */
Aby wyeliminować śledzenie wstecz, wprowadź regułę
łap-wszystko:
%%
asm |
auto |
break |
... etc ...
volatile |
while /* to słowo kluczowe */
[a-z]+ |
.|\n /* a to nie... */
Obecnie, jeśli mamy zagwarantowane, że mamy dokładnie jedno
słowo w linii, możemy zredukować całkowitą
liczbę dopasowań o połowę przez włączanie w
rozpoznawanie tokenów łapanie nowych linii.
%%
asm\n |
auto\n |
break\n |
... etc ...
volatile\n |
while\n /* to słowo kluczowe */
[a-z]+\n |
.|\n /* a to nie... */
Trzeba być tu ostrożnym, gdyż właśnie
wprowadziliśmy do skanera cofanie. W szczególności, jeśli
my wiemy, że w wejściu nie będzie nigdy znaków
innych niż litery i nowe linie, to
flex nie może tego
wiedzieć i będzie planował ewentualność cofania
podczas skanowania tokenu w rodzaju "auto", po którym nie
nastąpi nowa linia lub litera. W poprzednim wypadku nastąpiłoby
po prostu dopasowanie reguły "auto", lecz teraz nie ma
"auto", ale "auto\n". Aby wyeliminować
możliwość cofania, możemy albo zduplikować wszystkie
reguły bez końcowych nowych linii albo, jeśli nie spodziewamy
się takiego wejścia i nie [interesuje nas] jego klasyfikacja,
możemy wprowadzić regułę łap-wszystko, która nie
zawiera nowej linii.
%%
asm\n |
auto\n |
break\n |
... etc ...
volatile\n |
while\n /* to słowo kluczowe */
[a-z]+\n |
[a-z]+ |
.|\n /* a to nie... */
Po kompilacji z
-Cf, jest to prawie tak szybkie, jak tylko możliwe
dla
fleksa dla tego problemu.
Ostatnia uwaga:
flex jest wolny przy dopasowywaniu NUL-ów,
szczególnie jeśli token zawiera ich wiele. Najlepiej pisać
reguły, dopasowujące
krótkie fragmenty takich
tekstów.
Kolejna ostatnia uwaga o wydajności: jak wspomniano wyżej w sekcji Jak
Dopasowywane jest Wejście, dynamiczne zmiany rozmiarów
yytext
do przyjmowania dużych tokenów jest powolne, gdyż obecnie
wymaga by taki token był reskanowany od początku. Tak więc
jeśli wydajność jest istotna, to powinieneś
dopasowywać "duże" fragmenty tekstu, lecz nie
"olbrzymie". Granicą między tymi pojęciami jest
około 8K znaków/token.
GENEROWANIE SKANERÓW C++¶
flex daje dwie drogi tworzenia skanerów przeznaczonych dla C++.
Pierwszą z nich jest proste skompilowanie fleksowego skanera kompilatorem
C++ zamiast kompilatora C. Nie powinieneś napotkać żadnych
błędów kompilacji (jeśli się pojawią, to
zgłoś to pod adres wskazany niżej, w sekcji o autorze).
Możesz wówczas w akcjach swoich reguł używać kodu C++
zamiast C. Zauważ, że domyślnym źródłem dla
skanera pozostaje
yyin, a domyślnym echem jest wciąż
yyout. Obydwa urządzenia są zmiennymi
FILE *, a nie
strumieniami C++.
Można też użyć
fleksa do generowania klasy skanera
C++. Służy do tego opcja
-+ (lub, równoważnie
%option c++), co jest przyjmowane automatycznie jeśli nazwa pliku
wykonywalnego fleksa kończy się plusem, jak np.
flex++. Przy
użyciu tej opcji, flex generuje skaner do pliku
lex.yy.cc zamiast
lex.yy.c. Generowany skaner zawiera plik nagłówkowy
FlexLexer.h, który definiuje interfejsy do dwóch klas C++.
Pierwsza klasa,
FlexLexer, daje abstrakcyjną klasę bazową,
definiującą ogólny interfejs klasy skanera. Daje
następujące funkcje składowe:
- const char* YYText()
- zwraca tekst ostatnio dopasowanego tokenu,
równoważnik yytext.
- int YYLeng()
- zwraca długość ostatnio dopasowanego tokenu,
równoważnik yyleng.
- int lineno() const
- zwraca numer aktualnej linii wejściowej (zobacz
%option yylineno), lub 1 jeśli %option yylineno
nie zostało użyte.
- void set_debug( int flag )
- ustawia flagę debuggującą dla skanera,
równoważnik przypisania do yy_flex_debug (zobacz
wyżej sekcję o opcjach). Zauważ, że aby
włączać w skanerze informacje diagnostyczne, musisz
skompilować go z użyciem %option debug.
- int debug() const
- zwraca bieżące ustawienie flagi
debuggującej.
Udostępniane są też funkcje składowe równoważne
yy_switch_to_buffer(),
yy_create_buffer() (chociaż
pierwszym argumentem jest wskaźnik
istream*, a nie
FILE*),
yy_flush_buffer(),
yy_delete_buffer() i
yyrestart() (i
znowu, pierwszym argumentem jest wskaźnik
istream*).
Kolejną klasą zdefiniowaną w
FlexLexer.h jest
yyFlexLexer, który jest klasą pochodną
FlexLexer.
Zaiwera następujące dodatkowe funkcje składowe:
- yyFlexLexer( istream* arg_yyin = 0, ostream* arg_yyout =
0 )
- buduje obiekt yyFlexLexer stosując podane
strumienie jako wejście i wyjście. Jeśli nie zostaną
podane, to strumienie będą odpowiadały odpowiednio
cin i cout.
- virtual int yylex()
- odgrywa tę samą rolę co yylex() dla
normalnych skanerów fleksa: skanuje strumień wejściowy,
konsumuje tokeny aż akcja reguły nie zwróci wartości.
Jeśli z yyFlexLexer wyprowadzisz podklasę S i
zechcesz dostać się do funkcji i zmiennych składowych
S z wnętrza yylex(), to musisz użyć
%option yyclass="S" by poinformować fleksa,
że będziesz używać podklasy zamiast
yyFlexLexer. W tym wypadku zamiast generować
yyFlexLexer::yylex(), flex generuje S::yylex() (oraz
generuje prosty yyFlexLexer::yylex(), który woła
yyFlexLexer::LexerError() po wywołaniu).
- virtual void switch_streams(istream* new_in =
0,
- ostream* new_out = 0) przypisuje yyin do
new_in (jeśli jest nie-nil) oraz yyout do
new_out (ditto), kasując poprzedni bufor wejściowy
jeśli przypisywana jest nowa wartość yyin .
- int yylex( istream* new_in, ostream* new_out = 0
)
- najpierw przełącza strumienie wejściowe
poprzez switch_streams( new_in, new_out ), a następnie zwraca
wartość yylex().
Poza tym,
yyFlexLexer definiuje następujące chronione
(protected) funkcje wirtualne, które można przedefiniować w
klasach pochodnych, by dostosować skaner:
- virtual int LexerInput( char* buf, int max_size
)
- odczytuje maksymalnie max_size znaków do
buf i zwraca liczbę odczytanych znaków. Aby wskazać
koniec wejścia zwracane jest 0 znaków. Zauważ, że
skanery "interaktywne" (zobacz flagi -B oraz -I)
definiują makro YY_INTERACTIVE. Jeśli redefiniujesz
LexerInput() i potrzebujesz brać różne akcje,
zależnie od tego czy skaner skanuje źródło
interaktywne czy nie, to możesz sprawdzać obecność tej
nazwy poprzez #ifdef.
- virtual void LexerOutput( const char* buf, int size
)
- zapisuje size znaków z bufora buf
który, o ile jest zakończony zerem, może zawierać
też "wewnętrzne" zera jeśli reguły skanera
mogą łapać tekst z wewnętrznymi zerami.
- virtual void LexerError( const char* msg )
- zgłasza komunikat błędu krytycznego.
Domyślna wersja tej funkcji zapisuje komunikat do strumienia
cerr i kończy działanie programu.
Zauważ, że obiekt
yyFlexLexer zawiera swój
pełny stan skanowania. Tak więc można używać
takich obiektów do tworzenia wielobieżnych (reentrant)
skanerów. Możesz używać wielu instancji tej samej klasy
yyFlexLexer, jak również możesz w jednym programie
łączyć wiele klas skanerów w całość,
używając opisanej wyżej opcji
-P .
Dla skanerów C++ nie jest dostępna właściwość
%array, trzeba więc używać
%pointer (tj.
wartości domyślnej).
Oto przykład prostego skanera C++:
// Przykład użycia klasy skanera C++
%{
int mylineno = 0;
%}
string \"[^\n"]+\"
ws [ \t]+
alpha [A-Za-z]
dig [0-9]
name ({alpha}|{dig}|\$)({alpha}|{dig}|[_.\-/$])*
num1 [-+]?{dig}+\.?([eE][-+]?{dig}+)?
num2 [-+]?{dig}*\.{dig}+([eE][-+]?{dig}+)?
number {num1}|{num2}
%%
{ws} /* pomiń spacje i tabulacje */
"/*" {
int c;
while((c = yyinput()) != 0)
{
if(c == '\n')
++mylineno;
else if(c == '*')
{
if((c = yyinput()) == '/')
break;
else
unput(c);
}
}
}
{number} cout << "number " << YYText() << '\n';
\n mylineno++;
{name} cout << "name " << YYText() << '\n';
{string} cout << "string " << YYText() << '\n';
%%
int main( int /* argc */, char** /* argv */ )
{
FlexLexer* lexer = new yyFlexLexer;
while(lexer->yylex() != 0)
;
return 0;
}
Jeśli chcesz tworzyć wiele (różnych) klas leksera,
powinieneś użyć flagi
-P (lub opcji
prefiks=) do
zmiany nazwy każdego
yyFlexLexer na inny
xxFlexLexer.
Następnie możesz załączać
<FlexLexer.h>
do swoich innych źródeł, raz na klasę leksera,
zmieniając najpierw nazwę
yyFlexLexer w następujący
sposób:
#undef yyFlexLexer
#define yyFlexLexer xxFlexLexer
#include <FlexLexer.h>
#undef yyFlexLexer
#define yyFlexLexer zzFlexLexer
#include <FlexLexer.h>
o ile (na przykład) użyjesz opcji
%option prefix="xx"
dla jednego ze swoich skanerów, a
%option prefix="zz"
dla drugiego.
WAŻNE: obecna postać klasy skanującej jest
eksperymentalna
i może zmieniać się między głównymi wydaniami.
NIEZGODNOŚCI Z LEX I POSIX¶
flex jest przeróbką narzędzia
lex z AT&T Unix
(jednakże obie te implementacje nie mają wspólnego kodu).
Posiada pewne rozszerzenia i niezgodności, które są istotne dla
tych, którzy chcą pisać skanery działające z oboma.
Flex jest w pełni zgodny ze specyfikacją POSIX
lex poza
szczegółem, że gdy używa
%pointer (domyślne),
to wywołanie
unput() niszczy zawartość
yytext, co
jest niezgodne ze specyfikacją POSIX.
W sekcji tej omówimy wszystkie znane obszary niezgodności fleksa z
AT&T lex i specyfikacją POSIX.
fleksowa opcja -l włącza maksymalną
zgodność z oryginalnym AT&T
lex, okupując to jednak
znacznymi stratami wydajności generowanego skanera. Niżej
zaznaczymy, które niezgodności można pokonać
używając opcji
-l.
flex jest w pełni zgodny z
leksem poza
następującymi wyjątkami:
- -
- Nieudokumentowana zmienna wewnętrzna skanera
lex o nazwie yylineno nie jest obsługiwana bez
-l lub %option yylineno.
- yylineno powinno być obsługiwane na
poziomie buforowym, a nie na skanerowym (pojedyncza zmienna
globalna).
- yylineno nie jest częścią
specyfikacji POSIX.
- -
- Procedura input() nie jest redefiniowalna
chociaż może być wołana do czytania znaków
następującym po tym, co dopasowano do reguły. Jeśli
input() napotka koniec pliku, to wykonywane jest normalne
przetwarzanie yywrap(). ``Prawdziwy'' koniec pliku jest
sygnalizowany przez input() zwróceniem wartości
EOF.
- Wejście jest natomiast sterowane przez definiowanie
makra YY_INPUT.
- Ograniczenie fleksa, że input() nie
może być redefiniowany jest zgodne ze specyfikacją POSIX,
która po prostu nie określa innego żadnego sposobu
sterowania wejściem skanera niż poprzez dokonanie
początkowego przypisania do yyin.
- -
- Procedura unput() nie jest redefiniowalna.
Ograniczenie to jest zgodne z POSIX.
- -
- Skanery fleksa nie są tak wielobieżne
(reentrant) jak skanery lex. W szczególności, jeśli
masz interaktywny skaner i obsługę przerwań, która
robi długi skok ze skanera, a skaner jest następnie wołany
ponownie, to możesz uzyskać następujący komunikat:
fatal flex scanner internal error--end of buffer missed
Aby wejść na nowo do skanera, użyj najpierw
yyrestart( yyin );
Zauważ, że wywołanie to wyrzuci wszelkie buforowane
wejście; zwykle jednak nie jest to problem przy skanerach
interaktywnych.
- Zauważ też, że klasy skanerów C++
są wielobieżne (reentrant), więc używając
opcji C++ powinieneś ich używać. Zobacz sekcję o
generowaniu skanerów C++.
- -
- output() nie jest obsługiwany. Wyjście
makra ECHO jest wykonywane do wskaźnika plikowego yyout
(domyślnie stdout).
- output() nie jest częścią
specyfikacji POSIX.
- -
- lex nie obsługuje wykluczających
warunków początkowych (%x), choć znajdują się one
w specyfikacji POSIX.
- -
- Przy rozwijaniu definicji, flex ujmuje je w nawiasy.
W leksie, następujące:
NAME [A-Z][A-Z0-9]*
%%
foo{NAME}? printf( "Znalazłem\n" );
%%
nie dopasuje się do łańcucha "foo", gdyż makro
jest rozwijane tak, że reguła odpowiada
"foo[A-Z][A-Z0-9]*?", a pierwszeństwo jest takie, że
'?' jest wiązany z "[A-Z0-9]*". We fleksie reguła
zostałaby rozwinięta do "foo([A-Z][A-Z0-9]*)?" i
łańcuch "foo" zostałby dopasowany.
- Zauważ, że jeśli definicja rozpoczyna
się od ^ lub kończy się na $ to nie
jest rozwijana w nawiasach, aby umożliwić tym operatorom
pojawienie się w definicjach bez utraty ich znaczenia. Ale operatory
<s>, / i <<EOF>> nie mogą
być używane w definicji fleksa.
- Używanie -l skutkuje leksowym
zachowaniem braku nawiasów wokół definicji.
- POSIX nakazuje ujmowanie definicji w nawiasy.
- -
- Niektóre implementacje leksa
umożliwiają rozpoczynanie akcji reguł w osobnej linii
jeśli wzorzec reguły ma doklejoną białą
spację:
%%
foo|bar<tu spacja>
{ foobar_action(); }
flex nie obsługuje tej właściwości.
- -
- Leksowe %r (generuj skaner Ratfor) nie jest
obsługiwane. Nie jest częścią specyfikacji POSIX.
- -
- Po wywołaniu unput(), yytext jest
niezdefiniowane aż do dopasowania następnego tokenu, chyba
że skaner używa %array. Inaczej ma się sprawa z
leksem lub specyfikacją POSIX. Opcja -l załatwia
tę niezgodność.
- -
- Pierwszeństwo operatora {} (zakresu
numerycznego) jest inne. lex interpretuje "abc{1,3}" jako
"dopasuj 1, 2 lub 3 pojawienia 'abc'", a flex
interpretuje to jako "dopasuj 'ab' z doklejonym jednym, dwoma lub
trzema znakami 'c'". Interpretacja fleksowa jest zgodna ze
specyfikacją POSIX.
- -
- Pierwszeństwo operatora ^ jest inne. lex
interpretuje "^foo|bar" jako "dopasuj albo 'foo' z
początku linii albo 'bar' gdziekolwiek", podczas gdy flex
rozumie to jako "dopasuj 'foo' lub 'bar' jeśli pojawią
się na początku linii". To drugie jest zgodne ze
specyfikacją POSIX.
- -
- Specjalne deklaracje rozmiaru-tablicy, takie jak %a,
obsługiwane przez lex nie są wymagane przez skanery
fleksa; flex je ignoruje.
- -
- Nazwa FLEX_SCANNER jest #definiowana, więc skanery
mogą być pisane z przeznaczeniem do użycia z fleksem
lub leksem. Skanery zawierają również
YY_FLEX_MAJOR_VERSION i YY_FLEX_MINOR_VERSION wskazując
na wersję fleksa, która wygenerowała skaner (na
przykład dla wydania 2.5 definiowane są odpowiednio liczby 2 i
5).
Następujące właściwości
fleksa nie są
zawarte w specyfikacjach
lex ani POSIX:
Skanery C++
%option
zakresy warunków początkowych
stosy warunków początkowych
skanery interaktywne/nieinteraktywne
yy_scan_string() i koledzy
yyterminate()
yy_set_interactive()
yy_set_bol()
YY_AT_BOL()
<<EOF>>
<*>
YY_DECL
YY_START
YY_USER_ACTION
YY_USER_INIT
dyrektywy #line
%{} wokół akcji
wiele akcji w linii
plus prawie wszystkie flagi fleksa. Ostatnia właściwość
listy odnosi się do faktu, że we fleksie można wstawiać
wiele akcji do jednej linii, rozdzielając je średnikami, podczas gdy
w
leksie, następująca instrukcja
foo handle_foo(); ++num_foos_seen;
jest (raczej niespodziewanie) obcinana do
foo handle_foo();
flex nie obcina akcji. Akcje które nie są objęte klamrami
kończą się zwyczajnie na końcu linii.
DIAGNOSTYKA¶
warning, rule cannot be matched (ostrzeżenie, reguła nie
może być dopasowana) wskazuje, że podana reguła nie
może być dopasowana gdyż występuje za innymi
regułami, które zawsze dopasują jej tekst. Na przykład
następujące foo nie może być dopasowane, gdyż pojawia
się po regule łap-wszystko:
[a-z]+ got_identifier();
foo got_foo();
Użycie w skanerze
REJECT powstrzyma to ostrzeżenie.
warning,
-s option given but default rule can be matched
(ostrzeżenie, podano opcję -s, lecz dopasowana może być
reguła domyślna) oznacza, że możliwe jest (przypuszczalnie
tylko w konkretnym warunku początkowym), że reguła
domyślna (dopasowania dowolnego znaku) jest jedyną, która
dopasuje się do konkretnego wejścia. Ponieważ podano
-s,
zakłada się, że nie jest to celowe.
reject_used_but_not_detected undefined lub
yymore_used_but_not_detected undefined (niezdefiniowana fraza pierwsza
lub druga) - te błędy pojawiają się podczas kompilacji.
Wskazują one, że skaner używa
REJECT lub
yymore(), lecz
flex nie poinformował o tym fakcie. Znaczy
to, że
flex przeskanował pierwsze dwie sekcji w poszukiwaniu
pojawienia się tych akcji, ale ich nie znalazł, bo jakoś je
przemyciłeś (np. przez plik #include). Użyj
%option
reject lub
%option yymore do wskazania fleksowi, że
naprawdę używasz tych właściwości.
flex scanner jammed - skaner skompilowany z
-s napotkał
łańcuch wejściowy, który nie został dopasowany do
żadnej z jego reguł. Błąd ten może się
pojawić też z powodu problemów wewnętrznych.
token too large, exceeds YYLMAX (token zbyt duży, przekracza YYLMAX)
- twój skaner używa
%array a jedna z jego reguł
dopasowała się do łańcucha dłuższego niż
stała
YYLMAX (domyślnie 8K). Możesz zwiększyć
tę wartość zwiększając #definicję stałej
YYLMAX w sekcji definicji swojego wejścia
fleksa.
scanner requires -8 flag to use the character 'x' (skaner wymaga
flagi -8 do używania znaku 'x') - specyfikacja twojego skanera zawiera
rozpoznawanie znaku 8-bitowego
'x', a nie podana została flaga -8,
w wyniku czego skaner użył 7-bit z powodu wykorzystania opcji
kompresji tablic
-Cf lub
-CF. Dla szczegółów
zobacz dyskusję flagi
-7.
flex scanner push-back overflow - użyłeś
unput() do
wepchnięcia z powrotem tak długiego tekstu, że bufor skanera
nie potrafił przetrzymać wepchniętego tekstu i
bieżącego tokena w
yytext. Idealny skaner powinien
dynamicznie zmienić rozmiar bufora, lecz obecnie tak się nie dzieje.
input buffer overflow, can't enlarge buffer because scanner uses REJECT
(przekroczenie bufora wejściowego nie może powiększyć
bufora gdyż skaner używa REJECT) - skaner pracował nad
dopasowaniem bardzo dużego tokenu i potrzebował rozszerzyć
bufor wejściowy. Nie działa to ze skanerami, używającymi
REJECT.
fatal flex scanner internal error--end of buffer missed (krytyczny
błąd wewnętrzny skanera flex -- rozminięto się z
końcem bufora) - Może się to pojawić w skanerze,
który jest uruchomiony po długim skoku z ramki aktywacji skanera.
Przed powrotem do skanera użyj:
yyrestart( yyin );
albo, jak wspomniano wyżej, przełącz się na używanie
skanerów C++.
too many start conditions in <> construct! (zbyt wiele
warunków początkowych w konstrukcji <>) - w konstrukcji
<> pojawiło się więcej warunków początkowych
niż istnieje w rzeczywistości (więc przynajmniej jeden z nich
pojawił się dwukrotnie).
PLIKI¶
- -lfl
- biblioteka, z którą muszą być
łączone skanery.
- lex.yy.c
- generowany skaner (nazywany na niektórych systemach
lexyy.c).
- lex.yy.cc
- generowana klasa skanera C++, po użyciu
-+.
- <FlexLexer.h>
- plik nagłówkowy definiujący klasę
bazową skanera C++, FlexLexer i klasę pochodną,
yyFlexLexer.
- flex.skl
- skaner szkieletowy. Plik ten jest używany tylko przy
budowaniu fleksa, nie przy jego uruchamianiu.
- lex.backup
- informacje wspierające (backing-up) dla flagi
-b (nazywany jest mianem lex.bck na niektórych
systemach).
NIEDOSTATKI / BŁĘDY¶
Niektóre wzorce wiszącego kontekstu nie mogą być poprawnie
dopasowane i generują komunikaty ostrzegawcze ("dangerous trailing
context") (niebezpieczny wiszący kontekst). Są to wzorce, gdzie
zakończenie pierwszej części reguły dopasowuje się do
początku drugiej części, takie jak "zx*/xy*", gdzie
'x*' dopasowuje 'x' na początku wiszącego kontekstu. (Zauważ,
że projekt POSIX-a określa, że dopasowany w takich wzorcach
tekst jest niezdefiniowany.)
Dla niektórych reguł wiszącego kontekstu, części
które są w rzeczywistości określonej długości
nie są tak rozpoznawane. Prowadzi to do wspomnianej wyżej straty
wydajności. W szczególności, części
używające '|' lub {n} (takie jak "foo{3}") zawsze są
uważane za zmienno-długościowe.
Łączenie wiszącego kontekstu z akcją specjalną '|'
może spowodować, że
ustalony (fixed) wiszący
kontekst zostanie zmieniony w bardziej kosztowny,
zmienny wiszący
kontekst. Na przykład następujące:
%%
abc |
xyz/def
Używanie
unput() uszkadza yytext i yyleng, chyba że użyto
dyrektywy
%array lub opcji
-l.
Dopasowywanie wzorców NUL-i jest znacznie wolniejsze niż dopasowywanie
innych znaków.
Dynamiczne zmiany rozmiaru bufora są wolne i wymagają reskanowania
całego tekstu dopasowanego dotąd przez bieżący (zwykle
duży) token.
Z powodu buforowania wejścia i czytania z wyprzedzeniem, nie można
łączyć z regułami fleksa wywołań
<stdio.h>, np.
getchar(). Zamiast tego wołaj
input().
Wpisy całej tablicy (total table entries) wymieniane przez flagę
-v nie zawierają niektórych wpisów, potrzebnych do
określania, która reguła została dopasowana. Liczba
wpisów jeśli skaner nie używa
REJECT jest równa
liczbie stanów DFA, a w przeciwnym wypadku jest trochę większa.
REJECT nie może być używany z opcjami
-f lub
-F.
Wewnętrzne algorytmy
fleksa wymagają udokumentowania.
ZOBACZ TAKŻE¶
lex(1),
yacc(1),
sed(1),
awk(1).
John Levine, Tony Mason, and Doug Brown,
Lex & Yacc, O'Reilly and
Associates. Upewnij się, że bierzesz 2-gie wydanie.
M. E. Lesk and E. Schmidt,
LEX - Lexical Analyzer Generator
Alfred Aho, Ravi Sethi and Jeffrey Ullman,
Compilers: Principles, Techniques
and Tools, Addison-Wesley (1986). Opisuje techniki dopasowywania
wzorców używane przez
fleksa (deterministyczne automaty
skończone).
AUTOR¶
Vern Paxson, z pomocą wielu pomysłów i inspiracji od Vana
Jacobsona. Oryginalną wersję napisał Jef Poskanzer.
Reprezentacja szybkiej tablicy jest częściową
implementacją projektu Vana Jacobsona. Implementacja została
wykonana przez Kevina Gonga and Verna Paxsona.
Podziękowania dla wielu beta testerów, komentatorów i
kontrybutorów fleksa, z których szczególnie zasłużone
są następujące osoby: Francois Pinard, Casey Leedom, Robert
Abramovitz, Stan Adermann, Terry Allen, David Barker-Plummer, John Basrai,
Neal Becker, Nelson H.F. Beebe, benson@odi.com, Karl Berry, Peter A. Bigot,
Simon Blanchard, Keith Bostic, Frederic Brehm, Ian Brockbank, Kin Cho, Nick
Christopher, Brian Clapper, J.T. Conklin, Jason Coughlin, Bill Cox, Nick
Cropper, Dave Curtis, Scott David Daniels, Chris G. Demetriou, Theo Deraadt,
Mike Donahue, Chuck Doucette, Tom Epperly, Leo Eskin, Chris Faylor, Chris
Flatters, Jon Forrest, Jeffrey Friedl, Joe Gayda, Kaveh R. Ghazi, Wolfgang
Glunz, Eric Goldman, Christopher M. Gould, Ulrich Grepel, Peer Griebel, Jan
Hajic, Charles Hemphill, NORO Hideo, Jarkko Hietaniemi, Scott Hofmann, Jeff
Honig, Dana Hudes, Eric Hughes, John Interrante, Ceriel Jacobs, Michal
Jaegermann, Sakari Jalovaara, Jeffrey R. Jones, Henry Juengst, Klaus Kaempf,
Jonathan I. Kamens, Terrence O Kane, Amir Katz, ken@ken.hilco.com, Kevin B.
Kenny, Steve Kirsch, Winfried Koenig, Marq Kole, Ronald Lamprecht, Greg Lee,
Rohan Lenard, Craig Leres, John Levine, Steve Liddle, David Loffredo, Mike
Long, Mohamed el Lozy, Brian Madsen, Malte, Joe Marshall, Bengt Martensson,
Chris Metcalf, Luke Mewburn, Jim Meyering, R. Alexander Milowski, Erik Naggum,
G.T. Nicol, Landon Noll, James Nordby, Marc Nozell, Richard Ohnemus, Karsten
Pahnke, Sven Panne, Roland Pesch, Walter Pelissero, Gaumond Pierre, Esmond
Pitt, Jef Poskanzer, Joe Rahmeh, Jarmo Raiha, Frederic Raimbault, Pat Rankin,
Rick Richardson, Kevin Rodgers, Kai Uwe Rommel, Jim Roskind, Alberto Santini,
Andreas Scherer, Darrell Schiebel, Raf Schietekat, Doug Schmidt, Philippe
Schnoebelen, Andreas Schwab, Larry Schwimmer, Alex Siegel, Eckehard Stolz,
Jan-Erik Strvmquist, Mike Stump, Paul Stuart, Dave Tallman, Ian Lance Taylor,
Chris Thewalt, Richard M. Timoney, Jodi Tsai, Paul Tuinenga, Gary Weik, Frank
Whaley, Gerhard Wilhelms, Kent Williams, Ken Yap, Ron Zellar, Nathan Zelle,
David Zuhn, oraz ci, których nazwiska wyleciały z moich
zdolności archiwizowania poczty, lecz których wkład jest
równie ważny.
Keith Bostic, Jon Forrest, Noah Friedman, John Gilmore, Craig Leres, John
Levine, Bob Mulcahy, G.T. Nicol, Francois Pinard, Rich Salz i Richard Stallman
pomogli z różnymi problemami dystrybucji.
Esmond Pitt and Earle Horton pomógł z wsparciem 8-bit; Benson
Margulies i Fred Burke pomogli z wsparciem C++; Kent Williams i Tom Epperly
pomogli z wsparciem klas C++; Ove Ewerlid pomógł z wsparciem
NUL-ów; Eric Hughes pomógł z wielokrotnymi buforami.
Praca ta była początkowo wykonywana gdy byłem z Real Time Systems
Group w Lawrence Berkeley Laboratory w Berkeley, CA. Wielkie dzięki do
wszystkich za wsparcie, które uzyskałem.
Komentarze ślij do vern@ee.lbl.gov.
Powyższe tłumaczenie pochodzi z nieistniejącego już Projektu
Tłumaczenia Manuali i
może nie być aktualne. W razie
zauważenia różnic między powyższym opisem a
rzeczywistym zachowaniem opisywanego programu lub funkcji, prosimy o
zapoznanie się z oryginalną (angielską) wersją strony
podręcznika za pomocą polecenia:
- man --locale=C 1 flex
Prosimy o pomoc w aktualizacji stron man - więcej informacji można
znaleźć pod adresem
http://sourceforge.net/projects/manpages-pl/.