Пишу что хочу, но это должно быть до или после определенных токенов - PullRequest
0 голосов
/ 14 апреля 2020

Итак, у меня есть этот файл lex:

%{  
#include <stdlib.h>
#include <string.h> 
#include <errno.h>
#include "node.h" 
#include "y.tab.h"
char *dupstr(const char *s);
void yyerror(char *s);
int octal(char *s);
%} 

%%
\$\$.*          ; /* comment */
\$(.|\n)*\$     ; /* comment */
">="                  return GE; 
"<="                  return LE; 
":="            return AT;
"~="            return NEQ;
"if"                  return IF; 
"else"              return ELSE;
"then"          return THEN;
"elif"          return ELIF;
"fi"            return FI;
"for"           return FOR;
"until"         return UNTIL;
"step"          return STEP;
"do"            return DO;
"done"          return DONE;
"repeat"        return REP;
"stop"          return STOP;
"return"        return RET;
^"program"      return PROG;
^"module"       return MOD;
"start"         return ST;
^"end"          return END;
"void"          return VD;
"const"         return CT;
"number"        return NB;
"array"         return ARR;
"string"        return SG;
"function"      return FC;
"public"        return PB;
"forward"       return FW;

 0|[1-9][0-9]*        { errno = 0; yylval.i = strtol(yytext, 0, 10); if (errno == ERANGE) 
 yyerror("overflow in decimal constant"); return INTEGER; }
 0[0-7]+              { yylval.i = octal(yytext); return INTEGER; }
 0x[0-9a-fA-F]+       { yylval.i = strtol(yytext, 0, 16); return INTEGER; }
0b[01]+              { errno = 0; yylval.i = strtol(yytext+2, 0, 2); if (errno == ERANGE) 
yyerror("overflow in binary constant"); return INTEGER; }

\'[^\\\']\'|\'\\[nrt\\\']\'|\'\\[a-fA-F0-9]\' { yytext[yyleng-1] = 0; yylval.s = 
dupstr(yytext+1); return STRING; }

[A-Za-z][A-Za-z0-9_]*   { yylval.s = dupstr(yytext+1); return ID; }

\"[^"]*\"            { yytext[yyleng-1] = 0; yylval.s = dupstr(yytext+1); return STRING; }

 [-+*/%^:=<>~|&?#<\[\]();!,]    return *yytext;

 [ \t\n\r]+     ; /* ignore whitespace */ 

 .          yyerror("Unknown character");

 %%

 char *getyytext() { return yytext; }

 int yywrap(void) {
 return 1;
 }

 int octal(char *s)
 {
 int i, a = 0, b = 0;

 for (i = 0; i < strlen(s); i++) {
    if (s[i] < '0' || s[i] > '7') break;
       b = b * 8 + s[i] - '0';
    if (b < a) {
       yyerror("octal overflow");
       break;
}
a = b;
}
return a;
}

И я хочу ограничение, которое позволяет мне писать все, что я хочу, но только если я пишу это перед программой и модулем токенов или после окончания токена, это возможно? Я попробовал некоторые опции для соответствующего файла ya cc, но не смог этого сделать, также я думаю, что это проблема для lex, извините заранее, я впервые работаю с этим языком, и я не нашел в своем исследовании ничего, что могло бы помогите с этой проблемой.

1 Ответ

2 голосов
/ 14 апреля 2020

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

Это часто называют "островом" синтаксический анализ ", потому что вы пытаетесь проанализировать остров структурированной информации в море неструктурированного текста.

Генераторы сканеров на основе Lex имеют условие запуска по умолчанию, называемое <INITIAL>, которое является активным, когда лексер запускается в первый раз. Правила в <INITIAL> не должны быть написаны с явным начальным условием; другие правила делают. Это довольно раздражает в случае синтаксического разбора острова, потому что большинство правил находятся в начальном состоянии острова, что означает, что имя условия должно быть добавлено ко всем из них.

Но вы почти наверняка используете flex, и если это так, вы можете использовать полезное расширение flex, которое позволяет блоку правил быть назначенным начальному условию. Вот как я написал этот ответ, и если он работает для вас, вы должны изменить все правила сборки, которые ссылаются на «lex», чтобы они правильно называли генератор сканера, который вы используете (поскольку, если вы используете расширения Flex, вам понадобится обрабатывать файл с помощью flex).

Правильная запись синтаксического анализатора требует большой точности в спецификации ввода. В вашем кратком вопросе есть несколько неуказанных случаев; Я начинаю с перечисления тех, которые видел, и выбранного мной разрешения (которое обычно было с наименьшим усилием).

  1. Во внешнем <INITIAL> начальном условии любая строка текста который не начинается точно со слов program или module, это неструктурированный текст. Ваш вопрос не указывает, как вы хотите, чтобы это было обработано. Вы можете передать его парсеру, проигнорировать его, скопировать в yyout или любое другое количество альтернатив. Здесь я игнорирую это, так как это самое простое. Должно быть понятно, что нужно изменить для других альтернатив.

  2. Должно ли слово program или module быть единственным в строке, чтобы его можно было распознать ? Если нет, что может последовать за этим? Например, подойдет ли эта строка:

    program"FOO"{
    

    (я понятия не имею, что такое грамматика вашего языка; я просто привожу здесь гипотетические слова.) Самое простое решение - потребовать слово для быть на линии, но это не очень вероятное требование: мы часто хотим помещать такие вещи, как комментарии, в одну строку с такими токенами. С другой стороны, было бы очень удивительно, если бы строка

    programming is complicated because we're not using to thinking precisely
    

    рассматривалась как начало анализируемого блока. Итак, я сделал предположение, что подсчетом являются строки, в которых program (или модуль) находятся точно в начале строки, за которой сразу следует пробел (или за концом строки, который также является пробелом). Это не сможет распознать одно из следующего:

    program$$ This is a comment
    program;
    

    Но оно распознает

    program $$ This is a comment
    program MyProgram
    

    Так что, возможно, потребуется внести некоторые коррективы, в зависимости от ваших потребностей.

  3. У меня также были сомнения относительно точной обработки текста, следующего за островом. Вы ожидаете только один остров? Или вы могли бы иметь:

    неструктурированный текст неструктурированный текст программы ... конец неструктурированного текстового модуля ... конец неструктурированного текста

    В следующем предполагается, что вы захотите обрабатывать оба острова, опять же, потому что это самый простой Если вместо этого вы хотите игнорировать весь текст, следующий за end, вам нужно будет добавить третье условие запуска, которое просто игнорирует весь текст. (В качестве альтернативы, если вы не хотите ничего делать с текстом, следующим за островом, вы можете просто отправить сбросить входной поток после чтения токена end.)

  4. Действительно ли необходимо, чтобы токен end находился в начале строки, если встречено ключевое слово program или module? Если вам это требуется, то с помощью сканера неверно или непреднамеренно отступ end будет преобразован в ID. Это кажется мне маловероятным, поэтому я не учел ограничения. Я также работаю в предположении, что строка, начинающаяся с end в неструктурированном тексте, все еще является неструктурированным текстом; то есть правила <INITIAL> не нужно даже пытаться его обнаружить.

  5. Точно так же мне не ясно, являются ли program и module законными жетоны внутри острова или их следует рассматривать как идентификаторы. Если они являются легальными токенами, есть ли веская причина ограничивать их появление в начале строки? Я думаю, что нет, поэтому я пропустил ограничение.

Тем не менее, вот пример реализации. Мы начнем с объявления условия запуска (вы можете прочитать документацию по flex, связанную с подробным объяснением того, почему я использовал %x, чтобы объявить его), которая должна go войти в первый раздел ввода flex перед %%

%x ISLAND
%%

В состоянии <INITIAL> нас интересуют только строки, начинающиеся с program или module. Как указано выше, мы также должны убедиться, что за целевыми словами следует пробел. Это на самом деле немного сложно, потому что отрицательные совпадения («строки, которые не начинаются с program или module») очень сложно записать как регулярные выражения (без отрицательных проверочных утверждений, которые (f) lex не делает предоставлять). Вместо того, чтобы пытаться это сделать, мы отдельно распознаем первое слово в строке и остальную часть строки, что позволяет нам использовать правило самого длинного соответствия. Но сначала нам нужно распознать наши особые случаи, которые переключают условие запуска, используя специальное действие BEGIN. Здесь мы используем оператор «конечного контекста» flex /, чтобы гарантировать, что за ключевым словом следует пробел:

^program/[[:space:]]   { BEGIN(ISLAND); return PROG; }
^module/[[:space:]]    { BEGIN(ISLAND); return MOD; }
[[:alpha:]]+           ; /* Any other word (at the beginning of a line) */
[^[:alpha:]\n].*       ; /* See below */
\n                     ; /* The newline at the end of the line */

Третье правило соответствует алфавитному слову c в начале строки. [Примечание 1] Четвертое правило соответствует как остальной части строки после слова, так и любой строке, которая не начинается со слова. Мы должны быть осторожны, чтобы не соответствовать \n в начале строки; без исключения \n в классе отрицательных символов шаблон будет соответствовать \n пустой строки, а затем всей следующей строки, поэтому он пропустит program в случае, если он следует за пустой строкой. (Если это неясно, возможно, вы захотите поэкспериментировать.)

Начальное условие <ISLAND> - это, по сути, уже написанные вами правила, заключенные в блок условия запуска. По этой причине я не повторял все правила; только те, которые я изменил. Обратите внимание, что внутри блока условий запуска, flex снимает ограничение, которое правила должны начинаться в начале строки. Также обратите внимание, что нет необходимости заключать в кавычки шаблоны, состоящие только из букв и цифр. Только шаблоны с метасимволами должны быть заключены в кавычки.

<ISLAND>{              /* Open the block */
  [[:space:]]+         ; /* Ignore whitespace */
  end                  { BEGIN(INITIAL); return END; }
  program              { return PROG; }
  module               { return MOD; }
  /* And all the rest of the rules. */
}

Примечания:

  1. Теоретически третье правило может совпадать со словом алфавита c в любом месте , поскольку он не привязан к ^. На практике это правило невозможно вызвать, кроме как в начале строки, потому что четвертое правило всегда продолжается до конца строки. Но теоретически, какое-то действие может вызвать BEGIN(INITIAL) в тот момент, когда следующим символом для чтения будет алфавит c, а не в начале строки. Тщательное изучение кода покажет, что это невозможно, но flex не может выполнить такой анализ; с точки зрения flex это возможно, и если это произойдет, потребуется третье правило.

    Я знаю это, потому что я всегда использую %option nodefault в своих файлах flex, что заставляет flex предупреждать меня, если есть какая-либо вероятность того, что ни одно правило не будет применяться к входным данным. И так как я изначально написал правило 3 с привязкой, то Флекс был вынужден предупредить меня о возможности соответствия правилу по умолчанию. Поэтому мне пришлось снять якорь, чтобы убрать это предупреждение. Но, несмотря на досаду, я считаю, что предупреждение полезно, потому что, безусловно, возможно, что в какой-то момент в будущем кто-то может ввести действие BEGIN, которое создает условие, при котором нефиксированное совпадение слова c в алфавитном порядке быть необходимым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...