Flex i Bison/Flex wprowadzenie

Wprowadzenie

edytuj

Ćwiczenie na rozgrzewkę: zróbmy makefile dla programu tylko flexowego i dla pliku trzeci.l .

Struktura Pliku

edytuj

Zazwyczaj plik analizy leksykalnej ma strukturę:

nagłówek
%%
reguły
%%(opcjonalne)
kod programu(opcjonalne)

Nagłówek

edytuj

Przykł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)
%%
...
  1. Opcje dla flexa które są specyficzne dla danego pliku i nie możemy ich wpisać do LFLAGS.
  2. Stany flexa które muszą być najpierw zadeklarowane tutaj. Na razie tyle nam wystarczy.
  3. 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.
  4. Definicja wyrażenia regularnego - umożliwia nam budowanie przejrzystszych wyrażeń regularnych.

Reguły

edytuj

Ogó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.

Każ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

edytuj

Moż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

edytuj

Wartoś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

edytuj

Ta 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

edytuj

A 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)
 	}
  1. potrzebujemy atoi() i printf().
  2. To złapie nam liczby.
  3. A to złapie nam tekst.
  4. A to złapie nam koniec lini.
  5. Cały czas mówimy że koniec danych to absolutny koniec danych.
  6. 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

edytuj

Jeś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.

Poprzedni rozdział: Make+Gcc
Następny rozdział: Flex-po co nam yywrap