flex
szybki generator analizatora leksykalnego
- Provided by: manpages-pl (Version: 1:0.5-1)
- Report a bug
szybki generator analizatora leksykalnego
flex [-bcdfhilnpstvwBFILTV78+? -C[aefFmr] -ooutput -Pprefix -Sskeleton] [--help --version] [filename ...]
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
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.
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 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:
foo/bar$
<sc1>foo<sc2>bar
Zauważ, że pierwszy z nich może być zapisany
jako "foo/bar\n".
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.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).
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:
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).
%%
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ń.
%%
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.
{
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.
%%
"/*" {
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.)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.
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:
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).
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.
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:
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:
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();
}
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.
Sekcja ta zestawia różne wartości dostępne dla użytkownika w akcjach regułowych.
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;
flex ma następujące opcje:
--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.
"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.
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.
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.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:
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).
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.
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:
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:
Poza tym, yyFlexLexer definiuje następujące chronione (protected) funkcje wirtualne, które można przedefiniować w klasach pochodnych, by dostosować skaner:
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.
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:
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.
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.
%%
foo|bar<tu spacja>
{ foobar_action(); }
flex nie obsługuje tej właściwości.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.
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).
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.
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).
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:
Prosimy o pomoc w aktualizacji stron man - więcej informacji można znaleźć pod adresem http://sourceforge.net/projects/manpages-pl/.