Antlr4简明使用教程(笔记)

Antlr4简明使用教程(笔记)

参考连接

Antlr4简明使用教程-云社区-华为云 (huaweicloud.com)

入门 · ANTLR 4简明教程 (gitbooks.io)

下载工具包

antlr-4.7.1-complete.jar

1.Hello World入门(手动编译帮助理解原理)

1.编辑Hello.g4文件

编辑Hello.g4文件, 同时更具idea提示安装下插件(重启激活)

image-20230329000902577

grammar Hello; // 定义文法的名字, 必须和文件名称一样

s: 'Hello' ID; // 匹配关键字Hello和关键字ID
ID: [a-zA-Z]+; // 定义关键字ID是字母
WS: [ \t\r\n\u000c]+ -> skip; // 跳过空格, 制表符, 回车符, 换行符, 换页符

2.生成识别器

java -cp antlr-4.7.1-complete.jar org.antlr.v4.Tool Hello.g4

会生成后准为tokens,interp和java的8个文件

Antlr4简明使用教程(笔记)_第1张图片

3.编译由antlr生成的java代码

javac -cp .;antlr-4.7.1-complete.jar Hello*.java

Antlr4简明使用教程(笔记)_第2张图片

4.查看识别期间创建的记号

java -cp .;antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Hello s -tokens

Hello World # 输入并回车
EOF         # Linux系统输入Ctrl+d, windows系统输入ctrl+z, 并回车

实例:

hello-world>java -cp .;antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Hello s -tokens  
Hello World
^Z
[@0,0:4='Hello',<'Hello'>,1:0]
[@1,6:10='World',,1:6]
[@2,13:12='',,2:0]

以LISP风格的文本信息查看记号

java -cp .;antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Hello s -tree

示例:

hello-world>java -cp .;antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Hello s -tree  
Hello World
^Z
(s Hello World)

最直观的就是以可视化的方式查看语法分析树

java -cp .;antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Hello s -gui

实例:

hello-world>java -cp .;antlr-4.7.1-complete.jar org.antlr.v4.gui.TestRig Hello s -gui
Hello World
^Z

Antlr4简明使用教程(笔记)_第3张图片

以下是TestRig可用的所有参数:

  • -tokens 打印出记号流。
  • -tree 以LISP风格的文本形式打印出语法分析树。
  • -gui 在对话框中可视化地显示语法分析树。
  • -ps file.ps 在PostScript中生成一个可视化的语法分析树表示,并把它存储在file.ps文件中。
  • -encoding encodingname 指定输入文件的编码。
  • -trace 在进入/退出规则前打印规则名字和当前的记号。
  • -diagnostics 分析时打开诊断消息。此生成消息仅用于异常情况,如二义性输入短语。
  • -SLL 使用更快但稍弱的分析策略。

2.简易的计算器-入门

1.Calculator.g4文件定义

src\main\resources\calculator目录下创建

grammar Calculator;

@header {
    package len.hgy.antlr4.calculator;
}

// stmt是语法树的根
stmt: expr;

expr: '(' expr ')' # Bracket // 表达式可以被 括号包裹
    | expr MUL expr # Mul // 表达式可以是表达式和表达式运算
    | expr DIV expr # Div
    | expr ADD expr # Add
    | expr MIN expr # Min
    | INT # Int
    ;

INT: DIGIT+; //  定义一个数字类型, 提高可读性和简化定义
DIGIT: [0-9];

MUL: '*';
DIV: '/';
ADD: '+';
MIN: '-';

WS: [ \t\r\n\u000c]+ -> skip; // 跳过的字符其中 \u000c是换页符

SHEBANG: '#' '!' ~('\n'|'\r')* -> channel(HIDDEN); // 不需要处理的放入隐藏通道

2.添加依赖和插件

依赖

<dependency>
    <groupId>org.antlr</groupId>
    <artifactId>antlr4-runtime</artifactId>
    <version>4.9.3</version>
</dependency>

插件

<plugin>
    <groupId>org.antlr</groupId>
    <artifactId>antlr4-maven-plugin</artifactId>
    <version>4.9.3</version>
    <executions>
        <execution>
            <id>antlr</id>
            <goals>
                <goal>antlr4</goal>
            </goals>
            <phase>generate-resources</phase>
        </execution>
    </executions>
    <configuration>
        <sourceDirectory>${basedir}/src/main/resources/calculator</sourceDirectory>
        <!-- 注意这里和phase的那个不太一样, 这里有一个d结尾-->
        <outputDirectory>${project.build.directory}/generated-resources/calculator/len/hgy/antlr4/calculator</outputDirectory>
        <listener>true</listener>
        <visitor>true</visitor>
        <treatWarningsAsErrors>true</treatWarningsAsErrors>
    </configuration>
</plugin>

3.编译后标记生成源代码目录

Antlr4简明使用教程(笔记)_第4张图片

4.重写visit方法

// 重写观察方法
public class MyCalculatorBaseVisitor extends CalculatorBaseVisitor {
    @Override
    public Object visitDiv(CalculatorParser.DivContext ctx) {
        List<Double> lrValues = getLrValues(ctx.expr(0), ctx.expr(1));
        return lrValues.get(0) / lrValues.get(1);
    }

    @Override
    public Object visitAdd(CalculatorParser.AddContext ctx) {
        List<Double> lrValues = getLrValues(ctx.expr(0), ctx.expr(1));
        return lrValues.get(0) + lrValues.get(1);
    }

    @Override
    public Object visitBracket(CalculatorParser.BracketContext ctx) {
        // 去掉括号, 取中间的一个孩子
        return this.visit(ctx.getChild(1));
    }

    @Override
    public Object visitMin(CalculatorParser.MinContext ctx) {
        List<Double> lrValues = getLrValues(ctx.expr(0), ctx.expr(1));
        return lrValues.get(0) - lrValues.get(1);
    }

    @Override
    public Object visitMul(CalculatorParser.MulContext ctx) {
        List<Double> lrValues = getLrValues(ctx.expr(0), ctx.expr(1));
        return lrValues.get(0) * lrValues.get(1);
    }

    @Override
    public Object visitInt(CalculatorParser.IntContext ctx) {
        return Double.parseDouble(ctx.getText());
    }

    public List<Double> getLrValues(CalculatorParser.ExprContext left, CalculatorParser.ExprContext right) {
        List<Double> lrValues = new ArrayList<>();
        double leftValue = getValue(left);
        double rightValue = getValue(right);

        lrValues.add(leftValue);
        lrValues.add(rightValue);
        return lrValues;
    }

    private double getValue(CalculatorParser.ExprContext context) {
        if (context.getChildCount() > 1) {
            return Double.parseDouble(this.visit(context).toString());
        } else {
            return Double.parseDouble(context.getText());
        }
    }
}

5.测试类

class MyCalculatorBaseVisitorTest {
    @Test
    public void testVisit() {
        CharStream input = CharStreams.fromString("12 * (2 + 12) / 7");
        CalculatorLexer lexer = new CalculatorLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        CalculatorParser parser = new CalculatorParser(tokens);
        CalculatorParser.ExprContext tree = parser.expr();
        MyCalculatorBaseVisitor myCalculatorBaseVisitor = new MyCalculatorBaseVisitor();
        System.out.println(myCalculatorBaseVisitor.visit(tree));
    }
}

用例输出

24.0

你可能感兴趣的:(java,intellij-idea,eclipse)