[DIY编程语言] 前置知识 flex 和 bison(1)

525 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

flex 和 bison

Flex是快速词法分析器(The Fast Lexical Analyzer)的缩写。

GNU bison是一个自由软件,用于自动生成语法分析器程序,实际上可用于所有常见的操作系统。Bison把LALR形式的上下文无关文法描述转换为可做语法分析的C或C++程序。

要说它们的父辈,那大抵是 lex 和 yacc

下面是它们的下载地址

flexbison
github.com/westes/flexwww.gnu.org/software/bi…

当然,讲道理,在Linux,大多数情况下的系统是有这两个东西的

flex

flex 是 一个词法分析器

为什么要这个玩意呢? 用来干什么呢?

我们知道,在编译原理中,我们可以通过 RE 转化为一个 NFA

再由我们的 NFA 构造为 一个 DFA

在这些步骤中,我们也可以采取自己手动的方式去构建我们的 DFA

但是,往往更加常见的方法是,我们借助一些工具,来帮我们生成 DFA

flex就是一个可以用来帮我们把我们定义的规则,来转化为实际的代码逻辑,然后实现我们的分词功能

有什么作用呢 ?

举个例子,我们可以非常方便的拿它来写出一个 word count 的 程序

代码如下:

%option noyywrap
%{
#include <bits/stdc++.h>
    int chars = 0;
    int word = 0;
    int line = 0;
%}
%%
[^ \n\t\f\v\r]+         {word++; chars += strlen(yytext);}
\n                      {chars++; line++;}
.                       {chars++;} 
%%

int main() {
    yylex();
    printf("行%d 词%d 字符%d\n", line, word, chars);
    return 0;
}

解释

我们的 flex的语法是兼容 lex语法的,它大抵把整体分为三部分

%{
 // 定义段
%}

%%
 // 规则段
%%
 // 用户程序代码

int main() {
    yylex();
}

在规则段中 我们采取 regex {oper} 的形式

regex代码的是我们的规则

括号里面则是对应这一种东西,我们的操作

如何 使用呢 ?

  1. 使用 flex 命令生成 lex.yy.c
> flex wc.l
  1. 使用 gcc or g++ 编译 lex.yy.c 即可用
> g++ lex.yy.c

bison

Bison是一种通用解析器生成器,它将带注释的上下文无关文法转换为使用LALR(1)解析器表的确定性LR或广义LR(GLR)解析器 。

Bison与Yacc向上兼容:所有正确编写的Yacc语法都应与Bison一起使用,而无需进行任何更改。

bison 结构

  • 定义部分
  • %%
  • 规则部分
  • %%
  • 用户附加的C语言部分

通常可以和flex配合进行使用

它通常支持语法形式的匹配

类似于这种

expr ::= expr + expr
     |   expr - expr
     |   expr * expr
     |   expr / expr
     |   number

依次来递归推到规则匹配

下面来看一个例子

四则算术的计算器(不带括号)

首先,我们要解析出 "+ - * /" 运算符,以及数字

然后进行模式匹配

具体代码如下:

calc.l

%{
#include <bits/stdc++.h>
#include "calc.tab.h"
%}
%option noyywrap

%%
"+"         {return ADD;}
"-"         {return SUB;}
"*"         {return MUL;}
"/"         {return DIV;}
[0-9]+      {yylval = atoi(yytext); return NUMBER;}
\n          {return EOL;}
[ \t]       {}
.           {std::runtime_error("parser error");}
%%

calc.y

%{
extern int yylex(void);
extern "C" {
    int yyparse();
    void yyerror(char *s){}
}
#include <bits/stdc++.h>
%}

%token NUMBER
%token ADD SUB MUL DIV ABS
%token EOL

%%

list: /* null */
    | list expr EOL { std::cout << "val-> " << $2 << "\n";}
;
expr: factor {$$ = $1;}
    | expr ADD factor {$$ = $1 + $3;}
    | expr SUB factor {$$ = $1 - $3;}
;

factor: term {$$ = $1;}
    | factor MUL term {$$ = $1 * $3;}
    | factor DIV term {
        if ($3) $$ = $1 / $3;
        else {
            std::runtime_error("error");
        }
    }
;

term: NUMBER        {$$ = $1;}
;


%%

int main() {
    yyparse();
    std::cout << "ok" << std::endl;
    return 0;
}

void yyerror(const char* str) {
    fprintf(stderr, "error %s", str);
}

我们把规则分成了三部分

list -> ϵ | list expr  
expr -> factor | expr ADD factor | expr SUB factor  
factor -> term | factor MUl term | factor DIV term
term -> number

然后把 加减和乘除分开,因为优先级不同

然后,我们就实现了一个简单的四则运算解析器