我试图使用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]
我怎么能做到这一点,因为我对分组感到困惑。我可以让它看到密钥对很简单,但我不知道如何使用==和{}对来处理分裂?
答案 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
基本上复制了保存的单词缓冲区,然后将该缓冲区重置为将保存的不同序列(可能是表示相关值的序列)。