📄 pl_0语言编译程序分析.txt
字号:
begin
getsym; (* 获取下一个token,此处正常应为作为过程名的标识符 *)
if sym = ident then (* 如果token确为标识符 *)
begin
enter(procedur); (* 把这个过程登录到名字表中 *)
getsym (* 获取下一个token,正常情况应为分号 *)
end
else
error(4); (* 否则抛出4号错误 *)
if sym = semicolon then (* 如果当前token为分号 *)
getsym (* 获取下一个token,准备进行语法分析的递归调用 *)
else
error(5); (* 否则抛出5号错误 *)
block(lev + 1, tx, [semicolon] + fsys); (* 递归调用语法分析过程,当前层次加一,同时传递表头索引、合法单词符 *)
if sym = semicolon then (* 递归返回后当前token应为递归调用时的最后一个end后的分号 *)
begin
getsym; (* 获取下一个token *)
test(statbegsys + [ident, procsym], fsys, 6); (* 检查当前token是否合法,不合法则用fsys恢复语法分析同时抛6号错 *)
end
else
error(5) (* 如果过程声明后的符号不是分号,抛出5号错误 *)
end;
test(statbegsys + [ident], declbegsys, 7) (* 检查当前状态是否合法,不合法则用声明开始符号作出错恢复、抛7号错 *)
until not (sym in declbegsys); (* 直到声明性的源程序分析完毕,继续向下执行,分析主程序 *)
code[table[tx0].adr].a := cx; (* 把前面生成的跳转语句的跳转位置改成当前位置 *)
with table[tx0] do (* 在符号表中记录 *)
begin
adr := cx; (* 地址为当前代码分配地址 *)
size := dx; (* 长度为当前数据代分配位置 *)
end;
cx0 := cx; (* 记下当前代码分配位置 *)
gen(int, 0, dx); (* 生成分配空间指令,分配dx个空间 *)
statement([semicolon, endsym] + fsys); (* 处理当前遇到的语句或语句块 *)
gen(opr, 0, 0); (* 生成从子程序返回操作指令 *)
test(fsys, [], 8); (* 用fsys检查当前状态是否合法,不合法则抛8号错 *)
listcode (* 列出本层的类PCODE代码 *)
end(* block *);
(* PL/0编译器产生的类PCODE目标代码解释运行过程interpret *)
procedure interpret;
const
stacksize = 500; (* 常量定义,假想的栈式计算机有500个栈单元 *)
var
p, b, t: integer; (* program base topstack registers *)
(* p为程序指令指针,指向下一条要运行的代码 *)
(* b为基址指针,指向每个过程被调用时数据区中分配给它的局部变量数据段基址 *)
(* t为栈顶寄存器,类PCODE是在一种假想的栈式计算上运行的,这个变量记录这个计算机的当前栈顶位置 *)
i: instruction; (* i变量中存放当前在运行的指令 *)
s: array[1..stacksize] of integer; (* datastore *) (* s为栈式计算机的数据内存区 *)
(* 通过静态链求出数据区基地址的函数base *)
(* 参数说明:l:要求的数据区所在层与当前层的层差 *)
(* 返回值:要求的数据区基址 *)
function base(l: integer): integer;
var
b1: integer;
begin
b1 := b; (* find base 1 level down *) (* 首先从当前层开始 *)
while l > 0 do (* 如果l大于0,循环通过静态链往前找需要的数据区基址 *)
begin
b1 := s[b1]; (* 用当前层数据区基址中的内容(正好是静态链SL数据,为上一层的基址)的作为新的当前层,即向上找了一层 *)
l := l - 1 (* 向上了一层,l减一 *)
end;
base := b1 (* 把找到的要求的数据区基址返回 *)
end(* base *);
begin
writeln('start pl0'); (* PL/0程序开始运行 *)
t := 0; (* 程序开始运行时栈顶寄存器置0 *)
b := 1; (* 数据段基址为1 *)
p := 0; (* 从0号代码开始执行程序 *)
s[1] := 0;
s[2] := 0;
s[3] := 0; (* 数据内存中为SL,DL,RA三个单元均为0,标识为主程序 *)
repeat (* 开始依次运行程序目标代码 *)
i := code[p]; (* 获取一行目标代码 *)
p := p + 1; (* 指令指针加一,指向下一条代码 *)
with i do
case f of (* 如果i的f,即指令助记符是下面的某种情况,执行不同的功能 *)
lit: (* 如果是lit指令 *)
begin
t := t + 1; (* 栈顶指针上移,在栈中分配了一个单元 *)
s[t] := a (* 该单元的内容存放i指令的a操作数,即实现了把常量值放到运行栈栈顶 *)
end;
opr: (* 如果是opr指令 *)
case a of (* operator *) (* 根据a操作数不同,执行不同的操作 *)
0: (* 0号操作为从子过程返回操作 *)
begin (* return *)
t := b - 1; (* 释放这段子过程占用的数据内存空间 *)
p := s[t + 3]; (* 把指令指针取到RA的值,指向的是返回地址 *)
b := s[t + 2] (* 把数据段基址取到DL的值,指向调用前子过程的数据段基址 *)
end;
1: (* 1号操作为栈顶数据取反操作 *)
s[t] := -s[t]; (* 对栈顶数据进行取反 *)
2: (* 2号操作为栈顶两个数据加法操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := s[t] + s[t + 1] (* 把两单元数据相加存入栈顶 *)
end;
3: (* 3号操作为栈顶两个数据减法操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := s[t] - s[t + 1] (* 把两单元数据相减存入栈顶 *)
end;
4: (* 4号操作为栈顶两个数据乘法操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := s[t] * s[t + 1] (* 把两单元数据相乘存入栈顶 *)
end;
5: (* 5号操作为栈顶两个数据除法操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := s[t] div s[t + 1] (* 把两单元数据相整除存入栈顶 *)
end;
6: (* 6号操作为判奇操作 *)
s[t] := ord(odd(s[t])); (* 数据栈顶的值是奇数则把栈顶值置1,否则置0 *)
8: (* 8号操作为栈顶两个数据判等操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := ord(s[t] = s[t + 1]) (* 判等,相等栈顶置1,不等置0 *)
end;
9: (* 9号操作为栈顶两个数据判不等操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := ord(s[t] <> s[t + 1]) (* 判不等,不等栈顶置1,相等置0 *)
end;
10: (* 10号操作为栈顶两个数据判小于操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := ord(s[t] < s[t + 1]) (* 判小于,如果下面的值小于上面的值,栈顶置1,否则置0 *)
end;
11: (* 11号操作为栈顶两个数据判大于等于操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := ord(s[t] >= s[t + 1]) (* 判大于等于,如果下面的值大于等于上面的值,栈顶置1,否则置0 *)
end;
12: (* 12号操作为栈顶两个数据判大于操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := ord(s[t] > s[t + 1]) (* 判大于,如果下面的值大于上面的值,栈顶置1,否则置0 *)
end;
13: (* 13号操作为栈顶两个数据判小于等于操作 *)
begin
t := t - 1; (* 栈顶指针下移 *)
s[t] := ord(s[t] <= s[t + 1]) (* 判小于等于,如果下面的值小于等于上面的值,栈顶置1,否则置0 *)
end;
14: (* 14号操作为输出栈顶值操作 *)
begin
write(s[t]); (* 输出栈顶值 *)
write(fa2, s[t]); (* 同时打印到文件 *)
t := t - 1 (* 栈顶下移 *)
end;
15: (* 15号操作为输出换行操作 *)
begin
writeln; (* 输出换行 *)
writeln(fa2) (* 同时输出到文件 *)
end;
16: (* 16号操作是接受键盘值输入到栈顶 *)
begin
t := t + 1; (* 栈顶上移,分配空间 *)
write('?'); (* 屏显问号 *)
write(fa2, '?'); (* 同时输出到文件 *)
readln(s[t]); (* 获得输入 *)
writeln(fa2, s[t]); (* 把用户输入值打印到文件 *)
end;
end; (* opr指令分析运行结束 *)
lod: (* 如果是lod指令:将变量放到栈顶 *)
begin
t := t + 1; (* 栈顶上移,开辟空间 *)
s[t] := s[base(l) + a] (* 通过数据区层差l和偏移地址a找到变量的数据,存入上面开辟的新空间(即栈顶) *)
end;
sto: (* 如果是sto指令 *)
begin
s[base(l) + a] := s[t]; (* 把栈顶的值存入位置在数据区层差l偏移地址a的变量内存 *)
t := t - 1 (* 栈项下移,释放空间 *)
end;
cal: (* 如果是cal指令 *)
begin (* generat new block mark *)
s[t + 1] := base(l); (* 在栈顶压入静态链SL *)
s[t + 2] := b; (* 然后压入当前数据区基址,作为动态链DL *)
s[t + 3] := p; (* 最后压入当前的断点,作为返回地址RA *)
(* 以上的工作即为过程调用前的保护现场 *)
b := t + 1; (* 把当前数据区基址指向SL所在位置 *)
p := a; (* 从a所指位置开始继续执行指令,即实现了程序执行的跳转 *)
end;
int: (* 如果是int指令 *)
t := t + a; (* 栈顶上移a个空间,即开辟a个新的内存单元 *)
jmp: (* 如果是jmp指令 *)
p := a; (* 把jmp指令操作数a的值作为下一次要执行的指令地址,实现无条件跳转 *)
jpc: (* 如果是jpc指令 *)
begin
if s[t] = 0 then (* 判断栈顶值 *)
p := a; (* 如果是0就跳转,否则不跳转 *)
t := t - 1 (* 释放栈顶空间 *)
end;
end(* with,case *)
until p = 0; (* 如果p等于0,意味着在主程序运行时遇到了从子程序返回指令,也就是整个程序运行的结束 *)
close(fa
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -