武义建设局网站首页辅导班培训机构
ANTLR4入门学习(四)
- 一、设计语法
- 1.语法
- 2.ANTLR核心标记
- 3.常见计算机语言模式
- 4.左右递归
- 5.识别常见的语法结构
- 5.1 匹配标识符
- 5.2 匹配数字
- 5.3 匹配字符串常量
- 5.4 匹配注释和空白字符
- 5.5 基础的语法规则
- 5.6 划定词法分析器和语法分析器的界线
一、设计语法
- 序列:一列元素,一个数组初始化语句中的值
- 选择:在多种可选方案中做出选择,例如编程语言中的不同种类的语句
- 词法符号依赖:一个词法符号需要和某处的另外一个词法符号配对,例如左右括号匹配
- 嵌套结构:一种自相似的语言结构,例如编程语言中的嵌套算数表达式或者嵌套语句块
1.语法
语法由一个为该语法命名的头部定义和一系列可以相互引用的语言规则组成;
grammar MyG;
rule1 : <<stuff>>;
rule2 : <<more stuff>>;
...
必须指明需要的语言规则,其中<<stuff>>的具体内容,以及那条规则是起始规则;
一个CSV文件就是一系列以换行符为终止的行。(a comma-separated-value[CSV]file is a sequence of rows terminated by newlines.)其中,is a左侧的单词file就是规则名,右侧的全部内容就是规则定义中的<<stuff>>。
file : <<sequence of rows terminated by newlines>>;
降低层次,描述起始规则右侧所指定的元素,通常是词法符号或者尚未定义的规则。
再降低一层,一个行就是一系列由逗号分隔的字段(a row is sequence of fields separated by commas)。一个字段就是一个数字或者字符串(a field is a number or string)。
file : <<sequence of rows terminated by newlines>>;
row : <<sequence of fields separated by commas>>
field : <<number or string>>;
2.ANTLR核心标记
用法 | 描述 |
---|---|
x | 匹配词法符号、规则引用或者子规则x |
x y … z | 匹配一列规则元素 |
(…|…|…) | 一个具有多个备选分支的子规则 |
x? | 匹配x或者忽略它 |
x* | 匹配x零次或多次 |
x+ | 匹配x一次或多次 |
r : … ; | 定义规则r |
r : … | … | …; | 定义具有多个备选分支的规则r |
3.常见计算机语言模式
模式名 | 描述 |
---|---|
序列模式 | 它是一个有限长度或者任意长度的序列,序列中的元素可以是词法符号或者子规则、序列模式的例子包括变量声明(类型后面紧跟着标识符)和整数序列,范例: x y … z // x后面跟着y , … , z ‘[’ INT+ ‘]’ // Matlab的整数向量 |
带终止符的序列模式 | 它是一个任意长的、可能为空的序列,该序列由一个词法符号分隔开,通常是分号或者换行符,其中的元素可以是词法符号或子规则,范例: (statement ‘;’)* //java的语句集合 (row ‘\n’)* //多行数据 |
带分隔符的序列模式 | 它是一个任意长的、可能为空的序列,该序列由一个词法符号分隔开,通常是逗号、分号或是句号,其中的元素可以是词法符号或子规则,范例: expr (‘,’ expr)* // 函数调用时传递的参数 (expr (‘,’ expr)* )? // 函数调用时传递的参数是可选的 ‘/’? name (‘/’ name)* //简化的目录名 stat (‘.’ stat)* //若干个SmallTalk(程序设计语言)语句 |
选择模式 | 它是一组备选分支的集合,这样的例子包括不同种类的类型、语句、表达式或者XML标签,范例: type : ‘int’ | ‘flloat’ ; stat : ifstat | whilestat | ‘return’ expr ‘;’ expr : ‘(’ expr ‘)’ | INT | ID ; tag : ‘<’ Name attribute* ‘>’ | ‘<’ ‘/’ Name ‘>’ ; |
词法符号依赖 | 一个词法符号需要和一个或者多个后续词法符号匹配,这样的例子包括配对的圆括号、花括号、方括号和尖括号,范例: ‘(’ expr ‘)’ // 嵌套表达式 ID ‘[’ expr ‘]’ // 数组索引表达式 ‘{’ stat* ‘}’ // 花括号包裹的若干个语句 ‘<’ ID (‘,’ ID)* ‘>’ //泛型声明 |
嵌套模式 | 它是一种自相似的语言结构,这样的例子包括表达式、Java的内部类、嵌套的代码块以及嵌套的Python函数定义,范例: expr : ‘(’ expr ‘)’ | ID ; classDef : ‘class’ ID ‘{’ (classDef | method | field) ‘}’ ; |
4.左右递归
ANTLR4 可以处理直接左递归,但是不能处理间接左递归,但最好不要使用左递归
ANTLR 由上到下 优先级依次降低
expr : expr '^' <assoc=right> expr // ^运算符是右结合的 最好写成 <assoc=right> expr '^' expr| expr '*' expr // 匹配由 * 运算符连接的子表达式| expr '+' expr // 匹配由 + 运算符连接的子表达式| INT // 匹配简单的整数因子;
但是ANTLR无法处理间接左递归
error:
expr : expo // 通过expo规则间接左递归调用expr规则| ...;
expo : expr '^' <assoc=right> expr;
尽管语义等价,但是无法将expr规则按上述分解
5.识别常见的语法结构
5.1 匹配标识符
语法伪代码中,一个基本的标识符就是一个由大小写字母组成的字符序列,可以用(…)+表示,也可以用正则表示
ID : ('A'..'Z'|'a'..'z')+ ; 匹配1个或者多个大小写字母
ID : [a-z]A-Z]+ ; 匹配1个或者多个大小写字母
ANTLR从文法规则中筛选除所有的字符串常量,并将他们和词法规则放在一起。'enum’这样的字符串常量被隐式定义为词法规则,然后放置在文法规则之后、显示定义的词法规则之前,ANTLR词法分析器解决歧义问题的方法是优先使用位置靠前的词法规则。ID规则必须定义在所有的关键字规则之后 。
5.2 匹配数字
匹配整数
INT : '0'..'9'+ ; // 匹配1个或多个数字
INT : [0-9]+ ; // 匹配1个或多个数字
匹配浮点数
一个浮点数以一列数字为开头,后面跟着一个点,然后是可选的小数部分:浮点数的另一个格式,以点为开头,后面是一列数字,使用选择模式或序列模式
FLOAT : DIGIT+ ',' DIGIT* // 匹配1.,39.,3.14159| '.' DIGIT+ //匹配 .1, .14159;
fragment
DIGIT : [0-9]; // 匹配单个数字
fragment 可以告诉ANTLR,这条规则本身不是一个语法符号,它只会被其他的词法规则使用,这意味着我们不能再文法规则中引用DIGIT
5.3 匹配字符串常量
STRING : '"' .*? '"' ; // 匹配"..."间的任意文本
.*是一个循环,匹配零个或多个字符组成的任意字符序列,可以匹配到文件结束,ANTLR通过标准正则表达式的标记(?后缀)提供了对非贪婪匹配子规则的支持,获取一些字符,直到发现匹配后续子规则的字符为止,再保证整个父规则完成匹配的前提下,非贪婪的子规则匹配数量最少的字符。
STRING : '"' (ESC|.)*? '"' ;
fragment
ESC : '\\"' | '\\\\' ; // 双字符序列 \" 和 \\
其中ANTLR语法本身需要对转义字符\进行转义,因此我们需要\来表示单个反斜杠字符
STRING规则中的循环既能通过ESC片段规则(fragment rule)来匹配转义字符序列,也能通过通配符来匹配任意单个字符。?运算符会使 (ESC|.)? 循环在看到子后续子规则,即一个未转义的双引号时终止。
5.4 匹配注释和空白字符
assign : ID (WS|COMMENT)? '=' (WS|COMMENT)? expr (WS|COMMENT)? ;
LINE_COMMENT : '//' .*? '\r'? '\n' -> skip ; // 匹配 "//" 任意字符序列 '\n'
COMMENT : '/*' .*? '*/' -> skip ; // 匹配"/*" 任意字符序列 "*/"
WS : (' '|'\t'|'\r'|'\n'])+ -> skip ; // 匹配一个或多个空白字符并将它们丢弃
WS : [ \r\t\n]+ -> skip ;
skip指令通知词法分析器将它们丢弃即可
词法分析器可以接受许多种位于-> 操作符之后的指令,skip只是其中之一。
5.5 基础的语法规则
词法符号类型 | 描述及范例 |
---|---|
标点符号 | 处理运算符和标点符号最容易的方式就是直接在文法规则中引用它们。 call : ID ‘(’ exprList ‘)’ ; 一些开发者更愿意定义类型LP(左括号, left parcnthesis)的词法符号标签。 call : ID LP exprList RP ; LP : ‘(’ ; RP : ‘)’ ; |
关键字 | 关键字是保留的标识符,我们既可以直接引用它们,也可以为它们定义词法符号类型 returnStat : ‘return’ expr ‘;’ |
标识符 | 几乎每种语言中的标识符看上去都差不多,它们之间的差异通常在与第一个字符的可选值以及是否允许Unicode字符。 ID : ID_LETTER (ID_LETTER | DIGIT) *; // C语言的语法片段 fragment ID_LETTER : ‘a’…‘z’|‘A’…‘Z’|‘…’ ; fragment DIGIT : ‘0’…9’; |
数字 | 范例: INT : DIGIT+; FLOAT : DIGIT+ ‘.’ DIGIT* | ‘.’ DIGIT+; |
字符串 | 匹配双引号包围的字符串 STRING : ‘"’ (ESC |.)*? ‘"’ ; fragment ESC : ‘\"’ | ‘\\’ ; // 双字符序列 " 和 | |
注释 | 匹配并丢弃注释 LINE_COMMENT : ‘//’ .? ‘\r’? ‘\n’ -> skip ; // 匹配 “//” 任意字符序列 ‘\n’ COMMENT : '/’ .? '/’ -> skip ; // 匹配"/*" 任意字符序列 “*/” |
空白字符 | WS : [ \r\t\n]+ -> skip ; |
5.6 划定词法分析器和语法分析器的界线
- 在词法分析器中匹配并丢弃任何语法分析器无须知晓的东西。对于编程语言来说,要识别并丢弃的就是类似注释和空白字符的东西。
- 由词法分析器来匹配类似标识符、关键字、字符串和数字的常见词法符号。语法分析器的层级更高,所以我们不应当让它处理将数字组合成整数这样的事情。
- 将语法分析器无须区分的词法结构归为同一个词法符号类型。
- 将任何语法分析器可以以相同方式处理的实体归为一类。
- 如果语法分析器许哟把一种类型的文本拆开处理,那么词法分析器就应该将它的各组成部分作为独立的词法符号输送给语法分析器。
- 语法分析器无须区分特定的词法结构或者无须关心某个词法结构的内容时,实际上是我们编写的程序不关心它们。
例如
192.168.209.85 "GET /download/foo.html HTTP/1.0" 200
- 如果只想要统计总行数,可以忽略除换行符之外的一切字符
file : NL+ ; // 匹配换行符序列的语法分析器
STUFF : ~'\n'+ -> skip; // 除'\n'之外的字符全部丢弃
NL : '\n' ; // 将设定的换行符返回给语法分析器或者其他的调用者
- 从日志文件中提取IP地址的列表
file : row+; // 匹配日志文件中的全部行的文法规则
row : IP STRING INT NL ; // 匹配日志文件中的一行记录IP : INT '.' INT '.' INT '.' INT ; // 192.168.209.85
INT : [0-9]+ ; // 匹配IP地址中的一个字节或者HTTP的状态码
STRING : '"' .*? '"' ; // 匹配HTTP请求的首行
NL : '\n' ; // 匹配一行记录的终止符
WS : ' '-> skip ; // 忽略空格