Compiling to Java

http://matt.might.net/articles/compiling-to-java/

Compiling to Java

In my advanced compilers class, we cover a range of intermediate and target languages.C is a popular target language for performance reasons, but Java hasoften-overlooked advantages.Some of Java's less-utilized features conspire to make it an easytarget for high-level language constructs, e.g., lexically scopedclosures become anonymous classes.It takes roughly a third to half the time (and code) to write a compiler that targets Java instead of C.In fact, we can compile the essential core of Scheme to Java withpurely local transforms.(Scheme's macro system can easily desugar full Scheme into core Scheme.)As a result, the reference compiler at the bottom of this page isbarely over 400 lines of highly-commented code.(Thecompanion compiler into C is just over 1000 lines of code.)

Helpful resources:

  • The official Java Language Specification covers the nooks and crannies of the language which, while rarely or never used in ordinary programming, become extremely convenient during compilation.

Related blog posts:

Pros and cons

On top of these implementation benefits, targeting Java gives thelanguage access to the Java library system, the portability of theJVM, just-in-time compilation/optimization and garbage collection.The lack of tail-call optimization in Java is a downside with thedirect compilation strategy, but it's possible to do less directcompilation into Java that performs tail-call optimization with trampolining.

More broadly, in my class, I teach that language implementation is about trade-offsbetween implementation complexity and performance:

  1. I argue that first, you should try an interpreter for your language.
  2. If that's not fast enough, try an SICP-style optimizing interpreter.
  3. If that's not good enough, try compiling to Java.
  4. If that's still not fast enough, try compiling to C.
  5. If that's still too slow, try continuation-passing-style compilation to assembly.
  6. If you need more speed, start doing basic compiler optimizations.
  7. If you're still out of luck, start doing static-analysis-driven compiler optimizations.

Each time you go down from n to n + 1 on thisladder, performance will go up, but implementation code size andcomplexity will go up by about a factor of two.It turns out that Java occupies a sweet spot: relatively lowimplementation complexity, but the biggest percentage-wise gain inperformance.

Core Scheme with Sugar

The compiler I created is for a core Scheme, but it would not behard to apply these same techniques to compiling a language likePython or 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>

A Scheme compiler could easily use macros to desugar full Schemeinto this language, or in fact, an even simpler one.Many real Scheme compilers do exactly that.

Encoding Scheme values in Java

The first task in compiling to Java is to come up with aJava-level encoding of the corresponding Scheme values.To do that, I created the Java interfaceValue, and hadall Scheme values inherit from that.The sub-classes and -interfaces ofValue includeVoidValue, BooleanValue,IntValue,ProcValue andPrimitive.The Java stub code also provies aRuntimeEnvironmentclass, which binds all of the top-level primitives like addition,subtraction and I/O to Java names.Compiled programs are supposed to inherit fromRuntimeEnvironment.

The top-level compile function has a typically Schemish dispatching feel:

; 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))))
So, compilation breaks down into individual constructs.

Integers

Integers compile into a IntValue objects, rather than to themselves.For example, Scheme-level3 compiles to new IntValue(3) in Java.

Primitives

Primitives are looked up in table and translated into their RuntimeEnvironment name:

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

Variable references

Variable references have to be name-mangled, since Schemeidentifiers are richer than Java identfiers.Mutable variables (those which are set!'d) are also handled differently.These are wrapped inValueCell objects and prefixed withm_, since variables captured by anonymous functions inJava have to be markedfinal.A pre-compilation code walk finds all of the mutable variables.

Lambda terms

Lambda terms are compiled into anonymous classes.For example, (lambda (v1 ... vN) exp) becomes:

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] ;
} 
There is a NullProcValueN class for each procedure arity N.The NullProcValue classes provide default definitions for some of the methods defined in Value.

Clearly, [mangle v] stands for the mangled name of the variable v,and [compile exp] stands for the compiled text of exp.

Conditionals

The form (if exp1 exp2 exp3) actually compiles intothe ternary operator?: instead of if () {} else{}:

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

Mutable variables

The construct (set! var exp) relies on thecompilation of lambda terms and variables references to wrapvar in aValueCell, so that it compiles to:

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

Variable binding

The let construct desugars into the application of a lambda term.That is, (let ((v e) ...) body) becomes:

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

Recursion

Letrec can be desugared into "lets and sets" or the Ycombinator.I opted for theYcombinator just to show that it can be done without using sideeffects.Actually, the compiler generates a new Y combinator on the fly so thatit matches the arity of the recursive procedure:

; 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))))))))

In Java, the Y combinator for one-argument procedures ends up as:

((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)
 ;
}}
)
 ;
}}
 ;
}}
)

Sequencing

Sequencing statements are desugared into let-bindingsof unused variables. That is,(begin e1 ... eN) becomes:

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

Code

[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.

External resources

  • As mentioned, if you want to write a compiler into Java, it helps to know all of Java's features. For that, the official language specification is indispensible:
    I'd been programming Java for years when I read this book for my Ph.D. qualifier, and I remember being stunned by all the stuff I never knew was in Java.
matt.might.net is powered by linode.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值