我做的YACC --1

前言

如果在WiKi中查找YACC。就可以找到这样的一段话:

yaccYet Another Compiler Compiler),是Unix/Linux上一个用来生成编译器的编译器(编译器代码生成器)。yacc生成的编译器主要是用C语言写成的语法解析器Parser),需要与词法解析器Lex一起使用,再把两部份产生出来的C程序一并编译。yacc本来只在Unix系统上才有,但现时已普遍移植往Windows及其他平台。

yacc的输入是巴科斯范式BNF)表达的语法规则以及语法规约的处理代码,Yacc输出的是基于表驱动的编译器,包含输入的语法规约的处理代码部分。

yacc是开发编译器的一个有用的工具,采用LALR(1)语法分析方法。

yacc最初由AT&TSteven C. JohnsonUnix操作系统开发,后来一些兼容的程序如Berkeley YaccGNU bisonMKS yaccAbraxas yacc陆续出现。它们都在原先基础上做了少许改进或者增加,但是基本概念是相同的。

由于所产生的解析器需要词法分析器配合,因此Yacc经常和词法分析器的产生器——一般就是Lex——联合使用。IEEE POSIX P1003.2 标准定义了LexYacc功能需求

 

 

此外还有一种很不错的YACC,也是开源的即LemonSqlite社区使用它生成Sql语言的语法分析器。

但是,你打开这些工具生成的代码yytab.c 你就发现这些代码几乎无法理解。开源的yacc的源代码如BisonLemonBerkeleyYacc我不了解)均是用C写成。代码中全局变量超多,函数名和变量名的采用不知含义的缩写。再者就是非常庞大,注释又特别少,有些文件除了头部版权声明的注释外,代码中没有一行注释。我学习编译原理时,曾对LALR(1)感兴趣,就尝试去看开源YACC的代码,没看到几百行就抓狂了。此外,开源项目的文档极少,也造成理解困难。于是我决心自己写一个LALR1)语法分析工具,依照《编译原理》(大名鼎鼎的龙书)和《编译原理及实现》(这本书没那么出名,但也是老外写的)给出的算法,做成功能弱化的YACC,才14个文件。所使用的语言是C++,在VC6.0编译通过。和其他版本的YACC相比,要小很多。我把它命名为WALL,其中W表示weak,如果弄好听一点可以叫轻量级。我看到很多命名例子,如C++的命名,和《编译原理及实现》所指的C-,均采用C语言的++操作符表示超集和子集:C++含义是C语言的超集,C-指为C语言的子集。但咱是中国人,象形才是中国人的思维传统,所以表示YACC功能子集,咱就把CC头部砍去,就得到LL。于是得到名字WALL了,表示弱化的功能不全的YACC

WALLYACC的很多特性都不支持的,如YACC%token%left%right%nonassoc

%{ %} ,%start,%union , %type%XXX符号均不支持,它仅仅生成一个包含LALR1)表的和语法分析函数的文件AnsyTable.cpp 语义驱动函数不能像YACC那样直接嵌入到语法文件中,而需要你自己到AnsyTable.cpp文件添加。语法文件中只能嵌入规约时的动作,而不能嵌入移近的动作,不过没关系,你可以在gettoken函数实现它。此外,YYTYPE默认定义为int,需要定义其他的类型,也要自己在AnsyTable.cpp文件中修改。不过您不用恐惧,与其他YACC生成的yytab.c相比,AnsyTable.cpp文件是很容易理解的,而且有大量的注释。

WALL在处理二义文法时,不能显式指定优先级,没有像YACC那样可以使用%left%right%nonassoc那样处理。你可以自己定义无二义文法来解决。如果你觉得不够,您可以自己实现这个功能。

WALL语法文件对YACC一些规则做了简化,无需学习很多规则就可以自己写语法文件。规则简化后实现方法也会简单一些。此外,WALL构造LALR(1)项目集的算法谈不上优雅,并没有在性能上做任何优化,不过咱使用时并没有明显感到速度慢哦。

WALL同样也不能报告移进与规约和规约与规约冲突,但它也像YACC那样提供LALR1DFA列表,你可以检查DFA列表发现冲突。

虽然WALL有这么多YACC特性不支持,但是我还是较快的用它生成一个简单的桌面计算器和一个Pascal语言子集的解释器。可能使用起来没YACC那么方便。咱写这个东西只是为了学习编译原理,现在已经不想再做这个东西了。

WALL是什么

基本类图

 

 

WALL的使用

工具

u       文本编辑器UltraEditor.也可以使用其它的,但最好是能支持运行外部命令。

u       Visual c++Window操作系统,实际它是使用并没有调用Windows任何API,可以直接到Linux系统下使用g++。但我只在VC下使用,所以没有写可以在Linux下运行的makefile

语法

语法定义:

非终结符 -> 产生式{规约动作};

产生式由0个或多个symbol(符号)组成。

其中每个Symbol(符号,不是字符)均由一个字符后跟零个多个字符或数字组成,用正则表达式表示就是[a-zA-Z][a-zA-Z0-9]* ,这里习惯使用全大写来表示终结符。小写字符串或大小写混用的字符串表示非终结符,实际上代码中把没有产生式中的符号当作终结符,有产生式的符号当作非终结符。

注意,与C/C++语言定义变量或标志符不同,我做的不支持下划线。

此外,文法规则必须是command->开始符号; YACC能自动添加这条规则,但我的不行。

一个简单的例子

1)        制作语法文件:

command->exp;

exp->exp ADD term

{

       DD = D(1) + D(3);

 

       printf("Hello/n");

};

exp->exp MINU term

{

       DD = D(1) - D(3);

       printf("reduce: exp->exp MINU term/n");

};

exp->term

{

       DD = D(1);

       printf("reduce: exp->term/n");

};

term->term MUL factor

{

       DD = D(1)*D(3);

       printf("reduce: term->term MUL factor/n");

 

};

term->factor

{

       DD = D(1);

       printf("reduce:term->factor/n");

 

};

factor->NUMBER

{

       DD = D(1);

       printf("reduce:factor->NUMBER/n");

 

};

factor->LP exp RP

{

       DD = D(2);

       printf("reduce:factor->LP exp RP/n");

 

};

2)        生成AnsyTable.cpp

3)        自行在main函数中添加如下代码:

int main()

{

       /*This is a model*/

       /* 1 + (2 - 3*1)*/

       int val = 1;

  int res ;

       parse(NUMBER,&val,&res);

       parse(ADD,&val,&res);

       parse(LP,&val,&res);

       val = 2;

       parse(NUMBER,&val,&res);

       parse(MINU,&val,&res);

       val = 3;  

       parse(NUMBER,&val,&res);    

       parse(MUL,&val,&res);

       val = 1;

       parse(NUMBER,&val,&res);

       parse(RP,&val,&res);

       printf("The result is %d/n",res);

       return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shell程序的具体要求如下: (1)本实验的user-sh程序设计不包括对配置文件和命令行参数的支持。User-sh应提供一个命令提示符,如“user-sh>”,表示等待用户的输入,执行命令输出的必要信息,然后再打印下一个命令提示符。当用户没有输入时,user-sh需要一直处于随时等待输入状态,同时在屏幕上显示一些基本提示信息。 (2)实现以下内部命令。 exit 结束所有的子进程并退出。 jobs 打印当前正在后台执行的作业和被挂起的作业信息。输出信息应采用便于用户理解的格式。jobs自身是一条内部命令,所以不需要先是在输出上。 history 列出用户最近输入过的N条命令,不论这个命令是否正确执行过。 fg%<pid> 把<pid>所标识的作业放到前台运行。若这个作业原来已经挂起则让其继续运行。 bg%<pid> 把<pid>所标识的已挂起的进程放在后台运行。 (3)进行前台和后台作业切换 user-sh应当在打印新的命令提示符之前等待前台运行的子程序结束。前台作业和后台作业的区别是:shell在前台作业执行完之前要一直处于等待状态。而在开始执行后台作业时要立刻打印出提示符,让用户继续输入下一条命令。 执行前台作业即在提示符后输入一个可执行文件的路径(绝对路径)即可,执行后台作业则需在可执行文件路径后加上一个“&”符号。 前台作业的执行总是优先于一个后台作业的执行,user-sh不需要在打印下一个提示符前等待后台作业的完成无论是否会有后台作业的执行,只要完成一个前台作业,便立即输出提示符。一个后台作业结束时,user-sh应当在作业执行结束后立刻打印出一条提示信息,后面会在命令语法分析程序中介绍相应的语法来支持后台作业。 user-sh通过处理组合键实现前/后台作业切换: Ctrl+Z 产生SIGTSTP信号,这个信号不是挂起user-sh,而是让user-sh挂起在前台运行的作业,如果没有任何前台作业,则该特殊键无效。 Ctrl+C 产生SIGINT信号,这个信号不终止user-sh,而是通过user-sh发出信号杀死前台作业中的进程。如果没有任何前台作业,则该特殊键无效
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值