语法分析器与dot图
引入库
// 语法分析器
implementation 'org.antlr:antlr4:4.10.1'
// dot图
implementation 'net.sourceforge.plantuml:plantuml:1.2022.13'
新建Expr.g4文件,编写一下内容
grammar Expr;
prog: expr EOF ;
expr: expr ('*'|'/') expr #MultiOrDiv
| expr ('+'|'-') expr #AddOrSub
| INT #Lieteral
| '(' expr ')' #Single
;
NEWLINE : [\r\n]+ -> skip;
INT : [0-9]+ ;
idra可安装antlr插件进行调试
右键文件,选择generate ANTLR Recognier生成文件,将生成的文件拷贝到自己的包下
创建文件EvalExprVisitor.java
public class EvalExprVisitor extends ExprBaseVisitor<Integer> {
/**
* 入口处调用
* @param ctx the parse tree
* @return
*/
@Override
public Integer visitProg(ExprParser.ProgContext ctx) {
ExprParser.ExprContext expr = ctx.expr();
return visit(expr);
}
/**
* expr: expr ('*'|'/') expr
* | expr ('+'|'-') expr
* | INT
* | '(' expr ')'
* @param ctx the parse tree
* @return
*/
/**
* expr ('*'|'/') expr
*/
@Override
public Integer visitMultiOrDiv(MultiOrDivContext ctx) {
Integer op1 = visit(ctx.expr(0));
Integer op2 = visit(ctx.expr(1));
String operator = ctx.getChild(1).getText();
if (Objects.equals(operator, "*")){
return op1 * op2;
}
if (Objects.equals(operator, "/")){
return op1 / op2;
}
return 0;
}
/**
* expr ('+'|'-') expr
*/
@Override
public Integer visitAddOrSub(AddOrSubContext ctx) {
Integer op1 = visit(ctx.expr(0));
Integer op2 = visit(ctx.expr(1));
String operator = ctx.getChild(1).getText();
if (Objects.equals(operator, "+")){
return op1 + op2;
}
if (Objects.equals(operator, "-")){
return op1 - op2;
}
return 0;
}
/**
*
* @param ctx the parse tree
* @return
*/
@Override
public Integer visitSingle(SingleContext ctx) {
return visit(ctx);
}
/**
* INT
* @return
*/
@Override
public Integer visitLieteral(LieteralContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
}
创建文件EvalExprListener.java
public class EvalExprListener extends ExprBaseListener {
private Graph graph = new Graph();
Stack<String> current = new Stack<>();
String prefix = "exp_";
@Override
public void enterEveryRule(ParserRuleContext ctx) {
if (ctx instanceof ExprParser.ProgContext){
return;
}
// 生成唯一的节点标识
String n = prefix + System.identityHashCode(ctx);
String node = n + String.format("[label=<%s>]",ctx.getText());
graph.nodes.add(node);
if (!current.empty()){
graph.edge( current.peek(), n );
}
current.push(n);
}
@Override
public void exitEveryRule(ParserRuleContext ctx) {
if (ctx instanceof ExprParser.ProgContext){
return;
}
current.pop();
}
public String toDot(){
return graph.toDot();
}
static class Graph {
Set<String> nodes = new OrderedHashSet<>();
MultiMap<String, String> edges = new MultiMap<>();
public void edge(String source, String target) {
if (source == null || target == null){
return;
}
edges.map(source, target);
}
public String toDot() {
StringBuffer sb = new StringBuffer();
sb.append("digraph G {\n");
sb.append("node[shape=plaintext,style=filled];graph[splines=ortho];\n");
// 声明节点
for (String node : nodes) {
sb.append(node).append(";");
sb.append("\n");
}
sb.append("\n");
for (String source : edges.keySet()) {
List<String> targets = edges.get(source);
for (String target : targets) {
sb.append(" ")
.append(source)
.append(" -> ")
.append(target).append("\n");
}
}
sb.append("}\n");
return sb.toString();
}
}
}
然后编写代码进行测试
@Test
public void test3() {
List<String> testSet = Arrays.asList(
"1+2",
"1+2+3*4",
"3/3",
"10/2",
"5*5+10+5*5"
);
List<Integer> res = Arrays.asList(
3, 15, 1, 5, 60
);
for (int i = 0; i < testSet.size(); i++) {
// 构建字符流
String str = testSet.get(i);
CodePointCharStream charStream = CharStreams.fromString(str);
// 从字符流分析词法, 解析为token
ExprLexer lexer = new ExprLexer(charStream);
// 从token进行分析
ExprParser parser = new ExprParser(new CommonTokenStream(lexer));
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
e.printStackTrace();
super.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e);
}
@Override
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) {
super.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs);
}
@Override
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {
super.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs);
}
@Override
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) {
super.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs);
}
});
// 使用监听器,遍历语法树,根据语法定义,prog为语法树的根节点
ExprParser.ProgContext prog = parser.prog();
// 使用visitor,生成自定义的对象
Integer integer = prog.accept(new EvalExprVisitor());
System.out.println(integer);
Assert.isTrue(Objects.equals(integer, res.get(i)), "");
ParseTreeWalker walker = new ParseTreeWalker();
// 生成dot图
EvalExprListener listener = new EvalExprListener();
walker.walk(listener, prog);
String dot = listener.toDot();
System.out.println(dot);
try (OutputStream os = Files.newOutputStream(Paths.get("Z_" + IdUtil.fastSimpleUUID().toUpperCase() + ".svg"))) {
generateDotImage(dot, os);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
生成dot图
public static void generateDotImage(String digraphString, OutputStream os) {
FileFormat gif = FileFormat.valueOf("SVG");
// 注意 digraph g {的后面不要有空格
String str = "@startuml\n"
+ digraphString
+ "@enduml";
System.out.println(str);
new SourceStringReader(str)
.outputImage(os, new FileFormatOption(gif));
os.flush();
}
生成的dot图