与野牛团体打交道

时间:2012-07-15 04:46:53

标签: c++ bison flex-lexer

我试图使用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]

我怎么能做到这一点,因为我对分组感到困惑。我可以让它看到密钥对很简单,但我不知道如何使用==和{}对来处理分裂?

2 个答案:

答案 0 :(得分:3)

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

Flex可以使用任意正则表达式,甚至可以使用状态来允许不同上下文中的不同RE,但通常它只能单独识别事物 - 过多的上下文(超过几个起始状态可以轻松提供)或者在flex中很难/不可能递归任何东西

另一方面,Bison很容易处理递归,并且可以很容易地在不同的上下文中关联令牌,但规则只是简单的令牌序列,没有正则表达式和有限(1个令牌)的外观。

因此,在您的情况下,您需要查看难以识别的事物。首先出现的是: - 您使用此字符将值与值和值内的值分开。它可以出现在键内吗?如果没有那么你可能只是想在一行专门处理第一个:(在初始状态下很容易在flex中完成;通过描述可以包含冒号的所有值,它需要在野牛中更难)

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

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

当我用一个模糊不完整的例子给出这样的东西时,我倾向于说其他任何东西都是非法的,应该给出一个关于出错的错误信息。所以我最终得到的灵活扫描仪看起来像:

{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 ;

答案 1 :(得分: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
    ;

然后这是我用来定义value_block

的规则
value_block:
      LBRACE NEWLINE sub_properties RBRACE
    ;

而且,sub_property看起来就像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基本上复制了保存的单词缓冲区,然后将该缓冲区重置为将保存的不同序列(可能是表示相关值的序列)。