【ANTLR学习笔记】2:基本工作流程和歧义处理方法

1 解析的整体流程

首先是词法分析器处理字符序列(对应CharStream类),生成Token流(对应TokenStream类,这是连接词法分析和语法分析过程的桥梁)传给语法分析器,语法分析器再用它检查语法正确性,然后解析得到语法树(叶子结点对应TerminalNode类,非叶子结点对应RuleNode类)。
在这里插入图片描述

在词法分析之后,不仅要标记一个个Token,还需要记录这些Token对应的具体内容(比如知道是一个变量,也要记录变量名是什么)。ANTLR的做法是不去记录这个字符串,而是像上图一样,首先为字符流记录一个个位置号,然后在TokenStream里记录这个Token对应的字符开始和结束的索引

在生成的语法树中,叶子结点TerminalNode就是要记录这些Token的具体值(的起止索引)了,而对于非叶子结点RuleNode,则会根据不同的语法规则生成不同的子类,即上篇说到的形如*Context的类,*处是.g4文件中语法规则的名字首字母大写。

例如,下图是上面的赋值语句解析出的语法分析树上各个结点的类。根节点是stat语法规则(表示语句)生成的StatContxt类,它的子结点是assign语法规则(表示赋值)生成的AssignContext类,它的四个孩子对应了assign所匹配的四个词法符号:
a s s i g n   :   I D     =     e x p r     ; assign \ : \ ID \ \ \ = \ \ \ expr \ \ \ ; assign : ID   =   expr   ;

对于标识符ID和具体符号=;都无法继续展开,因此会作为树的叶子,并使用TerminalNode记录。表达式expr可以再展开,在这里为了解析100,它选择了其中一个与之匹配的分支(整形数值),因此它生成唯一孩子TerminalNode记录这个100

在这里插入图片描述
这些生成的*Context类(作为生成的语法Parser的静态内部类)可以访问它所对应的词组中的所有元素(图中它的子树)。例如,上图的AssignContext类就可以通过ID()方法访问标识符子结点(返回值是TerminalNode类型),通过expr()方法访问表达式子树(返回值是ExprContext类型)。

2 语法分析器的工作过程

ANTLR根据给出的语法规则,生成一个递归下降的语法分析器,当待解析的语法规则有多条分支时,语法分析器会去前瞻词法符号(不必是LL(1),可以前瞻若干个词法符号),这个过程和手写的Parser是类似的,对于:

stat: assign
	|	ifstat
	|	whilestat
	...
	;

这表示语句(stat)可以是赋值语句(assign),可以是IF语句(ifstat),可以是WHILE语句(whilestat)或者其它语句。仅就这三条而言,它们的第一个词法符号分别是标识符、IF关键字、WHILE关键字,因此可以前瞻一个词法符号解决,解析stat的逻辑是:

void stat() {
	switch(/*当前输入的词法符号*/) {
		case ID : assign(); break;
		case IF : ifstat(); break;
		case WHILE : whilestat(); break;
		...
		default: /*全都匹配不上,抛出异常*/
	}
}

3 歧义处理方法

如果可以通过多条分支解析输入的文本,那么就说明输入文本是有歧义的,可以有多种语义去解释。用户提供的.g4文件中不论是Token的匹配还是语法的描述都可能存在歧义。

3.1 语法描述上的歧义

例如:

stat : expr ';'			// 表达式语句
	| ID '(' ')' ';'	// 函数调用语句
	;
expr : ID '(' ')'
	| INT
	;

对于输入f();,可以走stat的第一条分支,将f()当作一个表达式expr来解析,此时f();被认为是一个表达式语句。也可以走stat的第二条分支,此时f();被认为是一个函数调用语句,其中标识符f被视为函数名。
在这里插入图片描述
ANTLR解决语法歧义的方法是,匹配所有可匹配分支的第一条。因此对于刚刚的例子,会将f();作为表达式解析。

3.2 Token匹配时的歧义

最常见的是语法关键字和标识符之间的歧义,例如:

BEGIN : 'begin';
ID : [a-z]+;

这表示BEGIN关键字匹配begin序列,标识符匹配一个至多个小写字母序列。ANTLR解决Token歧义的方法是,匹配定义最靠前的语法规则。利用这一点可以自然的保证begin不能作为标识符的问题,因为BEGIN的声明就在ID的前面。

词法分析器在匹配Token时是贪婪模式的,即会尽可能匹配一个最长的字符串来生成Token,因此beginner会匹配为ID,而不是BEGIN后面接名为ner的标识符。

3.3 语言语法本身的歧义

这里书中举了两个例子。其一是表达式优先级的歧义,如
1 + 2 ∗ 3 1+2*3 1+23

在Smalltalk里就是自左向右处理(因此计算出来是9),在其它语言里*优先级高于+(因此计算出来是7)。因此如何隐式指定表达式运算符优先级是一个问题。

另一种是C语言里的,如i*j;*是乘号还是指针符号,取决于i的Token是一个表达式还是一个类型(比如int*j;就是定义指针变量,8*j;则是一个表达式语句)。也就是说这类歧义要通过检查上下文信息解决。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ANTLR是一种流行的工具,用于设计和实现编译器、解释器和其他语言处理器。ANTLR是一个强大的生成器,它可以根据语法规则生成解析器和词法分析器。ANTLR使用Java编写,但可以生成多种语言的代码,包括Java、C++、Python和JavaScript等。ANTLR的主要优点是易于使用和学习,同时具有强大的功能和灵活性。 下面是使用ANTLR设计编译器的一些步骤: 1.定义语法规则:使用ANTLR的语法规则定义语言的语法。ANTLR使用EBNF(扩展巴克斯-诺尔范式)表示法,可以轻松地定义语言的语法。 2.生成解析器和词法分析器:使用ANTLR生成解析器和词法分析器。ANTLR会根据语法规则自动生成解析器和词法分析器的代码。 3.编写语义动作:在ANTLR的语法规则中,可以添加语义动作,这些动作会在解析器解析输入时执行。语义动作可以执行任何操作,例如构建抽象语法树或生成目标代码。 4.测试和调试:使用ANTLR生成的解析器和词法分析器解析输入,并检查输出是否符合预期。如果出现错误,可以使用ANTLR提供的调试工具进行调试。 下面是一个使用ANTLR解析简单算术表达式的示例: ```antlr grammar Expr; expr: term (('+'|'-') term)* ; term: factor (('*'|'/') factor)* ; factor: INT | '(' expr ')' ; INT: [0-9]+ ; WS: [ \t\n\r]+ -> skip ; ``` 在这个示例中,我们定义了一个简单的算术表达式语言,它支持加、减、乘、除和括号。使用ANTLR生成解析器和词法分析器后,我们可以使用以下代码解析输入: ```java String input = "2 * (3 + 4)"; ExprLexer lexer = new ExprLexer(CharStreams.fromString(input)); CommonTokenStream tokens = new CommonTokenStream(lexer); ExprParser parser = new ExprParser(tokens); ExprParser.ExprContext tree = parser.expr(); ``` 这将解析输入并构建抽象语法树。我们可以遍历抽象语法树并执行任何操作,例如计算表达式的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值