lex 和 yacc 程序的示例程序

本节包含 lexyacc 命令的示例程序。

这些示例程序一起创建了简单的桌面计算器程序,它能够执行加、减、乘、除运算。 此计算器程序还允许您为变量赋值(每个变量都由单个小写字母指定),然后在计算时使用变量。 包含示例 lexyacc 程序的文件如下:

文件 内容
calc.lex 指定定义词汇分析规则的 lex 命令指定文件。
calc.yacc 指定定义语法分析规则的 yacc 命令语法文件,并调用 lex 命令创建的 yylex 子例程以提供输入。

下面的描述假定 calc.lexcalc.yacc 示例程序位于您的当前目录中。

编译示例程序

要创建桌面计算器示例程序,请执行以下操作:

  1. 使用 -d 可选标记(此标记通知 yacc 命令创建文件,该文件定义除了 C 语言源代码之外还要使用的标记)处理 yacc 语法文件:
    yacc -d calc.yacc
  2. 使用 ls 命令验证下面的文件已创建:
    y.tab.c
    yacc 命令为解析器创建的 C 语言源文件
    y.tab.h
    头文件,包含解析器所使用的标记的定义语句
  3. 处理 lex 说明文件:
    lex calc.lex
  4. 使用 ls 命令验证下面的文件已创建:
    lex.yy.c
    lex 命令为词法分析器创建的 C 语言源文件
  5. 编译并链接两个 C 语言源文件:
    cc y.tab.c lex.yy.c
  6. 使用 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);
              }
%%