45fan.com - 路饭网

搜索: 您的位置主页 > 网络频道 > 阅读资讯:使用yacc的方法

使用yacc的方法

2016-09-04 09:02:28 来源:www.45fan.com 【

使用yacc的方法

被遗忘的强大的工具

UNIX系统的功能的之所以强大,不是在于它本身有多好的内核,

而是在于它为我们提供了很多能完成小功能的命令,而这些命令的组

合使用使得它更加的强大。其实在这里它为我们体现了这样的一个观

点:

要完成一个项目,或者是个大型的程序。应该先从小做起,然后

不断的发展壮大。

在本文中,主要为您介绍一写UNIX 系统下面的3 个命令的,似乎

它不常用。可能make用得多一些,但是他们功能确实强大。

yacc 语法分析程序生成器,可以从语言的语法描述生成语法

分析程序。

make 通过指定和控制程序对复杂的程序进行编译的程序。

lex 类似yacc的程序,主要用语生成词法分析程序。

本文主要通过一个经典的hoc(high-order calculator)程序出

发,结合笔者自身在学习过程中的一些体会为您介绍它。本文只涉及

基本原理,我们假设您懂得C语言,或者身边正好有C语言方面的书。

下面我们进入正题:

1 hoc计算器

hoc 是一个功能强大的四则运算计算程序,当然在这里我不方便

去揣摩如果让您去开发一个类似的程序您会采用什么样的思路。下面

就来看看开发一个四则运算的小程序经典思路。

首先需要向您阐述一下巴科斯-诺尔范式(Backus-Naur Form),(正则

文法)作为一个四则运算,我们可以简单抽象成下面这样一个表达形

语句->表达式

表达式->token

表达式->表达式+表达式

表达式->表达式-表达式

表达式->表达式*表达式

表达式->表达式/表达式

表达式->(表达式)

或者我们用另外一个依赖关系来表达这样一个事实。

语句:表达式

表达式:token

表达式+表达式

表达式-表达式

表达式*表达式

表达式/表达式

(表达式)

其实看到这里您也许会觉得这样是否会非常得像我们平时写的

makefile文件呢?我无法回答makefile的语法和这有什么直接的联

系。但是makefile文件确实体现这样一个依赖关系的推导式的思想。

对于该疑问我们暂且放下。回归正题,其实按照严格的语法,上面的

形式语言描述是不完整的,在该文法中我们没有指定操作操作符号的

优先关系和运算符的结合性。

2 yacc程序

yacc 是一个语法分析生成器,它是叫一种语言的规范化的描述转换

成一个语法分析程序。作为yacc主要分4个阶段。

1) 描述语法(如上面所写的那样),yacc 可以帮我们检查我们描

述的语法的错误以及二义性。

2) 语法对应的C程序。

3) 词法分析程序。(词法的块一般标记为token)。

4) 控制流程,调用yacc生成的语法分析程序。

yacc 一般是将语法和语义操作封装为一个语法分析函数,名字是

(yyparse)。如果没有任何问题,yacc将为我们创建一个C程序文件,

我们可以使用任何的C编译器去编译该文件。值得我们注意的是语法

分析程序的入口必须命名为yylex。

因为每次yyprase在进行语法分析的时候都会去调用一个名为yylex

的函数。不过这一切都不是固定的。如果您有兴趣的话您可以在yacc

创建了C程序文件后,手工去修改一下函数的名字也可以的。

下面再为您介绍一下yacc的输入文件格式:

%{

C语句

%}

Yacc定义:词法标记(token),语法变量等

%%

语法规则动作

%%

其它的C代码

这就是一个yacc的输入原文件的格式,在经过yacc处理后会把输出

一个名字为y.tab.c的文件。该文件的格式一般是

%{和%}之间的C语句

第二个%%后的C语句

我真得很欣赏yacc 程序的设计者,它直接为我们生成C 文件,而不

是已经被编译过的目标文件。在这一点上,我觉得真是做得太好了。

其实在这一点上它体现了UNIX 的处理一般方法。其实该方法非常的

灵活。当我们有了新的想法我们甚至可以移植代码,正是因为这个原

因,我们得以将代码移植到WINDOWS平台上去。在本文的最后将为您

介绍如何将y.tab.c在WINDOWS平台下面的Visual Studio IDE环境

编译。不过yacc 确实强大,但是我们要想掌握它的话也是非常的不

容易,但是当您一旦会用了yacc 后您回发觉您掌握它而花费的努力

是值得的。它提供了一个可以随语言定义变化而随意快速生成C原代

码的一个途径。

下面我们首先来看看一个 yacc的输入文件

%{

/* 该部分在本文中未涉及到*/

%}

%token NUMBER

%left '+' '-'

%left '*' '/'

%%

list:

|list 'q' {exit(0);};

|list '/n'

|list expr '/n' {printf("/t%ld/n", $2);}

;

expr: NUMBER {$$ = $1;}

|expr '+' expr {$$ = $1 + $3;}

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

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

|expr '/' expr {$$ = $1 / $3;}

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

;

%%

#include <stdio.h>

#include <ctype.h>

int lineno;

void main(int argc, char *argv[])

{

yyparse();

}

int yylex()

{

int c;

while((c = getchar()) == ' ' || c == '/t')

;

if(c == EOF)

return 0;

if(isdigit(c))

{

ungetc(c, stdin);

scanf("%d", &yylval);

return NUMBER;

}

if(c == '/n')

lineno++;

return c;

}

yyerror(char *s)

{

fprintf(stderr, "%s", s);

fprintf(stderr, " near line %d/n", lineno);

}

我分别用3种颜色区别了yacc输入文件的3个部分,这部分程序

包括了许多的信息,在这里不一一解释,也不详细描述分析器如何来

工作。更多的信息请参考yacc手册。希望读者能更多的自己去思考。

首先来解释一下第一部分。提供选择的规则用|分割,当输入的语

法规则被识别出后,该动作将被执。对应的C代码动作在最后被体现

出来,如下所示:

它的基本结构是

| 动作标志 {C代码}

值得注意的地方是C 代码最后一定要写上;yacc 无法为我们检查

这一问题,只是这个问题将在最后的C编译器检查出来,我曾经就遇

见过一次,又是初学。都不知道该从什么地方去检查问题。$n($1,$2)

分别表示子成分的返回值,$$是整个表达试的返回值。一般来说$$

就是$1,除非用户将它设置为其它的值。那么在上面中,我们可以这

样去理解。一个换行符号(/n)可以被识别为一个list。换句话说就

是一个语句的结束点。这有点像我们的C 语法中必须要用符号(;)来

结束一句一样的道理。

如果您学过编译原理,我们也可以从推导式的概念上来理解这一

个问题。List 可以推导出(;)(expr;)这样2 种可能性。expr 可以同

理去这样理解。这样把问题推导下去。下图是一个语法分析过程的推

导树。

/n

list

2

expt

expr

+ *

toke

n

3 4

toke

n

toke

n

好了,我们继续再看看蓝色部分的代码,在当中使用了%left 来

指定结合的方式。这样意味着(a-b)-c不会被解释成a-(b-c),您可以

试着将%left改为%right看看有什么效果。

关于C 代码的部分,我不想做太多的详细解释了。您只需要记着

yyprase,yylex,yyerror 就行了。有yyprase 是yacc 程序将会为我

们按照我们的描述文法创建的解析器代码,yylex是词法分析的部分

(将句子打断为token 串的功能)yyerror 是yacc 程序统一的错误

出口点。

3 编译

在看懂上面的代码后,OK,下面我们继续看看如何使用yacc生成

语法分析程序,您完全可以使用您个人最喜欢的文本编辑器去编辑这

样一个文件,您只需要保证您编辑的文件的内容和上面的一样。我们

假设您保存的文件的名字是guoguo.y

在shell命令行中执行命令:

yacc guoguo.y

看看屏幕有没有什么错误的提示?如果有的话,请仔细检查一下

guoguo.y文件里面有没有不符合yacc输入文件格式的地方。如果没

有错误,那就恭喜您了,实在就太棒了。我们可以继续进行下面的实

验。

下面一步,我们查看一下当前目录下面是不是有个y.tab.c 的文

件。这简直太好了。yacc 已经为我们创建了一个可以按照我们指定

的文法进行语法分析的一个c原程序。这一切结果我们都要感谢yacc

的作者steve Johnon 先生。呵呵,还是回归正题吧。我们拿到这个

c程序可以直接编译连接它。试着在shell命令中执行命令:

cc –o guoguo y.tab.c。

编译它,如果一切顺利的话,我们将得到一个名字为guoguo的执

行程序。如果今天您运气差了点的话,可能会在cc 编译的时候会报

告一些错误出来,无法创建guoguo 执行程序。不过您不要紧张,让

我们一起来和您分析一下可能的错误。下面我把我在学习过程中曾经

遇到过的问题列举出来。

问题1:

cc -o guoguo y.tab.c

"hoc.y", line 10: error: Syntax error before or at: }

"hoc.y", line 21: warning: statement not reached

"hoc.y", line 43: error: Syntax error before or at: <EOF>

*** Error code 1 (bu21)

这样的错误,CC编译器已经值出在文件hoc.y的10行出了错误。

这样的错误一般来说是规则的对应动作C 函数没有;号结束符号.对

于稍有一些经验的程序员来说这样的问题其实很好定位的。但是为什

么我要在这里说这个问题呢?如果您观察够仔细的话,您也许会注意

到我们编译的是y.tab.c文件,我们用cc编译器是在编译y.tab.c 文

件呀?它怎么能知道是hoc.y 文件的第10 行出了问题了呢?oh my

god!我相信上帝不会和我们开这个玩笑的。这一点问题您必须要了解

清楚,其实它非常有意思的。仔细看看y.tab.c 文件后您知道,在

y.tab.c文件中加上了#line 的C预处理命令。这个#line 的预处理

命令是告诉C编译器如何定位y.tab.c的行号处理的。我在这里扩展

一下,如果您经常都在写UNIX下面的ec程序,通常我们常常可以在

日志中通过__FILE__宏来得到行号,要知道ec 程序都是被预先处理

成.c的文件然后再又C编译器去编译它。因为ec程序和c程序之间

一些EXEC SELECT,EXEC UPDATA……等代码的处理将首先被ec 预编

译器解释成一些C 代码。这样一来ec 程序的行数和C 程序的行数有

有差别了。但是__FILE__只能处理C程序的那个行号。那么我们是否

就无法知道在ec程序中的行号了呢?其实不然。在ec的预编译器中

都加了#line指令的处理。这就是为什么我们能直接能在日志文件中

看到EC 原程序的行数。而不是被预编译为C程序的行数了。如这样:

xxx.ec->L->38 了。至于#line 具体的工作是怎么完成的,我在这里

不再描述,读者可以自行去研究。

好了,这个就是我在编译y.tab.c 的程序的时候遇见了的一个问

题。当解决了这个问题后。就得到C 编译器为我们生成的执行程序

guoguo。执行一下看看

1+2 Enter

3

2*3 Enter

0

2.0+1.2 Enter

Syntax error near line 2

上面的Enter 部分是表示我在键盘敲入了Enter 键。千万不要以

为是我敲入了Enter字母哦!从上面的结果来看,好象有逻辑问题。

在加法运算的时候guoguo 程序能正常工作。但是剩法的时候缺没办

法正常的工作。另外一个问题,我们目前的代码无法完成浮点类型的

计算。这个结果有2个问题值得我们去思考。有兴趣的读者可以试去

解决它。

1. 为什么2*3的结果是0

2. 我们怎么完成浮点数的运算。

由于本文仅仅是入门级的对yacc介绍,所以例子也是个非常的简

单的例子,其实用用yacc 还可以完成更加复杂的语法分析的程序,

有兴趣的朋友建议可以在网上去搜索一下hoc6的yacc的原程序。它

对我们学习yacc将会有非常高的知道价值。yacc所创建的分析器程

序,是采用LALR(1)设计的,在《UNIX 程序员手册》中有对中有对

yacc 非常详细的描述。另外它还分析了其它类似的分析器产生器的

原理。也许您还可以设计出来采用递归下降法的代码生成器。

4 y.tab.c代码的移植问题

有时候我们可能需要我们的语法分析程序y.tab.c 能够运行的

WINDWOS平台上。其实不难,只需要将y.tab.c的程序复制到WINDOWS

上去。只是需要在编译的时候需要注意2点问题。

1. 屏蔽y.tab.c 中的#include <unistd.h>语句,在cl.exe 中无

这个文件。这个文件主要是好象描述一些POSIX 标准的头文件

的。在LINUX下面它主要描述0x80系统调用的函数原形。也许

在这一点TMD 有点搞笑,我不知道Visual Studio 环境中为什

么没这个文件。不过你还可以试着使用著名的DOS下面的djgpp

(GUN for windows (32bit)gcc 编译器)编译器去编译它,编

译出来的程序完成可以在32位环境下使用,但是它好象编译出

来的程序不符合PE 执行文件格式,NT 系统下面有些程序好象

无法正常运行!它可以不修改#include <unistd.h>一句。

2. 如果您是在命令行中编译y.tab.c编译的时候cl.exe中一定要

加上/D "__NO_GETTXT__" 参数。因为WINDOWS 平台上没有

_gettxt函数。如果您是在IDE需要在您在project->settings

中的C/C++标签中的Preprocessor definitions 中加上宏

__NO_GETTXT__。

5 结束言

首先,语言开发工作很有用,它可以使我们集中精力去完成语法

的规则的定义。例如我们的设计我们自己系统的脚本文件。然后yacc

又提供了给我们一个非常广阔的空间,可以让我们自由的去发挥。

其次,把工作当做语言来开发,而不仅仅是“写一个程序”,这个

思考方式是具有一定的意义的。它给我们指导了一个思想。一个程序

将组织为语言处理器要求句法规则,换句话说就是用户接口。并构造

实现它。这一点也许和我们传统的编程概念不太一样。其实它体现了

“语言”并不局限于我们传统的编程语言-它还包括了很多其它的东

东。

最后,UNIX下面的大量的小工具;单独或者是组合使用;帮助我

们完成了很多的机械的工作。这也许就正是显示它存在的价值和意义

吧。

欢迎大家能来邮件和我一起讨论本文中的一些相关的问题。至于

本文中提到的make 和lex 命令由于时间关系,我会在以后再为大家

描述。文中不正确的地方尽请大家能指正。

我将在下一期的文中,为大家整理一篇自己在曾经网络编程上遇

见的一些问题《TCP服务端编程一些问题》文章。

天用唯勤 阳凌

yl.tienon@gmail.com

2006-07-23
 

本文地址:http://www.45fan.com/a/question/72008.html
Tags: 工具 大的 yacc
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部