最长匹配原则和它的问题
下列模式是无法正确地扫描块注释的。
SKIP { <"/*"(~[])* "*/"> }
注释的终结符为止都和模式“(~[])”匹配
原因在于“~[]”和任意一个字符匹配,所以和“”“/”也是匹配的。并且“”模式会尽可能和最长的字符串进行匹配,因此结果就是和最后(第2处)出现的“/”之前的部分都匹配了。这里的“尽可能和最长的字符串匹配”的方针称为最长匹配原则
为了解决模式“(~[])*”在块注释的情况下过度匹配的问题,需要进行如下修改。
SKIP: { <"/*"> : IN_BLOCK_COMMENT }
SKIP: { <~[]> }
SKIP: { <"*/"> : DEFAULT }
这样在规则定义中写下{模式:状态名}的话,就表示匹配模式后会迁移(transit)到对应的状态。上述例子中会迁移到名为IN_BLOCK_COMMENT的状态。扫描器在迁移到某个状态后只会运行该状态专用的词法分析规则。也就是说,在上述例子中,除了IN_BLOCK_COMMENT状态专用的规则之外,其他的规则将变得无效。要定义某状态下专用的规则,可以如下这样在TOKEN等命令前加上<状态名>。
<状态名> TOKEN: {~}
<状态名> SKIP: {~}
<状态名> SPECIAL_TOKEN: {~}
只有当扫描器处于IN_BLOCK_COMMENT状态下时,这两个规则才有效,而其他规则在这个状态下将变得无效。最后再看一下示例代码的第3行。
该行中的<"/“>:DEFAULT也表示状态迁移,意思是匹配模式”/"的话就迁移到DEFAULT状态。DEFAULT状态(DEFAULT state)表示扫描器在开始词法分析时的状态。没有特别指定状态的词法分析规则都会被视作DEFAULT状态。也就是说,至今为止所定义的保留字的扫描规则、标识符的规则以及行注释的规则实际上都属于DEFAULT状态。
<"/“>:DEFAULT的意思是匹配模式”/"的话就回到最初的状态。
SKIP: { <"/*"> : IN_BLOCK_COMMENT }
SKIP: { <~[]> }
SKIP: { <"*/"> : DEFAULT }
上述代码仍然存在问题,上述代码在扫描过程中到达文件的尾部时会出现很糟糕的情况。
int
main(int argc, char **argv)
{
return 0;
}
/* 文件结束
如果对上述程序进行处理,理想的情况是提示“注释未关闭”这样的错误,但如果使用刚才的词法分析规则,则不会提示错误而是正常结束。未提示错误的原因在于使用了3个SKIP命令的规则进行扫描。像这样分成3个规则来使用SKIP命令的话,3个规则就会分别被视为对各自的token的描述,因此匹配到任何一个规则都会认为扫描正常结束。所以即使块注释中途结束,上述规则也无法检测出来。实际是用3个规则对一个注释进行词法分析,所以要将“这3个规则用于解析一个注释”这样的信息传给扫描器。这时就可以使用MORE命令(MORE directive)。
MORE: { <"/*"> : IN_BLOCK_COMMENT }
MORE: { <~[]> }
SKIP: { <"*/"> : DEFAULT }