打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Lex和Yacc入门教程(八).使用堆栈编译语法
分类: C++/C/C# 2007-06-05 11:08 4571人阅读 评论(26) 收藏 举报

Lex和Yacc应用方法(八).使用堆栈编译语法

草木瓜  20070604

一、序

    前面一些系列文章着重介绍了递归语法树在编译理论方面的应用。本文则会介绍另一种
实现方式----堆栈。  
    堆栈在底层系统有十分广泛的应用,同样也十分擅长处理语法结构,这里通过实际示例
探讨如何构造堆栈完成语法分析。

    重要补充:下面是本系列文章全示例代码统一的调试测试环境,另对于lex,yacc文件需
要存储为Unix格式,这一点和Linux,Unix下shell很类似,DOS格式的Shell是不能够被执行
的,同样bison,lex编译DOS格式文件会出错误提示:
   
    Red Hat Linux release 9 (Shrike)
    Linux 2.4.20-8
    gcc version 3.2.2 20030222
    bison (GNU Bison) 1.35
    lex version 2.5.4
    flex version 2.5.4

    注:本站文章难免有错误疏漏之处。Lex,Yacc系列文章 http://blog.csdn.net/liwei_cmg/category/207528.aspx


二、具体示例

    本示例主要完成功能:
   
    1  支持整型,浮点型和字符串型
    2  支持变量存储,变量名可为多个字符
    3  支持整型,浮点型的+-*/()=运算法则
    4  支持字符串型赋值
    5  支持print打印整型,浮点型和字符串型
    6  支持打印变量值
    7  支持while if else switch四种控制结构,并支持控制结构的嵌套
    8  支持> >= < <= != == 六种比较运算,同时也支持字符串的比较
    9  支持 && || 复合比较运算
    10 支持对空格和TAB的忽略处理
    11 支持#的单行注释
    12 支持{}多重组合
    13 支持编译错误的具体显示
    14 支持外部变量值传入(整型,浮点型和字符型)
    15 支持外部变量获取(整型,浮点型和字符型)
    16 完整的企业应用模式
   
三、示例全代码(略)

 

  
A.stack.l
----------------------------------------------


B.stack.y
----------------------------------------------

 


C.stack.h
----------------------------------------------

 


D.stackparser.c
----------------------------------------------



E.public.h
----------------------------------------------

 


F.main.c
----------------------------------------------

 


G.mk 编译shell文件
----------------------------------------------

bison -d stack.y
lex stack.l
gcc -g -c lex.yy.c stack.tab.c stackparser.c
ar -rl stack.a *.o
gcc -g -o lw main.c stack.a


H.mkclean shell文件
----------------------------------------------

rm stack.tab.c
rm stack.tab.h
rm lex.yy.c
rm *.o
rm *.a
rm lw


四、思路说明

    上面列出的代码是目前最长的。
    可见写一个堆栈编译器并不是一簇而就的事情,即使对于目前的实例,也需要有很多完
善的地方。设计一个堆栈编译器,我们往往需要从最简单最容易的语句开始。

    A.简单的堆栈分析思想
   
    我们先举一个简单的例子,a=1+2。要完成这个公式的计算,我们首先需要将1和2,压
入堆栈,然后分析到+运算,此时又需要将1和2出栈,执行+法后将3压入。继续分析,需要
压入a,在最后的=运算时,将3和a出栈进行赋值运算后,将a入栈。运作序列如下:

  id:  0 act:  pushvalue                  
  id:  1 act:  pushvalue                  
  id:  2 act:        add                  
  id:  3 act:    pushvar                  
  id:  4 act:     assign
  
  使用堆栈进行编译的难点在于,将无比复杂的语法结构抽象到简单的入栈出栈操作。单
从这个角度讲,是很难一步倒位的。一般地,我们需要先将指令字符串根据设定的语法归并
规则编译成有序的指令序列。然后对指令序列制定堆栈动作执行函数,依次执行指令并调用
相应函数。

    值得欣慰的是,早在N年前,外国人就形成了一套十分强大的编译理论体系(lex,yacc)
去完成归并语法的工作。我们只需实现外部的规则动作。

    B.lex和yacc的归并语法设计
   
    与前面例子相似的是,使用G_Var存储编译时的变量信息,G_sBuff存储编译语句,这里
又增加了G_String统一存储编译语句中的所有字符串。至于lex和yacc设计方法也是相近的,
只是冗余了一些语句标志,如ifx,elsex,switchx等。这些标志是为了生成顺序正确的指令
序列。

    lex和yacc会把编译后的所有结果指令存于G_Command中。见AddCommand。
   
  /* 内存指令集结构 */
  typedef struct {
   
    int iTypeAction;
    int iTypeVal;
   float fVal;
   int iVar;
   int iString;
   int iControl;
  
  } TCommand;
  
  这个指令集的设计是关键所在,iTypeAction说明这个指令的类型,iTypeVal表示指令
的值类型。fVal存储整型浮点型数值,iVar存储变量索引,iString如果不是-1,则表示字
符串的索引,iControl表示指令返回的控制信息。


    C.堆栈编译
   
    lex和yacc编译后会把指令生成到G_Command中,随后对G_Command进行遍历处理,并调
用相关动作函数进行出入栈操作。(见Act系列函数) 这里出入栈操作的是G_Command索引,处
理的结果皆存于G_Command中,这是外人比较难以理解的一点。
    TCommand结构体元素是相对独立的,fVal,iString互斥,iVar标志变量索引,iControl
只用于控制堆栈的值。

    在刚开始时需要对各种语法结构做统筹分析。比如拿分支和循环这类语句来说,if分支
就需要维护一个控制状态,由于嵌套语句的存在,这个控制状态需要具有堆栈的特点。每次压
入新if语句要进行现有堆栈的判断,如果前一if为false,这个if即使为true也还是false,另
外else也要做相似处理。之后对于endif做出栈操作,标志这对if/else已处理。switch比if要
稍微复杂一些,还要记录原始值,每次case要做比较。while不仅需要条件还要进行跳转,这是
Act动作函数有返回值的重要原因。
    以上这个分析要进行比较体系化的考虑,这些也便于以后的功能扩展,如goto等。
    这里我引入了StackValue和StackControl两个堆栈。Value用于普通的顺序计算,Control
用于if else switch while等控制结构。关于控制堆栈,可以参见Act_If,Act_Else等控制
动作函数,在编译指令序列时,会读取控制信息来判断是否执行该指令。值的注意的是,Act
动作函数还返回了下一指令的索引,这主要用于对循环,跳转等方面的处理,默认是顺序执行。
    总得来说,TCommand的元素含义要独立,并严格保证处理指令时G_Command的指令数据的
合法性。


    D.变量传值和获取
   
    还是回调函数的思想,只是由于字符串的存在制造了一些小麻烦,所以使用了二级指针,
并通过返回值类型,来进行外部判断处理。不过Linux Unix下的gcc不支持引用传值,这里使
用的都是指针传递。
   

五、一些注意事项


    A.stack.l,stack.y 文件要求为Unix格式,这一点和Linux,Unix下shell很类似,DOS
格式的Shell是不能够被执行的,同样bison,lex编译DOS格式文件会出错误提示。

    B.SegmentFault多半产生于内存越界(前面已经着重说明过),除此以为还经常出现这类
情况,产生这类错误的位置的代码并无错误,但是内存值已出现乱码,这一般是指针使用不当。

    C.避免一切的warning项,比如在stack.y中,将函数的预说明去除,会提示warning,但
是在执行中,函数传值就会发生根本性的错误。

    D.还在在编译C/C++出现的堆栈溢出错误,当然可以用ulimit查看参数,但多半也由内存
越界有关,遇到莫名其妙的错误第一点就要想到内存问题。

    E.stack.l,stack.y 注意 规则应用顺序,shift-reduce的顺序
   
    F.耐心才是最重要的,尽量多打印一些调试信息,对于复杂的语法结构调试起来并不是很
轻松的事。


六、总结

    lex和yacc应用目前是在Unix/Linux平台下,生成的是C代码,固然C++使用这些接口没有
问题,但不能满足Windows平台的使用需求。从下文起会开始介绍Windows下这类工具的使用,
以及C/C++/Java代码的生成。

   
   
     

更多 0
查看评论
25楼 quweicunzhen 2012-03-10 00:27发表 [回复]
能不能讲一下这里使用到的yyvsp怎么用呀?我用的是win7+vc6.0配置,加上了预编译yydebug,加上了静态链接库,可是仍然有错误:“.\treeyacc.y(47) : error C2065: 'yyvsp' : undeclared identifier”!!!
谢谢了!
Re: pikaxuji 2012-06-20 11:21发表 [回复]
回复quweicunzhen:我也遇到了这个问题,用parse generator总会有些莫名其妙的问题,改成直接用windows版本的bison和flex就可以了,需要用MinGW的gcc编译
24楼 sashion 2009-08-23 22:00发表 [回复]
感谢你的文章,写得很精彩。
想问个问题,语法树会比用堆栈实现起来效率慢很多吗?我最近在关注这个技术,准备用于电信计费领域。
23楼 verygood 2007-10-22 14:58发表 [回复]
好文章,顶
22楼 cmg 2007-07-20 11:31发表 [回复]
楼上正解啊,也就是说用dev c++ ,更为简洁~
学习了,呵呵
21楼 hammergo@163.com 2007-07-19 23:20发表 [回复]
看说明书找到了答案
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* skip over program name */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
yylex();
}
20楼 hammergo@163.com 2007-07-19 22:17发表 [回复]
在windows 下可以使用Flex 和bison 代替Linux下的lex 和yacc,可以生成c代码,编译的话不要使用vc,它对标准支持不好,我用bcb 6.0直接编译也没有通过,可以要改一点东西,建议用dev c++ ,它是GNU开发的,windows下的gcc, 用Flex和biso生成的代码不用修改,可以直接编译通过。
不过,我还是有个问题想问一下,就是可以将生成的c文件,做成dll库,在其它程序里调用就行了,不过我看示例每次都是在命令行下输入文件,如果想输入IO流能行吗,怎么辚,输入文件路径好像可以,yacc和bison好像支持文件输入,能有人给我一点指点吗?
19楼 liwei_cmg 2007-07-17 16:54发表 [回复]
lex yacc 目前还没听说有支持C#的,这个归根到底要看.Net 是否提供了
18楼 hammergo@163.com 2007-07-15 08:56发表 [回复]
请问能生成C#代码吗?
17楼 cmg 2007-07-02 11:08发表 [回复]
原留言又丢了,再附上...

:)

:)


to CCXZXR
可以去下载Parser Generator 2 可以生成windows下的C/C++代码
16楼 CCXZXR 2007-06-28 15:07发表 [回复]
谢谢,以后会多关注你的
15楼 spiritx 2007-06-26 17:34发表 [回复]
写的非常好,我最近正研究这个东西
14楼 CCXZXR 2007-06-26 14:46发表 [回复]
这是编译的课程设计啊.直接用C写会没分的.帮帮忙啊
13楼 cmg 2007-06-25 20:41发表 [回复]
我晕,这个小程序,直接用c简单写一下即可啊,目的是让你练练手~不需要使用lex
12楼 CCXZXR 2007-06-25 15:36发表 [回复]
18、课题十八:将小写字母换为大写字母的LEX程序
设计内容
便写一个程序,它将一个正文文件中的全部小写字母均换为大写字母,并将其中的制表字符、空白字符序列均用单个空格字符进行替换。
a、给出算法的流程图或伪码说明。

不好意思,又要麻烦你了.这是我们的课程设计.上面那段不能在vc++上运行啊,能不能给个全一点的啊.可以发我邮箱里:ccx19850721@163.com.谢谢
11楼 CCXZXR 2007-06-25 14:52发表 [回复]
真的很感谢啊,我对这个一点都不懂.有问题还会请教你哦
10楼 cmg 2007-06-22 18:09发表 [回复]
这个简单一些,呵呵

upper.l

%{
#include <stdlib.h>
void yyerror(char *);
%}
%%
[a-z] printf("%c", *(int *)yytext-32);
. printf("%c",*yytext);
%%
int yywrap(void) {
return 1;
}


编译
flex upper.l
cc lex.yy.c -o parser -ll


输入

aabadf a3-3424958=3456
asdf
angnv.zxm,v04905ijdf
d
a;ldsgjp

输出

AABADF A3-3424958=3456
ASDF
ANGNV.ZXM,V04905IJDF
D
A;LDSGJP

其实就是字符值转换一下
a 97
A 65
9楼 CCXZXR 2007-06-22 16:25发表 [回复]
请教:将小写字母换为大写字母的LEX源程序
8楼 草木瓜 2007-06-13 15:34发表 [回复]
昨天发的评论又丢了!!!!


expr:
number
| VAR { AddCommand(D_ACT_PUSHVAR, D_VAR_INT, &yyvsp[0]); }
| expr '+' expr { AddCommand(D_ACT_ADD, D_VAR_CHAR, &yyvsp[-1]); }
| expr '-' expr { AddCommand(D_ACT_MINUS, D_VAR_CHAR, &yyvsp[-1]);}
| expr '*' expr { AddCommand(D_ACT_MUL, D_VAR_CHAR, &yyvsp[-1]); }
| expr '/' expr { AddCommand(D_ACT_DIV, D_VAR_CHAR, &yyvsp[-1]); }
| '(' expr ')'
;

其实这里也是利用 yyvsp 构建起一个外部 链表(只是为了简单使用了数组),个人感觉和你说得是差不多的,外部程序也只是操作这个数组进行实际的分析输出。
7楼 草木瓜 2007-06-13 15:26发表 [回复]
测试
6楼 jx 2007-06-12 02:24发表 [回复]
我只是不想在程序代码中使用yyvsp,建立一个堆栈,在语法分析的同时把内容存到堆栈里,通过对这个堆栈的操作实现输出。
5楼 草木瓜 2007-06-11 18:49发表 [回复]
互相交流~

“通过分析怎样在自己建立的文件上输出想要的代码?”
不是太明白你的意思,呵

yyvsp是由yacc内部维护,不使用,自已写语法分析?貌似不容易啊
4楼 jx 2007-06-11 02:25发表 [回复]
我想请问:
如果不使用yyvsp,通过分析怎样在自己建立的文件上输出想要的代码?对了,我使用的是c++语言和堆栈。

谢谢你!
3楼 jx 2007-06-10 00:22发表 [回复]
非常感谢,看了你的系列文章,对我的帮助很大。以后会一直关注你的文章!
2楼 草木瓜 2007-06-08 16:14发表 [回复]
我不知道你现在采用的方法是什么,堆栈还是树?

第一个问题
例:

x=2;
y=10;
while(y>x){
print(x);
x=x+1;
}

堆栈结构如下:

<Print>
id: 0 act: pushvalue iv: 0 fv:2 ic:0 is:-1
id: 1 act: pushvar iv: 0 fv:0 ic:0 is:-1
id: 2 act: assign iv: 0 fv:0 ic:0 is:-1
id: 3 act: pushvalue iv: 0 fv:10 ic:0 is:-1
id: 4 act: pushvar iv: 1 fv:0 ic:0 is:-1
id: 5 act: assign iv: 0 fv:0 ic:0 is:-1
id: 6 act: beginwhile iv: 0 fv:0 ic:0 is:-1
id: 7 act: pushvar iv: 1 fv:0 ic:0 is:-1
id: 8 act: pushvar iv: 0 fv:0 ic:0 is:-1
id: 9 act: > iv: 0 fv:0 ic:0 is:-1
id: 10 act: while iv: 0 fv:0 ic:0 is:-1
id: 11 act: pushvar iv: 0 fv:0 ic:0 is:-1
id: 12 act: print iv: 0 fv:0 ic:0 is:-1
id: 13 act: pushvar iv: 0 fv:0 ic:0 is:-1
id: 14 act: pushvalue iv: 0 fv:1 ic:0 is:-1
id: 15 act: add iv: 0 fv:0 ic:0 is:-1
id: 16 act: pushvar iv: 0 fv:0 ic:0 is:-1
id: 17 act: assign iv: 0 fv:0 ic:0 is:-1
id: 18 act: endwhile iv: 0 fv:0 ic:0 is:-1

循环操作,每次pushvar时,将查询内存中最新的变量值,进行比较。

第二个问题
其实
pushvar 后 加入一个运算符cos,并定义cos动作即可
1楼 jx 2007-06-08 12:53发表 [回复]
有一个问题想请问你:
对于如下的循环while(x>y){
x=sin(y);}
如何做到对于比较的变量x,y只实现输出功能;而对于复制语句中的x,y实现产生求导功能,即x=cos(y);

谢谢!
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
How To Build a Yacc
lex yacc 学习
make:yacc/lex:command not be found
《编译原理》这门课的作用
c++异常是如何工作的
编译原理及实践(China-Pub版) 下载
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服