Provided by:
manpages-pl_20051117-1_all 
NAZWA
flex - szybki generator analizatora leksykalnego
SKŁADNIA
flex [-bcdfhilnpstvwBFILTV78+? -C[aefFmr] -ooutput -Pprefix -Sskeleton]
[--help --version] [filename ...]
WPROWADZENIE
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 skanerw: 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ą reguami. 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 tokenw i informuje co zobaczył.
Szczegóły tego przykładu zostaną wyjaśnione w następnych sekcjach.
FORMAT PLIKU WEJŚCIOWEGO
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 warunkw
pocztkowych, 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ś wcity
(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 '-',
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 wyraenia. 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]" bdzie 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 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 regua
domylna: 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 wskanik 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 odgazieniem; 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
pocztek 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 wczajce warunki
początkowe, a druga wykluczajce. 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 wczajcy , to reguły bez warunków początkowych będą
również aktywne. Jeśli jest wykluczajcy, 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 wczajcy (%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 pominite). 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 nastpny
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 moesz 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 rwnowanoci, 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ć pene 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-rwnowanoci, 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 kadej 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
krtkie 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 peny 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.