用Scheme写一个Scheme编译器(一)

  在博主的大学生涯中,感觉最头痛的一门课程就是编译原理了,学习完这门课程之后,虽然知道了LL,LR算法,和一系列与编译原理相关的术语,可是对它的了解一直停留在做题上,虽然博主一直希望能够通过自己写一个编译器来加深对编译原理的理解,可是用C语言写编译器真的是一场噩梦,每天大把的时间都花在了调试bug上,更没有时间和精力去思考有关编译原理的东西@[email protected]。

编译的理论不好理解,但是使用C语言又很难将注意力集中于编译的学习之上,想学关于编译的知识还真是难啊。

后来在接触到函数式编程之后,特别是在学习过Scheme语言之后,博主又重新拾起了当初的那个幼稚的想法,用Scheme写一个编译器。

我先来介绍一下为什么要使用Scheme来写一个编译器。

1. 由于Scheme的语法是使用了S-expression的形式,所以Scheme的程序在语法分析的阶段中就有很明显的优势了(说白了就是我们可以跳过在传统语言中令人头痛的语法分析了:-P)

2. 在传统的编译器编写方式中,我们一上来就要定义一大堆程序的语法,然后在写语法分析算法的时候,要一次性把所有的语法都考虑到(这种不人道的行为简直会把人们的耐心给磨没了),而在我们这个编译器当中,我们可以一点一点地扩充语法,而不是一次性地就把编译器写好,这也是拜Scheme语言的灵活性所赐。

3. 优点还有很多,一时想不起来了^_^

博主将会把完成这个编译器以一系列的文章呈现出来,如果有任何错误,意见都可以直接和我进行联系(邮箱:[email protected])

我们今天就从数字开始,这个很简单,主要是希望大家熟悉一下Scheme的语法。(博主使用的是Ubuntu 14.04 操作系统,Scheme编译器使用的是DrRacket,C编译器使用的是gcc)

我们首先要准备两样东西,一个是编译出来的程序的运行时程序,一个是我们用Scheme编写的编译器程序

1 C运行时程序

这个运行时程序现在还是非常简单,仅需要一个printf函数,把编译出来的程序显示出来:

#include<stdio.h>

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_restore 5")

  (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-port p)))

我们将输出端口规定为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")))

执行程序。

好了,执行完以上步骤后,你就得到了一个能编译数字的编译器了,虽然很简单,但是这个编译器是我们以后构造更复杂的编译器的基础,希望感兴趣的朋友认真对待它。

最后,我们先休息一下,希望大家玩的开心

时间: 2024-08-11 12:12:35

用Scheme写一个Scheme编译器(一)的相关文章

python 写一个scheme解释器(一)

解释器的本质 ? 我们换一种语言来写解释器的时候,其实本质和scheme写scheme是一样的,即将输入的一串字符串作为源程序执行而语法和语义均由自己预先设计好并严格执行. ? 这里我们采用python 来实现我们的第二版的scheme解释器,首先python支持的列表推导式.lambda.模式匹配等语法糖十分适合去编写解释器,另一方面,python内置数据结构齐全,使用简单. 当然我们采用C++ 也是完全可行的,但由于C++对字符串的处理功能比较弱,并且不同类型数据的转换都需要手工去完成,实现

python 写一个scheme 解释器 (二)&mdash;&mdash;简单求值器内核

  这一篇开始正式完成求值器,首先本着一个基本原则: 先将整个流程实现,才逐步细化每个过程,最终扩充比较难的特性.   一. 词法分析 def tokenAnalysis(strings): return strings.replace('(',' ( ').replace(')',' ) ').split()     将字符串流按照空白字符分割成一个个子串,split 和 replace 函数的更多用法可以查阅python手册. 示例: (cons  1  2) 拆分结果:'(' 'cons'

【转】写一个C语言编译器 : BabyC

[转载]此文是转载,方便以后读与学习. 原文链接:http://blog.jobbole.com/77305/ 动手编写一个编译器,学习一下较为底层的编程方式,是一种学习计算机到底是如何工作的非常有效方法. 编译器通常被看作是十分复杂的工程.事实上,编写一个产品级的编译器也确实是一个庞大的任务.但是写一个小巧可用的编译器却不是这么困难. 秘诀就是首先去找到一个最小的可用工程,然后把你想要的特性添加进去.这个方法也是Abdulaziz Ghuloum在他那篇著名的论文“一种构造编译器的捷径”里所提

学了编译原理能否用 Java 写一个编译器或解释器?

16 个回答 默认排序? RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和解释器.其实用什么语言来实现编译器并不是最重要的部分(虽然Java也不是实现编译器最方便的语言),最初用啥语言都可以. 我在大学的时候,我们的软件工程和计算机科学的编译原理课的作业好像都是可以用Java来写的.反正我印象中我给这两门课写的作业都是用的Java. ===================

编译原理实战入门:用 JavaScript 写一个简单的四则运算编译器(四)结语

四则运算编译器,虽然说功能很简单,只能编译四则运算表达式.但是编译原理前端部分几乎都有涉及,词法分析,语法分析,还有代码生成. 再复杂的编译器.再简单的编译器,功能上是差不多的,只是复杂的编译器实现上会更困难. 这个系列的文章是为了帮助你入门,在这个基础上再去看编译原理相关书籍,不至于打瞌睡. 如果你对编译原理很有兴趣,并且想更深一步的学习,在这里强烈推荐你看一本书--我心目中的神书--<计算机系统要素-从零开始构建现代计算机>. 这本书神在哪? 神在它通俗易懂,对小白足够友好,但又不过分肤浅

从零写一个编译器(三):语法分析之几个基础数据结构

项目的完整代码在 C2j-Compiler 写在前面 这个系列算作为我自己在学习写一个编译器的过程的一些记录,算法之类的都没有记录原理性的东西,想知道原理的在龙书里都写得非常清楚,但是我自己一开始是不怎么看得下来,到现在都还没有完整的看完,它像是一本给已经有基础的人写的书. 在parse包里一共有8个文件,就是语法分析阶段写的所有东西啦 Symbols.java Production.java SyntaxProductionInit.java FirstSetBuilder.java Prod

从零写一个编译器(十):编译前传之直接解释执行

项目的完整代码在 C2j-Compiler 前言 这一篇不看也不会影响后面代码生成部分 现在经过词法分析语法分析语义分析,终于可以进入最核心的部分了.前面那部分可以称作编译器的前端,代码生成代码优化都是属于编译器后端,如今有关编译器的工作岗位主要都是对后端的研究.当然现在写的这个编译器因为水平有限,并没有优化部分. 在进行代码生成部分之前,我们先来根据AST来直接解释执行,其实就是对AST的遍历.现代解释器一般都是生成一个比较低级的指令然后跑在虚拟机上,但是简单起见我们就直接根据AST解释执行的

王垠:怎样写一个解释器

卖了好久关子了,说要写一个程序语言理论的入门读物,可是一直没有下笔.终于狠下心来兑现一部分承诺.今天就从解释器讲起吧. 解释器是比较深入的内容.虽然我试图从最基本的原理讲起,尽量让这篇文章不依赖于其它的知识,但是这篇教程并不是针对函数式编程的入门,所以我假设 你已经学会了最基本的 Scheme 和函数式编程.如果你完全不了解这些,可以读一下< SICP | 计算机程序的构造和解释> 的第一,二章.当然你也可以继续读这篇文章,有不懂的地方再去查资料.我在这里也会讲递归和模式匹配的原理.如果你已经

Android 自己写一个打开图片的Activity

根据记忆中eoe的Intent相关视频,模仿,写一个打开图片的Activity 1.在主Activity的button时间中,通过设置action.category.data打开一个图片.这时代码已经可以运行,将使用系统默认的工具打开图片. Intent intentImage = new Intent(Intent.ACTION_VIEW); intentImage.addCategory(Intent.CATEGORY_DEFAULT); File file = new File("/sto