【问题标题】:Regex for lexing first and second string (separately) in a pair用于对第一个和第二个字符串(分别)进行词法分析的正则表达式
【发布时间】:2017-01-23 00:26:08
【问题描述】:

我正在尝试编写一个词法分析器来解析如下所示的文件:

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 提供的所有帮助。

【问题讨论】:

  • 为什么不使用像^(.*)\s(.*)$ 这样的单个表达式并在代码中使用匹配的组
  • @fossil 你的意思是用你的正则表达式匹配整行,然后用普通的C代码将它按空格分开吗?
  • 正则表达式已经将它们分成两组。
  • @fossil 我刚刚尝试了你的正则表达式并且:(1)yytext 似乎包含两个字符串,(2)它无法匹配这个:one.html two/three
  • 测试字符串是什么?我会在本地查看。

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


【解决方案1】:

虽然有一些方法可以解决您的问题,但我认为您最好了解 (f)lex 的预期用途,并找到与其处理模型一致的解决方案。

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

a = b*7 + 2;

并产生一个令牌流:

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

这些标记中的每一个都有一个关联的“语义值”(并非所有标记都实际需要),因此两个 identifier 标记和两个 number 不是只是匿名 blob。

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

在许多方面,您的问题有点超出此范式,部分原因是您拥有的两种令牌类型无法仅根据它们的外观来区分。但是,如果它们没有有用的内部结构,那么您可以接受您的输入由

  • “路径”,不包含空格,并且
  • 换行符。

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

文件拆分器.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 */

文件拆分器.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 由两个PATHs 组成。它通过打印出来来处理每一行,尽管你可能想做一些不同的事情。正是这一行理解了一行中的第一个单词和同一行中的第二个单词具有不同的功能。请注意,它不需要词法分析器将这两个词标记为“FIRST”和“SECOND”,因为它可以自己看到这一切:)

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

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

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

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

在第二种模式中,我们给yylval赋值,这是token的语义值。 (我们不会在其他地方这样做,因为换行符不需要语义值。)yylval 始终具有类型YYSTYPE,我们通过#define 将其安排为char*。在这里,我们只是从yytext 中设置它,这是 (f)lex 刚刚匹配的字符串。复制这个字符串很重要,因为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 

【讨论】:

  • 当你说你应该能够“仅仅通过查看它(而不是它的上下文)来找出一个令牌的类型”,这就是“上下文无关语法”的意思吗?
  • @adrianmc: 不是。“上下文无关语法”中的“上下文无关”是一个技术术语,与CFG的定义相关,含义非常具体,与大多数人的不对应关于“无上下文”可能意味着什么的直觉。
猜你喜欢
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多