一、自己编写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条)