构建编译器的工具文档格式.docx
《构建编译器的工具文档格式.docx》由会员分享,可在线阅读,更多相关《构建编译器的工具文档格式.docx(15页珍藏版)》请在冰豆网上搜索。
词法分析器将:
∙辨认出“while”是一个关键字
∙辨认出“47452”是一个十进制数字
∙报告字符串“fr%$glp:
)”是一个无法辨认的字符串
词法分析器就类似一个源代码的拼写检查器
一个象JFlex这样的词法工具,从一个指定的文件中读入词法规则,然后生成相应的词法分析器。
我们不妨假设某个程序员需要定义一个名为pronto的语言,于是他将语言pronto的有效词法规则写在一个叫'
pronto.flex'
的文件中。
然后再以命令行方式执行操作:
'
JFlexpronto.flex'
。
这样他就可以得到一个叫'
Lexer.java'
的JAVA程序,这个程序就是一个JAVA版本的pronto的词法分析器。
当然,这样一个单独的词法分析器仅有简单的功能,它除了可以告诉你程序是不是完全由合法的单词组成之外不能完成任何其他事情。
我们要讨论的词法分析器可以做更多的事情。
它们和句法程序共同完成一些任务(在下一个标题中我们会讨论这个问题)。
在典型的应用中,词法分析器实际上是被句法程序调用以完成以下任务:
o辨认出一个词,并将该词相对应的标志值返回给句法程序;
o传送给句法程序与这个被辨认出来的字符串相关的信息,或者是这个整数的数值;
o在辨认出单词之后,调用一些可编程的函数。
这三项任务中的第一项使得词法分析器可以支持句法程序完成它的最重要的工作,句法分析。
而其余两项任务对于句法程序最终生成目标文件是相当有用的。
句法分析器(又称句法程序)
继续上一节的话题,如同词法分析器检查拼写错误一样,句法分析器通过检查源文件来保证所有的“单词”都是按照有效的语法规则排列的。
举例来说,对于某种“新”语言,词法分析器将会判断下面六个单词有效,并将它们报告给句法程序:
{if+while}}-词法分析器只关心单词和符号的有效性,而不在乎它们出现的地方。
不过,句法程序将会指出‘{if+while}}’是一个不合法的组合。
这就好象是词法分析器只检查拼写错误,而句法程序检查句法错误。
[注意:
实际编译器中的词法规则都是通过非常简单的所谓“正则语法”表达式描述的,而语法分析器的语法规则则是通过较为简单的“上下文无关语法”表达式描述的。
相关描述可以参照Chomskyhierachy的Typicalcompilerdesignbooks一书中有相关的理论介绍(国内的朋友可以参考编译原理的相关书籍✍)]
象CUP这样的工具,是通过从指定的文件中读入相应语言的语法规则的定义来生成对应的句法程序。
我们继续使用虚构的新语言pronto,程序员需要将语法规则写入指定的文件‘pronto.cup’内,然后在命令行下执行‘javajava_cup.Main<
pronto.cup’,它会生成多个文件。
其中有一个文件就是语言pronto的JAVA版的句法分析器,‘parser.java’。
当然了,这样一个简单的句法分析器除了能告诉你程序是不是符合语言的语法之外,什么也做不了。
我们所要讨论的句法程序将要完成更多的任务。
在判断程序正确与否的同时,程序还要调用相应的代码来进行一些编码(例如,读到变量定义的地方时,需要对定义的变量进行记录✍)。
‘钩子函数’一般以两种方式来支持目标代码的生成:
在句法分析完成之后再生成执行代码;
在句法分析的同时就生成可执行的代码。
(这里提到可执行代码,还是指编译器而言,而不是前文所称的语言传换器✍)
面向应用语言
我们所要讨论的这些工具可以被用来开发那些成熟语言的转换器,如C,Pascal,FORTRAN,Perl等等。
它们组成了主要的开发项目。
这里我想讨论那些为“专用应用语言(ASL)”所编写的程序转换器,这是很时髦的项目,同时,也是很有用处的。
我要通过两个例子来向你们说明“专用应用语言”的含义。
例子之一:
一个通用的工业控制语言
让我们假设一个虚构的人物——Fred,他为一家生产工业控制器的公司工作。
当Fred被雇佣的时候,公司已经开发出一个强大的、用途广泛的、基于广泛应用的并行机制的控制语言。
但是最终成为这些语言的使用者的是客户,这些客户可能是化学工程师,或者机械工程师,或技术员等等。
他们即没有热情也没有兴趣去学习一门新的用途广泛的编程语言。
公司产品的应用范围十分广泛,在多个工业领域内得到使用——汽车、石油工业、卫星控制等等。
Fred被派去为所有可以应用的工业领域做语言的前端(frontend)工作。
在每一个工作任务中,语言都要被改造,以适应该领域的客户,让他们可以很容易的掌握和使用它。
换而言之,前端的性质类似是一个GUI,用户要填入各种变动以利用控制语言达到自己的应用目的。
总之,这些不同的前端都是完全不同的新的语言,它们的目标语言就是公司的多用途的控制语言。
每一个前端就是一个很好“专用应用语言(ASL)”的例子。
通过使用编译构造工具,给Fred带来了很多好处:
o可以检查用户源代码的语法和词法错误,并返回有意义的错误信息;
o构造每一个专用语言的过程都是相似的;
o那些语法和词法描述文件都具有高度可重用性。
例子之二:
基于模糊逻辑的通用决策包
模糊逻辑被证明不仅仅是在它的传统领域——工业控制,而且在决策领域也是非常有用的。
它可以用在股票市场的分析(可以使你减少损失?
)或者帮助企业选择研究和市场战略,以预测各种可能的情况。
让我们假设另外一个Fred——Sally,编制了一个通用的模糊决策系统。
它可以根据不同的初始化数据文件对不同的问题作出决策。
Sally希望将这个产品应用到不同的领域内。
但是,以前的用户占用了Sally的大量时间。
这个问题大大的困扰了Sally。
产品的用户都是各自领域的专家,他们编制初始化的数据文件以供模糊逻辑模型作出判断。
但随着时间的推移,专家们又有新的知识希望加入到模型内,这就需要不断的对初始文件进行修改。
由于初始文件的格式是较为严苛的,需要仔细编写。
而这些用户只是各自领域的专家,而不是程序员,于是在准备数据文件时就十分容易犯错误。
然后他们就很经常的和Sally联系,希望能够得到她的帮助。
他们往往需要Sally在很短的时间内解决问题,而为了自己的声誉和商业利益,Sally不得不去解决这些问题。
她的解决方案是编制一个可以自动生成初始文件的“傻瓜”前端,这个前端根据不同领域客户的术语和问题空间进行定制。
这个前端就是一个以初始化文件为目标语言的“专用应用语言(ASL)”。
这个转换器可以利用编译工具进行创作。
就象Fred为工业控制语言所做的那样。
转换器将保证数据文件在词法或者句法上都保持正确。
如果这个转换器做的十分出色,用户将会发现它是很有用的。
请注意:
这些客户在自己领域内的问题的描述方法是十分清楚的。
对于这些描述方法,作为一个程序员,Sally几乎是一无所知。
客户将问题用自己的描述方法表达出来,而传换器将它翻译成正确的初始化数据文件。
结语
以上两个例子明显有一个共同的主题。
Sally和Fred在设计前端“专用应用语言(ASL)”时必须和相应领域的客户紧密合作。
毕竟,这些ASL对那些不熟悉编程的领域专家来说也是相当重要的。
如果合作不成功,这个ASL就不会被接受,也就不可能开拓进一步的市场。
这个模糊逻辑的例子将是这个学期编译设计课的Project。
假设Sally不介意我们这样做。
✍:
译者注
版权所有(C)1999NJLUG
出版于第39期《Linux公报》1999年4月中文版第六期
第二部分:
安装JFlex和CUP-具体化的指南
RichardA.Sevenich,计算机科学系
1999年4月26日
这是一个系列文章的第二篇,第一篇登载在1999年4月号的Linux公报(Gazette)[见:
编译器
构造工具,第一部分]。
第三篇则提供一个经典的计算器的例子,它将作为本篇的姊妹篇,也登载
在本期的公报中。
0.0背景
JFlex和CUP软件包带有安装指南。
它们编写的很好,语言直白。
尽管如此,本篇文章还是给出了
一个非常具体的安装情形。
以作者的经验,这种具体化对那些新用户很有用,他们在第一次尝试时
会觉得较少具体化的安装指南的普遍性和伸缩性会带来一些困难。
不言而喻,具体化会失去可伸缩性。
这个指南是用于一个装有bash作为缺省的Shell,同时也有tcsh,但不是缺省Shell的Linux
盒子。
假设用户已安装有某个版本的jdk(JavaDevelopmentKit),或是等同的软件。
如果没有,
你可以在www.blackdown.org找到。
在作者使用的系统中,来自blackdown的jdk1.1.5-v5-glibc.tar.gz
压缩包下载在/usr/lib目录中,并展开在那儿。
作者欢迎指正和建设性的建议。
联系方式:
rsevenich@ewu.edu
1.0系统准备
注意在本节中一些具体的选择都是任意的。
对初始者来说,假设你已经登录到了你的主目录,比如说,
/home/jsmith。
所有的路径都将是相对于文件系统中的这个位置的。
现在如下建立新目录:
>
mkdirjavatools
mkdirjavatools/CUP
然后修改你的.bash_profile和.bashrc文件,包含所需的javaCLASSPATH等。
这里是一些示例行:
JAVB=/usr/lib/jdk1.1
JAVT=/home/jsmith/javatools
CLASSPATH=./:
$JAVB/lib/classes.zip:
$JAVT/JFlex/lib/JFlex.jar:
$JAVT/CUP
PATH=$PATH:
$JAVB/bin:
$JAVT/JFlex/bin
exportPATHCLASSPATH
注意:
目录/usr/lib/jdk1.1是用于指示从blackdown的tarball里解开的文件所放的地方。
在作者
的实际情况中,它是一个到/usr/lib/jdk1.1.5v5-980311/的符号连接。
要使你的这些修改起作用,你必须退出系统,再重新注册。
2.0JFlex
2.1得到JFlex
JFlex可以从www.informatik.tu-muenchen.de/~kleing/jflex/index.html#Download得到,是一个
tar.gz格式的文件。
在写本文的时候,该文件的名字是"
jflex-1.2.tar.gz"
这个tar包包括一个
自带安装指南的丰富文档。
2.2一个具体的安装情形
∙下载tar包,jflex-1.2.tar.gz,到javatools/目录中;
∙用命令‘tarxvfzjflex-1.2.tar.gz’在javatools/目录中展开tar包。
这样就会创建带有相关的子目录树的目录'
JFlex'
它将含有所有需要的东西,包括在'
doc'
子目录中
的文档。
这儿的文档内容详尽,指出了怎样用JFlex来创建独立的词法分析器(例如下面2.3节中的例子)
或是用于其它程序(如CUP)创建的处理器的词法分析器。
在目录JFlex/bin中你可以找到一个Shell脚本
程序'
jflex'
,它需要做如下修改:
∙设置JFLEX_HOME变量:
'
JFLEX_HOME=/home/jsmith/javatools/JFlex'
(根据你的环境)
∙设置JAVA_HOME变量:
JAVA_HOME=/usr/lib/jdk1.1'
2.3对安装进行测试
∙切换目录到javatools/JFlex/examples/standalone(例如,'
cd/home/jsmith/javatools/JFlex
/examples/standalone/'
)
∙执行该目录中的README文件中的指令,会得到如下的屏幕输出:
Hellosomeone!
Thisisasampleinputfileforthe
standaloneexamplescanner.
Haveaniceday!
3.0CUP
3.1得到CUP
CUP从www.cs.princeton.edu/~appel/modern/java/得到。
在那儿你能找到所需的源代码(压缩的tar包)
和用户手册。
这个手册可以html方式下载以提供一个在线的手册或者以适于打印的形式(如postscript)
下载。
下一节将讨论下载源代码的问题。
注:
上述的Web站点还有一个JFlex的变种,叫JLex。
3.2一个CUP的具体的安装情形,包括测试
∙下载压缩的tar包到javatools/CUP目录中;
∙切换到那个目录中(如,cd/home/jsmith/javatools/CUP)
∙展开tar包
∙键入命令行:
./INSTALL'
来运行安装/测试文件。
注意安装程序是一个csh,或它的替代物tcsh
的脚本。
所以,tcsh是需要的。
这样将得到一个成功的消息:
Installandtestwassuccessful'
4.0下一步
随JFlex和CUP下载的文档提供了足够的信息让用户把它们运行起来。
出版于第41期《Linux公报》1999年5月中文版第七期
[译者注:
]本篇介绍的是在Linux下使用Java版的词法生成器和语法生成器来构造一个编译器,本文的示例是经典的计算器的例子,即把计算器支持的表达式看作一种简单的语言来处理.这里,词法器是JFlex,即Flex的Java版,CUP全称是:
(JavaBased)ConstructorofUsefulParsers,相当于常用的Yacc.
用JFlex和CUP创建一个计算器
ChristopherLopes,东华盛顿大学学生
这是一个系列文章的第三篇,第一篇登载在1999年4月号的Linux公报(Gazette)[见:
编译器构造工具,第一部分]。
第二部份提供了详细的JFlex和CUP的安装指南,也登载在本期的公报中。
本篇的这个具体的例子是CUP手册中的那个计算器例子的修改版本。
特别的,我们包含了随同的用于JFlex的详细说明文件。
而且,这个文件以及关联的CUP的详细说明文件都有大量的注释。
计算器例子是显示如何使用lex/yacc系列工具的经典例子。
我们现在正在进行一个包含更深一步的例子的项目-一个用于模糊逻辑引擎的初始化语言,它用作决策产生应用。
如果对于这个项目的长期有足够兴趣的话,我们将准备一篇文章在这儿或别的地方发表。
∙使用JFlex
∙使用CUP
∙我们的计算器程序的主体
∙编译计算器程序
∙计算器的输入输出的例子
使用JFlex
这个项目中使用JFlex的目的是为我们的计算器创建一个词法分析器。
这个词法分析器,或者叫扫描器,将为我们计算器检查输入,而且确认所有的字符归类是有效的。
用于JFlex的词法分析说明文件可以分成三个部分。
每个部分由%%分开。
用户代码段
%%
参数设置和声明段
词法规则段
用户代码段
这个段中的所有内容将被拷贝到生成的词法类的类声明之前。
在这个段中,常见的是package和import语句。
我们的词法说明在这个段中引入(import)了两个类,sym和java_cup.runtime.*,如下所示:
importjava_cup.runtime.*;
importsym;
在我们的例子中,sym类(与处理器一起)由CUP产生。
参数设置和声明段
这个段含有参数,词法状态,和宏定义。
设置参数将包含额外的代码,它们将被包括在产生的扫描器类。
参必须另开新行,以%开头。
可以包含的参数很多。
在随JFlex来的手册中可以得到一个可以包含的参数列表。
在我们的词法说明中用到的参数如下:
%classLexer
%line
%column
%cup
第一个参数,classLexer,告诉JFlex把生成的类命名为Lexer并把代码写到名为Lexer.java的文件。
参数Line打开行计数,使你可以用变量yyline存取输入的当前行号。
参数column有类似的作用,除了它是通过变量yycolumn存取当前列号。
最后一个参数,cup,把JFlex设置成一个特殊模式,使它与CUP产生的处理器兼容,我们使用的就是。
然后,你可以声明扫描器用到的成员变量和函数。
可以加入的代码是Java代码,并放在%{和}%之间。
它们将被拷贝到生成的词法类源代码中。
在我们的词法说明中,声明了两个成员函数。
这些函数创建java_cup.runtime.Symbol对象。
第一个仅仅记录当前记号的位置信息。
第二个还包含了记号的值。
以下是到这个声明的连接。
Declarations
这个段的最后是宏定义。
宏用作正则表达式的缩写。
一个宏定义包含一个宏标识符,其后为一个=,然后是宏要代表的正则表达式。
如下是一个我们的词法说明中用到的宏定义的连接。
还有一个连接包含了一个列表,列出了创建正则表达式可用的东西,及每一项的含义。
MacroDeclarations
可用于创建正则表达式的列表
词法规则段
词法分析说明的最后一段包含正则表达式和当扫描器匹配了相关的正则表达式后所要执行的动作。
扫描器会激活具有最大匹配的表达式。
所以如果存在两个正则表达式"
to"
和"
too"
,扫描器会匹配"
,因为它是最长的。
如果两个正则表达式完全相同,具有相同的长度,那么扫描器将匹配最先列在说明中的表达式。
假设扫描器读进了字符串"
,它试图在下面所列的正则表达式中寻找一个来匹配所读入的。
第二个正则表达式是可选的,因为它包含的字符串类是可以匹配字符串"
的。
但扫描器会选择列表中的第一个正则表达式,因为它列在最前面。
"
[a-z]*
每个正则表达式可以附带动作,这样当扫描器匹配了正则表达式后就可以激活它的动作。
每个正则表达式的动作就是你可以写的Java代码片段。
你想要的动作可以是打印出一些东西,或是返回扫描器发现的标识符给处理器。
用于打印出扫描器发现的标识符并且返回给处理器的示例代码如下所示:
+"
{System.out.print("
);
returnsymbol(sym.PLUS);
}
-"
returnsymbol(sym.MINUS);
*"
returnsymbol(sym.TIMES);
/"
returnsymbol(sym.DIVIDE);
JFlex允许程序员定义特殊的词法状态(lexicalstates)用作开始条件来细化说明。
YYINITIAL是一个预定义的词法状态,是词法分析器初始扫描输入的状态。
它是我们将用的唯一状态。
所以,我们所有的正则表达式都将从这个词法状态开始识别。
然而,可以定义其它这样的状态,本质上说将建立一个状态机的新的分支的开始。
在下面的例子中,从YYINITIAL通过一个变换到达词法状态<
STRING>
在这个<
状态段定义的正则表达式将只能在这个分支中被识别。
<
YYINITIAL>
{
\"
{string.setLength(0);
yybegin(STRING);
="
{returnsymbol(sym.EQ);
=="
{returnsymbol(sym.EQEQ);
}
{returnsymbol(sym.PLUS);
{yybegin(YYINITIAL);
returnsymbol(sym.STRINGLITERAL,string.toString());
[^\n\r"
\]+{string.append(yytext());
在如上的代码中,扫描器将从状态YYINITIAL开始。
当它匹配了正则表达式\"
,也就是找到了一个双引号,它将把状态转换到STRING状态。
那么现在能够匹配的正