Flex i Bison/Flex wprowadzenie
Wprowadzenie
edytujĆwiczenie na rozgrzewkę: zróbmy makefile dla programu tylko flexowego i dla pliku trzeci.l .
Struktura Pliku
edytujZazwyczaj plik analizy leksykalnej ma strukturę:
nagłówek
%%
reguły
%%(opcjonalne)
kod programu(opcjonalne)
Nagłówek
edytujPrzykładowy wycięty nagłówek:
%option <nazwa opcji> //(1)
%s <nazwa stanu> //(2)
%{
//kod który pojawi się (3)
//na początku pliku lexera
%}
liczba [0-9]+ //(4)
%%
...
- Opcje dla flexa które są specyficzne dla danego pliku i nie możemy ich wpisać do LFLAGS.
- Stany flexa które muszą być najpierw zadeklarowane tutaj. Na razie tyle nam wystarczy.
- Kod/definicje zawarte w tych klamerkach pojawią się prawie na początku programu. Przydatne zwłaszcza gdy chcemy użyć funkcji z plików nagłówkowych w regułach.
- Definicja wyrażenia regularnego - umożliwia nam budowanie przejrzystszych wyrażeń regularnych.
Reguły
edytujOgólna postać reguły:
^"przykladowe"-*wyrazenie[0-9]+ {/*przykładowa akcja*/;}
Wyrażenia regularne
edytuj- Tekst dosłowny = "slowo_kluczowe"
- Wszystko pomiędzy "" jest brane dosłownie jako tekst do wyszukania.
- Jedynie " i \ mają specjalne właściwości. Metaznaki + ? * [ ] ^ $ tracą swój charakter.
- Klasy znaków = [aA]
- Klasa znaków ma znaleść jeden z wymienionych znaków. Może także znaleść przedział znaków np. [a-z]. Znak - może wystąpić dosłownie jedynie na początku klasy znaków inaczej zaznacza przedział.
- Zanegowana klasa znaków = [^]
- Zanegowana klasa znaków dopasuje dowolny znak który nie występuje wewnątrz. Może dopasować znak nowej lini ! ("." nie)! Zanegowana klasa znaków nie oznacza że po wyrażeniu nie ma być takiego znaku, oznacza że będzie znak inny np. może pochłonąć literę z słowa kluczowego które potem nie zostanie dobrze dopasowane.
- Dowolny znak = .
- Dopasuje dowolny znak ale nie \n!!
- Kwantyfikatory = ? + *
- Opcjonalny = ?
- To co jest nim zaznaczone jest opcjonalne do wyszukania np. -?[0-9]+ to dowolna liczba z ew. znakiem minusa
- Raz lub więcej = +
- To co jest nim zaznaczone powino pojawić się raz lub więcej razy np. [a-zA-Z]+ dopasuje literę lub wyraz
- Zero lub więcej = *
- To co nim oznaczone może się pojawić raz , więcej lub w ogóle np [a-zA-Z][0-9a-zA-Z]* słowo z cyframi ale nie na początku.
- Grupowanie = (cos)
- Nawiasy służą do grupowania np a(b|c) oznacza ab lub ac gdy natomiast ab|c oznacza ab lub c.
- Grupowanie umożliwia też powtórzenie np. (ta)+to będzie pasować do tato tatato itp. .
- Własne definicje = {liczba}
- możemy nietypowe klasy znaków lub część wyrażeń zapisać w definicji a w wyrażeniach łączyć je.
- Sekwencje unikowe = \coś
- Żeby znaleść dosłowny znak + musimy napisać \+ , możemy również zarządać znaku tabulacji \t lub dowolnego znaku \0x34(szesnastkowo) \084(ósemkowo)
- Znaki które tak trzeba deklarować:
- \+ \? \* - kwantyfikatory.
- \( \) \[ \] \{ \} - nawiasy.
- \\ \/ slasze.
- \t \n - znaki specjalne.
- \0111 \0x44 - znaki ósemkowe i szesnastkowe.
- Przykłady
- \/\/.* - komentarz w stylu c++ do końca lini (. nie dopasuje \n).
- #.* - kometarz w stylu basha/make
- -?[0-9]+(\.[0-9]+)? - liczba z opcjonalnym znakiem - i opcjonalna kropka i cyframi po niej (w 73. dopasuje tylko 73).
- (-?[0-9]+)?\.[0-9]+ - liczby zmienoprzecinkowe zaczynające się od kropki i opcjonalnie liczba wcześniej.
- 2 poprzednie przykłady dopasują wszystkie liczby np .77 -34. 18.12 a nie dopasują . jako liczby.
- Dopisywać śmiało ale najpierw przetestować z własnym flexem ;)
- Więcej informacji na temat wyrażeń regularnych w flexie:
Akcje
edytujKażda akcja składa się z kodu w języku c w jednej albo wielu liniach.
- Akcje jedno-linijkowe:
- Wszystkie instrukcje zakończone średnikiem znajdują się w jednej lini:
... instrukcja1();instrukcja2();if(cos)return(cos-1);
- Akcje wielolinijkowe:
- Można rozszerzyć akcje na wiele linijek zamykając wszystko w klamerki {}:
... {instrukcja();
while(cos)
{
zrob_cos();
--cos;
}
}
- Nic nie broni nam pisać akcji jedno-linijkowych w sposób wielo-linijkowy.
Rozstrzyganie niejednoznaczności
edytujMoże się zdarzyć że dane wyrażenia mogą dopasować to samo:
int
[a-z]+
W przypadku "intonacja" flex wybierze wyrażenie które dopasuje więcej. W przypadku "int" flex użyje wyrażenie które jest wcześniej zadeklarowane.
Wartości akcji
edytujWartości akcji są to dane z których możemy korzystać w akcjach:
- char *yytext - zawiera wskaźnik na dopasowany ciąg znaków.
- Uwaga ! nie przekazujmy samego wskaźnika na zewnątrz tylko zróbmy kopię np strdup().
- int yyleng - zawiera ilość dopasowanych znaków.
- FILE *yyin - Uchwyt pliku używany przez wejście leksera, może się zmienić w yywrap.
- FILE *yyout - Uchwyt na ktorym działa akcja "ECHO" , można też używać fprintf na nim.
- <różnie> yyval - występuje gdy pracujemy z bisonem i w niej zwracamy wartość tokena.
- reszta bywa rzadko używana w praktyce może pojawi się w jakimś dodatku.
Reguły wbudowane
edytuj. ECHO;
- Każdy znak który nie został przechwycony zostanie przekazany na wyjście leksera.
<<EOF>> {if (yywrap() != 0) return 0;}
- Jeśli odczytano koniec pliku a yywrap powie że są jeszcze dane kontynuuje działanie.
Kod Programu
edytujTa sekcja umożliwia nam zintegrowanie z lekserem kodu własnych aplikacji. Przydatne zwłaszcza gdy chcemy zmieniać stany leksera z zewnątrz akcji. Jeśli chcemy wpływać na lekser z innego pliku źródłowego np parsera potrzebna nam będzie funkcja pośrednicząca np.:
void zrob_cos (void)
{
//robimy cos w lekserze
}
Przykład
edytujA oto drobny przykładzik ;)
%{//(1)
#include <stdio.h>
#include <stdlib.h>
%}
%%
[0-9]+ {printf("Liczba: %d\n", atoi(yytext));/*(2)*/}
[a-zA-Z]+ {printf("Nazwa: %s\n", yytext);/*(2)*/}
\n {printf("Koniec linii\n");/*(4)*/}
%%
int yywrap (void)//(5)
{
return 1;
}
int main (int argc,char** argv)
{
return yylex();//(6)
}
- potrzebujemy atoi() i printf().
- To złapie nam liczby.
- A to złapie nam tekst.
- A to złapie nam koniec lini.
- Cały czas mówimy że koniec danych to absolutny koniec danych.
- Wywołujemy leksera a jak skończy pracę zwróci nam 0 które przekażemy do systemu.
Ćwiczenia
edytuj- Liczby zmieno-przecinkowe
- Dodaj obsługę licz zmienoprzecinkowych w postaci -/+ liczba . liczba
- Muszą zostać dopasowane następujące liczby :
- 0.3333
- .567
- -0.13
- +4.
- . to nie liczba , pamiętaj że + i . to metaznaki ;)
- użyj atof() do konwersji.
- Liczby zmieno-przecinkowe 2
- Czasami wygodniej jest napisać 1E-10 zamiast 0.0000000001
- Więc dodaj obsługę takich liczb:
- 3.3333E-1
- .12E+17
- Inne liczby np hexowe/octowe
- Czasami prosciej wpisać 0x100 zamiast 256
- 0x34FF 0XAA
- 0227 013
- A może coś poważniejszego - interpreter języka Ook
- Można zadeklarować int *tablica , inicjować w main , zwalniać w main a jak się nie uda nie wołać yylex()
- Można [ \t]* ignorować białe znaki pomiędzy parami Ook/Ook.
Zakończenie
edytujJeśli już opanowaliśmy materiał możemy przejść do czegoś bardziej skąplikowanego czyli użycie yywrap, pliki wejściowe i i wyciąganie statystyki po zakonczeniu analizy.