编译原理-Antlr2

968 阅读3分钟

这一节使用Antlr重构脚本语言

expression表达式语法

我们的expression表达式语法是可以完善的:


expression
    : primary
    | expression bop='.'
      ( IDENTIFIER
      | functionCall
      | THIS
      )
    | expression '[' expression ']'
    | functionCall
    | expression postfix=('++' | '--')
    | prefix=('+'|'-'|'++'|'--') expression
    | prefix=('~'|'!') expression
    | expression bop=('*'|'/'|'%') expression  
    | expression bop=('+'|'-') expression 
    | expression ('<' '<' | '>' '>' '>' | '>' '>') expression
    | expression bop=('<=' | '>=' | '>' | '<') expression
    | expression bop=INSTANCEOF typeType
    | expression bop=('==' | '!=') expression
    | expression bop='&' expression
    | expression bop='^' expression
    | expression bop='|' expression
    | expression bop='&&' expression
    | expression bop='||' expression
    | expression bop='?' expression ':' expression
    | <assoc=right> expression
      bop=('=' | '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' | '>>=' | '>>>=' | '<<=' | '%=')
      expression
    ;

可以看到在Antlr中表达式的顺序蕴含着优先级,这是Antlr特有的。在语法文件中,Antlr 对于赋值表达式做了 <assoc=right>的属性标注,说明赋值表达式是右结合的。如果不标注,就是左结合的,交给 Antlr 实现。语法里面的 bop=、postfix=、prefix= 这些属性,作为某些运算符 Token 的别名,会成为表达式节点的属性,解析器在分析语义的时候就知道这是一个赋值还是逻辑运算语句,同样的解析器还可以分析Token的类型来判断语义。

statement语句语法


statement
    : blockLabel=block
    | IF parExpression statement (ELSE statement)?
    | FOR '(' forControl ')' statement
    | WHILE parExpression statement
    | DO statement WHILE parExpression ';'
    | SWITCH parExpression '{' switchBlockStatementGroup* switchLabel* '}'
    | RETURN expression? ';'
    | BREAK IDENTIFIER? ';'
    | SEMI
    | statementExpression=expression ';'
    ;

使用各种IF,WHILE等别名也即非终结符来匹配语句中的if,else等Token。

if的statement写法

  • 基本写法

statement : 
          ...
          | IF parExpression statement (ELSE statement)? 
          ...
          ;
parExpression : '(' expression ')';

可以看到parexpression用()起来的是条件,statement里由block相关,保证了语句可以用{}包围成一个整体

这里插入block语句块写法


block
    : '{' blockStatements '}'
    ;

blockStatements
    : blockStatement*
    ;

blockStatement
    : variableDeclarators ';'     //变量声明
    | statement
    | functionDeclaration         //函数声明
    | classDeclaration            //类声明
    ;
  • 消除二义性

if中存在二义性,也就是if else的结合问题,一般来说if都和最近的else结合,使用两个statement状态(full,partly)可以解决这个问题:


stmt -> fullyMatchedStmt | partlyMatchedStmt
fullyMatchedStmt -> if expr fullyMatchedStmt else fullyMatchedStmt
                   | other
partlyMatchedStmt -> if expr stmt
                   | if expr fullyMatchedStmt else partlyMatchedStmt
 

for 的statement写法


statement : 
         ...
          | FOR '(' forControl ')' statement
         ...
          ;

forControl 
          : forInit? ';' expression? ';' forUpdate=expressionList?
          ;

forInit 
          : variableDeclarators 
          | expressionList 
          ;

expressionList
          : expression (',' expression)*
          ;

使用visitor

在静态分析阶段,将源程序表示为一个抽象语法树,编译器需要在抽象语法树的基础上实施某些操作以进行静态语义分析。可能需要定义许多操作以进行类型检查、代码优化、流程分析、检查变量是否在使用前被赋值,等等。

使用evaluate来遍历整棵树运行脚本代价比较大,由于运算种类很多,操作比较复杂,在每个节点上添加visitor可以提高效率。

1 将相关操作封装在一个独立的对象(Visitor)中,并在遍历抽象语法树时将此对象传递给当前访问的元素。

2 当一个节点接受一个访问者时,该元素向访问者发送一个包含自身类信息的请求。该请求同时也将该元素本身作为一个参数。

3 访问者将对该元素执行该操作。

形象地说就是:我的电脑要拿去修,维修工是vistor,他们接收我的电脑各个零件(AST结点),产出好的零件(求出的值)

Antlr 能帮我们生成一个 Visitor 处理模式的框架,在命令行输入:


antlr -visitor PlayScript.g4

生成了框架后还需要自行修改,具体细节不粘贴在这里