antlr入门

语法分析器与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插件进行调试


image.png

右键文件,选择generate ANTLR Recognier生成文件,将生成的文件拷贝到自己的包下


image.png

创建文件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图


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容