嵌套块语言不是上下文无关的 [注 2],因此不能用 LALR(k) 解析器解析。
|然而,嵌套括号语言当然是上下文无关的,通过替换词法扫描器中的初始序列,将输入转换为括号形式相对容易。转换很简单:
这与用于解析布局感知语言(如 Python 和 Haskell)的INDENT
/策略非常相似。DEDENT
主要区别在于这里我们不需要一堆缩进级别。
转换完成后,语法将如下所示:
content: /* empty */
| content line
| content block
block : head BEGIN_BLOCK content END_BLOCK
| head
head : '*' line
一个 flex 实现的大致轮廓是这样的:(见下面的注 1)。
%x INDENT CONTENT
%%
static int depth = 0, new_depth = 0;
/* Handle pending END_BLOCKs */
send_end:
if (new_depth < depth) {
--depth;
return END_BLOCK;
}
^"|"[[:blank:]]* { new_depth = 1; BEGIN(INDENT); }
^. { new_depth = 0; yyless(0); BEGIN(CONTENT);
goto send_end; }
^\n /* Ignore blank lines */
<INDENT>{
"|"[[:blank:]]* ++new_depth;
. { yyless(0); BEGIN(CONTENT);
if (new_depth > depth) {
++depth;
if (new_depth > depth) { /* Report syntax error */ }
return BEGIN_BLOCK;
} else goto send_end;
}
\n BEGIN(INITIAL); /* Maybe you care about this blank line? */
}
/* Put whatever you use here to lexically scan the lines */
<CONTENT>{
\n BEGIN(INITIAL);
}
笔记:
不是每个人都会对此感到满意,goto
但它可以节省一些代码重复。depth
状态变量 (和new_depth
) 是局部变量这一事实static
使得词法分析器不可重入和不可重新启动(发生错误后)。这仅对玩具代码有用;对于任何真实的东西,您应该使词法扫描器可重入并将状态变量放入extra
数据结构中。
术语“上下文无关”和“上下文敏感”是语法的技术描述,因此有点误导。基于单词似乎意味着什么的直觉通常是错误的。上下文敏感性的一个非常常见的来源是一种语言,其中有效性取决于相同非终结符的两个不同派生产生相同的标记序列。(假设非终结符可以派生多个标记序列;否则,可以消除非终结符。)
在普通编程语言中有很多这种上下文敏感的例子。通常,语法将允许这些结构,并且稍后将在某些语义分析阶段执行检查。其中包括声明标识符的要求(IDENTIFIER
产生相同字符串的两个派生)或使用正确数量的参数调用函数的要求(这里只需要非终结符的派生长度匹配,但这足以触发上下文敏感性)。
在这种情况下,要求是可能bar-prefix
在连续行中调用的两个实例产生相同的|s 字符串。在这种情况下,由于效果确实是句法的,因此推迟到稍后的语义分析会破坏解析点。上下文敏感性的其他例子是“句法”还是“语义”是一个争论,它产生了惊人的热量,却没有对讨论产生太多影响。