C语言简单子集编译器详细设计书(第一版)

来源:互联网 发布:linux vi编辑器使用 编辑:程序博客网 时间:2024/06/10 09:58

1. 预处理器
1.1) 去除注释
     逐行读取源码文件xxx.c到缓存中,凡是遇到‘\\’,就将它和后续字符清空。将缓存字符串逐行写入文件xxx.c.tmp。


2. 词法语法分析器
2.1) 处理流程
     按照《C语言简单子集编译器功能设计书》第二章基本语法,生成语法树。
  2.2) 语法树结构
     参照scmpstx.h中所定义的数据结构。
  2.3) DEBUG
     将生成的语法树写入文件xxx.c.stx。
  2.4) 异常处理
     遇到语法、词法错误时,统一报如下异常:
     Syntax error at line @1. @2
     其中参数@1为行号,参数@2为详细出错信息。
  2.5) 内存管理
     使用动态内存的函数必须在退出函数时释放内存。
     全局作用域的动态内存(标志符表和语法树)使用如下配对函数进行申请和释放:
            initialize_XXX()   release_XXX()


3. 语义分析、汇编码生成器
在Windows下生成MASM汇编码,在Linux下生成AT&T汇编码。
[ MASM ]
  3.1) 汇编码的组织
     3.1.1) 头部
        使用如下固定内容:
        .MODEL SMALL
        .STACK 4096h
        INCLUDE PrintVal.mac
     3.1.2) 数据部
        使用如下模式:
        .DATA
        A  DW  0
       Name_A DB ‘A’,20H,20H
        B  DW  0
       Name_B DB ‘B’,20H,20H
     3.1.3) 代码部
        .CODE
        CALL Main
        Sub1 PROC
        …
        Sub1 ENDP
        …
        Main PROC
        …
        Main ENDP
        END Main
  3.2) 汇编码生成流程
     (1) 生成头部固定代码。
     (2) 生成数据部。遍历IdTable,生成所有变量的定义代码。生成变量名字符串Name_XXX。
     (3) 生成代码段。遍历语法树,生成除变量定义外的代码。
  3.3) 函数定义的实现
     3.3.1) 局部变量和全局变量都定义在数据段,不区分。
     3.3.2) 所有寄存器进入函数时入栈,出函数时出栈。
     3.3.3) 退出函数前,打印出函数名、函数内所有局部变量的名字和值。
         方法:遍历IdTable,将所有变量的名字和值打印。
  3.4) 赋值语句
     3.4.1) 生成FORMULA代码。
     3.4.2) FORMULA计算结果保存到ax寄存器。
     3.4.3) MOV 变量名, ax。
  3.5) if语句
     3.5.1) 生成BOOL_FORMULA代码。
     3.5.2) BOOL_FORMULA计算结果保存在ax寄存器。
     3.5.3) 维护标记列表,为if语句末尾加一个标记SIGN_XXX,其中XXX为当前标记的序号。(标记代码在3.5.5之后生成,名字在3.5.3生成)。
     3.5.4) 比较ax和0,相等,则跳转到标记SIGN_XXX,不等,则继续下条语句。
     3.5.5) 生成大括号中的STATEMENT_LIST代码。
  3.6) for语句
     3.6.1) 如果存在第一赋值语句,生成代码。
     3.6.2) 维护标记列表,为for语句BOOL_FORMULA之前加一个标记SIGN_XXX,其中XXX为当前标记的序号。
     3.6.3) 生成BOOL_FORMULA代码。
     3.6.4) BOOL_FORMULA计算结果保存在ax寄存器。
     3.6.5) 维护标记列表,为for语句末尾加一个标记SIGN_YYY,其中YYY为当前标记的序号。(标记代码在3.6.9之后生成,名字在3.6.5生成)。
     3.6.6) 比较ax和0,相等,则跳转到标记SIGN_YYY,不等,则继续下条语句。
     3.6.7) 生成大括号中的STATEMENT_LIST代码。
     3.6.8) 如果存在第二赋值语句,生成代码。
     3.6.9) 生成无条件跳转代码,跳转到SIGN_XXX。
  3.7) while语句
     3.7.1) 维护标记列表,为while语句BOOL_FORMULA之前加一个标记SIGN_XXX,其中XXX为当前标记的序号。
     3.7.2) 生成BOOL_FORMULA代码。
     3.7.3) BOOL_FORMULA计算结果保存在ax寄存器。
     3.7.4) 维护标记列表,为while语句末尾加一个标记SIGN_YYY,其中YYY为当前标记的序号。(标记代码在3.7.7之后生成,名字在3.7.4生成)。
     3.7.5) 比较ax和0,相等,则跳转到标记SIGN_YYY,不等,则继续下条语句。
     3.7.6) 生成大括号中的STATEMENT_LIST代码。
     3.7.7) 生成无条件跳转代码,跳转到SIGN_XXX。
  3.8) FORMULA
     利用栈进行运算。
     运算级别5: +  -
     运算级别6: *  /
     运算级别7: (
     运算级别0: )
     3.8.1) 算法描述
         由于只支持双操作数的操作符,因此比较简单。基本原理:如果当前操作符的运算级别小于或等于上个操作符的运算级别,则从栈中退出3个元素(2个操作数和1个操作符),计算出结果,如果算式还没结束,结果压栈,继续分析下个操作符。否则从栈中依次退出2个元素(1个操作符和1个操作数),与当前结果累计计算,直到栈被清空。注意记录栈中元素个数,防止越界。另外,如果当前操作符是“)”,退栈时需要将“(”一并退出,即一次退出4个元素。例外1:“( )”之间可能是单个的数字,这时退栈只2个元素,即“(”和这个数字。例外2:如果上个操作符是“(”,当前不退栈,必须等到下个操作符比当前操作符级别低再退栈计算。
        例:
        A+B*(C+D)
        栈                      操作
        A                       ----
        A+                      +
        A+B                     +
        A+B*                    *
        A+B*(                   (
        A+B*(C                  (
        A+B*(C+                 +
        A+B*(C+D                +
        A+B*                    C+D=R1
        A+                      B*R1=R2
        ----                    A+R2=R3
    
  3.9) BOOL_FORMULA
     运算级别0:)
     运算级别1:||
     运算级别2:&&
     运算级别3:==   !=
     运算级别4:>   >=   <   <=
     运算级别5:+  -
     运算级别6:*  /
     运算级别7:(
3.9.1) 算法描述
     具体算法与3.8.1)基本相同。
     注:BOOL运算结果为真,则结果为数值1,假则为0。
     3.9.2) 或运算代码模板:
; || Begin
     CMP AX, 0 ;第一操作数
     JNE SIGN_XXX
     CMP BX, 0 ;第二操作数
     JNE SIGN_XXX
     MOV AX, 0
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 1
SIGN_YYY: 
; || End
     3.9.3) 与运算代码模板
; && Begin
     CMP AX, 0 ;第一操作数
     JE SIGN_XXX
     CMP BX, 0 ;第二操作数
     JE SIGN_XXX
     MOV AX, 1
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 0
SIGN_YYY:
; && End
     3.9.4) ==运算代码模板
; == Begin
     CMP AX, BX ;第一操作数, 第二操作数
     JE SIGN_XXX
     MOV AX, 0
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 1
SIGN_YYY:
; == End
     3.9.5) !=运算代码模板
; != Begin
     CMP AX, BX ;第一操作数, 第二操作数
     JE SIGN_XXX
     MOV AX, 1
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 0
SIGN_YYY:
; != End
     3.9.6) >运算代码模板
; > Begin
     CMP AX, BX ;第一操作数, 第二操作数
     JA SIGN_XXX
     MOV AX, 0
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 1
SIGN_YYY:
; > End
     3.9.7) >=运算代码模板
; >= Begin
     CMP AX, BX ;第一操作数, 第二操作数
     JGE SIGN_XXX
     MOV AX, 0
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 1
SIGN_YYY:
; >= End
     3.9.8) <运算代码模板
; < Begin
     CMP AX, BX ;第一操作数, 第二操作数
     JGE SIGN_XXX
     MOV AX, 1
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 0
SIGN_YYY:
; < End
     3.9.9) <=运算代码模板
; <= Begin
     CMP AX, BX ;第一操作数, 第二操作数
     JA SIGN_XXX
     MOV AX, 1
     JMP SIGN_YYY
SIGN_XXX:
     MOV AX, 0
SIGN_YYY:
; <= End

  3.10) 函数调用
      代码模板
      CALL XXXX ; 函数名


[ AT&T ]
未稿

4. 可执行文件生成器
4.1) 生成方法
    在Windows下使用masm.exe(Microsoft (R) Macro Assembler Version 5.00)和link.exe(Microsoft (R) Overlay Linker  Version 3.60)编译为可执行文件。在Linux下使用as编译。


5. 附录
5.1) MASM汇编码

.MODEL SMALL
.STACK 200h

CR equ 13
LF equ 10
TERMINATOR equ '$'

.DATA

Message DB 'Hello, World !',0AH,0DH,'$'

.CODE

Main PROC
;lea ax, Message
;mov ds, ax
mov ax, DGROUP
mov ds, ax
lea ax, Message
mov dx, ax
mov ah, 9
int 21h

mov ax, 4c00h
int 21h
Main ENDP
END main

 

5.2) AT&T汇编码
#hello.s
.data # 数据段声明
msg : .string "Hello, world!\\n" # 要输出的字符串
len = . - msg # 字串长度
.text # 代码段声明
.global _start # 指定入口函数
_start: # 在屏幕上显示一个字符串
movl $len, %edx # 参数三:字符串长度
movl $msg, %ecx # 参数二:要显示的字符串
movl $1, %ebx # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
int $0x80 # 调用内核功能
# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能

 

原创粉丝点击