(三十二)深度解析领域特定语言(DSL)第六章——语法分析:第二个案例——减法表达式计算
本文探讨了递归下降法在减法表达式语法分析中的应用,重点处理了左递归消除问题。通过简化文法6-14示例,展示了如何实现带表达式求值的递归下降分析器。案例虽省略了语义模型和错误处理,但演示了同步计算差值的关键实现(代码6-4)。特别说明该方案要求输入必须含减号(如"3-2"),不支持单数字表达式,以保持示例简洁性。文章最后指出,完整解决方案需结合第7章的语法制导翻译技术。
在建立对递归下降语法分析方法的直观理解后,我们将通过减法表达式计算(即文法6-9)这一实例展开进一步探讨。相较于前文案例,该实例的复杂度有所提升,主要体现在对左递归问题的处理上。在自顶向下的语法分析框架下,必须对左递归进行消除处理,尽管转换后的文法可读性可能降低,但其是保证分析过程终止性的必要条件。
为保持示例的简明性,本案例暂不涉及语义模型构建及错误处理机制,但会演示如何在语法分析过程中同步完成表达式求值,即实现简单的语法制导翻译。需要说明的是,常规减法表达式通常支持括号运算,但为避免增加复杂度,相关扩展内容将留作读者的实践探索。
考虑到文法6-9中符号命名的复杂性可能对函数设计产生影响,我们对其进行简化处理,形成如文法6-14所示的等价表示。对于减法表达式这类问题,理想的解决方案是采用完整的语法制导翻译技术,这涉及符号综合属性、继承属性等概念,相关内容将在第7章详细阐述。在当前案例中,我们聚焦于左递归消除方法及其在递归下降分析器中的实现策略,通过简化模型展示核心处理流程。
文法6-14
E -> DIGIT R
R -> '-' DIGIT R | ε
DIGIT -> '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
同上一个案例,我们并不会展示词法分析器的代码,您可参考第二七篇文章的案例。不过词法单元的类型出现了些许变化,如代码6-3所示:
代码6-3
enum TokenType {
DIGIT(""),
MINUS("-"),
EOF("<EOF>"),
UNKNOWN("<unknown>");
}
笔者对语法分析器的实现代码,尤其是与前一个案例有交集的部分进行了省略,以避免对读者的阅读体验产生影响,余留下的核心片段如代码6-4所示。另外值得注意的是,笔者给出的实现方案不允许表达式出现单数字的情况,比如“1”、“2”等,合法的输入必须至少包含一个减法符号,如“3-2”、“3-2-1”等。
代码6-4
class SubtractionParser {
...
int result;
void parse() {
result = digit();
r();
}
void r() {
if (lookahead.type == TokenType.EOF) {
return;
}
if (lookahead.type == TokenType.MINUS) {
matchAndMove(TokenType.MINUS);
int value = digit();
result -= value;//代码1
r();
return;
}
throw new ParseException();//代码2
}
int digit() {
Token token = lookahead;
matchAndMove(TokenType.DIGIT);
return Integer.valueOf(token.lexeme);
}
void matchAndMove(TokenType target) {
if (this.lookahead.type == target) {
lookahead = this.lexer.scanNext();
return;
}
throw new ParseException();
}
}
可以看到,虽然文法经过了去左递归处理,但并不影响语法分析程序的实现,其执行逻辑仍然与文法是一一对应的。
方法r()的实现逻辑要相对复杂一点,主要体现在如下两点上:
- 语法分析的过程中同步对减法表达式的值进行计算,计算后的结果会被存储到类变量result中,见“代码1”处。
- 执行到该方法的时候,向前看符号的类型必须为TokenType.MINUS,也就是减号“-”。否则的话应该让程序报错,见“代码2”处。这样的处理是为了避免输入表达式为单个数字的情况,即未包含减号。
其它方法的含义与上一个案例基本相同,笔者不再赘述。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)