Antlr4介绍和Helloworld

摘要:本文将首先介绍Antlr4,以及如何通过Antlr4 grammer生成对应的hello的AST
1. Antlr4简单介绍

Antlr4(Another Tool for Language Recognition)是一款基于Java开发的开源的语法分析器生成工具,能够根据语法规则文件生成对应的语法分析器,广泛应用于DSL构建,语言词法语法解析等领域。现在在非常多的流行的框架中都用使用,例如,在构建特定语言的AST方面,CheckStyle工具,就是基于Antlr来解析Java的语法结构的(当前Java Parser是基于JavaCC来解析Java文件的,据说有规划在下个版本改用Antlr来解析),还有就是广泛应用在DSL构建上,著名的Eclipse Xtext就有使用Antlr。

Antlr可以生成不同target的AST(https://www.antlr.org/download.html),包括Java、C++、JS、Python、C#等,可以满足不同语言的开发需求。当前Antlr最新稳定版本为4.9,Antlr4官方github仓库中,已经有数十种语言的grammer(https://github.com/antlr/grammars-v4,不过虽然这么多语言的规则文法定义都在一个仓库中,但是每种语言的grammer的license是不一样的,如果要使用,需要参考每种语言自己的语法结构的license)。

2,ANTLR v4 安装和hello world运行环境避坑

在ANTLR上有个downloads界面,但是点击老是报错,
Antlr4介绍和Helloworld_第1张图片

实际上,downloads的界面不再此处,
antlr资源下载
Antlr4介绍和Helloworld_第2张图片
点击下载 antlr-4.9.3-complete.jar

现在来测试下ANTLR工具是否工作正常:

java -jar antlr-4.5.1-complete.jar  # 启动org.antlr.v4.Tool

如果正常的话会看到以下帮助信息:

ANTLR Parser Generator  Version 4.5.1
 -o ___              specify output directory where all output is generated
 -lib ___            specify location of grammars, tokens files
 ...

每次运行ANTLR工具都要输入这么长的命令是否有些痛苦?写个脚本来解放我们的手指吧!

#!/bin/sh
java -cp .:./antlr-4.5.1-complete.jar:$CLASSPATH org.antlr.v4.Tool $*

把它保存为antlr.sh,以后就可以使用下列命令来运行ANTLR工具了:

antlr
3,idea插件安装

file -> settings -> plugins 搜索 ANTLR 点击安装即可

Antlr4介绍和Helloworld_第3张图片
修改下文件生成路径
Antlr4介绍和Helloworld_第4张图片

创建springboot或者maven项目添加jar

       
            org.antlr
            antlr4
            4.9.3
        
创建Hello.g4

grammar 的名字要和文件名保持一致

grammar Hello;

compilation_unit
    :
    prog+ EOF //EOF结束符号匹配
    ;

prog
    :
    level_num '9' HELLO ID '!'
    ;

level_num
    :
    NUM | '9'
    ;

//  '9' 是隐式词法,一般情况下,在词法解析树后,第一个排列(也就是当前位置) 匹配时首个匹配
// '9' 直接做本字符匹配

//词法解析 由这些组成
HELLO : H E L L O;

// 表示 由纯数字组成 最少有1个,可以有多个 至少出现一次
NUM : [0-9]+ ;

//表示 由数字和字母组成 最少有1个,可以有多个 至少出现一次
ID : [a-zA-Z0-9]+ ;

//如果是以这些符号开头的,这一行不匹配 放入 HIDDEN 隐藏通道
// 匹配双斜线后的任意字符,可以有可以没有
// .*? 非判断模式 可有可无
// '\r'? 可有可无  (适配linux)(liunx下的换行符为\n,win是\r\n)
LINE_COMMIT : '//' .*? '\r'? '\n' -> channel(HIDDEN);

//无效字符 跳过不匹配 放入隐藏通道
WS : [ \t\r\n]+ -> skip;

fragment H : [Hh];
fragment E : [Ee];
fragment L : [Ll];
fragment O : [Oo];

点击生成
Antlr4介绍和Helloworld_第5张图片

最终项目结构

Antlr4介绍和Helloworld_第6张图片
继承监听器,并实现替换输出

package com.yz.antlr.hello.impstand;

import com.yz.antlr.hello.g4.HelloBaseListener;
import com.yz.antlr.hello.g4.HelloParser;
import org.antlr.v4.runtime.TokenStreamRewriter;
import org.antlr.v4.runtime.tree.TerminalNode;

public class JCLAna extends HelloBaseListener {

    private TokenStreamRewriter tokenStreamRewriter;

    public JCLAna(TokenStreamRewriter tokenStreamRewriter) {
        super();
        this.tokenStreamRewriter = tokenStreamRewriter;
    }

    @Override
    public void enterProg(HelloParser.ProgContext ctx) {
//        super.enterProg(ctx);
        System.out.println(">>>" + ctx.getText());

        HelloParser.Level_numContext level_numContext = ctx.level_num();
        TerminalNode num = level_numContext.NUM();
    }

    @Override
    public void exitLevel_num(HelloParser.Level_numContext ctx) {
//        super.exitLevel_num(ctx);

        String text = ctx.getText();

        Integer level_num = Integer.valueOf(text);
        level_num += 1;

        // 修改替换 level_num,
        tokenStreamRewriter.replace(ctx.getStart(), ctx.getStop(), String.valueOf(level_num));


    }
}

`

获取树结构并替换值

package com.yz.antlr.hello.main;

import com.yz.antlr.hello.g4.HelloLexer;
import com.yz.antlr.hello.g4.HelloParser;
import com.yz.antlr.hello.impstand.JCLAna;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.TokenStreamRewriter;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * @author yz
 * @version 1.0
 * TODO
 * @date 2022/3/24 19:18
 */
public class HelloAntlrMain {
    private static final Logger log = LoggerFactory.getLogger(HelloAntlrMain.class);
    public void execMath(String str) {
        CharStream input = CharStreams.fromString(str);

        // 将内容传递到词法解析器
        HelloLexer lexer = new HelloLexer(input);
        //getAllTokens() 行为会取出字符,导致后续无法解析
//        lexer.getAllTokens().forEach(token -> System.out.println(token.toString()));

        // 传递到流
        CommonTokenStream tokenStream = new CommonTokenStream(lexer);

        //获取到g4捕获到所有内容
        TokenStreamRewriter tokenStreamRewriter = new TokenStreamRewriter(tokenStream);

        //将流传递到语法解析器
        HelloParser parser = new HelloParser(tokenStream);

        //两个方法都能生成树,但是prog会报结尾错误,具体原因不详
//        ParseTree tree = parser.prog();
        ParseTree tree = parser.compilation_unit();

        System.out.println(tree.toStringTree());

        //创建树遍历器
        ParseTreeWalker parseTreeWalker = new ParseTreeWalker();

        //添加自定义解析器
        JCLAna jclAna = new JCLAna(tokenStreamRewriter);

        // 添加树和自定义解析器到
        parseTreeWalker.walk(jclAna, tree);

        // 输出打印最终的内容
        System.out.println(tokenStreamRewriter.getText());
    }

    public static void main(String[] args) throws IOException {
        HelloAntlrMain antlrMain = new HelloAntlrMain();
        String str = "01 9 Hello world!";
        antlrMain.execMath(str);
    }
}

最终实现数据读取和替换输出

([] ([6] ([13 6] 01) 9 Hello world !) )
>>>019Helloworld!
29Helloworld!

·
·
·

参考资料

Antlr4 —入门介绍

Antlr4 入门

从定义到AST及其遍历方式

Antlr4介绍和Helloworld_第7张图片

·
·

我是离离原上草,要是有用就请一键三连吧。

你可能感兴趣的:(java,antlr4,antlr4)