
一、自己编写TestRig测试语法 1.Antlr文件编写:此文档进行深入的学习Antlr的学习,主要通过VScode进行编码,以及结合Java语言进行实现一些相关功能,能更好的理解Antlr的原理和使用。
grammar ArrayInit ;
init: '{' value ( ',' value)* '}' ;
value: init
| INT
;
INT: [0-9]+ ;
WS: [ trn] ->skip ;
2.辅助Java程序:
编写完antlr文件后,进行生成java代码
antlr4 ArrayInit.g4 javac *.java
import javax.sound.sampled.SourceDataLine;
//其中ArrayInitbaseListener为antlr编译java文件时自动生成
public class ShortToUnicodeString extends ArrayInitbaseListener {
// 将 { 翻译为 "
public void enterInit(ArrayInitParser.InitContext ctx) {
System.out.println('"');
}
// 将 } 翻译为 "
public void exitInit(ArrayInitParser.InitContext cxt) {
System.out.println('"');
}
//将整型按十六进制字符码输出,并且前面加上u
@Override
public void enterValue(ArrayInitParser.ValueContext ctx) {
int value = Integer.valueOf(ctx.INT().getText());
System.out.printf("\u%04x",value);
//u表示Unicode
//04x表示以十六进制长度为4位来表示,并且前面不足的用0表示
}
}
知识补充:此处用到了语法分析树监听器
语法分析树监听器:为了将遍历树时触发的事件转化为监听器的调用,ANTLR运行库提供了PaserTree-Walker类。我们可以自行实现ParseTreeListener接口,在其中填充自己的逻辑实现代码,从而构建出我们自己的语言类应用程序。
3.主程序入口:ANTLR为每个语法文件生成一个PaserTreeListener的子类,在该类中,语法中的每一条规则都有对应的enter方法和exit方法,例如,当遍历器访问到init规则时,就会调用enterInit()方法,然后将对应的语法分析树节点—InitContext的实例—当作参数传递给它。而又在遍历器访问了Init节点的全部子节点后,它会调用exitInit()。如上述代码。
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class Test{
public static void main(String[] args) throws Exception{
//新建一个CharStream,从标准输入读取数据
ANTLRInputStream input = new ANTLRInputStream(System.in);
//新建一个词法分析器,处理输入的CharStream
ArrayInitLexer lexer = new ArrayInitLexer(input);
//新建一个词法符号缓冲区,用于存储词法分析器将产生的词法符号
CommonTokenStream tokens = new CommonTokenStream(lexer);
//新建一个语法分析器,处理词法符号缓冲区的内容
ArrayInitParser parser = new ArrayInitParser(tokens);
//针对init规则,开始语法分析
ParseTree tree = parser.init();
//System.out.println(tree.toStringTree(parser));
//新建一个通用的、能够触发回调函数的语法分析树遍历器
ParseTreeWalker walker = new ParseTreeWalker();
//遍历语法分析过程中生成的语法分析树,触发回调
walker.walk(new ShortToUnicodeString(), tree);
System.out.println(); //翻译完成后打印换行
}
}
5.运行结果:
编译运行Java文件
javac *.java java Test二、匹配算术表达式语言 1.Antlr文件编写:
grammar Expr ;
import CommonLexerRules ; //
//起始规则 ,语法分析的起点
prog: stat+ ;
stat: expr newline
| ID '=' expr newline
| newline
;
expr: expr ( '*'|'/') expr
| expr ( '+'|'-') expr
| INT
| ID
| '(' expr ')'
;
lexer grammar CommonLexerRules ; ID: [a-zA-Z]+ ; //匹配标识符 newline: 'r'? 'n' ; //告诉语法器新的一行开始,即语句终止标志 INT: [0-9]+ ; //匹配数字 WS: [ t] ->skip ;2.输入文本编写
编写一个文本输入,文件名为 t.expr
193 a = 5 b = 6 a + b * 2 (1 + 2) * 3
# 执行命令行 生成并编译java代码 antlr4 Expr.g4 javac *.java3.主程序入口:
import java.io.FileInputStream;
import java.io.InputStream;
import javax.swing.InputMap;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class TestRide {
public static void main(String[] args) throws Exception{
//为词法分析器新建一个处理字符的输入流
String inputFile = null;
if( args.length>0)
{
inputFile = args[0];
}
InputStream is = System.in;
if(inputFile != null )
{
is = new FileInputStream(inputFile);
}
//新建词法分析器和语法分析器对象,以及一个架设在两者之间的词法符号流管道
ANTLRInputStream input = new ANTLRInputStream(is);
ExprLexer lexer = new ExprLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExprParser parser = new ExprParser(tokens);
//启动语法分析器,开始解析文本
ParseTree tree = parser.prog();
//用文本形式将该规则方法prog()返回的语法分析树打印出来
System.out.println(tree.toStringTree(parser));
}
}
4.运行结果:
# 编译自己写的测试代码 javac TestRide.java # 运行 并指定文件 t.expr java TestRide t.expr
整理一下: 三、利用访问器构建计算器 1.Antlr文件编写:
grammar Calcular ;
import CommonLexerRules ;
//起始规则 ,语法分析的起点
prog: stat+ ;
stat: expr newline # printExpr
| ID '=' expr newline # assign
| newline # blank
;
expr: expr op=( '*'|'/') expr # MulDiv
| expr op=( '+'|'-') expr # AddSub
| INT # int
| ID # id
| '(' expr ')' # parens
;
lexer grammar CommonLexerRules ; MUL: '*' ; //为语法中使用的'*'命名 以下同理 DIV: '/' ; ADD: '+' ; SUB: '-' ; ID: [a-zA-Z]+ ; //匹配标识符 newline: 'r'? 'n' ; //告诉语法器新的一行开始,即语句终止标志 INT: [0-9]+ ; //匹配数字 WS: [ t] ->skip ;
# 生成java代码并编译 antlr4 Calcular.g4 javac *.java2.重写访问接口:
首先,通过以下命令使ANTLR自动生成一个访问器接口,并为其中每个带标签的备选分支生成了一个方法。
#生成访问者接口 Calcular.g4为自己写的Antlr文件 antlr4 -no-listener -visitor Calcular.g4
例如visitAssign 表示antlr文件中的分支 ID ‘=’ expr newline # assign
该接口使用java的泛型定义,参数化的类型是visit方法的返回值的类型,此处简单起见,使用整型。因此我们重写的访问类应该继承 CalcularbaseVisitor类,并覆盖访问器中表达式和赋值语句规则对应的方法。
import java.util.HashMap; import java.util.Map; public class evalVisitor extends CalcularbaseVisitor3.主程序入口:{ Map memory = new HashMap (); @Override public Integer visitPrintExpr(CalcularParser.PrintExprContext ctx) { Integer value = visit(ctx.expr()); //计算expr子节点的值 System.out.println(value); //打印结果 return 0; } @Override public Integer visitAssign(CalcularParser.AssignContext ctx) { String id = ctx.ID().getText(); //id在 '='的左侧 int value = visit(ctx.expr()); //计算右侧表达式的值 memory.put(id, value); //将这个映射关系存储在计算器的内存中 return value; } @Override public Integer visitParens(CalcularParser.ParensContext ctx) { return visit(ctx.expr()); //返回子表达式的值 } @Override public Integer visitMulDiv(CalcularParser.MulDivContext ctx) { int left = visit(ctx.expr(0)); //计算左侧子表达式的值 int right = visit(ctx.expr(1)); //计算右侧子表达式的值 // 判断是乘法还是除法 if(ctx.op.getType() == CalcularParser.MUL ){ return left * right ; } return left / right; } @Override public Integer visitAddSub(CalcularParser.AddSubContext ctx) { int left = visit(ctx.expr(0)); //计算左侧子表达式的值 int right = visit(ctx.expr(1)); //计算右侧子表达式的值 // 判断是加法还是减法 if(ctx.op.getType() == CalcularParser.ADD ){ return left + right ; } return left - right; } @Override public Integer visitId(CalcularParser.IdContext ctx) { String id = ctx.ID().getText(); //判断计算器内存中是否有对应id,有则返回对应的值,无则返回0 if( memory.containsKey(id) ){ return memory.get(id); } return 0; } @Override public Integer visitInt(CalcularParser.IntContext ctx) { return Integer.valueOf(ctx.INT().getText()); } }
import java.io.FileInputStream;
import java.io.InputStream;
import javax.swing.InputMap;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class TestRide {
public static void main(String[] args) throws Exception{
//为词法分析器新建一个处理字符的输入流
String inputFile = null;
if( args.length>0)
{
inputFile = args[0];
}
InputStream is = System.in;
if(inputFile != null )
{
is = new FileInputStream(inputFile);
}
//新建词法分析器和语法分析器对象,以及一个架设在两者之间的词法符号流管道
ANTLRInputStream input = new ANTLRInputStream(is);
CalcularLexer lexer = new CalcularLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CalcularParser parser = new CalcularParser(tokens);
//启动语法分析器,开始解析文本
ParseTree tree = parser.prog();
//新建一个自定义访问器
evalVisitor eval = new evalVisitor();
//调用visit()方法,开始遍历prog()方法返回的语法分析树
eval.visit(tree);
}
}
4.运行结果:
# 这里需要再次执行该行命令,否则不能输出 antlr4 -no-listener -visitor Calcular.g4 # 用utf-8编码编译所有java文件 javac -encoding UTF-8 *.java # 连接文件或标准输入并打印 查看输入的文件(这一步可有可无) cat t.expr # 运行Java程序,并以指定文件输入 java TestRide t.expr
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)