原文
概述
词法和语法可以使用正则表达式和BNF范式表达,而最终描述文法含义的事状态转换图
Lex与YACC
词法分析器Lex
词法分析词Lex,是一种生成词法分析的工具,描述器是识别文本中词汇模式的程序,这些词汇模式是在特殊的句子结构中定义的
Lex 接收到文件或文本形式的输入时,会将文本与常规表达式进行匹配:一次读入一个输入字符,直到找到一个匹配的模式
如果能够找到一个匹配的模式,Lex就执行相关的动作(比如返回一个标记Token)。另外,如果没有可以匹配的常规表达式,将会停止停止进一步的处理,Lex将显示一个错误信息
Lex 和 C语言是强耦合的,一个.lex文件通过Lex解析并生成C的输出文件,这些文件被编译为词法分析器的可执行版本
lex 或 .l 文件在格式上分为以下3段
-
- 全局变量声明部分
-
- 词法规则部分
-
- 函数定义部分
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个规则是找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