lex 和 yacc 程序的示例程序
本节包含 lex 和 yacc 命令的示例程序。
这些示例程序一起创建了简单的桌面计算器程序,它能够执行加、减、乘、除运算。 此计算器程序还允许您为变量赋值(每个变量都由单个小写字母指定),然后在计算时使用变量。 包含示例 lex 和 yacc 程序的文件如下:
| 文件 | 内容 |
|---|---|
| calc.lex | 指定定义词汇分析规则的 lex 命令指定文件。 |
| calc.yacc | 指定定义语法分析规则的 yacc 命令语法文件,并调用 lex 命令创建的 yylex 子例程以提供输入。 |
下面的描述假定 calc.lex 和 calc.yacc 示例程序位于您的当前目录中。
编译示例程序
要创建桌面计算器示例程序,请执行以下操作:
- 使用 -d 可选标记(此标记通知 yacc 命令创建文件,该文件定义除了 C 语言源代码之外还要使用的标记)处理 yacc 语法文件:
yacc -d calc.yacc - 使用 ls 命令验证下面的文件已创建:
- y.tab.c
- yacc 命令为解析器创建的 C 语言源文件
- y.tab.h
- 头文件,包含解析器所使用的标记的定义语句
- 处理 lex 说明文件:
lex calc.lex - 使用 ls 命令验证下面的文件已创建:
- lex.yy.c
- lex 命令为词法分析器创建的 C 语言源文件
- 编译并链接两个 C 语言源文件:
cc y.tab.c lex.yy.c - 使用 ls 命令验证下面的文件已创建:
- y.tab.o
- y.tab.c 源文件的对象文件
- lex.yy.o
- lex.yy.c 源文件的对象文件
- a.out
- 可执行程序文件
要直接从 a.out 文件运行程序,请输入:
$ a.out或
要将程序移动到具有描述性更强的名称的文件中(如以下示例所示)并运行它,请输入:
$ mv a.out calculate
$ calculate在任一情况下,在启动程序后,光标将移动到$(命令提示符)。 然后,在计算器上输入您想要的数字和运算符。 当您按 Enter 键时,程序将显示运算结果。 在您如下为变量赋值之后,光标将移动到下一行。
m=4 <enter>
_当您在后续计算中使用变量时,它将有已赋的值:
m+5 <enter>
9
_解析器源代码
下面的示例显示了 calc.yacc 文件中内容。 此文件有 yacc 语法文件的所有三部分中的条目:声明、规则和程序。
%{
#include<stdio.h>
int regs[26];
int base;
%}
%start list
%union { int a; }
%token DIGIT LETTER
%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS /*supplies precedence for unary minus */
%% /* beginning of rules section */
list: /*empty */
|
list stat '\n'
|
list error '\n'
{
yyerrok;
}
;
stat: expr
{
printf("%d\n",$1);
}
|
LETTER '=' expr
{
regs[$1.a] = $3.a;
}
;
expr: '(' expr ')'
{
$$ = $2;
}
|
expr '*' expr
{
$$.a = $1.a * $3.a;
}
|
expr '/' expr
{
$$.a = $1.a / $3.a;
}
|
expr '%' expr
{
$$.a = $1.a % $3.a;
}
|
expr '+' expr
{
$$.a = $1.a + $3.a;
}
|
expr '-' expr
{
$$.a = $1.a - $3.a;
}
|
expr '&' expr
{
$$.a = $1.a & $3.a;
}
|
expr '|' expr
{
$$.a = $1.a | $3.a;
}
|
'-' expr %prec UMINUS
{
$$.a = -$2.a;
}
|
LETTER
{
$$.a = regs[$1.a];
}
|
number
;
number: DIGIT
{
$$ = $1;
base = ($1.a==0) ? 8 : 10;
} |
number DIGIT
{
$$.a = base * $1.a + $2.a;
}
;
%%
main()
{
return(yyparse());
}
yyerror(s)
char *s;
{
fprintf(stderr, "%s\n",s);
}
yywrap()
{
return(1);
}该文件包含以下部分:
- 声明部分。 此部分包含条目:
- 包含标准 I/O 头文件
- 定义全局变量
- 定义list规则作为开始处理的位置
- 定义解析器所使用的标记
- 定义运算符和它们的优先顺序
- 规则部分。 规则部分定义对输入流进行语法分析的规则。
- %start - 指定整体输入应与 stat 相匹配。
- %union - 缺省情况下,操作和词法分析器返回的值是整数。 yacc 还可支持其他类型的值,包括结构。 此外,yacc 会记录类型,并插入适当的并集成员名称以便针对生成的解析器严格检查类型。 yacc 值堆栈声明为期望值的各种类型的并集。 用户声明并集,并将并集成员名关联至具有值的每个标记和非终止符号。 通过 $$ 或 $n 构造引用该值时,yacc 将自动插入适当的并集名称,以便不会发生意外转换。
- %type - 使用 %union 声明的成员并对与语法的每个部分相关联的值给出一种类型。
- %toksn - 列示来自 LEX 工具的标记及其类型。
- 程序部分。 程序部分包含下面的子例程。 因为这些子例程已包含在此文件中,您在处理此文件时不需使用 yacc 库。
子例程 描述 main 所需的主程序,它调用 yyparse 子例程来启动程序。 yyerror(s) 此错误处理子例程仅显示语法错误消息。 yywrap 结尾子例程,当输入结束时,它将返回值 1。
词法分析器源代码
此文件包含标准输入和输出以及 y.tab.h 文件的包含语句。 如果您将 -d 标记与 yacc 命令一起使用,yacc 程序将从 yacc 语法文件信息生成该文件。 y.tab.h 文件包含解析器程序所使用的标记的定义。 另外,calc.lex 文件包含从输入流生成这些标记的规则。 以下是 calc.lex
文件的内容。
%{
#include <stdio.h>
#include "y.tab.h"
int c;
%}
%%
" " ;
[a-z] {
c = yytext[0];
yylval.a = c - 'a';
return(LETTER);
}
[0-9] {
c = yytext[0];
yylval.a = c - '0';
return(DIGIT);
}
[^a-z0-9\b] {
c = yytext[0];
return(c);
}
%%