要求分析和规格说明.docx
《要求分析和规格说明.docx》由会员分享,可在线阅读,更多相关《要求分析和规格说明.docx(25页珍藏版)》请在冰豆网上搜索。
要求分析和规格说明
一、要求分析和规格说明
1、PL语言分析
相对于PASCAL和PS语言而言,PL语言是一种定义非常狭窄的小的程序设计语言,它的简单类型仅有整型(integer)和布尔型(Boolean),过程定义不能有参数,
a)符号:
保留字:
BooleanProcarraybegincallconstdoendfalsefiifintegerodreadskiptruewrite
专有符号:
->*:
=,/=>[(<-\~|+)];&[]
数字
数字是十进制整数,范围从-32767到32767。
标识符
由字母开头的,后跟0个或多个字母或数字组成。
b)数据类型:
PL具有有两种基本数据类型,整型integer和布尔类型Boolean。
结构类型只有数组,不可以定义自己新的数据类型。
c)程序结构:
得到一个PL程序是<分程序>后跟有一个句号(圆点)的程序。
分程序有定义部分DP和语句部分SP组成:
beginDPSPend;
分程序描述了在命名对象上的操作。
对象是一个常量、变量或过程(类型由保留字表示)。
每个对象有定义说明。
不存在标准对象。
作用域规则与PS相同。
分程序内每个操作产生其内定义的变量的一个新实例。
分程序的定义部分是后跟有分号的定义的序列:
D1;D2;…Dn;
例如:
begin
consta=1;$常量部分
integerb;$变量定义
b:
=10;$数值输出
end.
d)常量定义
一般形式:
const<常量名>=<常量>
常量定义说明常量的名字:
constn=100
e)变量定义
一般形式:
<类型符><变量表>
或:
<类型符>array<变量表>[常量]
前者是定义一般变量,后者是定义数组变量。
数组的常量必须是自然数1,2,……,n。
它代表要定义的数组的元素个数,而数组的下标是从1开始的。
例:
integera,b,c;
integerA[10];$定义有10个元素的数组变量A
PL遵循变量先定义后使用的规则,而且在同一个作用域范围内不允许有相同的变量名。
但不同作用域变量名可以相同。
也不能用PL的保留字做变量名。
f)过程定义说明
一般形式:
Proc<过程名><分程序>
PL的过程遵循先定义后使用的规则,而且在同一个作用域范围内不允许有相同的过程名。
但不同作用域过程名可以相同。
也不能用PL的保留字做过程名。
Procwrite$错误,这是PL的标准过程,不能重定义
g)PL的语句
空语句:
一般形式:
skip;
它表示什么也不做。
读语句
一般形式:
read<变量访问表>;
其中变量访问表:
<变量访问>{,<变量访问>}
它是PL的标准过程,也是唯一的一条输入语句。
表示输入一个或多个整数并赋值给相应变量。
例:
reada,b,c;
写语句
一般形式:
write<表达式表>;
其中表达式表:
<表达式>{,<表达式表>}
它是PL的标准过程,也是唯一的一条输出语句。
表示输出一个或多个由表达式定义的整数。
例:
writea,b,c*d,100;
赋值语句
一般形式:
<变量访问表>:
=<表达式表>;
PL的赋值语句具有并行赋值的特点。
例:
a,b:
=1,x+y;
它表示依次把右边的表达式的值分别赋给左边对应的变量。
上例就是把1赋给变量a,把x+y的值赋给变量b。
过程语句
一般形式:
call<过程名>;
它表示调用用户自己定义过程,PL规定过程必须是先定义后使用的。
if语句
一般形式:
if<警戒命令表>fi;
其中警戒命令表:
<警戒命令>{[]<警戒命令>}
警戒命令:
<表达式>-><语句部分>
PL依次检测各警戒命令,如果表达式的值为true则执行相应的语句部分,然后忽略其余警戒命令,执行if后面的语句。
如果所有表达式的值均为false这发出“if语句逻辑错误”的提示并终止程序的执行。
例:
ifa>b->t:
=a;[]
at:
=b;
fi;
do语句
一般形式:
do<警戒命令表>do;
其中警戒命令表:
<警戒命令>{[]<警戒命令>}
警戒命令:
<表达式>-><语句部分>
PL依次检测各警戒命令,如果表达式的值为true则执行相应的语句部分,然后重新检测各警戒命令,直到所有表达式的值均为false才继续执行do后面的语句。
例:
doi>100->readi;od
h)程序注释
PL的注释以“$”字符开头,直到本行末为止。
例如:
begin
I:
=90;$这里也是程序的注释
end.
i)运算
PL有下列四种运算与或运算:
&:
与运算,只有当两个布尔值均为true结果才为true
|:
或运算,只要其中一个布尔值为true结果就为true
关系运算:
PL的关系运算和关系运算符有<=>运算的结果为布尔值
加减运算
加减运算+-参加运算的两个操作数和结果均为整数。
乘除运算
乘除运算*/\参加运算的两个操作数和结果均为整数。
*:
两个数相乘
/:
两个数相除结果只取整数部分
\:
两个数相相除结果只取余数部分
非运算
运算符~表示把原来的布尔值取反。
(3)文法分析:
PL的BNF文法:
<程序>:
:
=<分程序>“.”
<分程序>:
:
=“begin”<定义部分><语句部分>“end”
<定义部分>:
:
={<定义>“;”}
<定义>:
:
=<常量定义>|<变量定义>|<过程定义>
<常量定义>:
:
=“const”<常量名>“=“<常量>
<变量定义>:
:
=<类型符><变量表>|
<类型符>“array”<变量表>“[“<常量>“]”
<类型符>:
:
=“integer”|“Boolean”
<变量表>:
:
=<变量名>{“,”<变量名>}
<过程定义>:
:
=“Proc”<过程名><分程序>
<语句部分>:
:
={<语句>“;”}
<语句>:
:
=<空语句>|<读语句>|<写语句>|<赋值语句>|<过程语句>||
<空语句>:
:
=“skip”
<读语句>:
:
=“read”<变量访问表>
<变量访问表>:
:
=<变量访问>{“,”<变量访问>}
<写语句>:
:
=“write”<表达式表>
<表达式表>:
:
=<表达式>{“,”<表达式表>}
<赋值语句>:
:
=<变量访问表>“:
=”<表达式表>
<过程语句>:
:
=“call”<过程名>
:
:
=“if”<警戒命令表>“fi”
:
:
=“do”<警戒命令表>“od”
<警戒命令表>:
:
=<警戒命令>{“[]”<警戒命令>}
<警戒命令>:
:
=<表达式>“->”<语句部分>
<表达式>:
:
=<初等表达式>{<与或运算符><初等表达式表>}
<与或运算符>:
:
=“&”|“|”
<初等表达式>:
:
=<简单表达式>{<关系运算符><简单表达式>}
<关系运算符>:
:
=“<”|“=”|“>”
<简单表达式>:
:
=[“-”]<项>{<加减运算符><项>}
<加减运算符>:
:
=“+”|“-”
<项>:
:
=<因子>{<乘除运算符><因子>}
<乘除运算符>:
:
=“*”|“/”|“\”
<因子>:
:
=<常量>|<变量访问>|“(“<表达式>“)”|“~”<因子>
<变量访问>:
:
=<变量名>[<下标选择器>]
<下标选择器>:
:
=“[“<表达式>“]”
<常量>:
:
=<数>|<布尔量>|<常量名>
<数>:
:
=<数字>{<数字>}
<布尔量>:
:
=“false”|“ture”
<名>:
:
=<字母>{<字母>|<数字>|“_”}
从上面的文法中,我们可以得出各语法成分的first和follow集合,这两个集合也是以后进行编译程序编写的关键。
First集合
<程序>begin
<分程序>begin
<定义部分>constintegerBooleanProc
<定义>constintegerBooleanProc
<常量定义>const
<变量定义>integerBoolean
<类型符>integerBoolean
<变量表>namearray
<过程定义>Proc
<语句部分>skipreadwritenamecallifdo
<语句>skipreadwritenamecallifdo
<空语句>skip
<读语句>read
<变量访问表>name
<写语句>write
<表达式表>-namenumeral(~,
<赋值语句>name
<过程语句>call
if
do
<警戒命令表>-namenumeral(~[]
<警戒命令>-namenumeral(~[]
<表达式>-namenumeral(~
<与或运算符>&|
<初等表达式>-namenumeral(~
<关系运算符><=>
<简单表达式>-namenumeral(~
<加减运算符>+-
<项>namenumeral(~
<乘除运算符>*/\
<因子>namenumeral(~
<变量访问>name
<下标选择器>[
<常量>namenumeral
<数>numeral
<布尔量>falsetrue
<名>name
Follow集合
<分程序>.
<定义部分>first(语句部分)
<定义>first(语句部分);
<常量定义>first(变量定义)first(过程定义)first(语句部分)follow(定义)
<变量定义>first(常量定义)first(过程定义)first(语句部分)follow(定义)
<类型符>first(变量表)
<变量表>[follow(变量定义)
<过程定义>first(常量定义)first(变量定义)first(语句部分)follow(定义)
<语句部分>end
<语句>;
<空语句>;
<读语句>;
<变量访问表>follow(读语句):
=
<写语句>;
<表达式表>follow(写语句)follow(赋值语句)
<赋值语句>;
<过程语句>;
;
;
<警戒命令表>fiod
<警戒命令>[]follow(警戒命令表)
<表达式>,follow(表达式表)->)]
<与或运算符>first(初等表达式)
<初等表达式>first(与或运算符)follow(表达式)
<关系运算符>first(简单表达式)
<简单表达式>first(关系运算符)follow(初等表达式)
<加减运算符>first(项)
<项>first(加减运算符)follow(简单表达式)
<乘除运算符>first(因子)
<因子>first(乘除运算符)follow(项)
<变量访问>,follow(变量访问表)follow(因子)
<下标选择器>follow(变量访问)
<常量>follow(常量定义)]
2、PL编译程序的功能要求:
(1)分析器
读入源程序文件作为输入,逐个扫描字符。
识别保留字和专用符号以及名字,把程序正文转化为符号序列,而且用简单值表示符号,定义一个表示这些符号的枚举类型。
输出数的时候,在符号名后跟一个整数,即名字索引;是保留字的时候就输出它的顺序值。
当扫描器检测到一个不认识的名字时,就输出符号Unknown1。
(2)语法分析
以PASS1的输出文件作为输入。
这个语法分析的过程将识别正确的句子和检测语法错误,根据的文法规则分析程序是否符合Pl文法规则,检查其是否组成PL句子。
并做作用域分析,类型分析,并在错误的时候输出错误信息来。
最后根据Pl的指令集输出中间代码。
(3)代码生成器
代码生成器是为PL计算机生成代码,代码生成有两部分,一个是扩展分析器,输出PL代码,另外一个是汇编器,定义向前引用和代码的优化。
代码生成器有4个输出过程,分别输出指令操作码、参数、寻址、以及对应的命令。
选择代码优化可以节省更多的空间。
(4)解释执行器
解释执行器部分是对所提供的样本程序进行解释执行,前提是前面所有的词法和语法分析、代码生成已经全部通过执行完毕。
二、设计
我们的目标是编写一个PL语言的集成IDE环境,它即能编辑PL程序,又可以在集成环境下运行PL程序的目标代码,而且还具应有出错提示等基本功能。
1.总体设计思想
我们先来看一下编译程序的一般工作流程。
先对高级语言原代码进行词法分析,得到中间代码,然后进行语法分析、语义分析,得到第二个中间代码,接着进行代码优化,得到高效中间代码,再则生成汇编代码,最后是生成可执行的程序代码。
我们看到从一个原程序到可执行代码的生成是要经过很多阶段的,所以我们必须用模块化的思想来进行程序设计,不然程序的编写和维护将会变得异常复杂。
又由于各功能部分经常是互相调用的,如果用面向过程的思想进行程序设计,那么各函数和过程间的调用次序就变得非常重要了,因为函数和过程必须是先定义后使用的。
而且过多的使用全程变量,当程序的复杂性提高时,必将增加程序的出错率。
所以我们用面向对象的思想来程序的设计。
把每个模块定义成一个类,而由于编译过程的每一步又是在它上一步完成后才能执行的,所以具有一种传递性,这样我们又可以用继承的思想来设计我们的类。
模块划分
由于我们的PL语言语法简单,所以我们的编译程序的编译流程可以大大的简化。
从下面的流图我们可以看到整个编译流程得到了巨大的简化。
首先是省略了代码优化模块,然后是把语法分析、作用域分析、类型分析和汇编代码生成合成在一个模块中,最后把语意分析后的中间代码的生成也省略掉了,而直接生成汇编代码。
为了进一步代码的重用性,我们把所有可能重复使用的代码都方在一个单元中――ShareCommand。
而且定义了一个基类TshareCommand。
TshareCommand是实行文件的存取和错误处理。
因为这两个处理所以模块都要用到的。
把他定义成基类的确是好方法。
所以整个编译程序划分为三个模块,基本处理模块,词法分析模块和语法分析模块(包括代码生成),而每个模块又定义成一个类,分别为TshareCommand、PSscan和Tsyntax。
它们的继承关系为:
被继承被继承
2.基本处理模块
1.文件操作
编译程序涉及大量的文件操作,如文件的打开、关闭、读取、写入等等。
因此类TshareCommand中有相应的文件操作方法:
文件读取操作:
procedureTShareCommand.LoadFromFile(FileName:
string);
文件写入操作:
procedureTShareCommand.Emit(Value:
integer);
2.错误处理
错误处理涉及的方法有:
长度检测:
procedureTestLimit(Text1:
string);overload;
functionTestLimit(Length,Maximum:
integer):
Boolean;overload;
错误提示:
procedureError(Kind:
TErrorKind);overload;
procedureError(Msg:
String);overload;
大家可能看有两个TestLimit和Error方法,不要奇怪,这就是面向对象的好处,这里利用了函数名的重载,这样的好处就是对于处理同类信息的方法我们不用定义多个方法名称,这样类的封装性得到了进一步的提高,隐含了更多的细节,提高了系统的可靠性。
TshareCommand的完整定义
所以TshareCommand的完整定义是:
TShareCommand=class
PSName:
string;
LineNo:
integer;
Emitting,Errors,CorrectLine:
Boolean;
Input1,Output1,Notes:
TextFile;
procedureLoadFromFile(FileName:
string);
procedureTestLimit(Text1:
string);overload;
functionTestLimit(Length,Maximum:
integer):
Boolean;overload;
procedureEmit(Value:
integer);
procedureRerun;
procedureNewLine(Number:
integer);
procedureError(Kind:
TErrorKind);overload;
procedureError(Msg:
String);overload;
private
{Privatedeclarations}
public
{Publicdeclarations}
end;
3。
词法分析模块
程序编译的第一步就是词法分析,这个任务由类PSscan来完成。
他是继承TshareCommand类的,所以他具有了文件操作功能和错误处理功能。
我们来看一下他的完整定义。
2.PSscan的完整定义
PSScan=class(TShareCommand)
procedurePass1;
private
{Privatedeclarations}
AlphaNumeric,Digits,
EndComment,Invisible,
Letters,Separators,
SmallLetters,CapitalLetters:
TCharSet;
ch:
char;
Characters:
integer;
Link:
TWordPointer;
procedureEmit1(Symbol:
TSymbolType);
procedureEmit2(Symbol:
TSymbolType;Argument:
integer);
procedureBeginLine(Number:
integer);
procedureEndLine;
procedureNextChar;
procedureInsert(IsName:
Boolean;Text1:
string;
constIndex:
integer=-1);
procedureDefine(IsName:
Boolean;Text1:
string;Index:
integer);
procedureSearch(Text1:
string;varIsname:
Boolean;varIndex:
integer);
procedureInitialize;
procedureComment;
procedureNextSymbol;
public
{Publicdeclarations}
end;
字符读取
词法分析模块逐个字符的读取PL原程序,并用一个控制字符ETX来表示正文的结尾。
字符的读取就由方法NextChar负责。
它可以跳过除新行符(NL)和正文结束符(ETX)以外的所有不可见字符。
扫描
对输入的字符进行的处理我们称为扫描。
这里我们采用超前读一字符的方法。
该功能有NextSymbol负责,具体实现为:
启动扫描器时,预先读入原程序的第一个字符
每当扫描器识别一个字符为一个特定符号的一部分时,立即读入下一个字符
分隔符
扫描器跳过每个符号前的任何空格、新行符和注解。
分隔符以下列字符开头
Separators:
=[SP,NL,Dollar];
在每行末尾,扫描器调用方法EndLine,把行号加1并与符号NewLine一起输出。
whilechinSeparatorsdo
ifch=SPthenNextChar
else
ifch=NLthen
begin
EndLine;
NextChar
end
else{ch=Dollar}Comment;
注释是以“$”字符开头,直到本行末为止,属于实现起来比较简单
procedureTScan.Comment;
begin{ch=Dollar}
NextChar;
whilenot(chinEndComment)do//到本行末尾
NextChar
end;
专用符号
如果读入的是专用符号,如“+”,则立即输出该符号的代码并超前读下一个字符。
Emit1(Plus1);NextChar
但对应多字符组成的专用符号,例如“[]”就必须如下处理
Nextchar;
ifch=']'then
begin
Emit1(bracket1);
NextChar
end
elseEmit1(LeftBracket1);
因子“[”和“[]”都是以“[”开头的,所以必须用超前读下一字符的方法来处理。
数字
数字的处理只要解决的是数的最大值问题,程序规定数值的绝对值不能超过32767,所以在ShareCommand模块中有一句MaxInt=32767,具体的算法如下:
ifchinDigitsthen//数字
begin
Value:
=0;
whilechinDigitsdo
begin
Digit:
=ord(ch)-ord('0');
ifValue<=(MaxInt-Digit)div10then
begin
Value:
=10*Value+Digit;
NextChar
end
else
begin
Error(Numeral3);
whilechinDigitsdo
NextChar
end
end;
Emit2(Numeral1,Value)
End
单词
单词的识别,也就是识别保留字和名字。
算法如下,
ifchinLettersthen
begin
Text1:
=