翻译Antlr的内容来学习Antlr,还会让我不会睡觉,不觉得困。
以一个简单的计算器开始,当作学习Antlr的一个例子。
任何一个语言处理程序都有至少两部分:
1、一个词法解释器:获得字符串流,将这个流按预先设定要的规则分割成一个个的token
2、一个语法分析器:读token,根据规则翻译他们。
让我们为一个简单的算数表达式定义一个规则:
grammar SimpleCalc; add : NUMBER PLUS NUMBER; NUMBER : ('0'..'9')+ ; PLUS : '+';
这个例子包含两部分规则:NUMBER and PLUS ,和一个语法规则 add。词法规则常常以大写字母开头,语法规则以小写字母开头。
- NUMBER 定义了包含0到9的字符的token,这些字符可以重复一次或一次以上
- PLUS 定义了一个简单的字符token:+
- add 定义了一个语法规则,这个语法表示希望接收一个NUMBER token,一个PLUS token,一个NUMBER token,并顺序固定。如果以不同的顺序将会触发错误消息。
分析这个计算
让我们接受更复杂的表达式,像1或1+2或1+2+3+4,这些表达式以一个数字开头,然后加上一个加法标志和一个数(有可能多于一次):
add : NUMBER (PLUS NUMBER)*
*表示0或多次
如果你既想实现加法又想实现减法,你可以做一个小的调整:
add : NUMBER ((PLUS | MINUS) NUMBER)*
MINUS : '-';
你可能已经猜到了,| 意味着“或者”,也就是“加”或者“减”。
如果你希望语法分析完成算术表达式像1+2*3,有一个标准的递归方法来实现它:
expr : term ( ( PLUS | MINUS ) term )*; term : factor ( ( MULT | DIV ) factor )*; factor : NUMBER ; MULT : '*' ; DIV : '/' ;
(计算机一个表达式,通常以expr开头)
我们的语法是不能容忍空格的:当遇到空格,标签符,回车等它会给出警告。我们要告诉词法分析器丢弃任何它所找到的空格是安全的。
首先,我们要定义空格:
- 一个空格:‘ ’
- 一个标签符是这样表示的:‘\t’
- 新的一行是这样表示的:‘\n’
- 一个回车符是这样表示的:‘r’
- 一个换页符有一个十进制值12和一个十六进制值0C。Antlr用Unicode编码,所以我们将它定义为4个十六进制数字:‘\u000C’
把这些放在一块,并用一个“or”连接,允许一个或多个一块出现,那么你会得到:
WHITESPACE : ( '\t' | ' ' | '\r' | '\n' | '\u000C' )+;
那么,如果我们写3 + 4*5这个表达式,词法分析器会生成 NUMBER WHITESPACE PLUS WHITESPAC NUMBER MULT NUMBER,而且这将导致语法分析器抱怨未知WHITESPACE标记。我们需要一种方法对于语法分析器,将他们隐藏起来。
Antlr在词法分析器和语法分析器之间包含两种沟通渠道,默认渠道和隐藏渠道。语法分析器一次只听取一个渠道的内容,所以你可以用将它至于隐藏渠道的方法来隐藏一个标记。
(可以有多于两种渠道而且语法分析器可以单独听取他们中的任一个,也可以从所有合并的渠道获取信息。这在你正在写一个文字处理工具的时候非常有用,这个文字处理工具需要解析出空白和注释的输出,而且让语法分析器忽略这些元素)
对于连续不断的隐藏要求,你用设置这些token的$channel来隐藏他们。这要求你加一些大括号来在词法分析器中添加一点点代码。
WHITESPACE : ( '\t' | ' ' | '\r' | '\n' | '\u000C' ) + { $channel = HIDDEN; };
(如果你现在正在键盘上试图实现词法分析和语法分析的代码,而且正在词法分析器中搜寻 channel = HIDDEN
ANTLR默认生成java代码。你将会在一分钟内学会怎么更改它)
整理你的代码
你可以用很多技术来让你的语法更具有可读性:
- 添加注释,比如 // 或者/*....*/
- 将你的简单token定义(单字符,单词等)收集到文件顶部的token中
- 考虑用fragment规则定义子部分的标记。一个fragment规则将不会自己生成标记,但可以被用作规则的一部分,来定义其余的标记。
这是一个整理好的代码:
1 grammar SipleCalc; 2 3 tokens{ 4 PLUS = '+' ; 5 MINUS = '-' ; 6 MULT = '*'; 7 DIV = '/'; 8 } 9 /*------------------------------------------------------------------ 10 * PARSER RULES 11 *------------------------------------------------------------------*/ 12 13 expr : term ( ( PLUS | MINUS ) term )* ; 14 15 term : factor ( ( MULT | DIV ) factor )* ; 16 17 factor : NUMBER ; 18 19 /*------------------------------------------------------------------ 20 * LEXER RULES 21 *------------------------------------------------------------------*/ 22 23 NUMBER : (DIGIT)+ ; 24 25 WHITESPACE : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ { $channel = HIDDEN; } ; 26 27 fragment DIGIT : '0'..'9' ;
这表明了通常模式:得到一个输入流,进行词法分析,得到标记流,进行语法分析,然后在语法分析中调用其中的方法。(每个语法规则在语法分析器中添加一个相应的方法)