词法与语法分析器介绍

44 阅读2分钟

原文

词法与语法分析器介绍

概述

词法和语法可以使用正则表达式和BNF范式表达,而最终描述文法含义的事状态转换图

Lex与YACC

词法分析器Lex

词法分析词Lex,是一种生成词法分析的工具,描述器是识别文本中词汇模式的程序,这些词汇模式是在特殊的句子结构中定义的

Lex 接收到文件或文本形式的输入时,会将文本与常规表达式进行匹配:一次读入一个输入字符,直到找到一个匹配的模式

如果能够找到一个匹配的模式,Lex就执行相关的动作(比如返回一个标记Token)。另外,如果没有可以匹配的常规表达式,将会停止停止进一步的处理,Lex将显示一个错误信息

Lex 和 C语言是强耦合的,一个.lex文件通过Lex解析并生成C的输出文件,这些文件被编译为词法分析器的可执行版本

lex 或 .l 文件在格式上分为以下3段

    1. 全局变量声明部分
    1. 词法规则部分
    1. 函数定义部分

Lex 变量表

| 变量名 | 详细解释 |

| --- | --- |

| yyin | FILE* 类型。它指向lexer 正在解析的当前文件 |

| yyout | FILE* 类型。它指向记录lexer输出的位置。默认情况下,yyin 和 yyout 都指向标准输入和输出 |

| yytext | 匹配模式的文本存储在这一变量中(char*) |

| yyleng | 给出匹配模式的长度 |

| yylineno | 提供当前的行数信息(lexer不一定支持) |

Lex 函数表

| 函数名 | 详细解释 |

| --- | --- |

| yylex() | 这一函数开始分析。它由Lex自动生成 |

| yywrap() | 这一函数在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解析。它可以用来解析多个文件 |

| yyless(int) | 这一函数可以用来输出除来前n个字符外的所有读出标记 |

| yymore() | 这一函数告诉Lexer将下一个标记附加到当前标记后 |

代码

文件 a.l


%{

#include <stdio.h>

extern char *yytext;

extern FILE *yyin;

int count = 0;

%}

%%// 两个百分号标记指出了 Lex 程序中这一段的结束和第二段的开始

\$[a-zA-Z][a-zA-Z0-9]* {count++; printf(" 变量%s", yytext);}

[0-9\/.-]+ printf("数字%s", yytext);

= printf("被赋值为");

\n printf("\n");

[ \t]+ /* 忽略空格 */;

%%

// 函数定义部分

int main(int avgs, char *avgr[]) {

yyin = fopen(avgr[1], "r");

if (!yyin) {

return 0;

}

yylex();

printf("变量总数为:%d\n", count);

fclose(yyin);

return 1;

}

对于以上代码,解释如下

  • 全局变量声明部分: 声明了一个int型全局变量count,用来记录变量的个数

  • 规则部分: 第1个规则是找符号开头、第2个符号为字母且后面为字符或数字的变量,类似于符号开头、第2个符号为字母且后面为字符或数字的变量,类似于a,并计数加1. 同时,将满足条件的yytext输出;第2个规则是找数字;第3个规则是找"=" 号;第4个规则是输出"\n"; 第5个规则是忽略空格

  • 函数定义部分: 打开一个文件,然后调用yylex 函数进行词法解析,输出变量的技术,最后调用fclose关闭文件

lex 代码编译


lex a.l

gcc lex.yy.c -o test -ll

测试文件 file


$a = 1

$b = 2

执行如下命令


./test file

变量$a被赋值为数字1

变量$b被赋值为数字2

变量总数为:2

语法分析词YACC

YACC(Yet Another Compiler-Compiler) 是 UNIX/Linux 上一个用来生成编译器的编译器(编译器代码生成器). YACC使用BNF范式定义语法,能处理上下文无关文法

YACC 语法规则

YACC 语法包括3部分,即定义段、规则段和用户代码段

... 定义段 ...

%%

... 规则段 ...

%%

... 用户代码段 ...

代码

词法分析文件: cal.l


%{

#include "y.tab.h"

#include <math.h>

%}

%%

([0-9]+|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) {

yylval.dval = atof(yytext);

return NUMBER;

}

[ \t] ;

\n |

. return yytext[0];

%%

语法分析文件: calc.y


%{

#include <stdio.h>

#include <string.h>

#include <math.h>

int yylex(void);

void yyerror(char *);

%}

%union {

double dval;

}

%token <dval> NUMBER

%left '-' '+'

%left '*' '/'

%nonassoc UMINUS

%type <dval> expression

%%

statement_list: statement '\n'

| statement_list statement '\n'

;

statement: expression { printf("= %g\n", $1); }

;

expression: expression '+' expression {$$ = $1 + $3;}

| expression '-' expression { $$ = $1 - $3; }

| expression '*' expression { $$ = $1 * $3; }

| expression '/' expression

{

if ($3 == 0.0)

yyerror("divide by zero");

else

$$ = $1 / $3;

}

| '-' expression %prec UMINUS { $$ = -$2; }

| '(' expression ')' { $$ = $2; }

| NUMBER { $$ = $1; }

%%

void yyerror(char *str) {

fprintf(stderr, "error:%s\n", str);

}

int yywrap() {

return 1;

}

int main() {

yyparse();

}

从代码中可以看出,规则部分使用BNF范式

  • expression 最终是NUMBER,以及使用+、-、* 、/ 和 ()的组合,对加、减、乘、除、括号、负号进行表达

  • statement 是由expression 组合而成的,可以输出计算结果

  • statement 是由expression 组合而成的,可以输出计算结果

  • statement_list 是statement的组合

lex 编译


lex cal.l

  • 通过这个命令会生成lex.yy.c,里面维护了NUMBER这个Token的有穷自动机

使用 YACC对 calc.y


yacc -d calc.y

  • 会生成y.tab.c、y.tab.h

最终执行结果


gcc -o calc y.tab.c lex.yy.c

./calc

1+2

= 3

3+6

= 9

Re2C 与 Bison

词法分析器 Re2c