Antlr4 IDEA 计算器入门小例

作为一名程序员,不知道您有没有也想过自己开发自己的语言呢?我以前就想过,但是只限于想想而已,因为开发一门语言所需掌握的知识量是巨大的,且语言也是要一直维护升级的。不过如果您只是想学习了解一下的话。那就接着往下看吧,我给您介绍一个简单开发一门编程语言的方式 —— Antlr

Antlr是什么?

Antlr是指可以根据输入自动生成语法树并可视化的显示出来的开源语法分析器。ANTLR—Another Tool for Language Recognition,它为包括Java,C++,C#在内的语言提供了一个通过语法描述来自动构造自定义语言的识别器(recognizer),编译器(parser)和解释器(translator)的框架

  1. 词法分析器(Lexer
    词法分析器又称为ScannerLexical analyser和Tokenizer。程序设计语言通常由关键字和严格定义的语法结构组成。编译的最终目的是将程序设计语言的高层指令翻译成物理机器或虚拟机可以执行的指令。词法分析器的工作是分析量化那些本来毫无意义的字符流,将他们翻译成离散的字符组(也就是一个一个的Token),包括关键字,标识符,符号(symbols)和操作符供语法分析器使用。
  2. 语法分析器(Parser
    编译器又称为Syntactical analyser。在分析字符流的时候,Lexer不关心所生成的单个Token的语法意义及其与上下文之间的关系,而这就是Parser的工作。语法分析器将收到的Tokens组织起来,并转换成为目标语言语法定义所允许的序列。
    无论是Lexer还是Parser都是一种识别器,Lexer是字符序列识别器而ParserToken序列识别器。他们在本质上是类似的东西,而只是在分工上有所不同而已。如下图所示:
    引自百度百科
  3. 树分析器 (tree parser)
    树分析器可以用于对语法分析生成的抽象语法树进行遍历,并能执行一些相关的操作

ANTLR它允许我们定义识别字符流的词法规则和用于解释Token流的语法分析规则。然后,ANTLR将根据用户提供的语法文件自动生成相应的词法/语法分析器。用户可以利用他们将输入的文本进行编译,并转换成其他形式(如AST—Abstract Syntax Tree,抽象的语法树)。

介绍一下与语法相关的概念(简单了解即可)

术语 含义
语言 一门语言是一个有效语句的集合。语句词组组成,词组子词组组成,子词组又由更小的子词组组成,依此类推。
语法 语法定义了语言的语义规则。语法中的每条规则定义了一种词组结构。
语义树或语法分析树 代表了语句的结构,其中的每个子树的根节点都使用一个抽象的名字给其包含的元素命名。即子树的根节点对应了语法规则的名字。树的叶子节点是语句中的符号或者词法符号。
词法符号 词法符号就是一门语言的基本词汇符号,它们可以代表像是“标识符”这样的一类符号,也可以代表一个单一的运算符,或者代表一个关键字。

Antlr使用

下面我就以IDEA为例,来创建这个Antlr项目吧(如果您使用的是eclipse,不用担心,步骤不会差太多)

  1. 首先创建一个Maven工程
创建一个Maven工程

填写GroupId和ArtifactId

以下是配置文件的信息pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>top.itreatment</groupId>
    <artifactId>antlr</artifactId>
    <version>1.0</version>

    /*我使用的是当前的最新版本,但是当您看到这篇文章后可能已有更新,可以下载更新的版本*/
    <dependencies>
        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4</artifactId>
            <version>4.7.2</version>
        </dependency>
    </dependencies>
</project>
  1. 导好包后,我们就要安装一个IDEA插件了
    安装插件
查看是否安装插件成功
创建文件

下面就是要填入AntlrTest“代码”

grammar AntlrTest;
prog:stat+;

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

expr:expr (MULTIPLY|DIVIDE) expr    # MulDiv
|expr (PLUS|MINUS) expr             # AddSub
|'('expr')'                         # Private
|value                              # IdInt
;
value:INT
|ID
;

MULTIPLY:'*';
DIVIDE:'/';
PLUS:'+';
MINUS:'-';
ID:[a-z]+;
INT:[1-9]+;
NEWLINE:'\r'?'\n';
WS:[ \t\r\n] -> skip;

打开后缀为g4的文件,点击鼠标右键,就可以看到下面的样子


点击Configure ANTLR,就可以看到下面的样子

这里我就先用默认的方式,执行命令Cenerate ANTLR Recognizer,生成目录及文件如下:
生成的目录和文件

如果您已经生成了这些文件,说明基础的现在已经全部准备就绪,那我们开始进行编写计算器吧,GO

  1. 首先创建一个执行检验我们生成的java文件的启动类


    带有全类名的

  2. 然后回到AntlrTest.g4文件,配置Antlr,重新生成文件

  3. 勾选generate parse tree visitor后,重新生成文件如下:

这里简单介绍这几个文件吧

文件名 作用
AntlrTestParser 语法分析器,在该类中我们定义的规则都将生成对应的方法,同时还包含其他的一些辅助代码
AntlrTestLexer ANTLR能够自动识别出我们的语法中的文法规则和词法规则。这个类包含的是词法分析器的类定义
  1. 我们将生成的文件中以java后缀的文件复制到,我们刚刚创建的Main类同包下

  2. 这里我就通过替换删除的方式,删除两个类的@Override

  3. 接下来我们创建一个EvalVisitor 类继承AntlrTestBaseVisitor,代码如下

import java.util.concurrent.ConcurrentHashMap;
public class EvalVisitor extends AntlrTestBaseVisitor<Integer> {
    ConcurrentHashMap<String, Integer> memory = new ConcurrentHashMap<String, Integer>();
    @Override
    public Integer visitPrint(AntlrTestParser.PrintContext ctx) {
        Integer value = visit(ctx.expr());
        System.out.println(value);
        return 0;
    }

    @Override
    public Integer visitAssign(AntlrTestParser.AssignContext ctx) {
        String id = ctx.ID().getText();
        Integer value = visit(ctx.expr());
        memory.put(id, value);
        return value;
    }

    @Override
    public Integer visitMulDiv(AntlrTestParser.MulDivContext ctx) {
        Integer left = visit(ctx.expr(0));
        Integer right = visit(ctx.expr(1));
        if (ctx.MULTIPLY() != null) {
            return left * right;
        } else {
            return left / right;
        }
    }

    @Override
    public Integer visitAddSub(AntlrTestParser.AddSubContext ctx) {
        Integer left = visit(ctx.expr(0));
        Integer right = visit(ctx.expr(1));
        if (ctx.PLUS() != null) {
            return left + right;
        } else {
            return left - right;
        }
    }

    @Override
    public Integer visitIdInt(AntlrTestParser.IdIntContext ctx) {
        String text = ctx.getText();
        if (text.matches("[a-z]+")) {
            String id = text;
            return memory.containsKey(id) ? memory.get(id) : 0;
        }
        return Integer.valueOf(text);
    }

    @Override
    public Integer visitPrivate(AntlrTestParser.PrivateContext ctx) {
        return visit(ctx.expr());
    }
}
  1. 在Main类中填入一下代码,我们的计算器就算是完成了。
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;

public class Main {

    public static void run(String expr) throws Exception {

        //对每一个输入的字符串,构造一个 CodePointCharStream
        CodePointCharStream cpcs = CharStreams.fromString(expr);

        //用 cpcs 构造词法分析器 lexer,词法分析的作用是产生记号
        AntlrTestLexer lexer = new AntlrTestLexer(cpcs);

        //用词法分析器 lexer 构造一个记号流 tokens
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        //再使用 tokens 构造语法分析器 parser,至此已经完成词法分析和语法分析的准备工作
        AntlrTestParser parser = new AntlrTestParser(tokens);

        //最终调用语法分析器的规则 prog,完成对表达式的验证
        AntlrTestParser.ProgContext pcontext = parser.prog();

        // 通过访问者模式,执行我们的程序       
        EvalVisitor evalVisitor = new EvalVisitor();
        evalVisitor.visit(pcontext);
    }

    public static void main(String[] args) throws Exception {
        run("a=5\nb=3\nc=a*b+3\nc*c\n");
    }
}
  1. 执行结果为:
    最后的结果
package top.coolsite;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;

import java.io.IOException;
import java.util.Scanner;

/**
 * StudyAntlr
 *
 * @author DELL
 * @Date 2019/10/11
 */
public class Main {

    public static void main(String[] args) throws IOException {

        Scanner scanner = new Scanner(System.in);
        StringBuilder sb = new StringBuilder();
        while (scanner.hasNext()) {
            String next = scanner.nextLine();
            if ("test".equals(next)){
                String s = sb.toString();
                sb.delete(0, s.length());
                test(s);
            }else{
                sb.append(next);
            }
        }
    }

    private static void test(String msg) {
        System.out.println("msg = " + msg);
        //对每一个输入的字符串,构造一个 CodePointCharStream
        CodePointCharStream cpcs = CharStreams.fromString(msg);
        //用 cpcs 构造词法分析器 lexer,词法分析的作用是产生记号
        jsonLexer lexer = new jsonLexer(cpcs);
        //用词法分析器 lexer 构造一个记号流 tokens
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        //再使用 tokens 构造语法分析器 parser,至此已经完成词法分析和语法分析的准备工作
        jsonParser parser = new jsonParser(tokens);
        //最终调用语法分析器的规则 prog,完成对表达式的验证
        jsonParser.FileContext fileContext = parser.file();

        ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
        parseTreeWalker.walk(new jsonBaseListener(), fileContext);
    }
}
grammar json;

file:row?(NEWLINE row)*NEWLINE?;

row: field(','field)*;

field:INT;

INT:[0-9]+;
NEWLINE:('\r'?'\n')+;
WS:[ \t\r\n] -> skip;

本人水平有限,如果文章有误,欢迎指正

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