学习ANTLR4—访问器构造计算器C++

一、ANTLR4安装(Windows)

二、文法文件生成语法分析器

  • 词法文件
    lexer grammar NClexer;      /*注意词法文件前为lexer grammar*/
    ID : [a-zA-Z]+;
    INT: [0-9]+;
    NEWLINE : '\r'?'\n' ;
    WS : [ \t]+ ->skip;
    MUL : '*';
    DIV : '/';
    ADD : '+';
    SUB : '-'; 
    
  • 语法文件
    grammar NC;
    import NClexer;                         /*导入词法文件*/
    /*开启Visitor模式 根据不同 # Label 会自动生成对应的方法*/
    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
        ;   
    
  • 生成语法分析器(NC.g4语法文件名称)
  1. 文件目录命令行cmd运行命令

    antlr4 -Dlanguage=Cpp -visitor NC.g4 
    

    使用以下命令,二者区别就在是否使用监听,不使用的选项相对应的默认类文件即不生成,可以自己尝试发现区别

    antlr4 -Dlanguage=Cpp -visitor -no-listener NC.g4
    

    同样的使用以下命令默认开启监听,生成监听类

    antlr4 -Dlanguage=Cpp NC.g4
    
  2. 运行后会生成如下文件:将其中.cpp源文件和.h头文件全部复制到vs中。

    1.PNG

    VS2019

三、访问器实现

  1. 分析器会自动生成默认的访问器实现类如下:其中每个函数名均是以标签名首字母大写加visit命名,每个方法的参数是每个标签所对应的上下文对象

    /*NCBaseVisitor.h*/
    #include "antlr4-runtime.h"
    #include "NCVisitor.h"
    /**
     * This class provides an empty implementation of NCVisitor, which can be
     * extended to create a visitor which only needs to handle a subset of the available methods.
     */
    class  NCBaseVisitor : public NCVisitor {
    public:
    virtual antlrcpp::Any visitProg(NCParser::ProgContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitPrintExpr(NCParser::PrintExprContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitAssign(NCParser::AssignContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitBlank(NCParser::BlankContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitParens(NCParser::ParensContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitMulDiv(NCParser::MulDivContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitAddSub(NCParser::AddSubContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitId(NCParser::IdContext *ctx) override {
        return visitChildren(ctx);
    }
    virtual antlrcpp::Any visitInt(NCParser::IntContext *ctx) override {
        return visitChildren(ctx);
    }
    };
    
  2. 自定义访问器类继承 NCBaseVisitor 类

    /*EvalVisitor.h*/
    #pragma once
    #include "NCBaseVisitor.h"
    #include <map>
    /*map传入antlrcp::Any类似boost::any可以使用任意类型*/
    typedef std::map<std::string, antlrcpp::Any> MAP_STRING_T;
    /*简易符号表*/
    class VarSymbol         
    {
    private:
        MAP_STRING_T symbol;
    public:
        inline void put(std::string s, antlrcpp::Any t) { symbol[s] = t; } /*存入符号及数值*/
        antlrcpp::Any containsKey(std::string s); /*查询符号对应数值*/
    };
    /*继承默认访问器类并对其方法进行重写*/
    class EvalVisitor : public NCBaseVisitor 
    {
    private:
        VarSymbol *var ;
    public:
        EvalVisitor()
        {
            var = new VarSymbol;
        }
        ~EvalVisitor()
        {
            delete var;
        }
        /* 对应语法文件中ID '=' expr NEWLINE */
        antlrcpp::Any visitAssign(NCParser::AssignContext* ctx) ; /*函数名是由 visit + 语法文件中#Label首字符大写组成*/
        /* 对应语法文件中expr NEWLINE */
        antlrcpp::Any visitPrintExpr(NCParser::PrintExprContext* ctx) ;
        /* 对应语法文件中INT */
        antlrcpp::Any visitInt(NCParser::IntContext* ctx);
        /* 对应语法文件中Id */
        antlrcpp::Any visitId(NCParser::IdContext* ctx);
        /* 对应语法文件中expr op=('-'|'+') expr */
        antlrcpp::Any visitAddSub(NCParser::AddSubContext* ctx);
        /* 对应语法文件中expr op=('*'|'/') expr */
        antlrcpp::Any visitMulDiv(NCParser::MulDivContext* ctx);
        /* 对应语法文件中'(' expr ')' */
        antlrcpp::Any visitParens(NCParser::ParensContext* ctx);
    };
    
  3. 类方法的实现

    /*EvalVisitor.cpp*/
    #include "EvalVisitor.h"
    
    antlrcpp::Any VarSymbol:: containsKey(std::string s)
    {
        MAP_STRING_T::iterator m_it = symbol.find(s);
        if (m_it != symbol.end())
        {
            return m_it->second;
        }
        else return NULL;
    }
    

    语法中 # assign 标签的访问方法

    antlrcpp::Any EvalVisitor::visitAssign(NCParser::AssignContext* ctx)
    {
        std::string s = ctx->ID()->getText();   //其中ctx表示标签所代表的语法树,ID是此语法树中的ID分支
        antlrcpp::Any value = visit(ctx->expr()); //因为expr不是一个终结符,调用visit()访问expr的值
        var->put(s, value); //存入符号表
        return value;
    }
    

    语法中 # printExpr 标签的访问方法,其中使用Any类,可以保存任意类型数据,不过经过尝试不知道为什么无法推导出string类。

    antlrcpp::Any EvalVisitor::visitPrintExpr(NCParser::PrintExprContext* ctx)
    {
        antlrcpp::Any value = visit(ctx->expr());  //因为expr不是一个终结符,调用visit()访问expr的值
        if (value.is<std::string>())        //对返回值的类型进行判断 antlrcpp::Any类的is()方法
        {
            std::cout << value.as<std::string>()<<std::endl;  //antlrcpp::Any类的as<>()方法返回给定类型值
        }
        else
        {
            std::cout << value.as<int>() << std::endl;
        }   
        return 0;
    }
    

    语法中 # int 标签的访问方法

    antlrcpp::Any EvalVisitor::visitInt(NCParser::IntContext* ctx)
    {
        std::string s = ctx->INT()->getText(); //终结符getText()返回值
        int value = stoi(s);
        return antlrcpp::Any(value);
    }
    

    语法中 # id 标签的访问方法

    antlrcpp::Any EvalVisitor::visitId(NCParser::IdContext* ctx)
    {
        std::string id = ctx->ID()->getText();
        antlrcpp::Any anyone = var->containsKey(id); //符号表查询id对应的值
        return anyone; //返回对应值 
        //由于没有写错误检查如果没有对id进行赋值 就会返回为0;
    }
    

    语法中 # AddSub 标签的访问方法

    antlrcpp::Any EvalVisitor::visitAddSub(NCParser::AddSubContext* ctx)
    {
        antlrcpp::Any l = visit(ctx->expr(0)); //暂时理解为addsub语法中,含有两个expr,此处相当于按索引值判定左右子树
        antlrcpp::Any r = visit(ctx->expr(1));
        if (ctx->op->getType() == NCParser::ADD) return l.as<int>() + r.as<int>();
        //语法中op属性和词法文件中终结符ADD进行比对
        else return l.as<int>() - r.as<int>();
    }
    

    语法中 # MulDiv 标签的访问方法

    antlrcpp::Any EvalVisitor::visitMulDiv(NCParser::MulDivContext* ctx)
    {
        antlrcpp::Any l = visit(ctx->expr(0));
        antlrcpp::Any r = visit(ctx->expr(1));
        if (ctx->op->getType() == NCParser::MUL) return l.as<int>() * r.as<int>();
        //语法中op属性和词法文件中终结符MUL进行比对
        else return l.as<int>() / r.as<int>();
    }
    

    语法中 # parens 标签的访问方法

    antlrcpp::Any EvalVisitor::visitParens(NCParser::ParensContext* ctx)
    {
        return visit(ctx->expr()); //返回对应值 
    }
    

四、主函数实现

    /*main.cpp*/
    #include <iostream>
    #include "antlr4-runtime.h"
    #include "NCLexer.h"
    #include "NCParser.h"
    #include "EvalVisitor.h"
    #include <Windows.h>

    #pragma execution_character_set("utf-8")
    #pragma comment(lib, "antlr4-runtime.lib") //安装方法中静态库引用
    using namespace antlr4;

    int main(int argc, const char* argv[]) {
        ANTLRInputStream input(std::cin);
        NCLexer lexer(&input);
        CommonTokenStream tokens(&lexer);

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

推荐阅读更多精彩内容