其他分享
首页 > 其他分享> > 如何用 ANTLR4 注入动作生成 symbol table

如何用 ANTLR4 注入动作生成 symbol table

作者:互联网

什么是 semantic actions

当 Parser 处理输入的代码的时候不仅要判断是否语法和句法都正确,还可以执行一些有用的操作,这些操作就叫做 Semantic actions。其实也就是一段代码,一般嵌入在在语法文件的规则里面。那么当 parser 应用这个规则的时候就会执行你设置的这段代码。换个角度理解,semantic actions 其实就是“触发器”,触发条件就是 parser 应用了对应的规则。

今天这篇文章要探讨的就是一个关于 semantic actions 的应用——实现一个简单的 symbol table,用到的工具是 ANTLR4

什么是 symbol table

编译器在处理我们的代码的时候会在内部维护一个 symbol table,用来存储程序里面所有关于变量的信息:变量名、数据类型、变量所属的作用域等。symbol table 可以是下面这种形式:

Symbol name Type Scope
bar function, double extern
x double function parameter
foo function, double global
count int function parameter
sum double block local
i int for-loop statement

如何在 ANTLR 的语法文件里面注入动作

ANTLR4 是一个强大的 parse generator,我们只要编写好语法文件,就能让它帮我们自动生成 Parser,生成的 Parser 可以支持多种目标语言,比如我自己用的是 Python,那么最后生成的 Parser 就是 Python 文件。

ANTLR4 也提供了方法让我们可以在语法文件里面插入动作,这些动作最后都会被 ANTLR4 注入到生成的 Parser 文件里面。因此,动作用什么语言写取决于你输出 Parser 的目标语言是什么

⚠️这里假定你对如何编写 ANTLR4 的语法文件(*.g4)有基本了解,不会进行赘述,本文只集中介绍如何在语法文件里面插入动作

动作注入的位置和方法

下面是简化的生成的 Parser 文件的代码模板。总体上来看,我们能注入的位置如下所示:

<header>
class xxxParser(Parser):    
    ...
    <member>
    def rule(self):
        ...
        <action>

下面我对每个位置的不同语法进行说明:

ANTLR4 本来就是用 Java 写的,所以如果你用的目标语言也是 Java 的话,这个会比较用得到。一般就是用来放 import 语句的。如果是 python 的话哪里都能 import 就比较没差。要往这个位置注入代码的格式如下(放在 *.g4 文件里面,下同)

@header{
    everything here will go to <header>
}

<member>

这个放的就是类的成员,可以是字段也可以是方法。ANTLR4 支持往 Lexer 和 Parser 分别或者同时注入代码。要往这个位置注入代码的格式如下:

@members {
    everything here will go to <member> in xxxLexer && xxxParser
}
@Lexer::members {
    everything here will go to <member> in xxxLexer
}
@Parser::members {
    everything here will go to <member> in xxxParser
}

⚠️在我使用的 antlr4-python3-runtime 中(4.10),还无法在插入类的字段进行注释

<action>

在 ANTLR4 中,动作是用花括号 {<specific-language-here>} 括起来的代码。正如前面提到的,看你最后输出的 Parser 想要是什么语言,你就用什么语言写动作。

动作一般会放在一条规则的某个 symbol 之后,意思是说在 parser 应用这条规则的时候执行到这里就执行相应的动作。这里的 symbol 可以是 terminal 也可以是 nonterminal。

我们可以用 $symbol.attr 的方式访问到对应的属性,有下面这几个:

$terminal.text           # origin text
$terminal.type           # an integer stands for type
$terminal.attributes
$terminal.line           # the line number
$terminal.pos            # the position of the first char in the line,0-based
$terminal.index          
$terminal.channel        # the channel of this terminal, won't discuss in this post
$terminal.int            # return an interger if this terminal is an integer

$nonterminal.text            # origin text
$nonterminal.start           # 1st Token
$nonterminal.stop            # last Token
$nonterminal.ctx             # return context object

举个例子

⚠️下面都只会给出部分代码,完整的代码在我的 Github 项目

⚠️因为这个项目要求把 warning 输出在其他输出前面,所以我用 warning_listoutput_list 来存储,最后再一起输出

下面以普渡大学 2015 年的开设的编译器课程的项目作业为例,整个项目要实现一个 Micro 语言的编译器,关于 Micro 语言的语法,可以在这里看到。下面我会对步骤三的作业要求进行一个简单的介绍。

在这个作业中我们要构建一个 symbol table,并在相应的时刻输出相关的信息:

Symbol table <scope_name>
name <var_name> type <type_name>
name <var_name> type <type_name> value <string_value>

数据结构的选取及对应的方法

标签:作用域,symbol,list,ANTLR4,scope,table,self
来源: https://www.cnblogs.com/MartinLwx/p/16325297.html