一、ANTLR4安装(Windows)
- ANTRL4 C++版本的安装及使用方法参照 Antlr4安装与使用(包括python3与C++版本)
- 注意要使用vs2015以上版本
二、文法文件生成语法分析器
- 词法文件
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语法文件名称)
-
文件目录命令行cmd运行命令
antlr4 -Dlanguage=Cpp -visitor NC.g4
使用以下命令,二者区别就在是否使用监听,不使用的选项相对应的默认类文件即不生成,可以自己尝试发现区别
antlr4 -Dlanguage=Cpp -visitor -no-listener NC.g4
同样的使用以下命令默认开启监听,生成监听类
antlr4 -Dlanguage=Cpp NC.g4
-
运行后会生成如下文件:将其中.cpp源文件和.h头文件全部复制到vs中。
1.PNG
VS2019
三、访问器实现
-
分析器会自动生成默认的访问器实现类如下:其中每个函数名均是以标签名首字母大写加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); } };
-
自定义访问器类继承 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); };
-
类方法的实现
/*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;
}