3.05 flex和bison进阶,产生C++语法分析器

bison C++版本的语法分析器都是可重入的,所以bison为语法分析器创建了一个类。在使用可重入的语法分析器时,程序员可以创建他所需要数量的实例,然后传入在另一个类中保存的每个实例的应用数据。点击查看这篇文章,其中包含有关可重入语法分析器的信息。

每次创建C++ 版本的语法分析器时,bison会创建四个头文件:location.hh和position.hh用来定义位置结构,stack.hh定义内部语法分析器堆栈,以及一个定义语法分析器自生的头文件。前三个头文件内容并不会变化,最后一个头文件则包含该语法分析器的特定内容,与C版本的相似。语法分析器头文件会包含其他三个文件,这四个头文件可以被词法分析器和其他需要处理位置信息的模块包含。(有人可能会问为什么不把前三个文件做成标准库的包含文件。)创建语法分析器的头文件是强制性的,因为生成的C++源文件会包含它,尽管你依然需要告诉bison来创建头文件。

对于词法分析器,虽然flex看起来也能创建C++ 词法分析器文件,但它所生成的C++ 代码并不能正常工作。幸运的是,flex 创建的C版本词法分析器可以在C++ 里进行编译,而把flexC版本的词法分析器和bisonC++ 版本的语法分析器联合起来使用并不困难。下面的例子我们将做这样的尝试。

以下是程序各个文件的源码,同时你也可以从github上获取源码,点击

main

// file: main.cpp
// Created by cmp on 2020/5/11.
//

#include <iostream>
#include "driver.h"

using namespace Marker;

int main(int argc, char const *argv[]) {
  /* code */
  Driver driver;
  driver.parse();
  return 0;
}

driver的声明部分

// file:driver.h
// Created by cmp on 2020/5/11.
//

#ifndef FLEX_BISON_DRIVER_H
#define FLEX_BISON_DRIVER_H

#include <iostream>
#include "scanner.h"
#include "parser.hpp"

namespace Marker {
  class Driver {
  private:
    Parser _parser;
    Scanner _scanner;
  public:
    Driver();

    int parse();

    virtual ~Driver();
  };
} /*Marker */
#endif //FLEX_BISON_DRIVER_H

driver的具体实现

// file: driver.cpp
// Created by cmp on 2020/5/11.
//

#include "driver.h"
#include <iostream>

using namespace Marker;
using namespace std;

Driver::Driver() : _scanner(*this), _parser(_scanner, *this) {

}

Driver::~Driver() {

}

int Driver::parse() {
  _scanner.switch_streams(cin, cout);
  return _parser.parse();
}

继承yyFlexLexer并声明实现自定义词法分析器的类

// file: scanner.h
// Created by cmp on 2020/5/11.
//

#ifndef FLEX_BISON_SCANNER_H
#define FLEX_BISON_SCANNER_H

/***********************************/
/*对yyFlexLexer进行继承,获得更多的控制
*
***********************************/

/* 重要 */
#if !defined(yyFlexLexerOnce)
#undef yyFlexLexer
#define yyFlexLexer Marker_FlexLexer  // 根据prefix修改

#include <FlexLexer.h>

#endif
/* 替换默认的get_next_token定义 */
#undef YY_DECL
#define YY_DECL Marker::Parser::symbol_type Marker::Scanner::nextToken()

#include "parser.hpp"

namespace Marker {
  class Driver;

  class Scanner : public yyFlexLexer {
  private:
    /* data */
    Driver &_driver;

  public:
    Scanner(Marker::Driver &driver) : _driver(driver) {}

    virtual Marker::Parser::symbol_type nextToken(); // 不需要手动实现这个函数,Flex会生成YY_DECL宏定义的代码来实现这个函数

    virtual ~Scanner() {}
  };
} /* Marker */


#endif //FLEX_BISON_SCANNER_H

词法解析器部分

/* file: lexer.h*/

%{
  #include <iostream>
  #include <cerrno>
  #include <climits>
  #include <cstdlib>
  #include <string>

  #include "parser.hpp"  //包含由parser.y生成的头文件
  #include "scanner.h"   //包含yyFlexLexer子类的头文件
  #include "location.hh" //包含位置调试信息头文件

  static Marker::location loc;//声明位置实例
  #define YY_USER_ACTION  loc.columns (yyleng); /* 定义了YY_USER_ACTION,该宏在每个记号的语义动作之前被调用,来根据记号的长度设置位置的信息 */

  #undef yywrap
  #define yywrap() 1

  using namespace Marker;
  #define yyterminate() Parser::make_END(loc);
%}
/* 声明使用C++版本FLEXER,c++是大小写不敏感的 */
%option c++

/* 支持调试 */
%option noyywrap debug

/* 使用Scanner::yylex() */
%option yyclass="Scanner"

/* 一些与编译常量使用该前缀否则为yy */
%option prefix="Marker_"

/*正则式的各种简写,使程序有层次感而显得明晰 */
string  \"[^\n"]+\"
/**/
ws      [ \t]+
alpha   [A-Za-z]
dig     [0-9]
name    ({alpha}|{dig}|\$)({alpha}|{dig}|[_.\-/$])*
num1    {dig}+\.?([eE][-]?{dig}+)?
num2    {dig}*\.{dig}+([eE][-]?{dig}+)?
number  {num1}|{num2}
id      ({alpha})+


%%
%{
  // C++ 兼容的词法分析器的规则,step函数把位置的起始值设置为与结束值相等,这样位置就指向了上一个极少的结束位置。
  loc.step();
%}


{number}  {
            return Parser::make_NUMBER(std::strtof(yytext,(&yytext+yyleng)),loc); // strtof函数将字符串转换为浮点数
          }

"//".* |
[ \t]     {
            /* 跳过注释和空白符号 */
            // step函数把位置的起始值设置为与结束值相等,这样位置就指向了上一个极少的结束位置。
            // 由于注释和空白符号识别后并不会返回,而前一个step的调用是在上一次yylex返回时,所以此处需要手动更新记号的起始位置
            loc.step();
          }

\n        {
            loc.lines(yyleng); // 使用lines函数来更新位置信息中的行号
            loc.step();
            return Parser::make_EOL(loc);
          }
"+"       { return Parser::make_ADD(loc); }
"-"       { return Parser::make_SUB(loc); }
"*"       { return Parser::make_MUL(loc); }
"/"       { return Parser::make_DIV(loc); }
"("       { return Parser::make_LPAREN(yytext,loc); }
")"       { return Parser::make_RPAREN(yytext,loc); }
"|"       { return Parser::make_ABS(loc); }
{id}      { return Parser::make_IDENTIFIER(yytext,loc); }
<<EOF>>   { return yyterminate(); }
.         {
             printf("Mystery character %c\n", *yytext);
             loc.step();
          }
%%
//这里可以放一些C或者C++代码

语法解析器部分

/* file: parser.y */

/* 使用指令%skeleton "lalr1.cc"选择C++解析器的骨架 */
%skeleton "lalr1.cc"

/* 指定bison的版本 */
%require "3.0.4"

%define api.namespace {Marker} //声明命名空间与下面声明的类名结合使用 Marker::Parser::  在scanner.l中有体现
%define parser_class_name { Parser }
%define api.token.constructor
%define api.value.type variant //使得类型与token定义可以使用各种复杂的结构与类型
%define parse.assert  //开启断言功能
%defines  //生成各种头文件  location.hh position.hh  parser.hpp
%code requires
{
  /*requires中的内容会放在YYLTYPE与YYSTPYPE定义前*/
  #include <iostream>
  #include <string>
  #include <vector>
  #include <stdint.h>
  #include <cmath>
  using namespace std;

  namespace Marker { /*避免包含头文件时冲突*/
    class Scanner;
    class Driver;
  }
}

%code top
{
  /*尽可能放在parser.cpp靠近头部的地方,与requires相似*/
  #include <iostream>
  #include "scanner.h"
  #include "parser.hpp"
  #include "driver.h"
  #include "location.hh"

  /*注意:这里的参数由%parse-param决定*/
  static Marker::Parser::symbol_type yylex(Marker::Scanner& scanner,Marker::Driver &driver){
    return scanner.nextToken();
  }
  using namespace Marker;
}

/*定义parser传给scanner的参数*/
%lex-param { Marker::Scanner& scanner }
%lex-param { Marker::Driver& driver }

/*定义driver传给parser的参数*/
%parse-param { Marker::Scanner& scanner }
%parse-param { Marker::Driver& driver }

%locations
//%define parse-trace

/*详细显示错误信息*/
%define parse.error verbose

/*通过Marker::Parser::make_XXX(loc)给token添加前缀*/
%define api.token.prefix {TOKEN_}

%token <string>RPAREN
%token <string>IDENTIFIER
%token <float>NUMBER

%token EOL
%token END 0

%left ADD "+"
%left SUB "-"
%left MUL "*"
%left DIV "/"

%nonassoc ABS "|"

%nonassoc NEG // 负号具有最高优先级但没有结合性

%left <string>LPAREN

%type <float> exp calclist factor term

%start calclist

%%
calclist: %empty           { /* 使用%empty显示的声明该规则是一个空规则 */ }
        | calclist exp EOL { cout << "=" << $2 << "\n>"; }
        ;

exp: factor         { $$ = $1; }
   | exp ADD factor { $$ = $1 + $3; }
   | exp SUB factor { $$ = $1 - $3; }
   ;

factor: term            { $$ = $1; }
      | factor MUL term { $$ = $1 * $3; }
      | factor DIV term {
        if($3 == 0){
          error(@3, "zero divide");
          YYABORT;
        }
        $$ = $1 / $3;
      }
      ;

term: NUMBER               { $$ = $1; }
    | ABS term ABS         { $$ = $2 >= 0 ? $2 : -1 * $2; }
    | LPAREN exp RPAREN    { $$ = $2; }
    | SUB exp %prec NEG    { $$ = -1 * $2; }
    ;

%%
/*Parser实现错误处理接口*/
void Marker::Parser::error(const Marker::location& location,const std::string& message){
  std::cout<<"msg:"<<message
           <<", error happened at: "<<location<<std::endl;
}

cmake编译配置文件

# 必须预先设置好包含目录,因为在语法解析器和词法解析器文件中有包含其他头文件,Bison生成代码时会校验包含的头文件是否能够找得到

include_directories(${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_BINARY_DIR})

execute_process(COMMAND
    flex -+ -o ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lexer.l)

execute_process(COMMAND
    bison -o ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/parser.y)


add_executable(3.05
    ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/driver.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp)

target_link_libraries(3.05 PUBLIC
    -lm)

编译后运行

cmp@t3600:~/work_dir/source_code/yacc_bison_practice/cmake-build-debug/ch3/3.05$ ./3.05 
|1-(3*4)|/2
=5.5
>3*4-(3/2)+3-2+1
=12.5

在交互模式中,使用ctrl+d退出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值