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