在博主的大学生涯中,感觉最头痛的一门课程就是编译原理了,学习完这门课程之后,虽然知道了LL,LR算法,和一系列与编译原理相关的术语,可是对它的了解一直停留在做题上,虽然博主一直希望能够通过自己写一个编译器来加深对编译原理的理解,可是用C语言写编译器真的是一场噩梦,每天大把的时间都花在了调试bug上,更没有时间和精力去思考有关编译原理的东西@~@。
int scheme_entry();
int main(int argc, char** argv)
{
printf("%d\n", scheme_entry());
return 0;
}
2 Scheme编译程序
(define (compile-program x)
(emit " .text")
(emit " .global_scheme_entry")
(emit " .def_scheme_entry; .scl 2; .type 32; .endef")
(emit "_scheme_entry:")
(emit "LFB0:")
(emit " .cfi_startproc")
(emit " pushl %ebp")
(emit " .cfi_def_cfa_offset 8")
(emit " .cfi_offset 5,-8")
(emit " movl %esp,%ebp")
(emit " .cfi_def_cfa_register 5")
(emit " movl $~a,%eax" x)
(emit " popl %ebp")
(emit " .cfi_restore5")
(emit " .cfi_def_cfa 4,4")
(emit " ret")
(emit " .cfi_endproc")
(emit "LFE0:"))
这是编译器的主程序,emit函数就是把字符串输出到我们规定的output-port里面,这个程序的字符串由汇编构成,不熟悉汇编语言的朋友看起来会感觉比较奇怪,其中大部分我们都不需要了解,我们需要注意的是(emit " movl $~a,%eax" x),x就是我们的立即数,这里把它放入eax寄存器中,因为计算机在执行完一个函数后会把结果放入eax寄存器中。
(define (emit . args)
(apply fprintf (compile-port) args)
(newline (compile-port)))
这是emit函数的定义,将结果显示入我们规定的端口。
(define compile-port
(make-parameter
(current-output-port)
(lambda (p)
(unless (output-port? p)
(error 'compile-port (format "not an output port ~s" p)))
p)))
current-output-port在rnrs/io/ports-6模块中定义,我们需要(require rnrs/io/ports-6)。
(define (compile expr)
(run-compile expr)
(build)
(execute))
这个函数帮我们执行函数的编译,链接,执行。
(define (run-compile expr)
(let((p (open-output-file "program.s" #:exists 'replace)))
(parameterize ((compile-port p))
(compile-program expr))
(close-output-portp)))
我们将输出端口规定为program.s。
(define (build)
(unless (not (false? (system (format "gcc -m32 -Wall -o program ~a program.s"
(runtime-file)
))) )
(error 'make"could not build target")))
连接我们使用gcc与我们前面准备的C运行时程序连接。
(define (execute)
(unless (not (false? (system "./stst > stst.out")))
(error 'make"produced program exited abnormally")))
执行程序。
好了,执行完以上步骤后,你就得到了一个能编译数字的编译器了,虽然很简单,但是这个编译器是我们以后构造更复杂的编译器的基础,希望感兴趣的朋友认真对待它。
最后,我们先休息一下,希望大家玩的开心