antlr4 部署到idea

1、Antlr4概念

1、定义:
Antlr是指可以根据输入自动生成语法树并可视化的显示出来的开源语法分析器。它允许我们定义识别字符流的词法规则和用于解释Token流的语法分析规则。然后,ANTLR将根据用户提供的语法文件自动生成相应的词法/语法分析器。
2、语法:
编写一个语法和编写一个软件差不多,不同的是我们需要处理的是规则而不是函数或者处理过程。ANTLR会给语法中的每一个规则生成一个函数。
3、核心语法:
语法 描述
x 匹配一个符号,规则或子规则x
x y … z 匹配一个规则序列
(…|…|…) 带有多个选项的子规则
x? 匹配零次或一次x
x* 匹配零次或多次x
x+ 匹配一次或多次x
r:…; 定义规则r
r:…|…|…; 定义一个带有多个选项的规则r
这里写图片描述
4、词法:
词法规则和语法规则拥有着类似的结构,ANTLR中允许将这两个规则写在同一个语法文件中。但是,毕竟词法分析和语法分析在语言识别中是两个完全不同的阶段,所以我们必须告诉ANTLR哪条规则应该属于哪个阶段。我们通过规则名称的第一个字符来识别这两种规则,第一个字符是大写字母的是词法过则,第一个字符是小写的是语法规则。例如,ID是一个词法规则名称,而expr则是一个语法规则名称
这里写图片描述
这里写图片描述
这里写图片描述
5、解析过程
为了简单起见,我们将解析分为两个阶段,对应我们的大脑读取文字时的过程。当我们读到一个句子时,在第一阶段,大脑会下意识地将字符组成单词,然后像查词典一样识别出它们的意思。在第二阶段,大脑会根据已识别的单词去识别句子的结构。第一阶段的过程叫词法分析(lexical analysis),对应的分析程序叫做lexer,负责将符号(token)分组成符号类(token class or token type)。而第二阶段就是真正的parser,默认ANTLR会构建出一棵分析树(parse tree)或叫语法树(syntax tree)。如下图,就是简单的赋值表达式的解析过程:
这里写图片描述
语法树的叶子是输入token,而上级结点时包含其孩子结点的词组名(phase),线性的句子其实是语法树的序列化。最终生成语法树的好处是:

1) 树形结构易于遍历和处理,并且易被程序员理解,方便了应用代码做进一步处理。

2) 多种解释或翻译的应用代码都可以重用一个解析器。但ANTLR也支持像传统解析器生成器那样,将应用处理代码嵌入到语法中。

3) 对于因为计算依赖而需要多趟处理的翻译器来说,语法树非常有用!我们不用多次调用解析器去解析,只需高效地遍历语法树多次。
6、解析原则
ANTLR生成的解析器叫做递归下降解析器(recursive-descent parser),属于自顶向下解析器(top-down parser)的一种。顾名思义,递归下降指的就是解析过程是从语法树的根开始向叶子(token)递归,比较酷的是代码的调用图能与树结点对应上。

在内部,ANTLR的数据结构会尽可能地共享数据来节约内存,这种考量在Nginx的String中也能看到。如下图所示,解析树的叶子节点指向Token流中的Token,而Token中的起止字符索引指向字符流,而非拷贝子字符串。而像空格这种不与任何Token相关的字符会直接被Lexer丢弃掉。

ANTLR为每个Rule都会生成一个Context对象,它会记录识别时的所有信息。ANTLR提供了Listener和Visitor两种遍历机制。Listener是全自动化的,ANTLR会主导深度优先遍历过程,我们只需处理各种事件就可以了。而Visitor则提供了可控的遍历方式,我们可以自行决定是否显示地调用子结点的visit方法。

2、altlr4新增特性

1) 学习曲线低。antlr v4相对于v3,v4更注重于用更接近于自然语言的方式去解析语言。比如运算符优先级,排在最前面的规则优先级最高;

2) 层次更清晰、更易维护。引入访问者、监听器模式,使解析与应用代码分离;新増import功能,lexer、parser可以成为公共组件,増加可复用性;

3) 新算法。改进LL()算法,使用新的Adative LL()算法,在运行时动态分析语法,而LL(*)需要静态分析语法,考虑各种语法的可能性。

4) 新用法。引入了一些新用法,如rewrite the input stream、sending token in different channels、island grammars、associativity,可以更方便、灵活地在应用中处理解析对象。

5) 性能。相对于v3,解析代码跟应用代码都是自动生成的,而v4分离了解析与应用代码的实现,应用代码的实现及性能则可以由开发人员自主地控制,但新算法据官方指引说会消耗一定的速度上的性能,因此提供了SLL()、LL()的开关,可通过api控制。

3、Antlr4 计算器实例

1、idea安装antlr4
随便找个博客进行安装即可,安装antlr4
2、配置和代码
1、pom

 <dependencies>

        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4</artifactId>
            <version>4.7</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-maven-plugin</artifactId>
                <version>4.3</version>
                <executions>
                    <execution>
                        <id>antlr</id>
                        <goals>
                            <goal>antlr4</goal>
                        </goals>
                            <phase>none</phase>
                    </execution>
                </executions>
                <configuration>
                    <outputDirectory>src/test/java</outputDirectory>
                    <listener>false</listener>
                    <treatWarningsAsErrors>true</treatWarningsAsErrors>
                </configuration>
            </plugin>
        </plugins>
    </build>

2、g4文件

grammar Demo;

prog : stat+;

stat: expr NEWLINE          # printExpr
    | ID '=' expr NEWLINE   # assign
    | NEWLINE               # blank
    ;

expr: op=expr ('*'|'/') expr    # MulDiv
| expr op=('+'|'-') expr        # AddSub
| INT                           # int
| ID                            # id
| '(' expr ')'                  # parens
;

MUL : '*' ; // assigns token name to '*' used above in grammar
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip;

3、生成代码
注意生成visitor代码,半自动化代码。
需要写一个实现类,实现DemoBaseVisitor接口,实现类里面是自己在解析到对应代码所需要做的逻辑。

public class MyVisitor extends  DemoBaseVisitor {
    Map<String, Integer> memory = new HashMap<String, Integer>();

    public Integer visitPrintExpr(DemoParser.PrintExprContext ctx) {
        // TODO Auto-generatedmethod stub
        Integer value = (Integer) visit(ctx.expr()); // evaluate the exprchild
        System.out.println(value); // print theresult
        return 0; // return dummyvalue
    }

    @Override
    public Integer visitAssign(DemoParser.AssignContext ctx) {
        // TODO Auto-generatedmethod stub
        String id = ctx.ID().getText(); // id is left-hand side of '='
        int value = (int) visit(ctx.expr()); // compute valueof expression on right
        memory.put(id, value); // store it inour memory
        return value;
    }

    @Override
    public Integer visitBlank(DemoParser.BlankContext ctx) {
        // TODO Auto-generatedmethod stub
        return (Integer) super.visitBlank(ctx);
    }

    @Override
    public Integer visitParens(DemoParser.ParensContext ctx) {
        // TODO Auto-generatedmethod stub
        return (Integer) visit(ctx.expr()); // return childexpr's value
    }

    @Override
    public Integer visitMulDiv(DemoParser.MulDivContext ctx) {
        // TODO Auto-generatedmethod stub
        int left = (int) visit(ctx.expr(0)); // get value ofleft subexpression
        int right = (int) visit(ctx.expr(1)); // get value ofright subexpression
        if ( ctx.op.getType() == DemoParser.MUL ) return left * right;
        return left / right; // must be DIV
    }

    @Override
    public Integer visitAddSub(DemoParser.AddSubContext ctx) {
        // TODO Auto-generatedmethod stub
        int left = (int) visit(ctx.expr(0)); // get value ofleft subexpression
        int right = (int) visit(ctx.expr(1)); // get value ofright subexpression
        if ( ctx.op.getType() == DemoParser.ADD ) return left + right;
        return left - right; // must be SUB
    }

    @Override
    public Integer visitId(DemoParser.IdContext ctx) {
        // TODO Auto-generatedmethod stub
        String id = ctx.ID().getText();
        if ( memory.containsKey(id) ) return memory.get(id);
        return 0;
    }

    @Override
    public Integer visitInt(DemoParser.IntContext ctx) {
        // TODO Auto-generatedmethod stub
        return Integer.valueOf(ctx.INT().getText());
    }
}

此处需要注意的是,本人不知是不是环境问题,在visitMulDiv方法中ctx.op.getType()报错,显示没有getType方法,经过多番简称,发现MulDivContext这个方法中的ExprContext op属性确实没有getType方法,AddSubContext中的Token op属性有。把MulDivContext这个方法中的ExprContext op属性修改为Token op,对应地方对比AddSubContext的逻辑进行修改即可。
主类代码:

 InputStream is = new FileInputStream("E:/suning/ProjectCode/ssmp/Antlr/src/main/java/com/main/1.txt");

            ANTLRInputStream inputStream = new ANTLRInputStream(is);
            DemoLexer lexer = new DemoLexer(inputStream);

            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
            DemoParser parser = new DemoParser(tokenStream);
            ParseTree parseTree = parser.prog();
            MyVisitor visitor = new MyVisitor();
            Integer rtn = (Integer) visitor.visit(parseTree);
            System.out.println("#result#" + rtn.toString());

txt中

3+5
3*9

运行即可得出结果。

猜你喜欢

转载自blog.csdn.net/wzqllwy/article/details/78899617
今日推荐