通过编写自定义的监听器,解析CSV文件中的数据,并加载到由Map组成的List数据结构中,学习ANTLR4的监听器。 —参考《ANTLR4权威指南》
一、ANTLR4监听器
- ANTLR运行库提供两种遍历树机制,监听器和访问器。使用监听器遍历,可以通过自行实现ParseTreeListener类中的接口,使用ANTLR提供的遍历器ParseTreeWalker类自动调用。
- 监听器不同于访问器,此机制自动运行,不需要通过visit()方法访问子节点,而且监听器返回值是void,访问器可以返回值。
-
引用书中介绍的语法树:
对其语法其遍历过程如下:深度优先遍历过程
对于每条规则都会生成对应的enter和exit方法,比如访问assign节点,调用enterAssign()方法访问其所有子节点后调用exitAssign()方法完成遍历。 -
最终目的,期望生成如下数据结构
二、CSV语法文件及CSV文件
- 所用语法文件简单,语法词法文件合二为一
对TEXT、STRING 和空白分支设置标签 # ...grammar CSV; file : hdr row+; hdr : row; row: field (',' field)* '\r'? '\n'; field : TEXT # text | STRING # string | # empty ; TEXT : ~[,\n\r"]+; STRING : '"' ('""'|~'"')* '"';
需要注意的是,file规则中的hdr依然匹配的是row,这样是为了语法树构造过程先单独匹配一个row规则,这样方便提取标题行 -
所解析的CSV文件t.csv
t.csv
三、自定义监听器类
-
生成的默认监听器类
#include "antlr4-runtime.h" #include "CSVListener.h" /** * This class provides an empty implementation of CSVListener, * which can be extended to create a listener which only needs to handle a subset * of the available methods. */ class CSVBaseListener : public CSVListener { public: virtual void enterFile(CSVParser::FileContext * /*ctx*/) override { } virtual void exitFile(CSVParser::FileContext * /*ctx*/) override { } virtual void enterHdr(CSVParser::HdrContext * /*ctx*/) override { } virtual void exitHdr(CSVParser::HdrContext * /*ctx*/) override { } virtual void enterRow(CSVParser::RowContext * /*ctx*/) override { } virtual void exitRow(CSVParser::RowContext * /*ctx*/) override { } virtual void enterText(CSVParser::TextContext * /*ctx*/) override { } virtual void exitText(CSVParser::TextContext * /*ctx*/) override { } virtual void enterString(CSVParser::StringContext * /*ctx*/) override { } virtual void exitString(CSVParser::StringContext * /*ctx*/) override { } virtual void enterEmpty(CSVParser::EmptyContext * /*ctx*/) override { } virtual void exitEmpty(CSVParser::EmptyContext * /*ctx*/) override { } virtual void enterEveryRule(antlr4::ParserRuleContext * /*ctx*/) override { } virtual void exitEveryRule(antlr4::ParserRuleContext * /*ctx*/) override { } virtual void visitTerminal(antlr4::tree::TerminalNode * /*node*/) override { } virtual void visitErrorNode(antlr4::tree::ErrorNode * /*node*/) override { } };
-
自定义监听器重写对每个分支访问接口
#include "CSVBaseListener.h" #include <map> #include <list> using namespace std; typedef map<string, string> MAP_STRING_T; typedef list<string> LIST_STRING; typedef list<MAP_STRING_T> LIST_MAP; class Loadcsv : public CSVBaseListener /*继承默认访问器类并对其方法进行重写*/ { private: LIST_STRING *currentRowFieldValues = new LIST_STRING; // 记录每行信息 LIST_STRING *header = new LIST_STRING; //记录标题行信息 public: LIST_MAP *rows = new LIST_MAP; //最后输出的数据结构 ~Loadcsv(); void exitText(CSVParser::TextContext* ctx); void exitString(CSVParser::StringContext* ctx); void exitEmpty(CSVParser::EmptyContext* ctx); void exitHdr(CSVParser::HdrContext* ctx); void exitRow(CSVParser::RowContext* ctx); };
-
类方法的实现
对于各个叶节点的操作很简单就是将其放入List中储存#include "EvalVisitor.h" Loadcsv::~Loadcsv() { delete currentRowFieldValues; delete header; delete rows; } void Loadcsv::exitText(CSVParser::TextContext* ctx) { currentRowFieldValues->push_back(ctx->TEXT()->getText()); } void Loadcsv::exitString(CSVParser::StringContext* ctx) { currentRowFieldValues->push_back(ctx->STRING()->getText()); } void Loadcsv::exitEmpty(CSVParser::EmptyContext*ctx) { currentRowFieldValues->push_back(""); }
通过分析语法树遍历顺序可知,hdr规则是遍历标题规则时调用的
语法树void Loadcsv::exitHdr(CSVParser::HdrContext*ctx) { header->swap(*currentRowFieldValues); }
对于row规则需要判断其根节点,如果根节点是hdr规则不做操作,否则将其数据取出填入一个map中,标题与内容一一对应。
void Loadcsv::exitRow(CSVParser::RowContext*ctx) { if (dynamic_cast <antlr4::ParserRuleContext*>(ctx->parent)->getRuleIndex() == CSVParser::RuleHdr) { return; } LIST_STRING::iterator i = header->begin(); MAP_STRING_T temp; for (auto it :currentRowFieldValues) { temp[*i]= *it; i++; } currentRowFieldValues->clear(); //清空数据,防止重复填入map rows->push_back(temp); }
对于书中的getParent()方法,没有找到此方法,只能使用属性parent,parent是一个ParseTree类,ParseTree中没有getRuleIndex()方法,所以通过向下转型其派生类ParserRuleContext()访问到getRuleIndex()方法与CSVParser中enum对比,确定其根节点类型。
或者通过判断parent的子节点数量,因为语法树显示hdr有一个子节点而file有四个子节点。
if (ctx->parent->children.size() == 1) { return; }
四、主函数实现
- 主函数
#include <iostream>
#include "antlr4-runtime.h"
#include "CSVLexer.h"
#include "CSVParser.h"
#include "EvalVisitor.h"
#include <fstream>
#pragma execution_character_set("utf-8")
using namespace antlr4;
int main(int argc, const char* argv[]) {
std::filebuf in;
in.open("t.csv", std::ios::in);
std::istream iss(&in);
ANTLRInputStream input(iss);
CSVLexer lexer(&input);
CommonTokenStream tokens(&lexer);
CSVParser parser(&tokens);
tree::ParseTree* tree = parser.file();
tree::ParseTreeWalker walker;
Loadcsv load;
walker.walk(&load, tree);
for (auto it : load.rows)
{
cout << '[';
for (auto i : it)
{
cout << i->first << " = " << i->second;
if (*i == *(it->rbegin())) break;
cout << " , ";
}
cout << ']'<<endl;
}
system("pause");
is very limited.
return 0;
}
-
最终结果运行结果.png