Итак, у меня есть этот файл 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;
}

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

0
MuchoG 14 Апр 2020 в 19:00

1 ответ

Лучший ответ

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

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

Генераторы сканеров на основе 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 законными токенами внутри острова или их следует рассматривать как идентификаторы. Если это законные токены, есть ли веская причина ограничить их появление в начале строки? Думаю, что нет, поэтому я не учел ограничение.

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

%x ISLAND
%%

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

^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 */

Третье правило соответствует алфавитному слову в начале строки. [Примечание 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. Теоретически третье правило может сопоставлять буквенное слово где угодно, поскольку оно не привязано к ^. На практике это правило невозможно запустить иначе, как в начале строки, потому что четвертое правило всегда распространяется до конца строки. Но теоретически какое-то действие может вызвать BEGIN(INITIAL) в момент, когда следующий читаемый символ является буквенным, а не в начале строки. Тщательное изучение кода покажет, что это невозможно, но flex не может выполнять такого рода анализ; с точки зрения flex, это возможно, и если это произойдет, потребуется третье правило.

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

2
rici 14 Апр 2020 в 18:21