Нежадное сопоставление регулярных выражений в Flex

Я только начал работать с Flex и не могу понять, как сопоставить следующее выражение:

"Dog".*"Cat"
------------------
Input :
Dog Ca Cat Cc Cat
------------------
Output:
Dog Ca Cat Cc Cat

Но я хочу нежадное сопоставление со следующим выводом:

Output:
Dog Ca Cat

Как это может быть достигнуто на Flex?

ИЗМЕНИТЬ

Пробовал следующее:

%%
Dog.*Cat/.*Cat  printf("Matched : ||%s||", yytext);
dog.*cat        printf("Matched : ||%s||", yytext);
dOg[^c]*cAt     printf("Matched : ||%s||", yytext);
DOG.*?CAT       printf("Matched : ||%s||", yytext);
%%

Ввод:

Dog Ca Cat Cc Cat
dog Ca cat Cc cat
dOg Ca cAt Cc cAt
DOG CA CAT CC CAT

Вывод:

Matched : ||Dog Ca Cat Cc Cat||
Matched : ||dog Ca cat Cc cat||
Matched : ||dOg Ca cAt|| Cc cAt
Matched : ||DOG CA CAT CC CAT||

Также получено предупреждение:

lex4.l:2: warning, dangerous trailing context

Гибкая версия:

flex 2.5.35 Apple(flex-31)

person Kyuubi    schedule 21.10.2013    source источник


Ответы (3)


Это довольно распространенная проблема с использованием инструментов lex/flex, которая ставит в тупик новичков (а иногда и не новичков). Есть два решения проблемы, которые требуют двух разных расширенных функций инструментов. Такая фраза, как dog ... cat, представляет собой почти ту же проблему, что и сопоставление комментариев в различных языках программирования, таких как форма комментария C /* ... */ или даже 'comment' ... 'tnemmoc'. Они имеют точно такие же характеристики, как и ваш пример. Рассмотрим следующий код C:

/* This is a comment */ "This is a String */"

Жадное лексическое совпадение этого слова совпало бы с неправильным терминатором комментария (кстати, это хороший тест для студента-лексера!).

Есть предлагаемые решения на нескольких университетских курсах по компиляторам. Тот, который хорошо объясняет это, находится здесь (в Манчестере) . Что цитирует пару хороших книг, которые также охватывают проблемы:

  • Дж. Левин, Т. Мейсон и Д. Браун: Лекс и Якк (2-е изд.)
  • М.Э.Леск и Э.Шмидт: Lex - генератор лексического анализатора

Два описанных метода заключаются в использовании Start Conditions для явного указания конечного автомата. или ручной ввод для прямого чтения символов.

Для вашей задачи cat ... dog их можно запрограммировать следующими способами:

Условия запуска

В этом решении нам нужно несколько состояний. Ключевое слово dog вызывает переход в состояние DOG, которое продолжается до тех пор, пока не встретится буква c. Затем он переходит в состояние LETTERC, за которым должна следовать буква a, в противном случае состояние DOG продолжается; буква a вызывает переход в состояние CAT, за которым должна следовать буква t, которая приводит к сопоставлению всей фразы и возврату в состояние INITIAL. yymore приводит к тому, что весь текст dog ... cat сохраняется для использования.

%x DOG LETTERC CAT
d [dD]
o [oO]
g [gG]
c [cC]
a [aA]
t [tT]
ws [ \t\r\n]+
%%

<INITIAL>{d}{o}{g} {
        BEGIN(DOG);
        printf("DOG\n");
        yymore();
        }
<DOG>[^cC]*{c} {
        printf("C: %s\n",yytext);
        yymore();
        BEGIN(LETTERC);
        }
<LETTERC>{a} {
       printf("A: %s\n",yytext);
       yymore();
       BEGIN(CAT);
      }
<LETTERC>[^aA] {
        BEGIN(DOG);
        yymore();
        }
<CAT>{t} {
        printf("CAT: %s\n",yytext);
        BEGIN(INITIAL);
        }
<CAT>[^tT] {
        BEGIN(DOG);
        yymore();
        }
<INITIAL>{ws}  /* skip */ ;

Ручной ввод

Метод ручного ввода просто сопоставляет начальную фразу dog и вводит код C, который поглощает вводимые символы, пока не встретится нужная последовательность cat. (Я не стал заморачиваться как с прописными, так и с строчными буквами). Проблема с этим решением заключается в том, что трудно сохранить входное текстовое значение в yytext для последующего использования в синтаксическом анализаторе. Он отбрасывает его, что было бы нормально, если бы конструкция была комментарием, но в противном случае бесполезно.

d [dD]
o [oO]
g [gG]
ws [ \t\r\n]+
%%
{d}{o}{g}   {
   register int c;

                     for ( ; ; )
                         {
                         /* Not dealt with upper case .. left as an exercise */
                         while ( (c = input()) != 'c' &&
                                 c != EOF )
                             ;    /* eat up text of dog */

                         if ( c == 'c' )
                             {
                              if ( ( c = input()) == 'a' )
                                     if ( (c = input()) == 't' )
                                 break;    /* found the end */
                             }
                        if ( c == EOF )
                             {
                             REJECT;
                             break;
                             }
                         }
            /* because we have used input() yytext always contains "dog" */
            printf("cat: %s\n", yytext);
       }
{ws}  /* skip */ ;

(Оба этих решения были протестированы)

person Brian Tompsett - 汤莱恩    schedule 19.04.2015

Хороший вопрос. Вот чистое регулярное выражение без использования нежадного синтаксиса .*?:

Dog([^C]|C+(aC+)*([^Ca]|a[^Ct]))*C+(aC+)*at

person Paul    schedule 26.03.2016

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

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

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

Начальные условия помогают, когда целевой шаблон является условным и его необходимо включить после некоторого более раннего совпадения. Вы включаете начальное условие, чтобы включить сопоставление с целевым шаблоном, и отключаете его, сбрасывая состояние на 0 или INITIAL — или переключаясь в другое состояние для еще более условного сопоставления.

Состояния переключаются с помощью BEGIN — также есть стек состояний для использования через yy_push_state и yy_pop_state.

В руководстве по flex есть много примеров условий запуска.

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

Полный файл размещен в конце - для людей, незнакомых с flex, обратите внимание, что многие строки начинаются с пробела - это не случайно и требуется flex

%%
 /* flex rules section */

 string match;

dog {
// found a dog, change state to HAVE_DOG to start looking for a cat
 BEGIN(HAVE_DOG);
// save the found dog
 match = yytext;
}
 /* save and keep going till cat is found */
<HAVE_DOG>. match += yytext;

<HAVE_DOG>cat {
// save the found cat
 match += yytext;
// output the matched dog and cat
 cout << match << "\n";
// ignore rest of line
 BEGIN(SKIP_LINE);
}

 /* no cat on this line, reset state */
<HAVE_DOG>\n BEGIN(0);
 /* rules to ignore rest of the line then reset state */
<SKIP_LINE>{
  .*
  \n BEGIN(0);
}

 /* nothing to do yet */
.|\n

Вот некоторые тестовые данные

$ cat dogcat.in.txt

Dog Ca Cat Cc Cat
dog Ca cat Cc cat
dOg Ca cAt Cc cAt
DOG CA CAT CC CAT
cat dog dog cat cat
dog kitten cat dog cat
dig cat dog can dog cut
dig dug dog cow cat cat
doc dogcat catdog
dog dog dog
cat cat cat

Построить с

flex -o dogcat.flex.cpp dogcat.flex.l && g++ -o dogcat dogcat.flex.cpp

Бежать с

$ ./dogcat < dogcat.in.txt

Dog Ca Cat
dog Ca cat
dOg Ca cAt
DOG CA CAT
dog dog cat
dog kitten cat
dog cow cat
dogcat

Полный гибкий файл

 /* dogcat.flex.l */

 /*
 Build with:
 flex -o dogcat.flex.cpp dogcat.flex.l && g++ -o dogcat dogcat.flex.cpp
 */

 /*
 A minimal C++ flex lexer that shows nongreedy matching with flex
 start conditions
 matches the first occurrence of dog on a line till the first
 occurrence of cat 
 matching is case insensitive
 */
 /* C++ lexer using yyFlexLexer in FlexLexer.h */
%option c++

 /* case-insensitive patterns */
%option case-insensitive

 /* generate main function for executable */
%option main

 /* all input must be matched, no echo by default */
%option nodefault

 /* debug output with lexer.set_debug(1) */
%option debug
 /* start condition means dog was matched */
%x HAVE_DOG
 /* start condition means to ignore remaining line */
%x SKIP_LINE
%{

#include <string>
#include <iostream>

// C++ flex lexer class
// needed because header itself has no guard
#ifndef yyFlexLexerOnce
#  include <FlexLexer.h>
#endif

using namespace std;

namespace {
// the C++ lexer class from flex
  yyFlexLexer lexer;

// main generated by flex still calls free yylex function even for C++ lexer
  int yylex() {
    return lexer.yylex();
  }
}

%}
%%
 /* flex rules section */

 string match;

dog {
// found a dog, change state to HAVE_DOG to start looking for a cat
 BEGIN(HAVE_DOG);
// save the found dog
 match = yytext;
}
 /* save and keep going till cat is found */
<HAVE_DOG>. match += yytext;

<HAVE_DOG>cat {
// save the found cat
 match += yytext;
// output the matched dog and cat
 cout << match << "\n";
// ignore rest of line
 BEGIN(SKIP_LINE);
}

 /* no cat on this line, reset state */
<HAVE_DOG>\n BEGIN(0);
 /* rules to ignore rest of the line then reset state */
<SKIP_LINE>{
  .*
  \n BEGIN(0);
}

 /* nothing to do yet */
.|\n
person Zartaj Majeed    schedule 16.06.2021