return0;
}
4、运用命令行的lex.yy.exe<123.cpp运行得到结果:
五、实验小结
本次实验通过对flex基本知识的阅读基本掌握了简单的lex语法和规则,也可以自行设计编制调试一个具体的词法分析程序,不仅加深对了词法分析原理的理解,也初步掌握了在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。
实验二(第二周)语法分析器(bison简单实验)
一、实验目的
1、了解语法分析工具bison的用法和自动生成语法分析器的过程和原理;
2、学习和掌握flex与bison联合编译的思想和方法,能够通过这种方法编译实现基本编译器的构造和设计。
二、实验说明
bison是属于GNU项目的一个语法分析器生成器。
Bison把一个关于“向前查看从左到右最右”(LALR)上下文无关文法的描述转化成可以分析该文法的C或C++程序。
它也可以为二义文法生成“通用的从左到右最右”(GLR)语法分析器。
本次试验主要是学习并利用语法分析器生成工具Bison编写一个语法分析程序,与词法分析器结合,能够根据语言的上下文无关文法,识别输入的单词序列是否文法的句子,实验中可以编写一个测试程序,以给定的测试文件作为输入,输出运行结果到输出文件中。
三、实验原理与分析
Bison是一种通用目的的分析器生成器。
它将LALR
(1)上下文无关文法的描述转化成分析该文法的C程序。
使用bison的前提是使用flex事先生成相关词法分析器。
Flex可以识别正则表达式,而bison可以识别语法。
Flex把输入流分解为若干个片段(记号),而bison则可以将这些记号基于逻辑进行合并。
Bison基于我们所给定的语法来生成一个可以识别这个语法中有效语句的语法分析器。
而且bison只处理语法,你需要保证其他部分的完整性。
语法由一系列规则组成,语法分析器就是基于这些规则来识别语法上正确的输入。
Bison的.y文件也是分成三个部分:
1、声明部分:
所有词法单元的定义可以放在此处
2、规则部分:
具体的语法和相应的动作
3、用户自定义部分。
第三部分会被bison原封不动的拷贝进生成的.C文件
当bison读入一个终结符(token),它会将该终结符及其语意值一起压入堆栈。
这个堆栈叫做分析器堆栈(parserstack)。
把一个token压入堆栈通常叫做移进(shifting)。
但堆栈并不是每读入一个终结符就分配一个栈元素给它。
当已经移进的后n个终结符和组(groupings)与一个文法规则相匹配时,它们会被根据那个规则结合起来。
这叫做归约(reduction)。
栈中的那些终结符和组会被单个的组(grouping)替换。
那个组的符号就是那个规则的结果。
执行该规则的相应的动作(Action)也是归约处理的一部分,这个动作会计算这个组的语意值。
分析器通过移进和归约尝试着缩减整个输入到单个的组。
这个组的符号就是文法中的起始符号(start-symbol)。
Bison分析器并不总是在后n个终结符与组匹配某一规则时立即就进行归约。
这种策略对于大部分语言来说并不合适。
相反,当可以进行归约时,分析器有时会“预读”(looksahead)下一个终结符来决定做什么。
当一个终结符被读进来后,并不会立即移进堆栈,而是首先作为一个预读终结符(look-aheadtoken)。
此后,分析器开始对栈上的终结符和组执行一个或多个归约,而预读终结符仍然放在一边。
当没有归约可做时,这个预读终结符才会被移进堆栈。
这并不表示所有可能的归约都已经做了,这要取决于预读终结符的类型,一些规则可能选择推迟它们的使用。
四、实验过程详细分析和步骤
(1)简单bison与flex联合编译实验
1、window下首先将bison安装在与flex安装的相同目录下,编写编写bison文件即.y文件并保存在bison目录下,然后通过调用命令行生成.tab.c和.tab.h文件;
2、编写词法分析文件并将上述的.Tab.h包含在头文件中,然后后调用命令行生成.yy.c文件,利用命令行将.yy.c和.tab.c文件生成为可执行文件exe文件;
3、在命令行里利用生成的exe文件调用测试文件得到结果。
(2)利用语法分析器生成工具Bison编写一个语法分析程序,与词法分析器结合,能够根据语言的上下文无关文法,识别输入的单词序列是否文法的句子。
1、编写代码并分别编译flex和bison产生相应文件;
2、comment函数调用yyinput,编译的时候出现了链接错误,将lex.yy.c中的yyinput函数定义拷贝一份到input.lex,重命名为my_yyinput即可解决,另外还要修改生成的cgrammar-new.tab.c文件中的一段代码并删去“if(!
yyin)yyin=stdin”才可以编译通过(解析之前,还要设置yyin为输入文件指针);
3、在命令行里利用生成的exe文件调用测试文件得到结果。
五、实验小结
相对于flex源文件,bison源文件的编写更为艰难,但通过实验了解了flex与bison联合编译的思想和方法,并明白了bison将词法分析阶段的规则进行合并的过程,这些将有助于在接下来的实验中完成简单编译器。
实验三(第三周)简单桌面计算器
一、实验说明
使用flex和bison开发了一个具有全部功能的桌面计算器,能够支持变量,过程,循环和条件表达式,使它成为一个虽然短小但是具有现实意义的编译器。
重点学习抽象语法树的用法,它具有强大而简单的数据结构来表示分析结果。
该计算器具体需要实现的功能包括变量命名、实现赋值功能、实现比较表达式(大于、小于、等于等等)、实现if/then/else和do/while的流程控制、用户可以自定义函数;简单的错误恢复机制。
最后编写测试程序时首先自定义两个函数sq和avg,sq函数使用Newton方法来迭代计算平方根;avg函数计算两个数值的平均值。
利用定义好的函数进行计算,得到计算结果并显示出来。
二、实验原理与设计分析
还要分析下,在Bison与Flex联用时,Bison只定义标记的ID。
Flex则需要知道这些词法标记的ID,才能在识别到一个词法标记时返回这个ID给Bison。
Bison传递这些ID给Flex的方法,就是在调用bison命令时使用参数-d。
使用这个参数后,Bison会生成一个独立的头文件,该文件的名称形式为name.tab.h。
在Flex的词法规则文件中,在定义区段里包含这个头文件即可。
在编译器中最强大的数据结构之一就是抽象语语法树。
抽象语法树作为一种通用的中间表示,不仅包含各种语言共有的语法结构,某些特定类型的树节点还可以表示一些语言特有的语法结构。
抽象语法树易于转换成寄存器转移语言,而寄存器转移语言适合在不同平台下进行优化,这使得GCC的两层中间表示具有良好的通用性。
作为一种良好的中间表示,抽象语法树包含了完整的源程序信息。
利用抽象语法树可以实现多种源程序处理工具,比如智能编辑器、源程序浏览器等。
此外,抽象语法树的解析器也可以作为程序静态分析工具的前端,为其提供一种便于分析的输入。
抽象语法树结构比较简单,其对应的词法规则和语法规则易于构造,使用flex和bison工具生成的解析器能够有效地对抽象语法树进行解析。
解析器由三部分组成,分别是flex生成的词法分析器、bison生成的语法分析器和手工编写的驱动程序。
词法分析器识别抽象语法树文件的记号流,提供给语法分析器;语法分析器利用嵌入其中的语义动作识别语法树节点,完成解析任务;驱动程序负责为词法分析器和语法分析器提供一个调用接口,并提供解析所需的数据结构和函数的实现。
在计算器里,factor仅仅是为了告诉语法分析器各个操作符的相对优先级,抽象语法树可以把分析树中的不需要关注的节点移除。
三、实验步骤和设计实现过程分析
本部分主要说明一下计算器的设计过程以及在设计过程中用到的一些重点算法和思想等内容。
1、首先我们要做开始声明部分,在.h头文件中我们可以用以下语句来定义抽象语法树的
structast{intnodetype;structast*l;structast*r;};
节点,且所有节点都有公共的初始nodetype。
而删除和释放抽象语法树可以用语句voidtreefree(structast*)来实现即可。
常量使用numval,符号引用使用symref
赋值使用symasgn,它有一个指向被赋值符号的指针和使用抽象语法树表示的值;
2、语法分析器的设计,其中在语法分析器的最后提供了小部分错误恢复机制,这让我们有可能在错误发生时把语法分析器恢复到可以继续工作的状态;
3、词法分析器中设计六个比较操作符都返回一个带有字面值以便于区分的CMP记号,其中这六个关键字和四个内置函数通过文字模式加以识别,它们放在通用模式之前以便于在通用模式之前进行匹配;
4、最后还要加一个辅助函数,正如《flex与bison》中所讲的一样,例程treefree的扩展版本会递归的遍历一颗抽象语法树并释放这棵树的所有节点。
本计算器的核心例程是eval,它用来计算分析器中构造的抽象语法树。
我们采用深度优先遍历算法来计算表达式的值;
5、完成以上工作后我们就可以在命令行里依次输入指令得到运行结果了:
四、实验小结
使用bison和flex工具学习编译原理,远比单独看书然后自己编写一些程序生动的多。
这样你就不会在那些复杂的字符处理。
但对于初学者来说,完全编译出来本次试验的桌面计算器的编译器还是挺困难的。
因此本次计算器的实现主要还是依靠flex与bison这本书的帮助才得以实现。
本计算器虽然短小但却很具有代表意义。
我们添加了命名的变量和赋值、比较表达式、if和then等的流程控制内置和用户自定义函数以及错误恢复机制等。
实验四(第四周)操作系统实验(Lab0实验)
一、实验目的
1、掌握OS基本概念:
看在线课程,能理解OS原理与概念;看在线实验指导书并分析源码,能理解labcodes_answer的labs运行结果;
2、掌握OS设计实现:
在1的基础上,能够通过编程完成labcodes的8个lab实验中的基本练习和实验报告;
3、本次lab0实验主要是让我们熟悉实验环境以便于后续的实验操作。
二、实验说明
ucore的运行环境可以是真实的X86计算机,不过考虑到调试和开发的方便,我们可采用X86硬件模拟器,比如QEMU、BOCHS、VirtualBox、VMware、Player等。
ucore的开发环境主要是GCC中的gcc、gas、ld和MAKE等工具,也可采用集成了这些工具的IDE开发环境Eclipse-CDT等。
在分析源代码上,可以采用Scitools提供的understand软件(跨平台),windows环境上的source、insight软件,或者基于emacs+ctags,vim+ctags等,都可以比较方便在在一堆文件中查找变量、函数定义、调用/访问关系等。
软件开发的版本管理可以采用GIT、SVN等。
比较文件和目录的不同可发现不同实验中的差异性和进行文件合并操作,可使用meld、kdiff3、UltraCompare等软件。
调试(deubg)实验有助于发现设计中的错误,可采用gdb(配合qemu)等调试工具软件。
并可整个实验的运行环境和开发环境既可以在Linux或Windows中使用。
关于实验环境的配置基本是有五种方式(在线实验--基于"实验楼"在线平台、Windows下基于MingW进行实验、Windows下基于VirtualBoxorVMWare进行实验、在MACOS下进行实验和手动在物理PC中安装环境),我选择的是手动在自己的电脑上上安装ubuntu并在ubuntu系统中安装实验环境相关软件在shell(比如gnome-terminal)下可执行相关命令来安装相关软件。
以下是在自己的笔记本上安装实验环境成功的截图:
Linux文件系统被组织成一个有层次的树形结构。
文件系统的最上层是/,或称为根目录。
在Unix和Linux的设计理念中,一切皆为文件——包括硬盘、分区和可插拔介质。
这就意味着所有其它文件和目录(包括其它硬盘和分区)都位于根目录中。
例如:
/home/jebediah/cheeses.odt给出了正确的完整路径,它指向cheeses.odt文件,而该文件位于jebediah目录下,该目录又位于home目录,最后,home目录又位于根(/)目录下。
了解这些对今后更深一步了解操作系统的结构尤为重要。
使用命令行并不像您想象的那么困难。
使用命令行不需要专门知识,和其它软件一样,它也仅仅是一个程序。
Linux中绝大部分工作都可以用命令行完成,尽管大部分程序都有相应的图形工具,但有时这些图形工具会捉襟见肘,不够用。
此时便是命令行大显身手的时候。
终端常常被称为命令行或者shell。
QEMU是一个通用并开放源代码的模拟器,是一套由FabriceBellard所编写的以GPL许可证分发源码的模拟处理器,在GNU/Linux平台上使用广泛。
其功能相当的强大,例如:
可以用QEMU来模拟一个完整的系统,同时,也可以用QEMU来实现系统源码级的调试:
要想深入理解ucore,就需要了解支撑ucore运行的硬件环境,即了解处理器体系结构(了解硬件对ucore带来影响)和机器指令集。
ucore目前支持的硬件环境是基于Intel80386以上的计算机系统。
80386有四种运行模式:
实模式、保护模式、SMM模式和虚拟8086模式。
这里对涉及ucore的实模式、保护模式做一个简要介绍。
实模式:
80386加电启动后处于实模式运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB,且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力。
实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,操作系统和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。
保护模式:
实际上,80386就是通过在实模式下初始化控制寄存器,GDTR,LDTR,IDTR与TR等管理寄存器以及页表,然后再通过加载CR0使其中的保护模式使能位置位而进入保护模式的。
当80386工作在保护模式下的时候,其所有的32根地址线都可供寻址,物理寻址空间高达4GB。
在保护模式下,支持内存分页机制,提供了对虚拟内存的良好支持。
保护模式下80386支持多任务,还支持优先级机制,不同的程序可以运行在不同的优先级上。
优先级一共分0~3 4个级别,操作系统运行在最高的优先级0上,应用程序则运行在比较低的级别上;配合良好的检查机制后,既可以在任务间实现数据的安全共享也可以很好地隔离各个任务。
80386是32位的处理器,即可以寻址的物理内存地址空间为2^32=4G字节。
在理解操作系统的过程中,需要用到三个地址空间的概念。
地址是访问地址空间的索引。
物理内存地址空间是处理器提交到总线上用于访问计算机系统中的内存和外设的最终地址。
一个计算机系统中只有一个物理地址空间。
线性地址空间是每个运行的应用程序看到的地址空间,在操作系统的虚存管理之下,每个运行的应用程序都认为自己独享整个计算机系统的地址空间,这样可让多个运行的应用程序之间相互隔离。
处理器负责把线性地址转换成物理地址。
在Ubuntu Linux中的C语言编程主要基于GNU C的语法,通过gcc来编译并生成最终执行文件。
GNU make(简称make)是一种代码维护工具,在大中型项目中,它将根据程序各个模块的更新情况,自动的维护和生成目标代码。
make命令执行时,需要一个 makefile (或Makefile)文件,以告诉make命令需要怎么样的去编译和链接程序。
首先,我们用一个示例来说明makefile的书写规则。
以便给大家一个感兴认识。
这个示例来源于gnu的make使用手册,在这个示例中,我们的工程有8个c文件,和3个头文件,我们要写一个makefile来告诉make命令如何编译和链接这几个文件。
只要我们的makefile写得够好,