Red/System编译器实现分析(1)

来源:互联网 发布:手机如何修改mac地址 编辑:程序博客网 时间:2024/05/06 21:23

距离上一篇文章有一段时间了,之所以迟迟没有动笔,不是因为我太忙,而是我太懒了!抓狂嗯,要改,一定要改!

这一篇开始就要对Red编译器实现进行分析了,首先分析的是Red/System编译器的实现,之所以先讲 Red/System,是因为Red的运行时(Runtime)部分会使用Red/System编写,Red编译器生成的中间代码也是Red/System代码,熟悉了Red/System对后面理解Red编译器的实现有很大的好处。

计划以最快的速度完成下面三个主题:1.递归下降语法分析;2.代码生成(x86 code);3.Exe镜像文件的生成(以PE Format为例)。把整个流程打通后,逐步往里面添加内容就比较容易了,类似习武之人打通了任督二脉后的效果。;-)  下图是来自Red官方blog [Red/System compiler overview]。


因为目前Red正处于bootstrap阶段,所以大部分代码是使用Rebol来写的。一旦Red可用,将使用Red来重新实现Red。现阶段Red/System是由Rebol写成的。不熟悉Rebol的同学可移步[Rebol guide],这里就不再赘述。好了,准备好环境,我们就开始吧。

$ git clone https://github.com/red/red.git$ cd red$ git checkout v0.1.0
我们使用v0.1.0版本。从简单的开始,弄懂了这个版本之后,以此为基础,通过查看commit来学习,不但容易入门,还有助于理解一些特性的缘由。

这个版本已经是一个可用的编译器了,虽然功能还不完善。查看red目录中的“README”,强烈建议参照里面的步骤,编译一下“hello.reds”。生成的hello.exe只有2.5KB,是不是很小,就算直接用汇编来写,也相差不大吧。

整个项目文件数目很少,每个文件的主要职责如下:

-- compiler.r             ; 前端部分,如词法分析,语法分析-- emitter.r              ; 代码生成,根据命令行参数,会调用targets目录下相应的代码-- linker.r               ; 程序文件的组装,根据命令行参数,会调用formats目录下相应的代码-- rsc.r                  ; 命令行解析-- formats    -- PE.r               ; PE 文件格式定义-- targets    -- IA32.r             ; x86 目标代码生成
假设我们要编译的程序如下:

Red/System []a: 1
够简单吧,简直可以说是简单到毫无用处。; -)  不过对于我们学习编译器实现来说,是再好不过了。回忆一下,我们的学习都是从最简单的开始的。从1 + 1 = 2到微积分,从一个个汉字到长篇巨著,从单调的音符到绚丽的篇章,无不是由易到难,由简到繁。试了就知道,慢慢来真的会比较快。好了,不扯了,进入正题。

首先词法分析。使用Rebol有一个好处,那就是我们可以不用自己编写词法分析器,Rebol已经为我们做了这部分工作。

>> src: load "Red/System [] a: 1"== [Red/System [] a: 1]         ; 已经变成一个个token了>> print src/3== a>> type? src/3== set-word!

实际上compiler.r中在load前还会有一个preprocess的过程,那是因为Red/System中像C一样允许宏定义,而load函数并不支持这个功能。我们的程序很简单,不需要预处理,所以先忽略掉它。token拆分好,该语法分析了,接下来就是有意思的部分了。

因为接下来代码比较多,所以许多分析直接注释在代码中。

compiler/run src; run函数如下run: func [src [block!]][    pc: src                ; pc是一个全局变量,指向当前正在分析的token    comp-header            ; 检查文件头,也就是Red/System []    comp-dialect           ; 这个函数是接下来的重点,负责编译我们写的程序    comp-natives           ; 编译程序中定义的函数,暂时不讨论,以后回讲。]
在进入comp-dialect函数后,

[Red/System [] a: 1]            ; 此时pc变量指向的token               ↑               pc;-- 再看comp-dialect函数comp-dialect: does [    while [not tail? pc][        case [            issue? pc/1 [comp-directive]            find [                set-word!                 ; pc/1 指向的是 a:                word!                 path!                set-path!            ] type?/word pc/1 [                fetch-expression/final    ; 接下来运行这个函数,注意参数 'final'            ]            'else [throw-error 'syntax]        ]    ]]
跟进fetch-expression:

;-- 函数做了简化,去掉暂时不用的代码fetch-expression: func [/final /local expr][    ; ...    expr: switch/default type?/word pc/1 [        set-word!   [comp-set-word]              ; pc/1 指向的是 a: , 类型是set-word!                                                  ; 接下来跟进这个函数了        word!       [comp-word]        integer!    [also pc/1 pc: next pc]      ; 如果类型是integer!, 直接返回 pc/1 ,                                                 ; 并且向前移动pc        ; ...    ][;...]    if final [        case [            block? expr [                order-args expr                comp-expression expr            ]            ; ...        ]    ]    expr]comp-set-word: has [name value][    name: pc/1    pc: next pc                      ; 注意这里向前移动了pc,现在pc指向 1    switch/default pc/1 [       ; 忽略大段代码 ;-)    ][        value: fetch-expression      ; 又是这个函数,但没有refinement: 'final'                                     ; 同学们自己跟一下,就不细说了。此时 value: 1        new-line/all reduce [name value] no   ; 最后返回 [a: 1]    ]]

分析到这里,整个函数调用链如下:

; 只涉及到三个函数
comp-dialect --> fetch-expression/final --> comp-set-word --> fetch-expression
当函数 ‘comp-set-word’ 运行结束,返回到函数 ‘fetch-expression’ 时,由于设置了Refinement ‘final’,所以函数 ‘fetch-expression’ 中会运行下面的代码:

fetch-expression: func [/final /local expr][    ; ...    expr: switch/default type?/word pc/1 [        set-word!   [comp-set-word]             ; comp-set-word 返回         word!       [comp-word]        integer!    [also pc/1 pc: next pc]             ; ...    ][;...]                                     ; 此时 expr: [a: 1]    if final [                                  ; final 为 true        case [            block? expr [                ; ...                comp-expression expr            ; 整个表达式搜集完成,开始编译!            ]            ; ...        ]    ]    expr]
从函数名字就可以看出,fetch-expression负责获取表达式,搜集相关的信息。comp-expression负责根据这些信息将表达式编译成机器码。至于怎么编译成机器码,请看下回分解。

原创粉丝点击