我为我想要编写一个合理的词法分析器和解析器设计了一种新语言。 为了简洁起见,我将这种语言降低到最低限度,以便我的问题仍然存在。
该语言具有隐式和显式字符串,数组和对象。隐式字符串只是一个不包含<
,{
,[
或]
的字符序列。显式字符串看起来像<salt<text>salt>
,其中salt
是任意标识符(即[a-zA-Z][a-zA-Z0-9]*
),text
是不包含盐的任意字符序列。
数组以[
开头,后跟对象和/或字符串,以]
结尾。
数组中不属于数组,对象或显式字符串的所有字符都属于隐式字符串,每个隐式字符串的长度最大且大于0。
对象以{
开头,以}
结尾,由属性组成。属性以标识符开头,后跟冒号,然后是可选的空格,然后是显式字符串,数组或对象。
因此字符串[ name:test <xml<<html>[]</html>>xml> {name:<b<test>b>}<b<bla>b> ]
代表一个包含6个项目的数组:" name:test "
,"<html>[]</html>"
," "
,{ name: "test" }
,"bla"
和{{ 1}}(该对象在json中标记)。
可以看出,由于显式字符串(我不想错过),这种语言不是上下文。但是,语法树是明确的。
所以我的问题是:属性是否是令牌器可能返回的令牌?或者,如果标记生成器在读取对象属性时返回" "
,T_identifier
?
真实语言甚至允许属性的标识符中的前缀,例如, T_colon
其中ns/name:<a<test>a>
是命名空间的前缀。
如果标记生成器返回ns
,T_property_prefix("ns")
,T_property_prefix_separator
,T_property_name("name")
或仅T_property_colon
或甚至T_property("ns/name")
,T_identifier("ns")
,{{ 1}},T_slash
?
如果tokenizer应该识别属性(这对语法高亮显示器很有用),他必须有一个堆栈,因为T_identifier("name")
不是属性,如果它在一个数组中。要确定T_colon
中name:
是属性还是隐式字符串,标记生成器必须跟踪何时进入和离开对象或数组。
因此,令牌化器不再是有限状态机。
或者有两个标记器是有意义的 - 第一个将空格与字母数字字符序列和特殊字符(如bla:
或[{foo:{bar:[test:baz]} bla:{}}]
分开,第二个使用第一个构建器更多语义标记?然后解析器可以在第二个标记器之上操作。
无论如何,标记化器必须具有无限的预测,以查看显式字符串何时结束。或者是否应该在解析器中检测到显式字符串的结尾?
或者我应该为我的事业使用解析器生成器?由于我的语言不是上下文,我认为没有合适的解析器生成器。
提前感谢您的回答!
答案 0 :(得分:1)
flex
提供上下文堆栈,许多flex
扫描程序使用此功能。因此,虽然它可能不适合扫描仪扫描的纯粹视图,但它是一个完全可以接受和支持的功能。有关如何使用不同的词汇上下文(称为“开始条件”)的详细信息,请参阅flex手册的this chapter;最后是对上下文堆栈的简要描述。 (不要错过指出你需要%option stack
启用堆栈的句子。)[见注1]
稍微棘手的是要求将字符串与可变结束标记匹配。 flex
没有任何变量匹配功能,但它允许您使用函数input()
从扫描仪输入一次读取一个字符。这对你的语言来说已经足够了(至少如上所述)。
以下是可能的扫描仪概述:
%option stack
%x SC_OBJECT
%%
/* initial/array context only */
[^][{<]+ yylval = strdup(yytext); return STRING;
/* object context only */
<SC_OBJECT>{
[}] yy_pop_state(); return '}';
[[:alpha:]][[:alnum:]]* yylval = strdup(yytext); return ID;
[:/] return yytext[0];
[[:space:]]+ /* Ignore whitespace */
}
/* either context */
<*>{
[][] return yytext[0]; /* char class with [] */
[{] yy_push_state(SC_OBJECT); return '{';
"<"[[:alpha:]][[:alnum:]]*"<" {
/* We need to save a copy of the salt because yytext could
* be invalidated by input().
*/
char* salt = strdup(yytext);
char* saltend = salt + yyleng;
char* match = salt;
/* The string accumulator code is *not* intended
* to be a model for how to write string accumulators.
*/
yylval = NULL;
size_t length = 0;
/* change salt to what we're looking for */
*salt = *(saltend - 1) = '>';
while (match != saltend) {
int ch = input();
if (ch == EOF) {
yyerror("Unexpected EOF");
/* free the temps and do something */
}
if (ch == *match) ++match;
else if (ch == '>') match = salt + 1;
else match = salt;
/* Don't do this in real code */
yylval = realloc(yylval, ++length);
yylval[length - 1] = ch;
}
/* Get rid of the terminator */
yylval[length - yyleng] = 0;
free(salt);
return STRING;
}
. yyerror("Invalid character in object");
}
我没有彻底测试,但这是你的示例输入的样子:
[ name:test <xml<<html>[]</html>>xml> {name:<b<test>b>}<b<bla>b> ]
Token: [
Token: STRING: -- name:test --
Token: STRING: --<html>[]</html>--
Token: STRING: -- --
Token: {
Token: ID: --name--
Token: :
Token: STRING: --test--
Token: }
Token: STRING: --bla--
Token: STRING: -- --
Token: ]
在你的情况下,除非你想避免使用解析器,否则你实际上并不需要堆栈,因为唯一需要被压入堆栈的是对象上下文和只有一个堆栈的堆栈可以用计数器替换可能的值。
因此,您可以删除%option stack
并在扫描顶部定义一个计数器。您可以递增计数器并设置启动条件,而不是按下启动条件;如果计数器降低到0,则递减计数器并重置启动条件,而不是弹出。
%%
/* Indented text before the first rule is inserted at the top of yylex */
int object_count = 0;
<*>[{] ++object_count; BEGIN(SC_OBJECT); return '{';
<SC_OBJECT[}] if (!--object_count) BEGIN(INITIAL); return '}'
一次读取输入的一个字符并不是最有效的。因为在您的情况下,字符串终止必须以>
开头,所以最好定义一个单独的“显式字符串”上下文,您可以在其中识别[^>]+
和[>]
。其中第二个将执行一次一个字符匹配,与上面的代码一样,但如果找到除>
之外的不匹配字符,它将终止而不是循环。但是,提供的简单代码可能会变得足够快,而且无论如何它只是为了进行测试运行。
答案 1 :(得分:0)
我认为解析语言的传统方法是让标记生成器为T_identifier("ns")
返回T_slash
,T_identifier("name")
,T_colon
,ns/name:
无论如何,我可以看到三种合理的方式来实现对您的语言的支持:
使用lex / flex和yacc / bison。 lex / flex生成的tokenizer没有堆栈,因此您应该使用T_identifier
而不是T_context_specific_type
。我没有尝试这种方法,因此我无法对lex / flex和yacc / bison是否可以解析您的语言作出明确的评论。所以,我的评论是试试它是否有效。您可以找到有关lexer hack有用的信息:http://en.wikipedia.org/wiki/The_lexer_hack
实现手工构建的递归下降解析器。请注意,这可以在没有单独的词法分析器/解析器阶段的情况下轻松构建。因此,如果词汇依赖于上下文,则在使用此方法时很容易处理。
实现自己的解析器生成器,它根据解析器的上下文打开和关闭lexemes。因此,使用这种方法将词法分析器和解析器集成在一起。
我曾经为一家主要的网络安全供应商工作,使用方法(3)进行深度数据包检查,即我们有一个自定义解析器生成器。这样做的原因是方法(1)不起作用有两个原因:首先,数据不能以递增方式提供给lex / flex和yacc / bison,其次,使用lex / flex无法解析HTTP和yacc / bison因为字符串“HTTP”的含义取决于它的位置,即它可以是标题值或协议说明符。方法(2)不起作用,因为数据不能递增地递送到递归下降解析器。
我应该补充一点,如果你想要有意义的错误消息,强烈建议使用递归下降解析器方法。我的理解是当前版本的gcc使用手工构建的递归下降解析器。