正则表达式为lexing第一个和第二个字符串(单独)成对

时间:2017-01-23 00:26:08

标签: regex parsing flex-lexer lex text-parsing

我试图写一个词法分析器来解析一个像这样的文件:

one.html /two/
one/two/ /three
three/four http://five.com

每行有两个用空格分隔的字符串。我需要创建两个正则表达式模式:一个匹配第一个字符串,另一个匹配第二个字符串。

这是我对词法分析器的正则表达式的尝试(由lexer.l运行的名为flex的文件):

%%
(\S+)(?:\s+\S+)   { printf("FIRST %s\n", yytext); }
(?:\S+\s+)(\S+)   { printf("SECOND %s\n", yytext); }
.                 { printf("Mystery character %s\n", yytext); }
%%

我在Regex101测试中测试了(\S+)(?:\s+\S+)(?:\S+\s+)(\S+),它们似乎都运行正常:https://regex101.com/r/FQTO15/1

然而,当我尝试通过运行flex lexer.l来构建词法分析器时,我收到错误:

lexer.l:3: warning, rule cannot be matched

这是指我的第二条规则。如果我试图颠倒规则的顺序,我再次得到第二个错误。如果我只留下其中一条规则,那就完全可以了。

我认为这个问题与两个正则表达式相似且长度相同的事实有关,所以flex认为它是模棱两可的,即使两个正则表达式捕获不同的东西(但它们匹配相同的东西) ?)。

我能用正则表达式做些什么来捕捉/匹配我想要的而不会互相冲突吗?

编辑:更多测试示例

one.html /two/
one/two.html /three/four/
one /two
one/two/ /three
one_two/ /three
one%20two/ /three
one/two/ /three/four
one/two /three/four/five/
one/two.html http://three.four.com/
one/two/index.html http://three.example.com/four/
one http://two.example.com/three
one/two.pdf https://example.com
one/two?query=string /three/four/
go.example.com https://example.com

修改

事实证明flex使用的正则表达式引擎相当有限。它无法进行分组,也似乎没有\s用于空格。

所以这不会起作用:

^.*\s.*$

但这样做:

^.*" ".*$

感谢@fossil的帮助。

1 个答案:

答案 0 :(得分:1)

尽管有各种方法可以解决您所述的问题,但我认为您最好不要理解(f)lex的预期用途,并找到与其处理模型一致的解决方案。

(F)lex旨在将输入分成单个标记。每个标记都有一个类型,并且可以通过查看它(而不是在其上下文中)来确定标记的类型。令牌类型的经典模型是计算机程序中的对象,例如,我们具有标识符数字,某些关键字和各种运算符。给定一组适当的规则,(f)lex扫描器将采用类似

的输入
a = b*7 + 2;

并生成一个令牌流:

标识符 = 标识符 * 数字 + number ;

这些令牌中的每一个都有一个相关的“语义值”(并非所有这些都是实际需要的),因此两个标识符标记和两个数字不是只是匿名的blob。

请注意,上述行中的ab具有不同的角色。正在分配a,而b正在被引用。但这与他们的形式无关,而且从他们的形式来看并不明显。它们只是代币。弄清楚它们的含义以及它们之间的关系是解析器的作用,它是解析模型的一个独立部分。两阶段扫描/解析范例的目的是通过抽象出复杂性来简化两个任务:扫描程序对上下文或含义一无所知,而解析器可以推断出输入的逻辑结构,而不必关注表示的混乱细节。和无关的空白。

在很多方面,你的问题有点超出了这个范例,部分原因是因为的两个令牌类型不能仅根据它们的外观来区分。但是,如果他们没有有用的内部结构,那么你可以接受你的输入包含

  • “paths”,不包含空格,
  • 换行符。

然后,您可以使用词法分析器和解析器的组合将输入分解为行:

File splitter.l

%{
#include "splitter.tab.h"
%}
%option noinput nounput noyywrap nodefault
%%
\n             { return '\n'; }
[^[:space:]]+  { yylval = strdup(yytext); return PATH; }
[[:space:]]    /* Ignore whitespace other than newlines */

File splitter.y

%code { 
#include <stdio.h>
#include <stdlib.h>

int yylex();
void yyerror(const char* msg);
}

%code requires {
#define YYSTYPE char*
}

%token PATH

%%

lines: %empty
     | lines line '\n'

line : %empty
     | PATH PATH       { printf("Map '%s' to '%s'\n", $1, $2);
                         free($1); free($2);
                       }

%%
void yyerror(const char* msg) {
  fprintf(stderr, "%s\n", msg);
}

int main(int argc, char** argv) {
  return yyparse();
}

以上很多都是锅炉板;值得专注于语法和令牌模式。

语法非常简单:

lines: %empty
     | lines line '\n'

line : %empty
     | PATH PATH       { printf("Map '%s' to '%s'\n", $1, $2);
                         free($1); free($2);
                       }

有趣的一行是最后一行,它表示line由两个PATH组成。虽然你可能想要做一些与众不同的事情,但是通过打印它来处理每一行。正是这条线理解了一条线上的第一个字和同一条线上的第二个字具有不同的功能。请注意,它不需要词法分析器将两个单词标记为“FIRST”和“SECOND”,因为它可以看到所有单词:)

free的两次调用释放了词法分析器中strdup分配的内存,从而避免了内存泄漏。在实际应用程序中,您需要确保在不再需要它们之前不要释放字符串。

词法分析器模式也很简单:

\n             { return '\n'; }
[^[:space:]]+  { yylval = strdup(yytext); return PATH; }
[[:space:]]    /* Ignore whitespace other than newlines */

第一个返回一个特殊的单字符标记,一个换行符,用于行尾标记。第二个匹配任何非空白字符串。 ((F)lex不知道GNU正则表达式扩展,因此它没有\s和朋友。但它确实具有更易读的Posix字符类,列在{{3在flex manual之间。第三种模式跳过任何空格。由于\n已经由第一种模式处理,因此在这里无法匹配(这就是为什么这种模式是单个空白字符而不是重复。)

在第二种模式中,我们为yylval赋值,这是令牌的语义值。 (我们不在其他地方执行此操作,因为换行令牌不需要语义值。)yylval始终具有类型YYSTYPE,我们已将char*安排为#define 1}}。在这里,我们只是从yytext设置它,这是lex刚匹配的字符串(f)。复制此字符串非常重要,因为yytext是词法分析器内部结构的一部分,其值将在不发出警告的情况下更改。制作了字符串的副本后,我们就必须确保最终释放内存。

尝试这个程序:

bison -o splitter.tab.c -d splitter.y
flex -o  splitter.lex.c splitter.l
gcc -Wall -O2 -o splitter splitter.tab.c splitter.lex.c