-1

我正在尝试使用 flex 和 bison 编写解析器,但我对它的工作原理感到困惑。我正在尝试采用以下方式格式化的文本文件:

Version Header Version 1.00 <--- File always starts with a header
Key                     : Value <--- Each section includes these but these after the version header are in the "Root" section
==Section Name <--- A section
$Key                    : Value <--- These are properties
Key                     : Value <--- Same thing as in the "Root" section

样本格式:

NasuTek Licensing Version 1.00
Vendor                  : NASUTEKENTERPRISES
Notice                  : NasuTek Enterprises
License Group           : NasuTek Asheila
License Name            : NasuTek Asheila
Vendor Notice           : NasuTek Asheila Internal Build License
Serial                  : ASHEL-87267-4987-3737-37821:32742
Start Date              : Wed July 04 00:00:00 2012
End Date                : Sat July 20 00:00:00 2013
Trial                   : Yes
Count                   : 0
Components              : EXPORT
Host                    : Any

==Software Configuration
$Signed Section         : Yes
Export Configuration    : {
    Supports Export to XML        : Yes
    Supports Export to Text       : Yes
}

==Signature
vpUsQJ+Qo4OS+RQg0vuLW0mXjAj/o6v[trunicated]

当我对分组感到困惑时,我该如何做到这一点。我可以让它看到密钥对很简单,但我不知道如何处理使用 == 和 {} 对的拆分?

4

2 回答 2

3

好吧,每当试图决定如何设计一个基于 flex/bison 的解析器时,第一个问题是在 flex 中应该做什么,在 bison 中应该做什么?

Flex 可以使用任意正则表达式,甚至使用状态来允许在不同上下文中使用不同的 RE,但通常它只能识别孤立的事物——太多的上下文(比几个启动状态容易提供的更多)或任何东西递归在 flex 中很难/不可能

另一方面,Bison 可以轻松处理递归,并且可以很容易地将不同上下文中的标记关联起来,但规则只是简单的标记序列,没有正则表达式并且有限(1 个标记)lookahed。

所以在你的情况下,你需要看看那些难以识别的东西。出现的第一件事是:- 您使用此字符将键与值和值分开。它可以出现在键中吗?如果不是,那么您可能只想:专门处理一行中的第一个(在 flex 中以开始状态轻松完成;在野牛中有点困难,因为它需要通过描述所有可以包含冒号的值)

“难以识别”中的下一个是间距——键和值中有空格,其他地方也有其他可能应该忽略的空格。您可以在 flex 中轻松管理这些。但是,您没有描述输入文件中可能出现的任何类型的注释。如果有注释,您通常希望在 flex 中识别(并忽略)它们,将它们视为被忽略的空间。

最后还有可能出现在文件中的所有其他字符,但在您的示例中没有描述。这些包括所有其他标点符号——它们在键或值中是否合法?$and被描述为只出现在一行的==开头——如果它们在其他地方(在行开头的空格之后或在键或值之内/之后?)

当给出这样一个模糊、不完整的例子时,我的倾向是说其他任何事情都是非法的,应该给出一个关于出了什么问题的体面的错误信息。所以我最终会得到一个看起来像这样的柔性扫描仪:

{KVchar}    [-A-Za-z0-9.+/]      /* legal characters in a key or value */
{WS}        [ \t\r]              /* whitespace -- ignored but allowed in a key or value */

%s COLON                         /* seen a colon on the current line */
%%

":"        { BEGIN(COLON); return ':'; }
\n         { BEGIN(INITIAL); return '\n'; }
<INITIAL>({KVchar}+({WS}+{KVchar}+)*)           {
             yylval.key = strdup(yytext);
             return KEY; }
<COLON>({KVchar}+((:|{WS})+{KVchar}+)*)           {
             /* values may contain colons, but can't start or end with them */
             yylval.key = strdup(yytext);
             return VALUE; }
[${}]      { return *yytext; }
"=="       { return EQEQ; }
{WS}+      ; /* ignore */
.          { fprintf(stderr, "Spurious character '%c' in input\n"); }

野牛文件看起来像:

%union {
    char *key; /* or value */
}
%token<key>  KEY VALUE
%token       EQEQ
%%

input: header kvpairs sections ;

header: KEY
;

kvpairs: kvpairs kvpair
       | /* empty */
;

kvpair: key ':' value '\n'
      | '\n'
;

key: KEY
   | '$' KEY
;

value: VALUE
     | '{' '\n' kvpairs '}' '\n'
;

sections: sections section | /*empty*/ ;

section: EQEQ KEY '\n' kvpairs ;
于 2012-07-15T20:25:48.903 回答
1

好吧,你的语法不是那么简单。但是,我所做的是在词法分析器中定义一个标记以将其\n==视为节开始符号(我称之为EQEQ)。所以,语法规则看起来像:

section_line:
      EQEQ section_name NEWLINE
    ;

标记化规则如下所示:

"\n=="           { BEGIN(SEC); return EQEQ; }

我使用了一个开始条件,以便能够将单词视为Signature关键字,如果它就在EQEQ, 和另一个开始条件之后,以便签名部分将签名数据作为单个文本 blob 拉入:

<SEC>"Signature" { BEGIN(SIG); return SIGNATURE; }
<SIG>{text}      { return (BEGIN(INITIAL), TEXT); }

分组规则最容易在单个规则中定义。这是我用于属性键值对的语法:

section_property:
      key COLON value NEWLINE
    | key COLON value_block NEWLINE
    ;

然后这是我用来定义 a 的规则value_block

value_block:
      LBRACE NEWLINE sub_properties RBRACE
    ;

而且, asub_property看起来就像 a section_property

每当遇到新部分时,您的解析代码应该记住后续值对属于哪个部分。同样,在解析子属性块时,应保存封闭的属性键,以便可以适当地分配子属性。

一件事可能会让您yacc像解析器一样绊倒您的是它的自下而上的性质。当识别规则的叶元素时,将值保存在叶规则中,并在封闭规则中引用保存的值。例如,这条规则:

words:
      WORD { words_add($1); free($1); }
    | WORD { words_add($1); free($1); } words
    ;

将连续单词保存到表示单词序列的保存缓冲区中。然后,在封闭规则中,该保存缓冲区被再次保存:

key:
      words { words_save_as_key(); }
    ;

基本上words_save_as_key复制保存的单词缓冲区,然后将该缓冲区重置为将要保存的不同序列(可能是表示关联值的序列)。

于 2012-07-15T17:09:21.140 回答