📄 123.txt
字号:
k := k + 1;
a[k] := ch;
end;
getch (* 读下一个字符 *)
until not (ch in ['a'..'z','0'..'9']); (* 直到读出的不是字母或数字,由此可知PL/0的标识符构成规则是:
以字母开头,后面跟若干个字母或数字 *)
if k >= kk then (* 如果当前获得的标识符长度大于等于kk *)
kk := k (* 令kk为当前标识符长度 *)
else
repeat (* 这个循环用于把标识符缓冲后部没有填入相应字母或空格的空间用空格补足 *)
a[kk] := ' ';
kk := kk - 1
until kk = k;
(* 在第一次运行这个过程时,kk的值为al,即最大标识符长度,如果读到的标识符长度小于kk,
就把a数组的后部没有字母的空间用空格补足。
这时,kk的值就成为a数组前部非空格字符的个数。以后再运行getsym时,如果读到的标识符长度大于等于kk,
就把kk的值变成当前标识符的长度。
这时就不必在后面填空格了,因为它的后面肯定全是空格。反之如果最近读到的标识符长度小于kk,那就需要从kk位置向前,
把超过当前标识长度的空间填满空格。
以上的这样一个逻辑,完全是出于程序性能的上考虑。其实完全可以简单的把a数组中a[k]元素以后的空间不管三七二十一全填空格。
*)
(* 下面开始二分法查找看读出的标识符是不是保留字之一 *)
id := a; (* 最后读出标识符等于a *)
i := 1; (* i指向第一个保留字 *)
j := norw; (* j指向最后一个保留字 *)
repeat
k := (i + j) div 2; (* k指向中间一个保留字 *)
if id <= word[k] then (* 如果当前的标识符小于k所指的保留字 *)
j := k - 1; (* 移动j指针 *)
if id >= word[k] then (* 如果当前的标识符大于k所指的保留字 *)
i := k + 1 (* 移动i指针 *)
until i > j; (* 循环直到找完保留字表 *)
if i - 1 > j then (* 如果i - 1 > j表明在保留字表中找到相应的项,id中存的是保留字 *)
sym := wsym[k] (* 找到保留字,把sym置为相应的保留字值 *)
else
sym := ident (* 未找到保留字,把sym置为ident类型,表示是标识符 *)
end(* 至此读出字符为字母即对保留字或标识符的处理结束 *)
else (* 如果读出字符不是字母 *)
if ch in ['0'..'9'] then (* 如果读出字符是数字 *)
begin (* number *) (* 开始对数字进行处理 *)
k := 0; (* 数字位数 *)
num := 0; (* 数字置为0 *)
sym := number; (* 置sym为number,表示这一次读到的是数字 *)
repeat (* 这个循环依次从源文件中读出字符,组成数字 *)
num := 10 * num + (ord(ch) - ord('0')); (* num * 10加上最近读出的字符ASCII减'0'的ASCII得到相应的数值 *)
k := k + 1; (* 数字位数加一 *)
getch
until not (ch in ['0'..'9']); (* 直到读出的字符不是数字为止 *)
if k > nmax then (* 如果组成的数字位数大于最大允许的数字位数 *)
error(30) (* 发出30号错 *)
end(* 至此对数字的识别处理结束 *)
else
if ch = ':' then (* 如果读出的不字母也不是数字而是冒号 *)
begin
getch; (* 再读一个字符 *)
if ch = '=' then (* 如果读到的是等号,正好可以与冒号构成赋值号 *)
begin
sym := becomes; (* sym的类型设为赋值号becomes *)
getch (* 再读出下一个字 *)
end
else
sym := nul; (* 如果不是读到等号,那单独的一个冒号就什么也不是 *)
end(* 以上完成对赋值号的处理 *)
else (* 如果读到不是字母也不是数字也不是冒号 *)
if ch = '<' then (* 如果读到小于号 *)
begin
getch; (* 再读一个字符 *)
if ch = '=' then (* 如果读到等号 *)
begin
sym := leq; (* 购成一个小于等于号 *)
getch (* 读一个字符 *)
end
else (* 如果小于号后不是跟的等号 *)
sym := lss (* 那就是一个单独的小于号 *)
end
else (* 如果读到不是字母也不是数字也不是冒号也不是小于号 *)
if ch = '>' then (* 如果读到大于号,处理过程类似于处理小于号 *)
begin
getch; (* 再读一个字符 *)
if ch = '=' then (* 如果读到等号 *)
begin
sym := geq; (* 购成一个大于等于号 *)
getch (* 读一个字符 *)
end
else (* 如果大于号后不是跟的等号 *)
sym := gtr (* 那就是一个单独的大于号 *)
end
else(* 如果读到不是字母也不是数字也不是冒号也不是小于号也不是大于号 *)
begin (* 那就说明它不是标识符/保留字,也不是复杂的双字节操作符,应该是一个普通的符号 *)
sym := ssym[ch]; (* 直接成符号表中查到它的类型,赋给sym *)
getch (* 读下一个字符 *)
end
(* 整个if语句判断结束 *)
end (* getsym *);
(* 词法分析过程getsym总结:从源文件中读出若干有效字符,组成一个token串,识别它的类型
为保留字/标识符/数字或是其它符号。如果是保留字,把sym置成相应的保留字类型,如果是
标识符,把sym置成ident表示是标识符,于此同时,id变量中存放的即为保留字字符串或标识
符名字。如果是数字,把sym置为number,同时num变量中存放该数字的值。如果是其它的操作符,
则直接把sym置成相应类型。经过本过程后ch变量中存放的是下一个即将被识别的字符 *)
(* 目标代码生成过程gen *)
(* 参数:x:要生成的一行代码的助记符 *)
(* y, z:代码的两个操作数 *)
(* 本过程用于把生成的目标代码写入目标代码数组,供后面的解释器解释执行 *)
procedure gen(x: fct; y, z: integer);
begin
if cx > cxmax then (* 如果cx>cxmax表示当前生成的代码行号大于允许的最大代码行数 *)
begin
write('program too long'); (* 输出"程序太长",退出 *)
close(fa);
close(fa1);
close(fin);
halt(0)
{ goto 99 }
(* 我修改的代码,由于Turbo Pascal 7.0中不允许跨过程的goto,就只能用上面的方法退出程序了。 *)
end;
with code[cx] do (* 把代码写入目标代码数组的当前cx所指位置 *)
begin
f := x;
l := y;
a := z;
end;
cx := cx + 1 (* 移动cx指针指向下一个空位 *)
end (* gen *);
(* 测试当前单词是否合法过程test *)
(* 参数:s1:当语法分析进入或退出某一语法单元时当前单词符合应属于的集合 *)
(* s2:在某一出错状态下,可恢复语法分析正常工作的补充单词集合 *)
(* n:出错信息编号,当当前符号不属于合法的s1集合时发出的出错信息 *)
procedure test(s1, s2: symset; n: integer);
begin
if not (sym in s1) then (* 如果当前符号不在s1中 *)
begin
error(n); (* 发出n号错误 *)
s1 := s1 + s2; (* 把s2集合补充进s1集合 *)
while not (sym in s1) do (* 通过循环找到下一个合法的符号,以恢复语法分析工作 *)
getsym
end
end (* test *);
(* 语法分析过程block *)
(* 参数:lev:这一次语法分析所在的层次 *)
(* tx:符号表指针 *)
(* fsys:用于出错恢复的单词集合 *)
procedure block(lev, tx: integer; fsys: symset);
var
dx: integer; (* data allocation index *) (* 数据段内存分配指针,指向下一个被分配空间在数据段中的偏移位置 *)
tx0: integer; (* initial table index *) (* 记录本层开始时符号表位置 *)
cx0: integer; (* initial code index *) (* 记录本层开始时代码段分配位置 *)
(* 登陆符号表过程enter *)
(* 参数:k:欲登陆到符号表的符号类型 *)
procedure enter(k: object1);
begin (* enter object into table *)
tx := tx + 1; (* 符号表指针指向一个新的空位 *)
with table[tx] do (* 开始登录 *)
begin
name := id; (* name是符号的名字,对于标识符,这里就是标识符的名字 *)
kind := k; (* 符号类型,可能是常量、变量或过程名 *)
case k of (* 根据不同的类型进行不同的操作 *)
constant: (* 如果是常量名 *)
begin
if num > amax then (* 在常量的数值大于允许的最大值的情况下 *)
begin
error(31); (* 抛出31号错误 *)
num := 0; (* 实际登陆的数字以0代替 *)
end;
val := num (* 如是合法的数值,就登陆到符号表 *)
end;
variable: (* 如果是变量名 *)
begin
level := lev; (* 记下它所属的层次号 *)
adr := dx; (* 记下它在当前层中的偏移量 *)
dx := dx+1; (* 偏移量自增一,为下一次做好准备 *)
end;
procedur: (* 如果要登陆的是过程名 *)
level := lev (* 记录下这个过程所在层次 *)
end
end
end (* enter *);
(* 登录符号过程没有考虑到重复的定义的问题。如果出现重复定义,则以最后一次的定义为准。 *)
(* 在符号表中查找指定符号所在位置的函数position *)
(* 参数:id:要找的符号 *)
(* 返回值:要找的符号在符号表中的位置,如果找不到就返回0 *)
function position (id: alfa): integer;
var
i: integer;
begin (* find identifier in table *)
table[0].name := id; (* 先把id放入符号表0号位置 *)
i := tx; (* 从符号表中当前位置也即最后一个符号开始找 *)
while table.name <> id do (* 如果当前的符号与要找的不一致 *)
i := i - 1; (* 找前面一个 *)
position := i (* 返回找到的位置号,如果没找到则一定正好为0 *)
end(* position *);
(* 常量声明处理过程constdeclaration *)
procedure constdeclaration;
begin
if sym = ident then (* 常量声明过程开始遇到的第一个符号必然应为标识符 *)
begin
getsym; (* 获取下一个token *)
if sym in [eql, becomes] then (* 如果是等号或赋值号 *)
begin
if sym = becomes then (* 如果是赋值号(常量生明中应该是等号) *)
error(1); (* 抛出1号错误 *)
(* 这里其实自动进行了错误纠正使编译继续进行,把赋值号当作等号处理 *)
getsym; (* 获取下一个token,等号或赋值号后应接上数字 *)
if sym = number then (* 如果的确是数字 *)
begin
enter(constant); (* 把这个常量登陆到符号表 *)
getsym (* 获取下一个token,为后面作准备 *)
end
else
error(2) (* 如果等号后接的不是数字,抛出2号错误 *)
end
else
error(3) (* 如果常量标识符后接的不是等号或赋值号,抛出3号错误 *)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -