学习ANTLR4—自定义监听器加载CSV数据C++

通过编写自定义的监听器,解析CSV文件中的数据,并加载到由Map组成的List数据结构中,学习ANTLR4的监听器。 —参考《ANTLR4权威指南》

一、ANTLR4监听器

  • ANTLR运行库提供两种遍历树机制,监听器和访问器。使用监听器遍历,可以通过自行实现ParseTreeListener类中的接口,使用ANTLR提供的遍历器ParseTreeWalker类自动调用。
  • 监听器不同于访问器,此机制自动运行,不需要通过visit()方法访问子节点,而且监听器返回值是void,访问器可以返回值。
  • 引用书中介绍的语法树:

    对其语法其遍历过程如下:
    深度优先遍历过程

    对于每条规则都会生成对应的enter和exit方法,比如访问assign节点,调用enterAssign()方法访问其所有子节点后调用exitAssign()方法完成遍历。
  • 最终目的,期望生成如下数据结构

二、CSV语法文件及CSV文件

  • 所用语法文件简单,语法词法文件合二为一
    grammar CSV;
    file : hdr row+;
    hdr : row;
    row: field (',' field)* '\r'? '\n';
    field : TEXT        # text
          | STRING      # string
          |             # empty
          ;
    TEXT : ~[,\n\r"]+;
    STRING : '"' ('""'|~'"')* '"';
    
    对TEXT、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规则是遍历标题规则时调用的

    语法树
    退出hdr规则时将存储的row数据交换至header中保存,方便后续生成整体数据结构使用

    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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、ANTLR4安装(Windows) ANTRL4 C++版本的安装及使用方法参照 Antlr4安装与使用(包括...
    小白很菜阅读 3,201评论 0 1
  • 最近科研项目需要加入一些新东西,需要我构建一个程序信息库,然后就想设计一套类似sql的查询语言去查询库里包含的内容...
    yufeiyang1995阅读 6,208评论 0 3
  • > Antlr4 是一个强大的解析器的生成器,可以用来读取、处理、执行或翻译结构化文本,ANTLR可以从语法上来生...
    kikiki4阅读 87评论 0 2
  • > Antlr4 是一个强大的解析器的生成器,可以用来读取、处理、执行或翻译结构化文本,ANTLR可以从语法上来生...
    kikiki4阅读 189评论 0 2
  • > Antlr4 是一个强大的解析器的生成器,可以用来读取、处理、执行或翻译结构化文本,ANTLR可以从语法上来生...
    kikiki4阅读 173评论 0 2