antlr指南学习<一>

antlr指南学习<一>,第1张

antlr指南学习<一> Antlr深入学习

此文档进行深入的学习Antlr的学习,主要通过VScode进行编码,以及结合Java语言进行实现一些相关功能,能更好的理解Antlr的原理和使用。

一、自己编写TestRig测试语法 1.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接口,在其中填充自己的逻辑实现代码,从而构建出我们自己的语言类应用程序。

ANTLR为每个语法文件生成一个PaserTreeListener的子类,在该类中,语法中的每一条规则都有对应的enter方法和exit方法,例如,当遍历器访问到init规则时,就会调用enterInit()方法,然后将对应的语法分析树节点—InitContext的实例—当作参数传递给它。而又在遍历器访问了Init节点的全部子节点后,它会调用exitInit()。如上述代码。

3.主程序入口:

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 *.java
3.主程序入口:

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 *.java
2.重写访问接口:

首先,通过以下命令使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 CalcularbaseVisitor {

    
    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());
    }

}
3.主程序入口:

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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5719867.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存