На вході отримує текст у вільному форматі й правила виділення лексем.
На виході дає код аналізатора, в вигляді функції на мові C[a].
Правила задаються в вигляді регулярних виразів ліворуч і, здебільшого, коду на мові C праворуч.
Правила містять три секції, відокремлені рядком «%%»:
визначення
%% правила
%% код користувача
Визначення містять стартові значення й визначення, правила, безпосередньо самі вирази й дії, що відповідають їм, користувацький код просто включається в вивід flex.
Деякі секції можуть бути відсутніми.
Функція аналізатора отримує текст на вході й виконує заданий код для кожної знайденої лексеми.
Наприклад, даний код, для кожного входження username в тексті, виконає код printf(«%s», getlogin()):
%%usernameprintf("%s",getlogin());
Дана функція виведе в потік рядок, що повертається функцією getlogin().
Тобто, кожне входження username у вхідному потоці буде замінено значенням, поверненим getlogin(). Наприклад, іменем поточного користувача.
Правила, відповідно до яких підсумкова функція має друкувати на виході тип лексеми (if, змінна, число, унарна чи бінарна операція) та значення для деяких лексем:
%%ifprintf("IF statement\n");[a-z]+printf("tag, value %s\n",yytext);{D}+printf("decimal number %s\n",yytext);"++"printf("unary op\n");"+"printf("binary op\n");
Приклад підрахунку кількості рядків і символів у тексті:
%{intnum_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);}
Далі, функцію, створену генератором можливо використовувати з генераторами синтаксичних аналізаторів — зазвичай flex використовується разом з yacc чи bison. Синтаксичний аналізатор викликає функцію yylex(), створену генератором flex для пошуку наступної лексеми.
Приклад лексичного аналізатора
Одним із базових понять лексичного аналізу є логічний символ (токен). Логічний символ може складатися з одного чи кількох звичайних символів. Прикладом логічних символів, складених з кількох простих символів, можуть бути знаки операцій >=, <=, <>, ++ тощо. Ключові слова комп'ютерної мови також можуть розглядатись як різновид логічних символів. Тут і далі символами будуть називатись лише прості символи, щоб відрізнити їх від логічних символів.
Приклад сканера (написаного на C) для імперативної мови програмування PL/0.
FILE*source/* Початковий файл */intcur_line,cur_col,err_line,err_col/* Дані для інформації про помилку */intnum/* Останнє прочитане число, для синтаксичного аналізу */charid[]/* Останній прочитаний ідентифікатор, для синтаксичного аналізу */Hashtab*keywords/* Перелік ключових слів */
Зовнішні процедури:
error(constcharmsg[])/* Повідомити про помилку */Hashtab*create_htab(intestimate)/* Створити пошукову таблицю */intenter_htab(Hashtab*ht,charname[],void*data)/* Додати елемент до пошукової таблиці */Entry*find_htab(Hashtab*ht,char*s)/* Знайти елемент пошукової таблиці */void*get_htab_data(Entry*entry)/* Повернути дані з пошукової таблиці */FILE*fopen(charfn[],charmode[])/* Відкрити файл для читання */fgetc(FILE*stream)/* Прочитати наступний символ з потоку */ungetc(intch,FILE*stream)/* Повернути символ у потік */isdigit(intch),isalpha(intch),isalnum(intch)/* Класифікація символів */
Зовнішні типи:
Symbol/* Перелічуваний тип усіх логічних символів мови PL/0 */Hashtab/* Пошукова таблиця */Entry/* Елемент пошукової таблиці */
Сканування запускається викликом init_scan, з передачею імені початкового файла. Якщо початковий файл успішно відкрито, синтаксичний аналізатор циклічно викликає getsym, щоб повернути successive символи з вхідного файлу.
Основа сканера, getsym, має бути зрозумілою. По-перше, пропускаються пробіли. Далі завантажений логічний символ класифікується. Якщо поточний простий символ входить до логічного символу з кількох простих символів, слід виконати додаткові дії. Числа перетворюються до внутрішнього формату, ідентифікатори перевіряються на наявність збігів з ключовими словами.
intread_ch(void){intch=fgetc(source);cur_col++;if(ch=='\n'){cur_line++;cur_col=0;}returnch;}voidput_back(intch){ungetc(ch,source);cur_col--;if(ch=='\n')cur_line--;}Symbolgetsym(void){intch;while((ch=read_ch())!=EOF&&ch<=' ');err_line=cur_line;err_col=cur_col;switch(ch){caseEOF:returneof;case'+':returnplus;case'-':returnminus;case'*':returntimes;case'/':returnslash;case'=':returneql;case'(':returnlparen;case')':returnrparen;case',':returncomma;case';':returnsemicolon;case'.':returnperiod;case':':ch=read_ch();return(ch=='=')?becomes:nul;case'<':ch=read_ch();if(ch=='>')returnneq;if(ch=='=')returnleq;put_back(ch);returnlss;case'>':ch=read_ch();if(ch=='=')returngeq;put_back(ch);returngtr;default:if(isdigit(ch)){num=0;do{/* no checking for overflow! */num=10*num+ch-'0';ch=read_ch();}while(ch!=EOF&&isdigit(ch));put_back(ch);returnnumber;}if(isalpha(ch)){Entry*entry;id_len=0;do{if(id_len<MAX_ID){id[id_len]=(char)ch;id_len++;}ch=read_ch();}while(ch!=EOF&&isalnum(ch));id[id_len]='\0';put_back(ch);entry=find_htab(keywords,id);returnentry?(Symbol)get_htab_data(entry):ident;}error("getsym: invalid character '%c'",ch);returnnul;}}intinit_scan(constcharfn[]){if((source=fopen(fn,"r"))==NULL)return0;cur_line=1;cur_col=0;keywords=create_htab(11);enter_htab(keywords,"begin",beginsym);enter_htab(keywords,"call",callsym);enter_htab(keywords,"const",constsym);enter_htab(keywords,"do",dosym);enter_htab(keywords,"end",endsym);enter_htab(keywords,"if",ifsym);enter_htab(keywords,"odd",oddsym);enter_htab(keywords,"procedure",procsym);enter_htab(keywords,"then",thensym);enter_htab(keywords,"var",varsym);enter_htab(keywords,"while",whilesym);return1;}
Тепер порівняйте наведений вище код з кодом, щоб згенерувати сканер для тієї ж мови за допомогою flex:
Близько 50 рядків коду для flex проти близько 100 рядків отриманого коду.
Недоліки
Часова складність
Лексичний аналізатор Flex інколи має часову складність відносно розміру вхідних даних. Іншими словами, він виконує сталу кількість операцій для кожного вхідного логічного символу. Ця кількість достатньо низька: GCC генерує 12 інструкцій для циклу перевірки детермінованого автомату. Слід зазначити, що ця кількість не залежить від довжини токена, довжини регулярного виразу й розміру детермінованого автомату.
Однак, одна необов'язкова можливість Flex може змусити Flex генерувати сканер з нелінійною продуктивністю: використання макроса REJECT в сканері з можливістю позначати вкрай довгі токени. В цьому випадку, програміст безпосередньо вказав flex повернутись і спробувати ще раз після того, як він уже підібрав вхідні дані. В результаті, детермінований автомат буде змушений повернутись до пошуку інших прийнятних станів. Теоретично, часова складність становить , де — довжина найдовшого токена (це повертає до , якщо токени малі в порівнянні з розміром вхідних даних).[7] Можливість REJECT за замовчуванням не дозволена, і вплив її на продуктивність детально задокументовано в інструкції користувача Flex.
Повторне використання
За замовчуванням синтаксичний аналізатор, згенерований за допомогою Flex, не розрахований на повторне використання. Це може викликати серйозні проблеми з програмами, що викликають згенерований сканер з різних тредів. Для обходу цієї проблеми існує додаткова опція, з якою Flex забезпечує можливість паралельного використання. Детальний опис цих опцій можна знайти в інструкції користувача Flex [1].
Flex++
Flex++ — інструмент для створення лексичних аналізаторів. Генератор синтаксичних аналізаторів створює програму синтаксичного аналізу мови. Це ж стосувалось програми flex.
Flex надає два способи генерування сканерів. Він початково генерує код мовою C код для компіляції на противагу C++ бібліотекам і коду. Flex++, розширення flex, використовується для генерування C++ коду і класів. Класи й код Flex++ потребують компілятора C++ для створення програм лексичної перевірки та звіряння з шаблонами. Flex, альтернативний мовний синтаксичний аналізатор, за замовчуванням генерує a parsing scanner на мові C. Сканери на C++, згенеровані у Flex++, включають файл заголовку FlexLexer.h, який визначає інтерфейси двох згенерованих класів C++.
John Levine, Tony Mason, and Doug Brown, Lex & Yacc, O'Reilly and Associates (2nd edition).
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). Описує технології що використовуються в flex (детерміновані скінченні автомати)