Antlr4 C++ Visitor方法

Expr.g4文件

grammar Expr;

prog
    :   stat+
    ;

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

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

ID
    :   [a-zA-Z]+
    ;

INT
    :   [0-9]+
    ;

NEWLINE
    :   '\r'* '\n'
    ;

MUL
    :   '*'
    ;

DIV 
    :   '/'
    ;

ADD 
    :   '+'
    ;

SUB 
    :   '-'
    ;

WS
    :   [ \t] -> skip
    ;

#表示标签,放置在一个备选分支的右侧,标签可以是任意标识符,但不能与规则冲突。

生成对应的CPP文件

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

生成如下对应文件

-rw-rw-r-- 1    76  6月 14 16:12 ExprBaseVisitor.cpp
-rw-rw-r-- 1  1436  6月 14 16:12 ExprBaseVisitor.h
-rw-rw-r-- 1   642  6月 14 16:11 Expr.g4
-rw-rw-r-- 1  1531  6月 14 16:12 Expr.interp
-rw-rw-r-- 1  5830  6月 14 16:12 ExprLexer.cpp
-rw-rw-r-- 1  1200  6月 14 16:12 ExprLexer.h
-rw-rw-r-- 1  2385  6月 14 16:12 ExprLexer.interp
-rw-rw-r-- 1   137  6月 14 16:12 ExprLexer.tokens
-rw-rw-r-- 1 19082  6月 14 16:12 ExprParser.cpp
-rw-rw-r-- 1  4662  6月 14 16:12 ExprParser.h
-rw-rw-r-- 1   137  6月 14 16:12 Expr.tokens
-rw-rw-r-- 1    72  6月 14 16:12 ExprVisitor.cpp
-rw-rw-r-- 1  1084  6月 14 16:12 ExprVisitor.h

注意ExprBaseVisitor.h中的内容,会产生对应分支的visitor方法。

...
virtual std::any visitPrintExpr(ExprParser::PrintExprContext *ctx) override {
    return visitChildren(ctx);
  }
...

复写需要的方法

当前g4文件是来实现一个类似计算器的功能,需要复写遇到不同分支时对应的处理。MyVisitor是自己的一个实际操作的类。

#pragma once


#include "ExprBaseVisitor.h"


class MyVisitor : public ExprBaseVisitor
{
public:
    /// @brief visit assign (ID '=' expr NEWLINE)
    /// @param ctx 
    /// @return value 
    virtual std::any visitAssign(ExprParser::AssignContext *ctx) override;

    /// @brief visit print expr (expr NEWLINE)
    /// @param ctx 
    /// @return 0
    virtual std::any visitPrintExpr(ExprParser::PrintExprContext *ctx) override;

    /// @brief visit INT (INT)
    /// @param ctx 
    /// @return value
    virtual std::any visitInt(ExprParser::IntContext *ctx) override;

    /// @brief visit ID (ID)
    /// @param ctx 
    /// @return value or 0
    virtual std::any visitId(ExprParser::IdContext *ctx) override;

    /// @brief visit MulDiv (expr ('*'|'/') expr)
    /// @param ctx 
    /// @return value 
    virtual std::any visitMulDiv(ExprParser::MulDivContext *ctx) override;

    /// @brief visit AddSub (expr ('+'|'-') expr)
    /// @param ctx 
    /// @return value 
    virtual std::any visitAddSub(ExprParser::AddSubContext *ctx) override;

    /// @brief visit parens ('(' expr ')') 
    /// @param ctx 
    /// @return 
    virtual std::any visitParens(ExprParser::ParensContext *ctx) override;

    /// @brief visit clear (clear())
    /// @param ctx 
    /// @return 
    virtual std::any visitClear(ExprParser::ClearContext *ctx) override;

private:
    std::map<std::string, int> memory;

};
#include "MyVisitor.h"


std::any MyVisitor::visitAssign(ExprParser::AssignContext *ctx)
{
    std::string id = ctx->ID()->getText();
    int value = std::any_cast<int>(visit(ctx->expr()));

    memory[id] = value;

    return value;
}

std::any MyVisitor::visitPrintExpr(ExprParser::PrintExprContext *ctx)
{
    int value = std::any_cast<int>(visit(ctx->expr()));
    std::cout << value << std::endl;

    return 0;
}

std::any MyVisitor::visitInt(ExprParser::IntContext *ctx)
{
    return std::stoi(ctx->INT()->getText());
}

std::any MyVisitor::visitId(ExprParser::IdContext *ctx)
{
    std::string id = ctx->ID()->getText();
    if (memory.find(id) != memory.end())
        return memory.find(id)->second;
    else
        return 0;
}

std::any MyVisitor::visitMulDiv(ExprParser::MulDivContext *ctx)
{
    int left = std::any_cast<int>(visit(ctx->expr(0)));
    int right = std::any_cast<int>(visit(ctx->expr(1)));

    if (ctx->MUL() != nullptr)
        return left * right;
    else
        return left / right;
}

std::any MyVisitor::visitAddSub(ExprParser::AddSubContext *ctx)
{
    int left = std::any_cast<int>(visit(ctx->expr(0)));
    int right = std::any_cast<int>(visit(ctx->expr(1)));

    if (ctx->ADD() != nullptr)
        return left + right;
    else
        return left - right;
}

std::any MyVisitor::visitParens(ExprParser::ParensContext *ctx)
{
    return visit(ctx->expr());
}

std::any MyVisitor::visitClear(ExprParser::ClearContext *ctx)
{
    memory.clear();
    std::cout << "clear memory, size : " << memory.size() << std::endl;
    
    return 0;
}

main文件

main文件用来读取文件当中的表达式并用antlr来识别并计算。

#include <iostream>
#include <fstream>
#include <sstream>

#include "ExprLexer.h"
#include "ExprParser.h"
#include "MyVisitor.h"


bool ReadFile(std::string _filePath, std::string& _context)
{
    if (_filePath.empty())
        return false;

    std::ifstream in;
    
    in.open(_filePath, std::ios::in);
    if (in.is_open() == false)
    {
        return false;
    }
    else
    {
        std::stringstream buffer;
        buffer << in.rdbuf();
        _context = buffer.str();
    }

    return true;
}


int main(int argc, char** argv)
{
    if (argc != 2)
    {
        std::cout << "Usage : calc file" << std::endl;
        return -1;
    }
    
    std::string filePath = argv[1];
    std::string context;
    if (ReadFile(filePath, context) == false)
        return -1;
    std::cout << context << std::endl;

    antlr4::ANTLRInputStream input(context);
    ExprLexer lexer(&input);
    antlr4::CommonTokenStream tokens(&lexer);
    ExprParser parser(&tokens);

    antlr4::tree::ParseTree* tree = parser.prog();
    MyVisitor visitor;
    visitor.visit(tree);

    return 0;
}

编译

CMakeLists文件

cmake_minimum_required(VERSION 3.10)

project(calc)

set(CMAKE_CXX_STANDARD 17)

aux_source_directory(. DIR_SRCS)

add_executable(calc ${DIR_SRCS})

target_link_libraries(calc antlr4-runtime)

表达式文件

193
a = 5
b = 6
a+b*2
(1+2)*3
clear()

输出

193
a = 5
b = 6
a+b*2
(1+2)*3
clear()

193
17
9
clear memory, size : 0