们使用ANTLR来描述了Jerry语言的基本语法,并通过ANTLRWorks来实验该语法对样本代码生成的解析树。但如同上一篇最后所述,这样得到的解析树中有太多对后续处理来说无用的冗余信息。我们需要消除这些冗余信息,得到抽象语法树(AST)。
本篇将以之前做的语法为基础,通过添加树重写规则来将ANTLR默认生成的解析树简化整理为抽象语法树。

本文涉及的源码和运行时库打包在附件里了,懒得复制粘贴的话就直接下载附件的版本,用ANTLRWorks来查看和编辑语法文件吧~

修改后的语法文件如下:
Jerry.g(ANTLR 3.1语法文件,以Java为生成目标语言)

Java代码  
  1. grammar Jerry;
  2. options {
  3. language = Java;
  4. output = AST;
  5. ASTLabelType = CommonTree;
  6. }
  7. tokens {
  8. // imaginary tokens
  9. VAR_DECL;
  10. SIMPLE_TYPE;
  11. ARRAY_TYPE;
  12. ARRAY_LITERAL;
  13. SIMPLE_VAR_ACCESS;
  14. ARRAY_VAR_ACCESS;
  15. UNARY_MINUS;
  16. BLOCK;
  17. EXPR_STMT;
  18. }
  19. // parser rules
  20. program :   statementList EOF!
  21. {
  22. System.out.println(
  23. null == $statementList.tree ?
  24. "null" :
  25. $statementList.tree.toStringTree());
  26. }
  27. ;
  28. statementList
  29. :   statement*
  30. ;
  31. statement
  32. :   expressionStatement
  33. |   variableDeclaration
  34. |   blockStatement
  35. |   ifStatement
  36. |   whileStatement
  37. |   breakStatement
  38. |   readStatement
  39. |   writeStatement
  40. ;
  41. expressionStatement
  42. :   expression SEMICOLON
  43. -> ^( EXPR_STMT expression )
  44. ;
  45. variableDeclaration
  46. :   typeSpecifier
  47. ( Identifier
  48. (   -> ^( VAR_DECL ^( SIMPLE_TYPE typeSpecifier ) Identifier )
  49. | ( LBRACK Integer RBRACK )+
  50. -> ^( VAR_DECL ^( ARRAY_TYPE typeSpecifier Integer+ ) Identifier )
  51. | EQ expression
  52. -> ^( VAR_DECL ^( SIMPLE_TYPE typeSpecifier ) Identifier expression )
  53. | ( LBRACK Integer RBRACK )+ EQ arrayLiteral
  54. -> ^( VAR_DECL ^( ARRAY_TYPE typeSpecifier Integer+ ) Identifier arrayLiteral )
  55. )
  56. )
  57. ( COMMA id=Identifier
  58. (   -> $variableDeclaration ^( VAR_DECL ^( SIMPLE_TYPE typeSpecifier ) $id )
  59. | ( LBRACK dim1+=Integer RBRACK )+
  60. -> $variableDeclaration ^( VAR_DECL ^( ARRAY_TYPE typeSpecifier $dim1+ ) $id )
  61. | EQ exp=expression
  62. -> $variableDeclaration ^( VAR_DECL ^( SIMPLE_TYPE typeSpecifier ) $id $exp )
  63. | ( LBRACK dim2+=Integer RBRACK )+ EQ al=arrayLiteral
  64. -> $variableDeclaration ^( VAR_DECL ^( ARRAY_TYPE typeSpecifier $dim2+ ) $id $al )
  65. )
  66. { if (null != $dim1) $dim1.clear(); if (null != $dim2) $dim2.clear(); }
  67. )*
  68. SEMICOLON
  69. ;
  70. typeSpecifier
  71. :   INT | REAL
  72. ;
  73. arrayLiteral
  74. :   LBRACE
  75. arrayLiteralElement ( COMMA arrayLiteralElement )*
  76. RBRACE
  77. -> ^( ARRAY_LITERAL arrayLiteralElement+ )
  78. ;
  79. arrayLiteralElement
  80. :   expression
  81. |   arrayLiteral
  82. ;
  83. blockStatement
  84. :   LBRACE statementList RBRACE
  85. -> ^( BLOCK statementList )
  86. ;
  87. ifStatement
  88. :   IF^ LPAREN! expression RPAREN! statement ( ELSE! statement )?
  89. ;
  90. whileStatement
  91. :   WHILE^ LPAREN! expression RPAREN! statement
  92. ;
  93. breakStatement
  94. :   BREAK SEMICOLON!
  95. ;
  96. readStatement
  97. :   READ^ variableAccess SEMICOLON!
  98. ;
  99. writeStatement
  100. :   WRITE^ expression SEMICOLON!
  101. ;
  102. variableAccess
  103. :   Identifier
  104. (   -> ^( SIMPLE_VAR_ACCESS Identifier )
  105. | ( LBRACK Integer RBRACK )+
  106. -> ^( ARRAY_VAR_ACCESS Identifier Integer+ )
  107. )
  108. ;
  109. expression
  110. :   assignmentExpression
  111. |   logicalOrExpression
  112. ;
  113. assignmentExpression
  114. :   variableAccess EQ^ expression
  115. ;
  116. logicalOrExpression
  117. :   logicalAndExpression ( OROR^ logicalAndExpression )*
  118. ;
  119. logicalAndExpression
  120. :   relationalExpression ( ANDAND^ relationalExpression )*
  121. ;
  122. relationalExpression
  123. :   additiveExpression ( relationalOperator^ additiveExpression )?
  124. |   BANG^ relationalExpression
  125. ;
  126. additiveExpression
  127. :   multiplicativeExpression ( additiveOperator^ multiplicativeExpression )*
  128. ;
  129. multiplicativeExpression
  130. :   primaryExpression ( multiplicativeOperator^ primaryExpression )*
  131. ;
  132. primaryExpression
  133. :   variableAccess
  134. |   Integer
  135. |   RealNumber
  136. |   LPAREN! expression RPAREN!
  137. |   MINUS primaryExpression
  138. -> ^( UNARY_MINUS primaryExpression )
  139. ;
  140. relationalOperator
  141. :   LT | GT | EQEQ | LE | GE | NE
  142. ;
  143. additiveOperator
  144. :   PLUS | MINUS
  145. ;
  146. multiplicativeOperator
  147. :   MUL | DIV
  148. ;
  149. // lexer rules
  150. LPAREN  :   '('
  151. ;
  152. RPAREN  :   ')'
  153. ;
  154. LBRACK  :   '['
  155. ;
  156. RBRACK  :   ']'
  157. ;
  158. LBRACE  :   '{'
  159. ;
  160. RBRACE  :   '}'
  161. ;
  162. COMMA   :   ','
  163. ;
  164. SEMICOLON
  165. :   ';'
  166. ;
  167. PLUS    :   '+'
  168. ;
  169. MINUS   :   '-'
  170. ;
  171. MUL :   '*'
  172. ;
  173. DIV :   '/'
  174. ;
  175. EQEQ    :   '=='
  176. ;
  177. NE  :   '!='
  178. ;
  179. LT  :   '<'
  180. ;
  181. LE  :   '<='
  182. ;
  183. GT  :   '>'
  184. ;
  185. GE  :   '>='
  186. ;
  187. BANG    :   '!'
  188. ;
  189. ANDAND  :   '&&'
  190. ;
  191. OROR    :   '||'
  192. ;
  193. EQ  :   '='
  194. ;
  195. IF  :   'if'
  196. ;
  197. ELSE    :   'else'
  198. ;
  199. WHILE   :   'while'
  200. ;
  201. BREAK   :   'break'
  202. ;
  203. READ    :   'read'
  204. ;
  205. WRITE   :   'write'
  206. ;
  207. INT :   'int'
  208. ;
  209. REAL    :   'real'
  210. ;
  211. Identifier
  212. :   LetterOrUnderscore ( LetterOrUnderscore | Digit )*
  213. ;
  214. Integer :   Digit+
  215. ;
  216. RealNumber
  217. :   Digit+ '.' Digit+
  218. ;
  219. fragment
  220. Digit   :   '0'..'9'
  221. ;
  222. fragment
  223. LetterOrUnderscore
  224. :   Letter | '_'
  225. ;
  226. fragment
  227. Letter  :   ( 'a'..'z' | 'A'..'Z' )
  228. ;
  229. WS  :   ( ' ' | '\t' | '\r' | '\n' )+ { $channel = HIDDEN; }
  230. ;
  231. Comment
  232. :   '/*' ( options { greedy = false; } : . )* '*/' { $channel = HIDDEN; }
  233. ;
  234. LineComment
  235. :   '//' ~('\n'|'\r')* '\r'? '\n' { $channel = HIDDEN; }
  236. ;

稍微说明一下修改点。应该观察到lexer rules部分是完全没有改变的,修改的主要是一些选项和parser rules。

首先,在文件的开头添加了一组选项:

Java代码  
  1. options {
  2. language = Java;
  3. output = AST;
  4. ASTLabelType = CommonTree;
  5. }

ANTLR会知道应该使用生成AST的模式,以CommonTree作为AST的节点类型,并以Java作为生成的解析器源码的语言。上一篇是在 ANTLRWorks里编辑和实验语法的,这次我们需要生成实际能运行的解析器,所以需要指定这些选项(默认就是生成Java源码,不过后续文章中我应该 会换用CSharp2目标。这个以后再说)。

接下来,可以看到除了原本在lexer rules里定义的实际存在的token类型之外,这次我们在语法文件的开头还增加了一组虚拟的token类型。这些token类型是为了让生成出来的抽象语法树易于解析而添加的。
例如,观察VAR_DECL这个token类型。在原本的语法中,没有任何关键字能清楚的标识出当前处理的内容是一个变量声明。为了方便后续分析,我们可以“制造”出一个虚构的token作为一个变量声明语句的根元素,然后以变量的类型、标识符和初始值为子元素。

然后就是最重要的部分,树重写规则了。有两种形式来表述树重写规则:一是直接在原本的语法规则上添加树生成用的运算符(^和!),二是在原本的语法规则后添加一个箭头("->"),并在箭头后显式指定需要生成的节点的结构。
看两个例子:
while语句。原本的语法是:

Java代码  
  1. whileStatement : 'while' '(' expression ')' statement ;

这里我们想让生成出来的子树以'while'为根节点,以expression和statement为子节点。
可以直接在该语法上添加树生成运算符:在某个元素后加上帽子符号('^')来表示它是生成的子树的根节点,在某个元素后加上叹号('!')来表示生成的子树中应该忽略该元素。于是修改得到的语法是:

Java代码  
  1. whileStatement : 'while'^ '('! expression ')'! statement ;

也可以显式指定树重写规则。一棵子树用这种方式来表示:

Java代码  
  1. ^( root element1 element2 ... )

这里我们要的就是:

Java代码  
  1. whileStatement : 'while' '(' expression ')' statement
  2. -> ^( 'while' expression statement )
  3. ;

这种形式我们能一目了然看到最终生成的子树的结构。
两种形式是等价的,可以根据具体情况来选择能简单而清晰的表示出树改写规则的版本。

对表达式相关的语法规则,我们几乎都是用添加运算符的形式来表示树改写规则,因为对左结合的双目运算符,这样是最简洁的。
ANTLR生成的解析器使用LL(*)算法;与一般的LL解析器一样,ANTLR不支持左递归的语法规则。这使得书写左结合的双目运算符时,一般得写成这样的形式:

Java代码  
  1. exprWithHigherPrecedence
  2. : exprWithLowerPrecedence ( op exprWithLowerPrecedence )*
  3. ;

而不能以左递归来指定左结合。(但右结合还是可以用右递归来指定的。)
那么在表示树改写规则的时候,使用运算符来修饰语法就是这样:

Java代码  
  1. exprWithHigherPrecedence
  2. : exprWithLowerPrecedence ( op^ exprWithLowerPrecedence )*
  3. ;

只是在op的后面添加了一个帽子符号('^'),表明在没有匹配到op运算符时就直接返回exprWithLowerPrecedence规则所 生成的树;而如果匹配到了op运算符,则每匹配到一次就生成一个新的以op为根节点的、前后两个较低优先级的表达式节点为子节点的树。
这个树改写规则如果要显式指定,就得写成:

Java代码  
  1. exprWithHigherPrecedence
  2. : exprWithLowerPrecedence
  3. ( op exp=exprWithLowerPrecedence
  4. -> ^( op $exprWithHigherPrecedence $exp )
  5. )*
  6. ;

后者相比之下麻烦多了,所以一般都会使用前者。

可惜C风格的变量声明语句的语法很麻烦,结果variableDeclaration在修改后膨胀了好多 T T
最不爽的地方就是C风格的数组变量声明是把数组的维度写在变量名后面的。这就使得语句开头的类型(例如int、char等)可能只是变量的实际类型的一部分,而另一部分要在变量名的之前(例如表示指针的星号('*'))或之后(例如表示数组的方括号('[' ']'))。
就不能把整个类型写在一起么……T T 于是衍生出来的Java和C#明显都吸取了这个教训。

在语法的program规则中,我们添加了一条嵌入语法动作,让生成的解析器在匹配完program规则后将其对应的抽象语法树以字符串的形式输出出来。

如果是在ANTLRWorks里编辑该语法文件,可以在菜单里选择Generate -> Generate Code来生成出解析器的源码。这里例子中我们会得到JerryLexer.java和JerryParser.java。
要运行这个解析器,还需要写一个简单的启动程序来调用生成出来的JerryLexer和JerryParser。源码如下:
TestJerry.java

Java代码  
  1. import org.antlr.runtime.*;
  2. public class TestJerry {
  3. public static void main(String[] args) throws Exception {
  4. // Create an input character stream from standard in
  5. ANTLRInputStream input = new ANTLRInputStream(System.in);
  6. // Create an JerryLexer that feeds from that stream
  7. JerryLexer lexer = new JerryLexer(input);
  8. // Create a stream of tokens fed by the lexer
  9. CommonTokenStream tokens = new CommonTokenStream(lexer);
  10. // Create a parser that feeds off the token stream
  11. JerryParser parser = new JerryParser(tokens);
  12. // Begin parsing at rule prog
  13. parser.program();
  14. }
  15. }

它指定从标准输入流得到要解析的Jerry代码,然后通过JerryLexer将代码解析成token流,再将token流交给JerryParser进行句法分析。

将JerryLexer.java、JerryParser.java和TestJerry.java放在跟ANTLRWorks同一目录下,然后编译它们:

引用
javac -Xlint:unchecked -cp antlrworks-1.2.2.jar JerryLexer.java JerryParser.java TestJerry.java

(因为ANTLRWorks里含有ANTLR的运行时库,而我正好又是用ANTLRWorks来编辑语法文件的,所以直接用ANTLRWorks 的JAR包放在classpath里来得到需要的ANTLR运行时类。实际开发的话可以从ANTLR官网获得只含有ANTLR运行时库的JAR包并在编译 和运行的时候将其添加到classpath里。)

上一篇的最后有这样的一段Jerry例子:

C代码  
  1. // line comment
  2. // declare variables with/without initializers
  3. int i = 1, j;
  4. int x = i + 2 * 3 - 4 / ( 6 - - 7 );
  5. int array[2][3] = {
  6. { 0, 1, 2 },
  7. { 3, 4, 6 }
  8. };
  9. /*
  10. block comment
  11. */
  12. while (i < 10) i = i + 1;
  13. while (!x > 0 && i < 10) {
  14. x = x - 1;
  15. if (i < 5) break;
  16. else read i;
  17. }
  18. write x - j;

(语法是符合要求的,至于代码的意义就别追究了,只是用来演示各种语法结构随便写的)

用本篇的ANTLR语法文件生成的解析器,我们可以解析这个例子,得到对应的抽象语法树的字符串表示。表示方法是:

Java代码  
  1. (root element1 element2 ...)

跟LISP的S-expression非常类似。

于是执行测试程序。将要解析的代码保存到JerrySample.txt中,然后执行下面的命令:

引用
java -cp ".;antlrworks-1.2.2.jar" TestJerry < JerrySample.txt

得到输出:

Java代码  
  1. (VAR_DECL (SIMPLE_TYPE int) i 1) (VAR_DECL (SIMPLE_TYPE int) j) (VAR_DECL (SIMPLE_TYPE int) x (- (+ (SIMPLE_VAR_ACCESS i) (* 2 3)) (/ 4 (- 6 (UNARY_MINUS 7))))) (VAR_DECL (ARRAY_TYPE int 2 3) array (ARRAY_LITERAL (ARRAY_LITERAL 0 1 2) (ARRAY_LITERAL 3 4 6))) (while (< (SIMPLE_VAR_ACCESS i) 10) (= (SIMPLE_VAR_ACCESS i) (+ (SIMPLE_VAR_ACCESS i) 1))) (while (&& (! (> (SIMPLE_VAR_ACCESS x) 0)) (< (SIMPLE_VAR_ACCESS i) 10)) (BLOCK (= (SIMPLE_VAR_ACCESS x) (- (SIMPLE_VAR_ACCESS x) 1)) (if (< (SIMPLE_VAR_ACCESS i) 5) break (read (SIMPLE_VAR_ACCESS i))))) (write (- (SIMPLE_VAR_ACCESS x) (SIMPLE_VAR_ACCESS j)))

这样太乱了看不清楚。将其格式稍微整理一下得到:

Java代码  
  1. (VAR_DECL
  2. (SIMPLE_TYPE int)
  3. i
  4. 1
  5. )
  6. (VAR_DECL
  7. (SIMPLE_TYPE int)
  8. j
  9. )
  10. (VAR_DECL
  11. (SIMPLE_TYPE int)
  12. x
  13. (-
  14. (+ (SIMPLE_VAR_ACCESS i) (* 2 3))
  15. (/ 4 (- 6 (UNARY_MINUS 7)))
  16. )
  17. )
  18. (VAR_DECL
  19. (ARRAY_TYPE
  20. int
  21. 2
  22. 3
  23. )
  24. array
  25. (ARRAY_LITERAL
  26. (ARRAY_LITERAL 0 1 2)
  27. (ARRAY_LITERAL 3 4 6)
  28. )
  29. )
  30. (while
  31. (< (SIMPLE_VAR_ACCESS i) 10)
  32. (= (SIMPLE_VAR_ACCESS i) (+ (SIMPLE_VAR_ACCESS i) 1))
  33. )
  34. (while
  35. (&&
  36. (! (> (SIMPLE_VAR_ACCESS x) 0))
  37. (< (SIMPLE_VAR_ACCESS i) 10)
  38. )
  39. (BLOCK
  40. (= (SIMPLE_VAR_ACCESS x) (- (SIMPLE_VAR_ACCESS x) 1))
  41. (if
  42. (< (SIMPLE_VAR_ACCESS i) 5)
  43. break
  44. (read (SIMPLE_VAR_ACCESS i))
  45. )
  46. )
  47. )
  48. (write
  49. (- (SIMPLE_VAR_ACCESS x) (SIMPLE_VAR_ACCESS j)))

可以跟原本的代码对比一下,看看是否保持了原本的结构。

得到这棵抽象语法树之后,接下来就可以对树来做匹配和分析了。由于树本身已经有了结构,下面就可以用更干净的描述方式来表述我们要对树做的处理。

转载于:https://www.cnblogs.com/shihao/archive/2012/06/02/2532218.html

一个简单的语言的语法(二):ANTLR的重写规则相关推荐

  1. 使用ANTLR做一个简单的Python SQL语法解析器 - 推酷

    使用ANTLR做一个简单的Python SQL语法解析器 - 推酷 使用ANTLR做一个简单的Python SQL语法解析器 - 推酷 posted on 2016-11-14 13:11 lexus ...

  2. 实现一个简单的代码字计数器(二)

    分割字符串 实现一个简单的代码字计数器(一) 实现一个简单的代码字计数器(二) 实现一个简单的代码字计数器(三) 实现一个简单的代码字计数器(四) 这一篇里让我们先实现基本功能,特性和改善放在后几篇实 ...

  3. Python制作一个简单的抽奖软件(二)

    Python制作一个简单的抽奖软件(二) 认识QT 因为都对 QT和tkinter都没用过,之前简单使用tkinter后发现,界面调整不太好弄.然后度娘了之后,QT是强大GUI库之一,很多人都推荐它. ...

  4. go var 一个整数_go语言基本语法——数据类型

    一.基本数据类型 以下是go中可用的基本数据类型 1.1 布尔型bool 布尔型的值只可以是常量 true 或者 false.一个简单的例子:var b bool = true 1.2 数值型 1.整 ...

  5. 一起学习C语言:C语言基本语法(二)

    上一篇 <一起学习C语言:C语言基本语法(一)> 中,我们了解if语句的语法和不同的应用场景,以及语句块的使用方式.本篇文章中,我们分析逻辑运算符的用法,并通过几个示例分析逻辑运算符之间的 ...

  6. 使用UE4创建一个简单真实的地球(二)

    使用UE4创建一个简单真实的地球 如何创建一个简单的地球材质. BaseColor 基础颜色 排除由反射引起的杂光之后物体的颜色.主要用来模拟地球的真实表面. 白昼 地图与云图叠加,即图像的叠加运算( ...

  7. c语言点餐系统感悟,一个简单C语言点餐系统的学习心得

    首先附上源代码: 1 #include 2 3 voidmain_munu(){4 //打印主菜单 5 char a[6][20]={"菜单","凉菜",&qu ...

  8. 一个简单51c语言程序,三个简单的C语言程序(上)

    今天我们介绍三个最简单的C语言程序. 例1:要求在屏幕上输出下面一行信息:ThisisaCprogram. 解决方法:在主函数中使用printf函数,将以上单词原样输出. 写程序: 运行结果:This ...

  9. 使用PlayCanvas制作一个简单的小游戏(二)

    原文:http://developer.playcanvas.com/zh/tutorials/beginner/keepyup-part-two/ 设置材质 在这个游戏中,我们尽可能的使用了简单的图 ...

最新文章

  1. win7仿linux主题,linuxmint 16 cinnamon模仿win7/win8主题
  2. 数据绑定表达式(下):.NET发现之旅(二)
  3. Qt Creator将应用程序部署到QNX Neutrino设备
  4. 直击于丹软肋的作家——李悦
  5. MMKV集成与原理,赶紧学起来
  6. [转载] Java中的final变量、final方法和final类
  7. minheight能继承吗_遗嘱中的房屋被拆迁,指定继承人能继承对应的拆迁款或安置房吗?...
  8. Linux之touch命令
  9. python学习-(__new__方法和单例模式)
  10. Android OpenGL ES(十二):三维坐标系及坐标变换初步 .
  11. \t\t对80后玩家影响深远的12款单机游戏
  12. windows下.bat文件启动多个jar文件 (.bat 批量启动jar)
  13. 爬取微博评论,生成词云图!
  14. 围棋人机大战属于计算机在什么方面的应用,新华社评围棋人机大战:不怕电脑记性好 就怕爱学习...
  15. threejs 管子_使用webgl(three.js)搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课...
  16. 外部修改应用程序图标的做法
  17. 计算机拓扑结构定义,计算机网络拓扑结构的定义
  18. 金融数据中心建设模式浅析
  19. 风控ML[15] | 风控模型报告以及上线后需要监控的内容
  20. 华为云产品介绍—大数据

热门文章

  1. UI自动化之特殊处理三(日期控件\表格\富文本)
  2. [BZOJ2693]jzptab
  3. 正在学习的Angularjs包裹的插件
  4. Oracle函数的定义
  5. 摩卡业务服务管理 全面彰显强大产业推动优势——神华集团神东煤炭分公司
  6. 通过帧中继验证OSPF支持的不同网络类型
  7. NOtePad++快捷键大全
  8. oracle排序后的第一条记录
  9. C# 线程手册 第三章 使用线程
  10. 【转】HTML全解(1)