收藏 分享(赏)

工业机器人编译器和解释器..docx

上传人:dzzj200808 文档编号:2956528 上传时间:2018-09-30 格式:DOCX 页数:272 大小:4.41MB
下载 相关 举报
工业机器人编译器和解释器..docx_第1页
第1页 / 共272页
工业机器人编译器和解释器..docx_第2页
第2页 / 共272页
工业机器人编译器和解释器..docx_第3页
第3页 / 共272页
工业机器人编译器和解释器..docx_第4页
第4页 / 共272页
工业机器人编译器和解释器..docx_第5页
第5页 / 共272页
点击查看更多>>
资源描述

1、(基于 Java)编写编译器和解释器-简介(连载) Posted on 2012-07-05 13:21 Bang 阅读(584) 评论(0) 编辑 收藏 本书内容是编写编译器和解释器。强调“编写” 是因为真的是写了很庞大数量的代码。如果你想学习怎么去写编译器、解释器,交互式源码级调试器,带图形界面(GUI)的集成开发环境(IDE),那么本书 很适合你。所有代码使用 Java编写,我会解释里面的细节。本书不是讲写编译器隐含的理论,那是教科书干的事。如果你想学理论,它不适合你。不过我希望你 顺着本书完成代码后,能有股冲动去学习相应的基础理论。 本书的第一版使用 C作为实现语言,第二版用 C+,第

2、三版采用 Java. 尽管我会保留早期版本的结构,理念和方法,但这是一次真正彻底的重写。你会学到什么东西?通 过本书你将学到如何采用高级语言编写用于处理程序的编译器和解释器。(1) 你将会写能够执行程序的解释器。(2) 在引入调试器后,你能够在程序执行时,通过设置断点与解释器交互,显示调用栈,查看和修改变量值,还有逐行的单步调试。(3)在引入 IDE后你能在观察屏 幕程序过程中用鼠标做所有事情。(4)你还会学习到写为 JVM生成目标代码的编译器(也就是生成 JVM 汇编代码),这样能够在多个平台上运行编译后的程序。当然,因为编译器,解释器,IDE 都是用 Java写的,也能在多平台上运行他们。

3、你 写的编译器解释器将处理的源程序语言是 Pascal。我基于几个理由选择Pascal。首先它是个真的编程语言,不是为本书需要杜撰的。Pascal 是在 70年代中期和 80年代非常流程的面向过程的高级语言。这个语言有一个相对来说比较直白的语法但包含很多使编写充满乐趣的语言特性,比如结构化的用户自定 义类型,嵌套作用域,值传参/引用传参,完整的控制语句集等等。Pascal今天仍活着,你可在网上下载免费的 Pascal编译器和解释器,与你自己写的比比软件工程方法编译器和解释器很复杂,搞成功不容易。为处理这种复杂度,我采用软件工程方法诸如设计模式,统一建模语言(UML)图和其它现代面向对象的设计实

4、践让代码更好理解好管理。贯穿全书,特别是早期的篇章,设计笔记 指出相关设计要点比如用到的设计模式,我为何选用这种方式架构代码等。我坚信的方法是 增量开发,每一步完成少量代码,基于上一步构建下一步可运行的代码。基本上每章包含一个主工作程序和其它简练的增量代码。每章的程序构建以上一章的代码为基础。(提醒你不要跳跃式看书)书的组织方式第 一章是简介。第二章讲述了编译器和解释器的框架,怎样设计和测试框架为让后续的成功编写打下个好基础。紧接着两章关注基本(语言)转换工作:词法扫描(第 三章),符号表(第四章)。接下的几章会构建一个可运行的解释器。基于增量开发方法,这些章会迭代语法分析和解释好几次,每次增

5、加点新的语言特征。第五章 分析表达式和赋值语句,第六章解释她们;第七章分析控制语句,第八章解释;第九章分析声明(Declaration),第十章做语法检查,第十一章分析过 程(Procedure),函数(Function)和程序(Program)。第十二章完善解释器使其能够运行整个 Pascal程序。接下来的两章基于前面的工作。第十三章增加了一个能够通过命令行输入命令与之交互的源码级调试器。第十四章将命令行调试器以 GUI封装成为一个IDE,你初次通读时可以跳过这两章而不失连续性,但以后一定要看,因为它讲了不少真的很 NB的开发工具。本 书的最后部分讲述基于前面章节开发编译器。第十五章介绍了

6、Java虚拟机(JVM)的结构和 Jasmin即编译器生成的 JVM汇编语言。同样还是增量方 法,第十六章编译程序,赋值语句和表达式。十七章编译过程和函数调用以及字符串操作。十八章通过编译控制语句,数组和 Record(后续将会以纪录称呼)来完善整个编译器。末尾的 19章简要介绍前面章节没有涉及到的编译器编写相关话题,比如代码优化和表驱动词法扫描和语法分析器。哪儿能得到源程序你能在网页 http:/www.apropos- (不过建议你看每章中经过汉化改写的中文版代码,注释更全面)下载本书用到的所有源程序。你会找到怎么下载,编译,安装,运行程序的介绍。上面还有很多 Pascal测试程序。 (基

7、于 Java)编写编译器和解释器-第 1章:介绍(连载) Posted on 2012-07-11 10:51 Bang 阅读(1363) 评论(0) 编辑 收藏 本章描述了本书的目标和用到的方法并鸟瞰编译器和解释器的全貌。目标和方法本书讲授编译器和解释器的基本写法,目标是呈现给你怎样设计和开发它们: 用 Java 写的编译器,编译 Pascal(一个高级的面向过程的编程语言)的一个主要子集。(即包含主要的语言特征,但去掉一些为写编译器方便而去掉的无关大雅的特性) 。 用 Java 写的解释器且包含一个交互式的符号调试器( 符号调试器即基于符号表,而不是基于机器的指令集、硬件的调试功能),解释

8、同样的 Pascal 语言子集。 带图形用户界面的集成开发环境(IDE)。这个 IDE 是你看到的功能全面的开源的Eclipse 或者 Borland 的 JBuilder 等 IDE 的一个简化版。不过,它也包含一个源程序编辑器和一个交互界面用来设置断点,单步调试,查看和修改变量值等等其它。达 成这些个极具野心的目标是个大挑战。好的技能将会帮你如何如把程序编译成为机器语言或解释执行程序。现代软件工程法则和优秀的面向对象设计思想将会给你呈 现怎么通过代码实现一个编译器或解释器而最终所有组件能良好协作。编译器和解释器程序大且复杂。开发个小程序仅需要某种技能即可,然 NB的程序如编译器或 解释器还

9、需要软件工程法则和面向对象设计。因此本书强调必备技能,软件工程法则和面向对象思想。什么是编译器和解释器编译器和解释器的主要目的是“翻译”由高阶(High-Level)源语言写的源程序。把源程序翻译成什么样是接下几个段落的主题。本书中源语言为 Pascal的一个大子集,换句话说,你能够编译或解释正规的Pascal程序。因为编译器和解释器是用 Java写的,实现语言是 Java。Pascal编译器将 Pascal源程序翻译成为低阶(Low-Level)的某具体机器的机器语言(更准确的讲是 CPU的机器语言)。通常源程序是文本格式。如果编译器工作正常,对应的机器语言和最初的 Pascal源程序殊路同

10、归(一样的行为,只不过呈现方式不一样。比如你用钥匙而偷车的直接电线打火发动汽车一样)。机器语言是目标语言,编译器生成用机器语言组成目标代码。代码生成之后,编译器任务就算完成。目标代码一般写到文件里(一般是二进制文件)。一个程序可包含数个源文件,而编译器为每个文件生成一个目标文件。一个名叫“链接器”(linker)的辅助程序将这些目标文件的内容连同运行时库程序合成到一个计算机能够加载和执行的目标程序(如 windows的 PE程序)中。库程序一般来自于预先编译好的目标文件。因为机器语言不好记,编译器可生成汇编语言作为目标语言,汇编语言离机器语言只有一步之遥。通常每个汇编指令都有机器语言的指令与之

11、对应。如果你掌握了短助记名(比如 ADD和 MOV等)汇编语言好记多了。汇编器(另一个编译器)将汇编语言翻译成为机器语言。图 1-1 概括了将一个或多个源程序编译成为目标程序的过程。图 左边展示了将一个包含三个源文件sort1.pas、sort2.pas、sort3.pas的Pascal程序翻译成为三个相应机器语言目标文件 sort1.obj、sort2.obj、sort3.obj。链接器将三个目标文件(连带相关运行时库) 合成为一个可执行的目标程序 sort.exe。图右边展示了编译器将Pascal源文件翻译为汇编语言目标文件 sort1.asm、sort2.asm、 sort3.asm,接

12、着汇编器将其转化为机器语言目标文件。最后链接器产生目标程序sort.exe。图 1-1那么编译器和解释器到底有和不同?解 释器不生成任何目标程序,相反它读进源程序就会执行。这好比你被一个Pascal程序把住手,按照它说的某种语句读进顺序去做。你可以在一张草稿纸上记下 程序的变量值直到程序结束才输出每条语句的输出结果。本质上你做的正是 Pascal解释器干的事情。Pascal 解释器读进程序,执行程序。没有任何目标 程序需要生成和加载,相反,解释器将程序翻译成为一系列用来执行程序的动作(Action)。比较编译器和解释器该如何决策何时用编译器和何时用解释器?当你把一个源程序交给解释器,解释器接管

13、检查和执行。编译器也检查但生成目标代码。运行完编译器之后还有运行链接器产生目标程序,且还需加载目标程序到内存中去执行它。如果编译器生成汇编语言代码,你还得运行汇编器。所以很显然解释器需要更少步骤。解释器比编译器更常见。你可用 Java写个 Pascal解释器运行在基于微软Windows的 PC上,苹果的 MAC(麦金塔)或某个 Linux主机上,解释器能够在前面提到的平台上执行 Pascal程序。而编译器必须为某个具体的机器生成代码(无论直接生成或间接通过汇编器生成)。所以即使你要把原来为 PC写的Pascal编译器放到 MAC上运行,它生成的代码仍旧是 PC的,如果想让它为 MAC生成代码,

14、你可能得重写编译器的某些部分。(接下来讨论的编译器将问题的重心放在为 Java虚拟机生成代码上,因为虚拟机能够运行在很多平台上。所以为具体机器生成代码先放一边,有兴趣可以将虚拟机替换成为真实 PC机上生成 x86指令看看)如果源程序中包含逻辑错误,比如除值为 0的变量,直到运行时才发现,那么会发生什么情况?因 为解释器在执行程序过程中控制一切,它能停下来告诉你出问题的行数和变量名称。它甚至能提示你在继续执行程序之前可以做哪些正确操作比如修改变量值为非 零。解释器可包含一个交互式的源级(source-level)调试器,俗称符号调试器(symbolic debugger)。符号调试器意味着你可用

15、程序中的符号,比如变量名。另一方面,由编译器和链接器产生的目标程序通常自我运行(由机器执行,无需第三方)。源程序有关行号和变量名等信息在目标程序中不可见。当运行时抛错,程序简单中断,还可能打印一条包含出问题指令地址的消息。于是找出源程序中相关语句变量除零的问题就交给你了, 。所以通常就调试来说,解释器才是正道。有些编译器在目标代码中添加一些额外的信息,这样当错我发生时,目标程序能打印出相应的问题行数和变量名等。于是你改正错误,重新编译,然后重新运行。生成额外的信息会导致程序执行的比正常要慢(这也是 Visual C+为什么有 Run/Debug编译模式)。这提示你在认为程序到达最终“产品”版本

16、后,应关掉调试特征重新编译。假 设你已经成功调试好程序,那重点将是怎样使运行更快。因为机器能够以最快速度执行原生机器语言程序,编译程序能够比解释器快好几个量级。显然就速来来说编 译器是胜者,当优化版编译器知道怎么生成具体场景的优化代码的情况下尤其确定。所以是否使用编译器或解释器取决于程序的开发和执行谁更重要。理想情况是一 个带符号源级调试器的解释器用在开发过程中,一个生成机器代码的编译器在程序调试 OK之后以求更快的执行速度。这些就是本书的目标,因为它编译器,解释器 都教。情景变得有点模糊编译器和解释器的差异很容易说明清楚,但是随着虚拟机的快速流行,情景变得有点模糊。虚拟机是一个用来模拟机器(

17、计算机)的程序。此程序能够运行在不同的真实计算机平台上。举个例子,Java 虚拟机(JVM)能够运行在基于微软 Windows的PC上,苹果的 MAC(麦金塔),Linux 系统和其它很多平台上。(比如Sparc,IBM 小型机等)。虚拟机有自己的虚拟机器语言,而虚拟语言指令被真实宿主机所解释。那么如果你写了一个翻译器将 Pascal源程序翻译成为被宿主机解释的虚拟机语言,这个翻译器算编译器还是解释器?不斤斤计较了,我们本书约定如果一个翻译器将源程序转化成为机器语言,不管是真实的机器语言还是虚拟机器语言,那么这个翻译器就是编译器。翻译器没有优先生成机器语言去执行程序的就算解释器。为什么学习编译

18、器编写技术?我 们都想当然的认为对编译器和解释器学习了个大概,因为你在开发中需要聚焦在编写和调试程序上,你甚至不需要思考编译器的工作机制。你或许仅仅在搞错语法编 译器抛出错误信息后才留意到编译器的存在。如果没有语法错误,那么编译将会生成正确的代码无疑。如果你的程序运行失常,你有可能怪罪编译器,但大多时候, 你会发现错误在你的程序中。以上情形通常会出现在你在使用某个流行编程语言(比如 Java或 C+)它的编译器、解释器和 IDE都给你准备好了的时候。这先聊到这。不过最近我们看到很多新编程语言在被开发。驱动力包括 www(比如 HTML5)和与基于 web的应用相适应的新语言(典型比如 PHP,

19、纯 web)。对程序员生产力的更高要求催生与具体应用领域紧密结合的新语言(这个可以举很多例子,比如为系统管理员的各种 Shell语言,为数据库开发的各种 SQL/NO SQL语言,为电路板/DSP 开发的类 VHDL语言等,为工作流开发的各种 BPM语言等)。你可能非常期待自己有天能发明个新脚本语言表达算法或控制与你领域相关的流程。如果你要发明新语言,对应的编译器和解释器必不可少。编译器和解释器本身很好玩,但你前面注意到了,任何一个都不是个小程序,要开发成功相关的技能,现代软件工程法则和良好的 OO设计思想必不可少。除了学习编译器解释器工作机制带来的满足感外,你也要笑着面对编写它们带来的挑战。

20、概念设计为接下几章做准备,让我们重温编译器和解释器的概念设计设计笔记程序的概念设计是它的软件架构的一个高级视图。概念设计包含程序的主要组件,它们怎么组织,相互之间的交互细节等。它不需要说明组件怎么实现,更确切的说,它可让你先确认和理解组件而无需担心最终怎么去开发它们。你 可将编译器和解释器归为程序语言翻译器。如前面解释的那样,编译器将源程序翻译成机器语言而解释器将之翻译成系列动作(Action)。站在最高角度看翻 译器,它包含一个前端(front end)和一个后端(back end)。遵从软件重用法则,你将看到 Pascal编译器和 Pascal解释器共享前端,但有不同的后端。翻译器的前端读

21、入源程序然后执行最初的翻译过程。它的主要组件有parser(分析程序,解析), scanner(更学院派的说法是 Lexer即词法分析器,扫描仪),token(最小语言单位,最大词法单元,象征,表征)和 source(表示源代码)。paser控制前端的翻译过程。它不断的从 scanner读入 token,根据 token串(就是 token模式)判定当前正翻译的高阶语言元素,比如算术表达式,赋值语句,过程申明等。parser 检验源程序的语法是否正确。paser 干的事情称之为解析(parsing),parser 分析源程序然后将之转换。(转换成啥?后面会有,一般为抽象语法树之类的中间层)sc

22、anner一个接一个字符读入源程序的内容,然后构造 tokens即源语言的低阶元素。例如 Pascal tokens包含关键字如 BEGIN、END、IF、THEN 和 ELSE,标识符即变量、过程、函数名称(identifier,又称 ID)以及特殊符号如= := + - *和/ 。scanner 干的事情称为扫描(scanning)。scanner 扫描源程序,将之分成一个个 token。图 1-2 展示了编译器和解释器前端的概念设计图 1-2此 图中,箭头表示一个组件给另外一个发送命令。parser 告诉 scanner要下一个 token。scanner 从 source中获取字符然后

23、构造新的 token。token 也从source中读入字符。(13 章会讲到为何 scanner和 token组件都需要从 source中读取字符)编译器最终将源程序翻译成机器语言目标代码,所以后端的一个重要组件是代码生成器(目标代码生成器 code generator)。解释器执行程序,所以其后端的首要组件是执行器(executor)。如果你想让编译器和解释器共享前端,那么它们不同的后端需要有个通用接口用来与前端打交道(也就是只需要将前端传入这个接口即可)。记住前端处理最初的翻译过程。前端生成作为公共接口中间层的中间代码(intermediate code,分析树/语法树,抽象语法树等)和

24、符号表(symbol table)。中间码(intermediate code)是源程序的预摘要格式(pre-digested,可以理解为在源程序格式和机器语言格式中间的一个摘要格式,一般为分析树 parse tree或语法树 syntax tree)为方便后端的更有效处理(假设翻译器将塑料翻译成为瓶子,那么源程序为塑料,中间码为瓶盖,瓶身,包装纸,这样后端就能更快的装瓶子)。本书中的中间码是一个驻内存表示源程序语句的树状数据结构(也就是语法树,废话一堆啊)。符号表包含源程序的符号信息(比如标识符)。编译器的后端处理中间码和符号表,生成源程序对应的机器语言。解释器碰到中间码和符号表就直接执行了

25、(通常是树遍历过程)。为软件重用,你可将中间码和符号表设计成语言无关的结构。换句话说,你可用同样的结构应用于不同的源语言。因此,后端同样可以语言无关,当它处理这些结构(中间码和符号表)是根本不需要知道具体源语言。图 1-3 展示了一个更为复杂的编译器和解释器的概念设计。如果你万事安好,仅需前端知道源语言定义且仅需后端知道区分编译器和解释器。图 1-3 一个更完整的概念设计第 2 章开始通过设计一个编译器解释器框架来充实概念设计。第 3章讲的是扫描(scanning)。第 4章构建第一个符号表,第五章生成最初的中间码。第 6章 开始编写执行器(executor)且增量式开发直到 14章,其中包含

26、符号调试器和IDE。代码生成直到在 15章学了了 JVM架构之后的 16章才涉及。语法和语义(syntax and semantics)编程语言的语法是一系列规则用来断定用此语言写的语句或表达式是否正确。语言的语义传达语句和表达式的具体意思(赋值谁赋给谁,循环终止条件是什么)。举个例子,Pascal 的语法告诉我们 i := j+k 是一个有效的赋值语句。它的语义是说将变量 j 和 k的当前值加起来,然后将和赋给 i。parser 基于源语言的语法和语义执行有关动作。扫描源程序抽取 tokens是语法动作。查找赋值语句 := 之后的目标变量是语法动作。将标识符(identifiers) i、j

27、、k 当作变量存入符号表或日后在符号表中查找是语义动作,因为 parser必须明白当前表达式和赋值的意思才知道得用到符号表。生成代表此赋值语句 的中间码属于语义动作。语法动作尽在前端发生,语义动作在前后端都有。在后端执行程序或者生成目标代码需要知道语句的具体意思,所以是语义动作一部分。中间码和符号表存储语义信息。词法,语法和语义分析词法分析是扫描(scanning)的正式说法,所以 scanner也称词法分析器(lexical analyzer)。语法分析是 parsing(解析,parser 的主要任务)的正式称谓,语法分析器就是 parser。语义分析主要是检查语义规则是否完整。类型检查(

28、type checking)就是一例,它确保操作符(operator)的操作数(operand)类型保持一致。其它的语义分析操作有构造符号表和生成中间码。(基于 Java)编写编译器和解释器-第 2章:框架 I:编译器和解释器-第一部分(连载) Posted on 2012-07-11 14:50 Bang 阅读(778) 评论(1) 编辑 收藏 本章将会从前一章的概念设计带你到初级的实现过程。你将先为编译器和解释器构造一个灵活的框架,接着将初级版的编译器解释器组件集成到框架中。最后编写端对端的测试用例检验这些框架和组件。= 本章中文版源代码下载:svn co http:/ 源代码使用了 UT

29、F-8编码,下载到本地请修改!目标和方法此章的设计方法首先会让你觉得过于繁琐啰嗦,的确,本章结束后将会有一大堆超过你预期数量的代码。但请记你在用早被证明的软件工程法则和优秀面向对象设计构建编译器和解释器。如 在概念设计中描述的那样,编译器和解释器将尽可能复用组件,因只有后端有所不同。在这章中,你将构建一个灵活的框架并首先放置那些已被深度简化的编译器和 解释器组件。不过它们足够验证你设计的框架是否恰当即组件能很好的耦合并能协同工作。这个成功前提将会使得从公用前端到编译器解释器后端的端对端执行代码 编写,还有后续的增量式组件开发变得简单。本章的目标是: 一个语言无关的框架,可支持编译器和解释器。

30、集成进框架前端(front end)的初级版 Pascal语言相关组件。 集成进框架后端(back end)的初级版编译器和解释器组件。 通过从公共前端生成源程序清单以及从编译器或解释器后端生成消息,简单的运行端对端测试,测试相关组件。设计笔记不管任何时候开发负责程序如编译器或解释器,成功的首要步骤是: 设计和实现一个合适的框架。 开发能与框架良好集成的初级组件,且这些组件也能良好集成在一起。 通过运行简单端对端代码来测试框架和组件的融合程度。早期的组件集成是关键,甚至你已经简化了初级组件(没有完善的组件称之为初级组件)也一样。尽可能早的测试矿建和组件以让它们更好的协作。框架和初级组件组成你后

31、续开发的基础。开发将是增量式的进行,代码在每次增量后都能继续工作(附加更多功能)。你该永远基于可运行的代码去构建。语言无关的框架组件基于概念设计,框架包含三个包:frontend、 intermediate、 backend。框架组件是用来定义框架且语言无关的接口和类。有些是抽象类。一旦框架组件就绪,你能开发抽象类的 Pascal实现(组件语言无关,实现语言相关)。图2-1 展示了使用 UML 包和类图的框架组件。图 2-1:在 frontend,intermediate,backend 包中的语言无关组件一起定义了一个能支持编译器和解释器后续开发的框架。设计笔记统一建模语言是一个工业级的展示

32、面向对象软件架构和过程的图形化语言。各种图表(序列图,类图等)能表示程序的结构组件之间的静态关系,也能表示组件运行期的动态行为。前端在前端包中,语言无关类 Paser,Scanner,Token,Source 代表框架组件。框架类强制你在忽略具体源语言的情况下,能尽力思考每个前端组件的职责,还有它们之间的交互。图 2-2中的 UML 类图展示了它们的关系。图 2-2Parser 和 Scanner是抽象类;语言相关的子类将实现它们的抽象方法。parser和 scanner联系紧密,Parser 有一个受保护域 (protected field)scanner 指向 Scanner。Parser

33、 从 Scanner请求 token,所以它依赖 Token。Scanner 有一个私有域 currentToken,它通过受保护域 source引用 Source,还将 source引用传给每个自己构造的 token。每个 Token也能通过受 保护域 source拥有Source引用,在它的构造过程中,通过 source读取字符。图 2-3的类图更进一步展示了四个前端框架类。它展示了域,方法和其他的前端类和接口。例如每个 Token有一个用 TokenType类表示的 token类型,EofToken是 Token的子类。按 照概念设计,parser 控制翻译过程,它翻译源程序,所以 Pa

34、rser类有一个抽象方法 parser();语言相关的方法实现将不断的找 scanner 索取下一个token。Parser 的 currentToken()和 nextToken()仅仅是 scanner的代理方法而已(参考代理模式,不过这儿是为了少写点代码)。语言相关的getErrorCount()方法实现返回语法错误数量。设计笔记在 UML 类图中,一个未填充箭头的箭号表示一个类引用或依赖另一个类。虚线箭号(比如从Parser到 Token的肩头)表示一个仅仅在方法调用期间(比如 Parser 的 nextToken()方法返回一个 Token对象)存在的引用。实线箭号且在出发端有一个空

35、菱形意味着一个类通过在对象生命周期持续的引 用,拥有(owns)或聚合(aggregates)另一个类。(假设类 A通过引用域 ref聚合类B,那么类 A的对象 a1聚合类 B的对象 b1的这种关系在 a1的生命周期一直存在,聚合相当于包含,a1 负责 b1的生命周期)。 域名称保存标识箭头的引用(例如,Parser 类用它的 scanner域维护对 Scanner类的引用)。实心箭号带空箭头(如 EofToken类到 Token类)表示一个子类到它的父类。类名称下,一个类图可选择的包含域(field)描述区域和方法描述区域。标识箭号名称的域名不在域描述区域出现(Parser 有个域 scan

36、ner引用类 Scanner,它不在 Parser的域描述区域出现,在生成代码后就会有)。在域名或者方法名前面的字符表明访问控制。 + :public 公共 - :private 私有 # :protected 受保护 :package 包权限,即此包中的其它类都可以访问。跟在域名或方法名冒号后面的分别是域类型或者返回值类型。为省地方,类图通常不显示构造函数和域名的 getter和 setter方法。抽象类名以斜体出现。抽象方法名还是斜体。scanner 从源程序中抽取 token。Scanner 类抽象方法 extractToken()的语言相关实现将会根据具体语言从 Source中读取字符

37、,以便构造 Token。Scanner的快捷方法 currentChar()和 nextChar()会调用 Source类中的对应方法(还是代理模式)Token 的域保存有关 Token的有用信息,包括类型,文本串(即字面上的字符串),值和它在源程序中的位置(行号和位置【相对于行】)。Token 同样有 Source类的快捷方法 currentChar()和 nextChar()。Token 类型与具体语言有关。当前的 Token类型是一个占位符(因为一个具体类型都没有)。后面你将会根据具体语言创建语言相关的 Token子类。但目前只有语言无关EofToken子类,它表示源文件终止。使用 To

38、ken子类使得 scanner代码更加模块化,因为不同类型 Token需要不同计算方式。(原文是算法,我认为谈不上算法)。Parser清 单 2-1 展示了框架抽象类 Parser的关键方法。语言相关的 Parser子类要实现 parse()方法和 getErrorCount(),分别用来表示源程序分 析过程和返回语法错误。如上文提到的,Parser 的 currentToken()和 nextToken()方法是scanner对应方法的代理。1: /*2: * 语言无关的 Parser,有子类完成具体语言解析 3: */4: public abstract class Parser impl

39、ements MessageProducer5: 6: protected static SymTab symTab = null; / 生成的符号表7: 8: protected final Scanner scanner; / 扫描器 SCANNER,Parser 找它要 token9: protected ICode iCode; / 语法树根节点。10: 11: protected Parser(Scanner scanner)12: 13: this.scanner = scanner;14: this.iCode = null;15: 16: /*17: * 交由子类完成具体语言相

40、关的解析过程,这个方法调用之后将会产生符号表和中间码 iCode。18: * throws Exception19: */20: public abstract void parse()21: throws Exception;22: /*23: * return 解析过程中的错误数24: */25: public abstract int getErrorCount();26: 27: public Token currentToken()28: 29: return scanner.currentToken();30: 31: 32: public Token nextToken()33:

41、 throws Exception34: 35: return scanner.nextToken();36: 37: /.38: 因为前端只会产生一个符号表 SymTab,所以符号表在 Parser中以 symTab域出现。Source类清单 2-2 展示了框架类 Source的关键方法。1: /*2: * 此框架类的每个对象代表一个源文件3: */4: public class Source implements MessageProducer5: 6: / 行结束符,注意在 Windows平台上,默认行结束符是rn,7: /如果用记事本之类的写的 pascal源程序,可以使用 Ultra

42、edit之类的给转成 Unix格式的。8: public static final char EOL = n; 9: /文件结束标识10: public static final char EOF = (char) 0; 11: /源程序 reader12: private final BufferedReader reader;13: private String line; 14: private int lineNum; 15: private int currentPos; / 当前行相对位置,不是整个文件的 offset!16: public Source(BufferedReade

43、r reader)17: throws IOException18: 19: this.lineNum = 0;20: this.currentPos = -2; / 设置为-2 表示文件一行都没有读,后面的判断可以根据是否等于-2读文件第一行。21: this.reader = reader;22: this.messageHandler = new MessageHandler();23: 24: 25: /*26: * return 要去读的字符27: * throws Exception(read过程中的异常)28: */29: public char currentChar()30:

44、 throws Exception31: 32: / 第一次读?33: if (currentPos = -2) 34: readLine();35: return nextChar();36: 37: 38: / 文件结束?39: else if (line = null) 40: return EOF;41: 42: 43: / 行结束?44: else if (currentPos = -1) | (currentPos = line.length() 45: return EOL;46: 47: 48: / 超过一行,换一行再读49: else if (currentPos line.

45、length() 50: readLine();51: return nextChar();52: 53: 54: / 正常读取当前行的某一列的字符55: else 56: return line.charAt(currentPos);57: 58: 59: 60: /*61: *位置游标前进一步并返回对应的字符,记住 source的位置游标从来不后退,只有向前操作。62: * return 下一个要读取的字符63: * throws Exception64: */65: public char nextChar()66: throws Exception67: 68: +currentPos

46、;69: return currentChar();70: 71: 72: /*73: * 探测下一字符,位置游标不增加,跟 Stack(栈)的 Peek方法一样效果。74: * return 当前位置的字符75: * throws Exception 76: */77: public char peekChar()78: throws Exception79: 80: currentChar();81: if (line = null) 82: return EOF;83: 84: 85: int nextPos = currentPos + 1;86: return nextPos 语言无

47、关的 scanner,产生 Token3: */4: public abstract class Scanner5: 6: protected Source source; 7: private Token currentToken; /当前 Token8: public Scanner(Source source)9: 10: this.source = source;11: 12: public Token currentToken()13: 14: return currentToken;15: 16: 17: /*18: * 以 source中的 char序列模式抽取 token19:

48、 * return 下一个 token20: * throws Exception21: */22: public Token nextToken()23: throws Exception24: 25: currentToken = extractToken();26: return currentToken;27: 28: 29: /*30: * 因为每个源语言的 Token构成方式不一样,所以这个具体语言的子类去实现。31: * return 语言相关的 Token32: * throws Exception33: */34: protected abstract Token extractToken()35:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 专业基础教材

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报