将语言编译到 Java(译)

Posted by Matt Might, translated by David Gu on November 18, 2016

注:原文发表于 http://matt.might.net/articles/compiling-to-java/

在我的高级编译器课程中,我们涵盖了一系列中间语言和目标语言。出于对性能的追求,C 是一门受欢迎的目标语言;但在另一方面,Java 的优点却经常会被忽视。相较于更抽象的高级语言中的构造,一些 Java 中不那么“高级”的功能却恰好使其成为一门适宜的目标语言,例如,词法闭包在 Java 中便可以用匿名类来表示。从代价上来说,相比于编译到 C,写一个编译到 Java 的编译器只需要大约三分之一到一半的时间(和代码)。事实上,我们可以纯粹使用语法变换来编译 Scheme 的基本核心到 Java。(Scheme 的宏系统可以很容易地将完整的 Scheme 变换为最精简的核心 Scheme。)因此,本页底部的参考编译器实现只有 400 多行高度注释的代码。(而编译成 C 的话则有1000多行。)

有帮助的资源:

  • Java 语言官方规范 范涵盖了语言的方方面面,虽然其中有些知识很少或从未在普通编程中使用,但是对于了解 Java 的编译期则会非常方便实用。

相关博客:

编译到 Java 的“利弊”

除了上面提到的优点之外,编译到 Java 还提供了对 Java 库系统的访问,JVM 的可移植性,JIT 编译、优化以及垃圾回收等等特性。在 Java 中,尾递归优化的缺少是这个编译策略的一个缺点,但是这一点可以通过 trampolining 技术来得以改善。

更广泛地说,在我的课堂上,我在教学实现一门语言的时候,是在实现上的复杂性和性能之间进行权衡:

  1. 我会首先建议,你应该将你的语言实现成一个解释器;
  2. 如果那还不够快的话,试试 SICP 书里优化解释器的方法;
  3. 如果还不够,那就编译到 Java 吧;
  4. 如果还是有点嫌慢,那就试着编译到 C;
  5. 仍然不够快?试试在延续传递风格下编译到汇编代码;
  6. 如果依然要求更快的速度,那就开始进行基本的编译器优化吧;
  7. 最后,如果还是不够走运,那就开始静态分析驱动的编译器优化。

每从第 n 步跨到第 n+1 步,性能便会有所提升,但随之带来的问题就是代码量和复杂度大概会双倍地提升。所以这样看来,Java 处于一个很不错的位置:相对来讲较低的实现复杂度,但在性能上能有最大的百分比增益。

Scheme 核心与语法糖

此处我创建的编译器是用来编译 Scheme 最核心的部分的,不过将同样的技术应用到 Python 或者 Ruby 这样的语言上也不会是难事:

 <exp> ::= <const>
        |  <prim>
        |  <var>
        |  (lambda (<var> ...) <exp>)
        |  (if <exp> <exp> <exp>)
        |  (set! <var> <exp>)
        |  (let ((<var> <exp>) ...) <exp>)
        |  (letrec ((<var> (lambda (<var>...) <exp>))) <exp>)
        |  (begin <exp> ...)
        |  (<exp> <exp> ...)

 <const> ::= <int>

一个 Scheme 编译器可以很容易使用宏来把完整的 Scheme 变换到这样的一种语言,甚至更精简。事实上,很多 Scheme 编译器也确实是这么做的。

Scheme 的值在 Java 中的编码方式

编译到 Java 的第一个任务是明确 Scheme 中的值在 Java 中对应的编码方式。为此,我创建了 Java 接口 Value,并且所有的 Scheme 值都继承了它。Value 的子类和接口包括 VoidValueBooleanValueIntValueProcValuePrimitive。 真正的 Java 代码其实还提供了一个 RuntimeEnvironment 类,它将所有顶层的原语(如加法运算,减法运算和 I/O 操作)绑定到相应的 Java 方法。编译后的程序应该继承自 RuntimeEnvironment

顶层的 compile 函数有着典型的 Scheme “分发匹配”的风格:

; java-compile-exp : exp -> string
(define (java-compile-exp exp)
  (cond
    ; core forms:
    ((const? exp)       (java-compile-const exp))
    ((prim?  exp)       (java-compile-prim exp))
    ((ref?   exp)       (java-compile-ref exp))
    ((lambda? exp)      (java-compile-lambda exp))
    ((if? exp)          (java-compile-if exp))
    ((set!? exp)        (java-compile-set! exp))
    
    ; syntactic sugar:
    ((let? exp)         (java-compile-exp (let=>lambda exp)))
    ((letrec1? exp)     (java-compile-exp (letrec1=>Y exp)))
    ((begin? exp)       (java-compile-exp (begin=>let exp)))
    
    ; applications:
    ((app? exp)         (java-compile-app exp))))

所以,编译过程被分解为了各个独立的基础构造。

整型数

整型数被编译为一个 IntValue 对象,而不是它们本身的字面值。例如,Scheme 中的 3 会被编译到 Java 中一个新的对象 IntValue(3)

原语

原语会在表格中进行查找然后被翻译为在 RuntimeEnvironment 中的名字:

(define (java-compile-prim p)
  (cond
    ((eq? '+ p)       "sum")
    ((eq? '- p)       "difference")
    ((eq? '* p)       "product")
    ((eq? '= p)       "numEqual")
    ((eq? 'display p) "display")
    (else             (error "unhandled primitive " p))))

变量引用

变量引用必须进行名称限制,因为 Scheme 中对标识符的命名规则要比 Java 的更宽泛。可变变量(那些被 set! 修改过的)也将被区别对待。由于 Java 中的匿名函数捕获的变量必须声明 final 关键字,这些可变变量将被包装在 ValueCell 对象中并以 m_ 作为前缀。在编译之前的一次代码遍历旨在找到所有这些可变变量。

Lambda

Lambda 将会被编译到匿名类。例如,(lambda (v1 ... vN) exp) 会变成:

new NullProcValueN () {
 public apply (final Value [mangle v1],...,final Value [mangle vN]) {
  // for each mutable formal vi:
  final ValueCell m_[mangle vi] = new ValueCell([mangle vi]) ; 
  return [compile exp] ;
} 

对于每一个 N,就会有一个相应的 NullProcValueN 类。NullProcValue 类则为 Value 中定义的一些方法提供了默认定义。

很明显,[mangle v] 代表变量 v 被名称限制后的名字,而 [compile exp] 则代表 exp 被编译后的文本。

条件语句

形式 (if exp1 exp2 exp3) 事实上被编译成了逻辑三元式,而不是 if () {} else {} 的形式:

([compile exp1]) ? ([compile exp2]) : ([compile exp3])

可变变量

(set! var exp) 依赖于 lambda 表达式的编译以及要把 var 包装在一个 ValueCell 中,所以它会被编译成:

VoidValue.Void(m_[mangle var].value = [compile exp])

变量绑定

let 会被变换成 lambda 表达式的一次应用。也就是说,(let ((v e) ...) body) 会变成:

((lambda (v ...) body) e ...)

递归

letrec 可以被变换成 “lets and sets” 或者 Y combinator。我在此选择了 Y Combinator 只是为了表明它可以做到不使用副作用。实际上,编译器在运行时生成一个新的 Y Combinator,以便它匹配递归过程的精度:

; xargs : nat -> list[symbol]
(define (xargs n)
  (if (<= n 0)
      '()
      (cons (string->symbol (string-append "x" (number->string n)))
            (xargs (- n 1)))))
       
; Yn generates the Y combinator for n-arity procedures.
(define (Yn n)
  `((lambda (h) (lambda (F)
     (F (lambda (,@(xargs n)) (((h h) F) ,@(xargs n))))))
    (lambda (h) (lambda (F)
     (F (lambda (,@(xargs n)) (((h h) F) ,@(xargs n))))))))

在 Java 中,单参程序的 Y combinator 将会是这样:

((ProcValue1)(new NullProcValue1 () {
 public Value apply(final Value h) {

  return new NullProcValue1 () {
 public Value apply(final Value F) {

  return ((ProcValue1)(F)).apply(new NullProcValue1 () {
 public Value apply(final Value x) {

  return ((ProcValue1)(((ProcValue1)(((ProcValue1)(h)).apply(h)
)).apply(F)
)).apply(x)
 ;
}}
)
 ;
}}
 ;
}}
)).apply(new NullProcValue1 () {
 public Value apply(final Value h) {

  return new NullProcValue1 () {
 public Value apply(final Value F) {

  return ((ProcValue1)(F)).apply(new NullProcValue1 () {
 public Value apply(final Value x) {

  return ((ProcValue1)(((ProcValue1)(((ProcValue1)(h)).apply(h)
)).apply(F)
)).apply(x)
 ;
}}
)
 ;
}}
 ;
}}
)

顺序执行

顺序执行语句会被变换成一个在 let 绑定下但是未使用任何绑定的变量的情况。换句话说,(begin e1 ... eN) 会变成:

(let ((_ e1))
  (begin e1 ... eN))

代码

Value.java

/**
 * All runtime values must inherit from Value.
 */
interface Value {
    public int toInt() ;
    public boolean toBoolean() ;
}


/**
 * VoidValue.VOID is returned when the result can be ignored.
 */
class VoidValue implements Value {
    private VoidValue() {
    }

    public static final VoidValue VOID = new VoidValue() ;

    /**
     * Converts a value into the void value.
     */
    public static final VoidValue Void(Value v) {
        return VOID ;
    }

    public int toInt() {
        throw new RuntimeException("Cannot treat void as int!") ;
    }

    public boolean toBoolean() {
        return true ;
    }
}


/**
 * Integer values.
 */
class IntValue implements Value {
    private int n  ;

    public IntValue(int n) {
        this.n = n ;
    }

    public int toInt() {
        return this.n ;
    }
    
    public boolean toBoolean() {
        return true ;
    }

    public String toString() {
        return String.valueOf(n) ;
    }
}


/**
 * Boolean values.
 */
class BooleanValue implements Value {
    private boolean b ;

    private BooleanValue(boolean b) {
        this.b = b ;
    }

    public int toInt() {
        throw new RuntimeException("Cannot treat boolean as int!") ;
    }

    public boolean toBoolean() {
        return b ;
    }

    public static final BooleanValue TRUE  = new BooleanValue(true) ;
    public static final BooleanValue FALSE = new BooleanValue(false) ;

    public static final BooleanValue fromBoolean(boolean p) {
        return p ? TRUE : FALSE ;
    }
}


/**
 * Procedural values.
 */
interface ProcValue extends Value {
}


/**
 * Procedural values with standard procedures pre-defined.
 */
abstract class NullProcValue implements ProcValue {
    public int toInt() {
        throw new RuntimeException("Cannot treat procedure to int!") ;
    } 

    public boolean toBoolean() {
        return true ;
    } 
}

interface ProcValue0 extends ProcValue {
    abstract public Value apply() ;
}

interface ProcValue1 extends ProcValue {
    abstract public Value apply(Value arg1) ;
}

interface ProcValue2 extends ProcValue {
    abstract public Value apply(Value arg1, Value arg2) ;
}

interface ProcValue3 extends ProcValue {
    abstract public Value apply(Value arg1, Value arg2, Value arg3) ;
}


abstract class NullProcValue0 extends NullProcValue implements ProcValue0 {}
abstract class NullProcValue1 extends NullProcValue implements ProcValue1 {}
abstract class NullProcValue2 extends NullProcValue implements ProcValue2 {}
abstract class NullProcValue3 extends NullProcValue implements ProcValue3 {}


/**
 * Primitives values are multi-arity procedures.
 */
interface Primitive extends ProcValue, ProcValue0, ProcValue1, ProcValue2, ProcValue3 {
    public Value apply() ;
    public Value apply(Value arg1) ;    
    public Value apply(Value arg1, Value arg2) ;
    public Value apply(Value arg1, Value arg2, Value arg3) ;
}

abstract class NullPrimitive implements Primitive {
    public Value apply() {
        throw new RuntimeException("0 arguments not supported") ;
    }
    public Value apply(Value arg1) {
        throw new RuntimeException("1 argument not supported") ;
    }  
    public Value apply(Value arg1, Value arg2) {
        throw new RuntimeException("2 arguments not supported") ;
    }
    public Value apply(Value arg1, Value arg2, Value arg3) {
        throw new RuntimeException("3 arguments not supported") ;
    }

    public int toInt() {
        throw new RuntimeException("Cannot treat primitive as int!") ;
    }

    public boolean toBoolean() {
        return true ;
    }
}


/**
 * Top-level bindings for the run-time environment.
 */
class RuntimeEnvironment {

    public static final Primitive display = new NullPrimitive () {
            public Value apply(Value arg1) {
                System.out.println(arg1) ;
                return VoidValue.VOID ;
            }
        } ;

    public static final Primitive sum = new NullPrimitive () {
            public Value apply(Value arg1, Value arg2) {
                return new IntValue(arg1.toInt() + arg2.toInt()) ;
            }
        } ;

    public static final Primitive product = new NullPrimitive () {
            public Value apply(Value arg1, Value arg2) {
                return new IntValue(arg1.toInt() * arg2.toInt()) ;
            }
        } ;

    public static final Primitive difference = new NullPrimitive () {
            public Value apply(Value arg1, Value arg2) {
                return new IntValue(arg1.toInt() - arg2.toInt()) ;
            }
        } ;

    public static final Primitive numEqual = new NullPrimitive () {
            public Value apply(Value arg1, Value arg2) {
                return BooleanValue.fromBoolean(arg1.toInt() == arg2.toInt()) ;
            }
        } ;
}



/**
 * Mutable variables become ValueCells.
 */
class ValueCell {
    public Value value ;

    public ValueCell(Value initialValue) {
        this.value = initialValue ;
    }
}

scheme-to-java.scm

;; A Scheme-to-Java compiler.

;; The compiler is designed to show how close
;; the mapping between Scheme and Java can be.

;; Author: Matthew Might
;; Site:   http://matt.might.net/
;;         http://www.ucombinator.org/

;; The input language contains integers, variables,
;; a few primitives, lambda terms, let terms, explicit
;; recursion (letrec), conditionals and function
;; applications, sequencing and mutable variables.

;; <exp> ::= <const>
;;        |  <prim>
;;        |  <var>
;;        |  (lambda (<var> ...) <exp>)
;;        |  (if <exp> <exp> <exp>)
;;        |  (set! <var> <exp>)
;;        |  (let ((<var> <exp>) ...) <exp>)
;;        |  (letrec ((<var> (lambda (<var>...) <exp>))) <exp>)
;;        |  (begin <exp> ...)
;;        |  (<exp> <exp> ...)

;; <const> ::= <int>

;; To run this compiler, run an R5RS-compatible interpreter
;; on this file and pipe a Scheme expression into stdin:

;;  $ interpret thisfile.scm < input.scm > BOut.java
;;  $ javac Value.java BOut.java 
;;  $ java BOut

;; The file Value.java is required to compile the output.
;; Value.java defines internal data types as well as the
;; runtime environment.

;; To handle closures, the compiler uses Java's
;; anonymous class mechanism.

;; To handle recursion, the compiler creates a Y
;; combinator with the appropriate arity.

;; To handle mutable variables, the compiler first
;; analyzes programs to find the set!'d names.
;; Mutable names are then wrapped in ValueCell objects.

;; This compiler is reasonably close to meta-circular.
;; With a few modifications and an s-expression parser
;; in Java, it would be.



;; Utilities.

; void : -> void
(define (void) (if #f #t))

; tagged-list? : symbol value -> boolean
(define (tagged-list? tag l)
  (and (pair? l)
       (eq? tag (car l))))

; char->natural : char -> natural
(define (char->natural c)
  (let ((i (char->integer c)))
    (if (< i 0)
        (* -2 i)
        (+ (* 2 i) 1))))

; integer->char-list : integer -> string
(define (integer->char-list n)
  (string->list (number->string n)))



;; Data type predicates and accessors.

; const? : exp -> boolean
(define (const? exp)
  (integer? exp))

; ref? : exp -> boolean
(define (ref? exp)
  (symbol? exp))

; let? : exp -> boolean
(define (let? exp)
  (tagged-list? 'let exp))

; let->bindings : let-exp -> alist[symbol,exp]
(define (let->bindings exp)
  (cadr exp))

; let->exp : let-exp -> exp
(define (let->exp exp)
  (caddr exp))

; letrec1? : exp -> boolean
(define (letrec1? exp)
  (and (tagged-list? 'letrec exp)
       (= (length (cadr exp)) 1)))

; letrec1->binding : letrec1-exp -> (symbol exp)
(define (letrec1->binding exp)
  (caadr exp))

; letrec1->exp : letrec1-exp -> exp
(define (letrec1->exp exp)
  (caddr exp))

; lambda? : exp -> boolean
(define (lambda? exp)
  (tagged-list? 'lambda exp))

; lambda->formals : lambda-exp -> list[symbol]
(define (lambda->formals exp)
  (cadr exp))

; lambda->exp : lambda-exp -> exp
(define (lambda->exp exp)
  (caddr exp))

; if? : exp -> boolean
(define (if? exp)
  (tagged-list? 'if exp))

; if->condition : if-exp -> exp
(define (if->condition exp)
  (cadr exp))

; if->then : if-exp -> exp
(define (if->then exp)
  (caddr exp))

; if->else : if-exp -> exp
(define (if->else exp)
  (cadddr exp))

; app? : exp -> boolean
(define (app? exp)
  (pair? exp))

; app->fun : app-exp -> exp
(define (app->fun exp)
  (car exp))

; app->args : app-exp -> list[exp]
(define (app->args exp)
  (cdr exp))
  
; prim? : exp -> boolean
(define (prim? exp)
  (or (eq? exp '+)
      (eq? exp '-)
      (eq? exp '*)
      (eq? exp '=)
      (eq? exp 'display)))

; begin? : exp -> boolean
(define (begin? exp) 
  (tagged-list? 'begin exp))

; begin->exps : begin-exp -> list[exp]
(define (begin->exps exp)
  (cdr exp))

; set! : exp -> boolean
(define (set!? exp)
  (tagged-list? 'set! exp))

; set!-var : set!-exp -> var
(define (set!->var exp)
  (cadr exp))

; set!-exp : set!-exp -> exp
(define (set!->exp exp)
  (caddr exp))



;; Desugarings.

; let=>lambda : let-exp -> app-exp
(define (let=>lambda exp)
  (if (let? exp)
      (let ((vars (map car (let->bindings exp)))
            (args (map cadr (let->bindings exp))))
        `((lambda (,@vars) ,(let->exp exp)) ,@args))
      exp))


; arity : lambda-exp -> nat
(define (arity lam)
  (length (lambda->formals lam)))

; xargs : nat -> list[symbol]
(define (xargs n)
  (if (<= n 0)
      '()
      (cons (string->symbol (string-append "x" (number->string n)))
            (xargs (- n 1)))))
       
; Yn generates the Y combinator for n-arity procedures.
(define (Yn n)
  `((lambda (h) (lambda (F) (F (lambda (,@(xargs n)) (((h h) F) ,@(xargs n))))))
    (lambda (h) (lambda (F) (F (lambda (,@(xargs n)) (((h h) F) ,@(xargs n))))))))

; letrec1=>Y : letrec1-exp -> let-exp
(define (letrec1=>Y exp)
  (if (letrec1? exp)
      (let* ((binding  (letrec1->binding exp))
             (name     (car binding))
             (arg      (cadr binding))
             (num-args (arity arg)))
        `(let ((,name (,(Yn num-args) (lambda (,name) ,arg))))
           ,(letrec1->exp exp)))
      exp))
        
; begin=>let : begin-exp -> let-exp
(define (begin=>let exp)
  (define (singlet? l)
    (and (list? l)
         (= (length l) 1)))
  
  (define (dummy-bind exps)
    (cond
      ((singlet? exps)  (car exps))
      
      ((pair? exps)     `(let (($_ ,(car exps)))
                          ,(dummy-bind (cdr exps))))))
  (dummy-bind (begin->exps exp)))



;; Mutable variable analysis.

;; Variables which are mutable are 
;; wrapped in ValueCell objects.

; mutable-variables : list[symbol]
(define mutable-variables '())

; mark-mutable : symbol -> void
(define (mark-mutable symbol)
  (set! mutable-variables (cons symbol mutable-variables)))

; is-mutable? : symbol -> boolean
(define (is-mutable? symbol)
  (define (is-in? S)
    (if (not (pair? S))
        #f
        (if (eq? (car S) symbol)
            #t
            (is-in? (cdr S)))))
  (is-in? mutable-variables))

; analyze-mutable-variables : exp -> void
(define (analyze-mutable-variables exp)
  (cond 
    ((const? exp)    (void))
    ((ref? exp)      (void))
    ((prim? exp)     (void))
    ((lambda? exp)   (analyze-mutable-variables (lambda->exp exp)))
    ((let? exp)      (begin
                       (map analyze-mutable-variables (map cadr (let->bindings exp)))
                       (analyze-mutable-variables (let->exp exp))))
    ((letrec1? exp)  (begin
                       (analyze-mutable-variables (cadr (letrec1->binding exp)))
                       (analyze-mutable-variables (letrec1->exp exp))))
    ((set!? exp)     (begin
                       (mark-mutable (set!->var exp))
                       (analyze-mutable-variables (set!->exp exp))))
    ((if? exp)       (begin
                       (analyze-mutable-variables (if->condition exp))
                       (analyze-mutable-variables (if->then exp))
                       (analyze-mutable-variables (if->else exp))))
    ((begin? exp)    (begin
                       (map analyze-mutable-variables (begin->exps exp))
                       (void)))
    ((app? exp)      (begin 
                       (map analyze-mutable-variables exp)
                       (void)))
    (else            (error "unknown expression type: " exp))))



;; Name-mangling.

;; We have to name-mangle Scheme identifiers into
;; Java-compatible identifiers, because names like
;; "foo-bar/baz" are not identifiers in Java.

; mangle : symbol -> string
(define (mangle symbol)
  (define (m chars)
    (if (null? chars)
        '()
        (if (or (and (char-alphabetic? (car chars)) (not (char=? (car chars) #\_)))
                (char-numeric? (car chars)))
            (cons (car chars) (m (cdr chars)))
            (cons #\_ (append (integer->char-list (char->natural (car chars)))
                              (m (cdr chars)))))))
  (list->string (m (string->list (symbol->string symbol)))))



;; Compilation routines.

; java-compile-program : exp -> string
(define (java-compile-program exp)
  (string-append 
   "public class BOut extends RuntimeEnvironment {\n"
   " public static void main (String[] args) {\n"
   (java-compile-exp exp) 
   " ;\n"
   " }\n"
   "}\n"))

; java-compile-exp : exp -> string
(define (java-compile-exp exp)
  (cond
    ; core forms:
    ((const? exp)       (java-compile-const exp))
    ((prim?  exp)       (java-compile-prim exp))
    ((ref?   exp)       (java-compile-ref exp))
    ((lambda? exp)      (java-compile-lambda exp))
    ((if? exp)          (java-compile-if exp))
    ((set!? exp)        (java-compile-set! exp))
    
    ; syntactic sugar:
    ((let? exp)         (java-compile-exp (let=>lambda exp)))
    ((letrec1? exp)     (java-compile-exp (letrec1=>Y exp)))
    ((begin? exp)       (java-compile-exp (begin=>let exp)))
    
    ; applications:
    ((app? exp)         (java-compile-app exp))))


; java-compile-const : const-exp -> string
(define (java-compile-const exp)
  (cond
    ((integer? exp) (string-append 
                     "new IntValue(" (number->string exp) ")"))
    (else           (error "unknown constant: " exp))))

; java-compile-prim : prim-exp -> string
(define (java-compile-prim p)
  (cond
    ((eq? '+ p)       "sum")
    ((eq? '- p)       "difference")
    ((eq? '* p)       "product")
    ((eq? '= p)       "numEqual")
    ((eq? 'display p) "display")
    (else             (error "unhandled primitive " p))))

; java-compile-ref : ref-exp -> string
(define (java-compile-ref exp)
  (cond
    ((is-mutable? exp) (string-append "m_" (mangle exp) ".value"))
    (else              (mangle exp))))
  
; java-compile-formals : list[symbol] -> string
(define (java-compile-formals formals)
  (if (not (pair? formals))
      ""
      (string-append
       "final Value "
       (mangle (car formals))
       (if (pair? (cdr formals))
           (string-append ", " (java-compile-formals (cdr formals)))
           ""))))
 
; java-compile-lambda : lambda-exp -> string
(define (java-compile-lambda exp)
  (define (java-wrap-mutables vars)
    (if (not (pair? vars))
        ""
        (string-append
         (if (is-mutable? (car vars))
             (string-append 
              " final ValueCell m_" (mangle (car vars)) 
              " = new ValueCell(" (mangle (car vars)) ");\n")
             "")
         (java-wrap-mutables (cdr vars)))))
  
  (let* ((formals (lambda->formals exp))
         (num-args (length formals)))
    (string-append
     "new NullProcValue" (number->string num-args) " () {\n"
     " public Value apply(" (java-compile-formals formals) ") {\n"
     ; wrap mutables in ValueCell objects:
     (java-wrap-mutables formals)
     "\n"
     "  return " (java-compile-exp (lambda->exp exp)) " ;\n"
     "}}\n")))

; java-compile-args : list[exp] -> string
(define (java-compile-args args)
  (if (not (pair? args))
      ""
      (string-append
       (java-compile-exp (car args))
       (if (pair? (cdr args))
           (string-append ", " (java-compile-args (cdr args)))
           ""))))

; java-compile-set! : set!-exp -> string
(define (java-compile-set! exp)
  (string-append "VoidValue.Void(m_"
                 (mangle (set!->var exp))
                 ".value = "
                 (java-compile-exp (set!->exp exp))
                 ")"))

; java-compile-app : app-exp -> string
(define (java-compile-app exp)
  (let* ((args     (app->args exp))
         (fun      (app->fun exp))
         (num-args (length args)))
    (string-append
     "((ProcValue" (number->string num-args) ")(" 
     (java-compile-exp fun) ")).apply(" 
     (java-compile-args args) ")\n")))

; java-compile-if : if-exp -> string
(define (java-compile-if exp)
  (string-append
   "(" (java-compile-exp (if->condition exp)) ").toBoolean() ? (" 
       (java-compile-exp (if->then exp)) ") : ("
       (java-compile-exp (if->else exp)) ")"))



;; Read in an expression, compile it, and print it out:

(define input-program (read))

(analyze-mutable-variables input-program)

(display (java-compile-program input-program))

;; The resulting program requires Value.java to compile.

外部资源

  • 正如之前所提到的,如果你想写一个编译到 Java 的编译器,那么这本书会帮助你了解所有 Java 的特性。基于这样的原因,官方的语言规范是不可或缺的:

java book

在攻读 Ph.D 时,我阅读了这本书,而在那之前我已经写过不少年的 Java 了。尽管如此,我仍然发现了很多从未意识过在 Java 里也拥有的东西,可谓大吃一惊。