Лексический анализ с использованием lex


Прежде чем я покажу, что я сделал здесь, это задание, которое я пытался сделать(я новичок, поэтому я не совсем уверен, что делаю все правильно).

1.  Implement lexical analyzer (using FLEX), as follows:
- Lexical analyzer supplies services next_token(), back_token()
- Lexical analyzer reads text from the input file and identifies tokens. This 
   happens when function   next_token() is called. 
- When a token is identified in the input text, it should be stored in a data 
   structure. For each token, the following attributes are saved:
   * token type
   * token lexeme
   * number of the line in the input text in which this token was found.
- Blanks, tabs, new lines – are not tokens, and should be ignored
- For each token, print (on a separate line) its type (e.g. rel_op , number , etc.) 
   and lexeme 
- Each operation, keyword, separation sign and each type of number should be 
   implemented as a token of a different kind
- Kinds of tokens are coded with integer numbers, for example:
        # define  ID_tok  1
        # define COMMA_tok  2

Используя Flex я написал следующее:

%{
#include<stdio.h>
int line=1;
# define  ID_tok  1
# define COMMA_tok  2
#define REL_OP_tok 3
#define NUMBER_tok 4
#define KEYWORDS_tok 5
%}
binary_ar_op "*"|"/"|"+"|"-"
rel_op "=="|"!="|">"|"<"|"<="|">="
id      [a-z][a-z0-9]*
number    ["+"|"-"]?[0-9]*("."[0-9]+)?
keywords "prabegin"|"parend"|"task"|"begin"|"end"|"integer"|"real"|"do"|"until"|"od"|"send"|"accept"

%%
n   {line++;  printf("n%d:",line);}

{binary_ar_op}+ {printf( "An binary_ar_op: %s (%d) at line(%d)n", yytext,
                   atoi( yytext ),line);}

{rel_op}+ {printf( "An rel_op: %s (%d) at line(%d)n", yytext,
                   atoi( yytext ),line);}

{id}+ {printf( "An id: %s (%d) at line(%d)n", yytext,
                   atoi( yytext ),line);}


{number}+ {printf( "An number: %s (%d) at line(%d)n", yytext,
                   atoi( yytext ),line);}
%%
int yywrap()
{
return 1;
}
main()
{
printf("Enter a string of datan");
yylex();
}

Как вы можете видеть, я уже определил все типы, к которым я был допущен, я не понимаю, как реализовать next и back, некоторые указания, которые были бы великолепны,также я сохранил номер строки , но я предполагаю, что они означают другое, чем то, что я написал, Я также не понимаю часть определения, которую они написали о.

Я знаю, что этот вопрос кажется странным, но я получил это задание без каких-либо указаний или объяснений раньше, поэтому я изучаю его самостоятельно и не совсем понимаю все(хотя я знаю теорию, спасибо!).
1 2

1 ответ:

Я сделал нечто очень похожее в нашем проекте(проектах) компании.

О токенах

Я делаю перечисления для них и...

О next_token()

Мое намерение состояло в том, чтобы хранить всю информацию, связанную с маркерами, в объекте с:
  • острый токен (значение перечисления)
  • лексема (a std:: string)
  • позиция файла (состоящая из Указателя имени файла, строки и колонка).

Кроме того, я хотел использовать интеллектуальные указатели с этими сгенерированными объектами, не говоря уже о том, что они должны быть объектами C++.

Вот что я понял:

  1. Легко переопределить функцию yylex(). Таким образом, вы можете даже переименовать его и изменить его подпись.

  2. Это очень трудно (если не невозможно) совместить с yacc/bison. Основная проблема заключалась в том, что данные передаются из lex (сгенерированный код) в yacc/bison (сгенерированный код) с использованием объединения C (%union, Если я правильно помню). Объединение C и C++ объектов - это не работает хорошо. (Один объект в C union может работать, но несколько определенно нет.)

На мое счастье, 2-й выпуск на самом деле не существовал для меня, потому что я использую flex, но пишу (в то же время генерирую) рекурсивные Парсеры спуска (непосредственно в C++).

Итак, как решить 1-й вопрос? Это из моего кода:

/// redefines proto for generated yylex function.
#define YY_DECL \
  RF::YAMS::Sim::ActionScript::RefToken \
  RF::YAMS::Sim::ActionScript::Compiler::lex(yyscan_t yyscanner)

Это flex man page , где вы найдете документация. Чтобы найти объяснение, как переопределить функцию yylex, пожалуйста, найдите на этом сайте "YY_DECL".

Мой синтаксический анализатор вызывает lex() всякий раз, когда ему нужен новый токен.

Примечания:

  1. В моем случае я переименовал yylex() и даже сделал его методом моего класса parser. (Я сделал это, чтобы упростить доступ лексера к частным переменным парсера.)

  2. Я предоставил операторы ful scope, поскольку сгенерированный код lex не учитывает пространство имен I используйте в моем коде C++.

  3. Параметр yyscan_t yyscanner должен быть там, потому что я генерирую сканеры повторного входа. Вы должны решить, должен ли он быть там или нет. (Вместо этого вы можете привести и другие аргументы.)

  4. Возвращаемый RefToken является интеллектуальным указателем на созданный токен. (Интеллектуальные указатели позволяют очень легко производить и потреблять маркеры в разных "местах" без опасности утечки памяти.)

Если генерируемый лексер должен быть объединен с парсером, генерируемым бизоном, это, вероятно, не так просто. В этом случае я бы использовал статические переменные и организовывал очереди для передачи значений от лексера к синтаксическому анализатору. Это сработало бы, но, конечно, не так элегантно и экономно, как вышеприведенный метод.

О back_token()

Как только у вас есть парсер, который потребляет токены, вы можете делать с ними все, что захотите. В моем случае одним из требований был вариант обратного отслеживания. Таким образом, время от времени мне приходится отталкиваться жетоны для ввода. Для этого я просто складываю их в парсер. Если требуется новый токен, я проверяю 1-й, пуст ли этот стек. Если не самый верхний маркер выскочил, то метод lex() вызывается для получения действительно нового маркера. Я думаю, что подобное решение можно было бы использовать для реализации back_token() в вашем случае.

О заготовках

На самом деле в моем лексере есть два типа правил (т. е. действия правил):

  1. Действия, которые заканчиваются return new Token(...);

  2. Действия, которые заканчиваются break;

Последние я использую для потребления сепараторов (т. е. заготовок и т. д.) и даже комментарии (парсер их даже не видит). Это работает, потому что лексер на самом деле не что иное, как switch(), завернутый в петлю for(). (Я научился этому "трюку" у flex doc. где-то это явно упоминается.)

А что еще?..

Кроме YY_DECL, я также переопределил YY_INPUT. Я сделал это, чтобы использовать лексер с C++ std::stream (вместо yyin).

IMHO flex действительно предоставляет очень полное руководство. Однако всякий раз, когда я сомневаюсь, я смотрю в файл C, созданный flex. Существуют эти ужасные массивы int для конечного автомата, которые я обычно просто игнорирую. Остальное-это инфра-структура вокруг них, и вы найдете свои действия C (записанные в правилах lex) где-то внедренными. Изучение кода вокруг может сделать вещи более ясными.