lex yacc与C++编写代码解析字符串代码示例

lex/yacc&C++使用及学习记录

(写在前面的话)本文是笔者在工作项目中实际使用到的lex/yacc的记录,主要介绍的是项目中如何组织、编译以及遇到的一些问题。不会提到一些十分基础的知识。如果需要学习基础知识可以移步去看o’reilly的书。

一、准备工作

使用lex yacc和C++一起实现功能时,需要建立四个文件来进行调用,即.l、.y、.cpp和.h文件。

xxxLexer.cpp (MyxxxLexer中的函数具体实现,需要重载yyFlexLexer中的一些函数)
xxxLexer.h (继承yyFlexLexer生成一个新的类MyxxxLexer)
xxxLexer.l (lex 词法分析器实现)
xxxParser.y (yacc语法分析器实现)

yyFlexLexer是继承自FlexLexer的一个类,其中有诸多接口,这个类会在.l和.y文件编译后存在与C代码中,这里贴一个之前看到的类文档页面,里面介绍了涉及到的一些接口,以作参考
casa: yyFlexLexer Class Reference (nrao.edu)

二 、编译过程

lex yacc需要生成一些C源代码(.cxx、.hxx)来使用,所以如果是在一个项目中进行编译,需要在CMakeLists中添加FLEX_TARGET和BISON_TARGET

FLEX_TARGET(xxxScanner xxxLexer.l ${CMAKE_CURRENT_BINARY_DIR}/xxxLexer.cxx)
BISON_TARGET(xxxParser xxxParser.y ${CMAKE_CURRENT_BINARY_DIR}/xxxParser.cxx
             COMPILE_FLAGS -v DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/xxxPraser.hxx)
ADD_FLEX_BISON_DEPENDENCY(xxxScanner xxxParser)  # 这里一定要把Scanner放在前面

三、代码编写注意事项

.l文件的编写中没太多需要注意的东西,唯一需要注意的是一些option

%option noyywrap
%option c++
%option prefix="xxx"  //表示生成的lexyacc的C代码中所有yy开头的函数和类都会变为xxx开头

.y文件中
①数据类型的定义,可以使用union来进行,也可以使用#define YYSTYPE type来进行,这个type可以是C的基本类型,也可以是自己定义的指针或自己定义的struct等,但是要在定义段声明或者指定命名空间才能在union中使用。如果没有定义的话,yacc中传递的数据默认为int。
②在.y中可以重写yylex等函数,调用cpp中自己生成的类对象,以将lex词法分析获取的信息赋给自己定义的数据类型,从而能作为参数用于语法分析中。
③在编写规则时,不同的两条规则直之间会有一些规约冲突,可能是编写的规则有重复的部分,需要对其进行寻找并修改。//定义段,引入头文件、数据类型等等定义

%{
#define yyFlexLexer xxxFlexLexer
#include <FlexLexer.h>
#include <string.h>
#include "xxxLexer.h"
#include "xxxParser.hxx"
#include <iostream>

#define CUR_LEXER static_cast<MyxxxLexer*>(_param) 
//这里define后,规则段可以调用对象里的函数,调用方式为CUR_LEXER->函数名()

%}
// 定义数据类型
%union 
{
  int *_value;
  char *_op    ;
}

%{
int yylex(YYSTYPE *yyvalp, void *param) // 可以在这里重写yylex函数
{
  auto lexer = static_case<MyxxxLexer*>(_param);
  int res = lexer->yylex();  
  yyvalp->_op = strdup(lexer->YYText());  // 这样来进行赋值
  return res;
}
// 这个函数的返回值是.l中返回的类型所对应的整型值
%}

%token <_value> VALUE
%token<_op> OP
%%
// 规则段
%%
// 自定义函数段

.h与.cpp中
如果同一个目录下的代码已经有另一些cpp代码定义过,即#define yyFlexLexer zzzFlexLexer,需要在.h头文件中写以下代码以避免继承yyFlexLexer的报错,让代码知道自己继承的是哪个yyFlexLexer

#if !defined(yyFlexLexerOnce)
#undef yyFlexLexer  // 注意拼写正确 如果拼写错误编译时会出现链接不到的错误
#define yyFlexLexer xxxFlexLexer  
#include <FlexLexer.h>
#endif
// 如果这里没重新定义自己的xxxFlexLexer,会在类定义的地方报error: expected class-name before ‘{’ token的错

一个编写模板如下:

/*.h文件中的类和接口定义*/
class MyxxxLexer : public yyFlexLexer
{
  public:
    // Construct function
    MyxxxLexer(const char *data) {}
    virtual ~MyxxxLexer() {}
    // 一些yyFlexLexer中的虚函数,可以重写以实现自己的功能
    virtual int  LexerInput(char* buf, int max_size);  // 读取数据用于词法语法分析
    virtual void LexerError(const char* msg);

    // 自己可以定义一些接口,在.y文件中可以实例化,以在规则段用于调用
  private:
    // 自己定义的数据
};

void processFuncLexer(const char *data, MyxxxLexer *funcLexer);
//自定义一个接口,目的是告诉编译器,调用这个函数的时候,开始对自己输入的数据进行解析
/*.cpp文件中的实现*/
extern int xxxparse(void*);    // 这里的外部调用函数,叫xxxparse,是因为之前prefix定义为了xxx
void processFuncLexer(const char *func, MyxxxLexer *funcLexer)
{  xxxparse(funcLexer); }

int YFuncLexer::LexerInput(char* buf, int max_size)
{
  // char _str是自己定义的数据类型,这个函数的目的就是把数据放到buf中,传给解析器
  size_t num = strlen(_str) + 1
  memcpy(buf, _str, num);
  return num;
}
// 其余函数的实现这里不做赘述

四、踩到的一些坑

①编译报错
在项目其他目录的代码文件中使用MyxxxLexer这个类时,如果在Linking CXX executable时报错,提示信息是undefined reference to `yyFlexLexer::yyFlexLexer(std::istream*, std::ostream*)等等,此时要首先看自己的一些宏定义是不是写错了,其次才是考虑CMakeLists中链接的问题。
②归约错误,即.y中规则段编写的注意事项,如果规则段没有编写好,出现了一些匹配冲突,yacc会报相关的冲突错误,这时需要自己寻找哪里写得不合理,避免规则之间有冲突。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值