cyclone/trans.scm
Justin Ethier 722b6f22ef WIP
2015-05-12 23:00:59 -04:00

1853 lines
59 KiB
Scheme

;;
;; Cyclone Scheme
;; Copyright (c) 2014, Justin Ethier
;; All rights reserved.
;;
;; This module performs Scheme-to-Scheme transformations, and also contains
;; various utility functions used by the compiler.
;;
(define *version* "0.0.1 (Pre-release)")
(define *version-banner*
(string-append "
:@
@@@
@@@@:
`@@@@@+
.@@@+@@@ Cyclone
@@ @@ An experimental Scheme compiler
,@ https://github.com/justinethier/cyclone
'@
.@
@@ #@ (c) 2014 Justin Ethier
`@@@#@@@. Version " *version* "
#@@@@@
+@@@+
@@#
`@.
"))
(define *c-file-header-comment*
(string-append "/**
** This file was automatically generated by the Cyclone scheme compiler
**
** (c) 2014 Justin Ethier
** Version " *version* "
**
**/
"))
;; Features implemented by this Scheme
(define *features* '(cyclone))
;; Built-in functions
;; TODO: relocate these somewhere else, like a lib.scm!!!
;; TODO: Longer-term, we will want to insert all these but then have an
;; algorithm in place to remove the definitions that are not used.
;; Basically, after the expansion phase but before alpha conversion,
;; there should be enough information to figure out what is unused, and
;; discard it. Obviously this may be a no-go in certain situations, such
;; as if a (read (eval)) REPL is present. Although maybe not, since that
;; would use the interpreter.
(define *built-ins* `(
(define *Cyc-version-banner* ,*version-banner*)
;; TODO: The whitespace characters are space, tab, line feed, form feed (not in parser yet), and carriage return.
(define call-with-current-continuation call/cc)
(define (Cyc-bin-op cmp x lst)
(cond
((null? lst) #t)
((cmp x (car lst))
(Cyc-bin-op cmp (car lst) (cdr lst)))
(else #f)))
(define (Cyc-bin-op-char cmp c cs)
(Cyc-bin-op
(lambda (x y)
(cmp (char->integer x) (char->integer y)))
c
cs))
(define (char=? c1 c2 . cs) (Cyc-bin-op-char = c1 (cons c2 cs)))
(define (char<? c1 c2 . cs) (Cyc-bin-op-char < c1 (cons c2 cs)))
(define (char>? c1 c2 . cs) (Cyc-bin-op-char > c1 (cons c2 cs)))
(define (char<=? c1 c2 . cs) (Cyc-bin-op-char <= c1 (cons c2 cs)))
(define (char>=? c1 c2 . cs) (Cyc-bin-op-char >= c1 (cons c2 cs)))
; TODO: char-ci predicates
(define (char-upcase c) ;; ASCII-only
(if (char-lower-case? c)
(integer->char
(- (char->integer c)
(- (char->integer #\a)
(char->integer #\A))))
c))
(define (char-downcase c) ;; ASCII-only
(if (char-upper-case? c)
(integer->char
(+ (char->integer c)
(- (char->integer #\a)
(char->integer #\A))))
c))
; TODO: char-foldcase
(define (char-alphabetic? c) (and (char>=? c #\A) (char<=? c #\z))) ;; ASCII-only
(define (char-upper-case? c) (and (char>=? c #\A) (char<=? c #\Z))) ;; ASCII-only
(define (char-lower-case? c) (and (char>=? c #\a) (char<=? c #\z))) ;; ASCII-only
(define (char-numeric? c) (member c '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)))
(define (char-whitespace? c) (member c '(#\tab #\space #\return #\newline)))
(define (digit-value c)
(if (char-numeric? c)
(- (char->integer c) (char->integer #\0))
#f))
(define (foldl func accum lst)
(if (null? lst)
accum
(foldl func (func (car lst) accum) (cdr lst))))
(define (foldr func end lst)
(if (null? lst)
end
(func (car lst) (foldr func end (cdr lst)))))
(define (not x) (if x #f #t))
(define (list? o)
(define (_list? obj)
(cond
((null? obj) #t)
((pair? obj)
(_list? (cdr obj)))
(else #f)))
(if (Cyc-has-cycle? o)
#t
(_list? o)))
(define (zero? n) (= n 0))
(define (positive? n) (> n 0))
(define (negative? n) (< n 0))
; append accepts a variable number of arguments, per R5RS. So a wrapper
; has been provided for the standard 2-argument version of (append).
;
; We return the given value if less than 2 arguments are given, and
; otherwise fold over each arg, appending it to its predecessor.
(define (append . lst)
(define append-2
(lambda (inlist alist)
(foldr (lambda (ap in) (cons ap in)) alist inlist)))
(if (null? lst)
lst
(if (null? (cdr lst))
(car lst)
(foldl (lambda (a b) (append-2 b a)) (car lst) (cdr lst)))))
(define (list . objs) objs)
(define (make-list k . fill)
(letrec ((x (if (null? fill)
#f
(car fill)))
(make
(lambda (n obj)
(if (zero? n)
'()
(cons obj (make (- n 1) obj) )))))
(make k x)))
(define (list-copy lst)
(foldr (lambda (x y) (cons x y)) '() lst))
(define (map func lst)
(foldr (lambda (x y) (cons (func x) y)) '() lst))
(define (for-each f lst)
(cond
((null? lst) #t)
(else
(f (car lst))
(for-each f (cdr lst)))))
(define (list-tail lst k)
(if (zero? k)
lst
(list-tail (cdr lst) (- k 1))))
(define (list-ref lst k) (car (list-tail lst k)))
(define (list-set! lst k obj)
(let ((kth (list-tail lst k)))
(set-car! kth obj)))
(define (reverse lst) (foldl cons '() lst))
(define (boolean=? b1 b2 . bs)
(Cyc-obj=? boolean? b1 (cons b2 bs)))
(define (symbol=? sym1 sym2 . syms)
(Cyc-obj=? symbol? sym1 (cons sym2 syms)))
(define (Cyc-obj=? type? obj objs)
(and
(type? obj)
(call/cc
(lambda (return)
(for-each
(lambda (o)
(if (not (eq? o obj))
(return #f)))
objs)
#t))))
(define (make-string k . fill)
(let ((fill* (if (null? fill)
'(#\space)
fill)))
(list->string
(apply make-list (cons k fill*)))))
(define (error msg . args)
(raise (cons msg args)))
(define (raise obj)
((Cyc-current-exception-handler) (list 'raised obj)))
(define (raise-continuable obj)
((Cyc-current-exception-handler) (list 'continuable obj)))
(define (with-exception-handler handler thunk)
(let ((result #f)
(my-handler
(lambda (obj)
(let ((result #f)
(continuable? (and (pair? obj)
(equal? (car obj) 'continuable))))
;; Unregister this handler since it is no longer needed
(Cyc-remove-exception-handler)
(set! result (handler (cadr obj))) ;; Actual handler
(if continuable?
result
(error "exception handler returned"))))))
;; No cond-expand below, since this is part of our internal lib
(Cyc-add-exception-handler my-handler)
(set! result (thunk))
(Cyc-remove-exception-handler) ; Only reached if no ex raised
result))
(define *exception-handler-stack* '())
(define (Cyc-add-exception-handler h)
(set! *exception-handler-stack* (cons h *exception-handler-stack*)))
(define (Cyc-remove-exception-handler)
(if (not (null? *exception-handler-stack*))
(set! *exception-handler-stack* (cdr *exception-handler-stack*))))
; (define (Cyc-current-exception-handler)
; (if (null? *exception-handler-stack*)
; Cyc-default-exception-handler
; (car *exception-handler-stack*)))
))
;; Built-in macros
;; TODO: just a stub, real code would read (define-syntax)
;; from a lib file or such
(define *defined-macros*
(list
(cons 'and
(lambda (expr rename compare)
(cond ((null? (cdr expr)))
((null? (cddr expr)) (cadr expr))
(else (list (rename 'if) (cadr expr)
(cons (rename 'and) (cddr expr))
#f)))))
(cons 'or
(lambda (expr rename compare)
(cond ((null? (cdr expr)) #f)
((null? (cddr expr)) (cadr expr))
(else
(list (rename 'let) (list (list (rename 'tmp) (cadr expr)))
(list (rename 'if) (rename 'tmp)
(rename 'tmp)
(cons (rename 'or) (cddr expr))))))))
(cons 'let (lambda (exp rename compare) (let=>lambda exp)))
(cons 'begin (lambda (exp rename compare) (begin=>let exp)))
(cons 'letrec (lambda (exp rename compare) (letrec=>lets+sets exp)))
(cons 'cond
(lambda (expr rename compare)
(if (null? (cdr expr))
(if #f #f)
((lambda (cl)
(if (compare (rename 'else) (car cl))
(if (pair? (cddr expr))
(error "non-final else in cond" expr)
(cons (rename 'begin) (cdr cl)))
(if (if (null? (cdr cl)) #t (compare (rename '=>) (cadr cl)))
(list (list (rename 'lambda) (list (rename 'tmp))
(list (rename 'if) (rename 'tmp)
(if (null? (cdr cl))
(rename 'tmp)
(list (car (cddr cl)) (rename 'tmp)))
(cons (rename 'cond) (cddr expr))))
(car cl))
(list (rename 'if)
(car cl)
(cons (rename 'begin) (cdr cl))
(cons (rename 'cond) (cddr expr))))))
(cadr expr)))))
(cons 'cond-expand
;; Based on the cond-expand macro from Chibi scheme
(lambda (expr rename compare)
(define (check x)
(if (pair? x)
(case (car x)
((and) (every check (cdr x)))
((or) (any check (cdr x)))
((not) (not (check (cadr x))))
;((library) (eval `(find-module ',(cadr x)) (%meta-env)))
(else (error "cond-expand: bad feature" x)))
(memq x *features*)))
(let expand ((ls (cdr expr)))
(cond ((null? ls)) ; (error "cond-expand: no expansions" expr)
((not (pair? (car ls))) (error "cond-expand: bad clause" (car ls)))
((eq? 'else (caar ls)) ;(identifier->symbol (caar ls)))
(if (pair? (cdr ls))
(error "cond-expand: else in non-final position")
`(,(rename 'begin) ,@(cdar ls))))
((check (caar ls)) `(,(rename 'begin) ,@(cdar ls)))
(else (expand (cdr ls)))))))
(cons 'quasiquote
;; Based on the quasiquote macro from Chibi scheme
(lambda (expr rename compare)
(define (qq x d)
(cond
((pair? x)
(cond
((compare (rename 'unquote) (car x))
(if (<= d 0)
(cadr x)
(list (rename 'list) (list (rename 'quote) 'unquote)
(qq (cadr x) (- d 1)))))
((compare (rename 'unquote-splicing) (car x))
(if (<= d 0)
(list (rename 'cons) (qq (car x) d) (qq (cdr x) d))
(list (rename 'list) (list (rename 'quote) 'unquote-splicing)
(qq (cadr x) (- d 1)))))
((compare (rename 'quasiquote) (car x))
(list (rename 'list) (list (rename 'quote) 'quasiquote)
(qq (cadr x) (+ d 1))))
((and (<= d 0) (pair? (car x))
(compare (rename 'unquote-splicing) (caar x)))
(if (null? (cdr x))
(cadr (car x))
(list (rename 'append) (cadr (car x)) (qq (cdr x) d))))
(else
(list (rename 'cons) (qq (car x) d) (qq (cdr x) d)))))
((vector? x) (list (rename 'list->vector) (qq (vector->list x) d)))
((if (symbol? x) #t (null? x)) (list (rename 'quote) x))
(else x)))
(qq (cadr expr) 0)))
))
(define (built-in-syms)
'(call/cc define))
(define (add-libs ast)
(cond
((list? ast)
(append *built-ins* ast))
(else
(error "Unexpected input program:" ast))))
;; Tuning
(define *do-code-gen* #t) ; Generate C code?
;; Trace
(define *trace-level* 2)
(define (trace level msg pp prefix)
(if (>= *trace-level* level)
(begin
(display "/* ")
(newline)
(display prefix)
(pp msg)
(display " */")
(newline))))
(define (trace:error msg) (trace 1 msg pretty-print ""))
(define (trace:warn msg) (trace 2 msg pretty-print ""))
(define (trace:info msg) (trace 3 msg pretty-print ""))
(define (trace:debug msg) (trace 4 msg display "DEBUG: "))
(define (cyc:error msg)
(error msg)
(exit 1))
;; File Utilities
;; Get the basename of a file, without the extension.
;; EG: "file.scm" ==> "file"
(define (basename filename)
(let ((pos (list-index #\. (reverse (string->list filename)))))
(if (= pos -1)
filename
(substring filename 0 (- (string-length filename) pos 1)))))
;; Find the first occurence of e within the given list.
;; Returns -1 if e is not found.
(define list-index
(lambda (e lst)
(if (null? lst)
-1
(if (eq? (car lst) e)
0
(if (= (list-index e (cdr lst)) -1)
-1
(+ 1 (list-index e (cdr lst))))))))
;; Utilities.
(cond-expand
(cyclone
; member : symbol sorted-set[symbol] -> boolean
(define (member sym S)
(if (not (pair? S))
#f
(if (eq? sym (car S))
#t
(member sym (cdr S)))))
; void : -> void
(define (void) (if #f #t)))
(else #f))
; 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)))
; gensym-count : integer
(define gensym-count 0)
; gensym : symbol -> symbol
(define gensym (lambda params
(if (null? params)
(begin
(set! gensym-count (+ gensym-count 1))
(string->symbol (string-append
"$"
(number->string gensym-count))))
(begin
(set! gensym-count (+ gensym-count 1))
(string->symbol (string-append
(if (symbol? (car params))
(symbol->string (car params))
(car params))
"$"
(number->string gensym-count)))))))
; symbol<? : symbol symobl -> boolean
(define (symbol<? sym1 sym2)
(string<? (symbol->string sym1)
(symbol->string sym2)))
; insert : symbol sorted-set[symbol] -> sorted-set[symbol]
(define (insert sym S)
(if (not (pair? S))
(list sym)
(cond
((eq? sym (car S)) S)
((symbol<? sym (car S)) (cons sym S))
(else (cons (car S) (insert sym (cdr S)))))))
; remove : symbol sorted-set[symbol] -> sorted-set[symbol]
(define (remove sym S)
(if (not (pair? S))
'()
(if (eq? (car S) sym)
(cdr S)
(cons (car S) (remove sym (cdr S))))))
; union : sorted-set[symbol] sorted-set[symbol] -> sorted-set[symbol]
(define (union set1 set2)
; NOTE: This should be implemented as merge for efficiency.
(if (not (pair? set1))
set2
(insert (car set1) (union (cdr set1) set2))))
; difference : sorted-set[symbol] sorted-set[symbol] -> sorted-set[symbol]
(define (difference set1 set2)
; NOTE: This can be similarly optimized.
(if (not (pair? set2))
set1
(difference (remove (car set2) set1) (cdr set2))))
; reduce : (A A -> A) list[A] A -> A
(define (reduce f lst init)
(if (not (pair? lst))
init
(reduce f (cdr lst) (f (car lst) init))))
; azip : list[A] list[B] -> alist[A,B]
(define (azip list1 list2)
(if (and (pair? list1) (pair? list2))
(cons (list (car list1) (car list2))
(azip (cdr list1) (cdr list2)))
'()))
; assq-remove-key : alist[A,B] A -> alist[A,B]
(define (assq-remove-key env key)
(if (not (pair? env))
'()
(if (eq? (car (car env)) key)
(assq-remove-key (cdr env) key)
(cons (car env) (assq-remove-key (cdr env) key)))))
; assq-remove-keys : alist[A,B] list[A] -> alist[A,B]
(define (assq-remove-keys env keys)
(if (not (pair? keys))
env
(assq-remove-keys (assq-remove-key env (car keys)) (cdr keys))))
;; Simplified version of filter from SRFI 1
(define (filter pred lis)
(let recur ((lis lis))
(if (null? lis)
lis
(let ((head (car lis))
(tail (cdr lis)))
(if (pred head)
(let ((new-tail (recur tail)))
(if (eq? tail new-tail) lis
(cons head new-tail)))
(recur tail))))))
;; Data type predicates and accessors.
; const? : exp -> boolean
(define (const? exp)
(or (integer? exp)
(real? exp)
(string? exp)
(char? exp)
(boolean? exp)))
; ref? : exp -> boolean
(define (ref? exp)
(symbol? exp))
; quote? : exp -> boolean
(define (quote? exp)
(tagged-list? 'quote 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)
(cddr exp))
; let->bound-vars : let-exp -> list[symbol]
(define (let->bound-vars exp)
(map car (cadr exp)))
; let->args : let-exp -> list[exp]
(define (let->args exp)
(map cadr (cadr exp)))
; letrec? : exp -> boolean
(define (letrec? exp)
(tagged-list? 'letrec exp))
; letrec->bindings : letrec-exp -> alist[symbol,exp]
(define (letrec->bindings exp)
(cadr exp))
; letrec->exp : letrec-exp -> exp
(define (letrec->exp exp)
(cddr exp))
; letrec->exp : letrec-exp -> list[symbol]
(define (letrec->bound-vars exp)
(map car (cadr exp)))
; letrec->exp : letrec-exp -> list[exp]
(define (letrec->args exp)
(map cadr (cadr exp)))
; lambda? : exp -> boolean
(define (lambda? exp)
(tagged-list? 'lambda exp))
(define (lambda-varargs? exp)
(and (lambda? exp)
(or (symbol? (lambda->formals exp))
(and (pair? (lambda->formals exp))
(not (list? (lambda->formals exp)))))))
; lambda->formals : lambda-exp -> list[symbol]
(define (lambda->formals exp)
(cadr exp))
(define (lambda-varargs? exp)
(let ((type (lambda-formals-type exp)))
(or (equal? type 'args:varargs)
(equal? type 'args:fixed-with-varargs))))
(define (lambda-varargs-var exp)
(if (lambda-varargs? exp)
(if (equal? (lambda-formals-type exp) 'args:varargs)
(lambda-formals exp) ; take symbol directly
(car (reverse (lambda-formals->list exp)))) ; Last arg is varargs
#f))
(define (lambda-formals-type exp)
(let ((args (lambda->formals exp)))
(cond
((symbol? args) 'args:varargs)
((list? args) 'args:fixed)
((pair? args) 'args:fixed-with-varargs)
(else
(error `(Unexpected formals list in lambda-formals-type: ,args))))))
(define (lambda-formals->list exp)
(if (lambda-varargs? exp)
(let ((args (lambda->formals exp)))
(if (symbol? args)
(list args)
(pair->list args)))
(lambda->formals exp)))
;; Repack a list of args (symbols) into lambda formals, by type
;; assumes args is a proper list
(define (list->lambda-formals args type)
(cond
((eq? type 'args:fixed) args)
((eq? type 'args:fixed-with-varargs) (list->pair args))
((eq? type 'args:varargs)
(if (> (length args) 1)
(error `(Too many args for varargs ,args))
(car args)))
(else (error `(Unexpected type ,type)))))
;; Create a proper copy of an improper list
;; EG: (1 2 . 3) ==> (1 2 3)
(define (pair->list p)
(let loop ((lst p))
(if (not (pair? lst))
(cons lst '())
(cons (car lst) (loop (cdr lst))))))
;; Create an improper copy of a proper list
(define (list->pair l)
(let loop ((lst l))
(cond
((not (pair? lst))
lst)
((null? (cdr lst))
(car lst))
(else
(cons (car lst) (loop (cdr lst)))))))
; lambda->exp : lambda-exp -> exp
(define (lambda->exp exp)
(cddr exp)) ;; JAE - changed from caddr, so we can handle multiple expressions
; 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 -> bool
;; Determines whether an if expression has an else clause
(define (if-else? exp)
(and (tagged-list? 'if exp)
(> (length exp) 3)))
; 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)
(member exp *primitives*))
(define *primitives* '(
Cyc-global-vars
Cyc-get-cvar
Cyc-set-cvar!
Cyc-cvar? ;; Cyclone-specific
Cyc-has-cycle?
+
-
*
/
=
>
<
>=
<=
apply
%halt
exit
Cyc-default-exception-handler
Cyc-current-exception-handler
cons
cell-get
set-global!
set-cell!
cell
eq?
eqv?
equal?
assoc
assq
assv
memq
memv
member
length
set-car!
set-cdr!
car
cdr
caar cadr cdar cddr
caaar caadr cadar caddr cdaar cdadr cddar cdddr
caaaar caaadr caadar caaddr cadaar cadadr
caddar cadddr cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
char->integer
integer->char
string->number
string-append
string->list
list->string
string->symbol
symbol->string
number->string
boolean?
char?
eof-object?
null?
number?
real?
integer?
pair?
procedure?
string?
symbol?
current-input-port
open-input-file
close-input-port
read-char
peek-char
write
display))
;; Is a primitive being applied in such a way that it can be
;; evaluated at compile time?
(define (precompute-prim-app? ast)
(and
(pair? ast)
(prim? (car ast))
;; Does not make sense to precompute these
(not (member (car ast)
'(Cyc-global-vars
Cyc-get-cvar
Cyc-set-cvar!
Cyc-cvar?
apply
%halt
exit
Cyc-default-exception-handler
Cyc-current-exception-handler
cell-get
set-global!
set-cell!
cell
set-car!
set-cdr!
string->symbol ;; Could be mistaken for an identifier
string->list ;; Mistaken for function call (maybe OK if it was quoted, though). same for above?
;; I/O must be done at runtime for side effects:
current-input-port
open-input-file
close-input-port
read-char
peek-char
write
display)))
(call/cc
(lambda (return)
(for-each
(lambda (expr)
(if (not (const? expr))
(return #f)))
(cdr ast))
#t))))
(define (prim-call? exp)
(and (list? exp) (prim? (car exp))))
; begin? : exp -> boolean
(define (begin? exp)
(tagged-list? 'begin exp))
; begin->exps : begin-exp -> list[exp]
(define (begin->exps exp)
(cdr exp))
; define : exp -> boolean
(define (define? exp)
(tagged-list? 'define exp))
(define (define-lambda? exp)
(let ((var (cadr exp)))
(or
;; Standard function
(and (list? var)
(> (length var) 0)
(symbol? (car var)))
;; Varargs function
(and (pair? var)
(symbol? (car var))))))
(define (define->lambda exp)
(cond
((define-lambda? exp)
(let ((var (caadr exp))
(args (cdadr exp))
(body (cddr exp)))
`(define ,var (lambda ,args ,@body))))
(else exp)))
; define->var : define-exp -> var
(define (define->var exp)
(cond
((define-lambda? exp)
(caadr exp))
(else
(cadr exp))))
; define->exp : define-exp -> exp
(define (define->exp exp)
(cddr 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))
; closure? : exp -> boolean
(define (closure? exp)
(tagged-list? 'closure exp))
; closure->lam : closure-exp -> exp
(define (closure->lam exp)
(cadr exp))
; closure->env : closure-exp -> exp
(define (closure->env exp)
(caddr exp))
(define (closure->fv exp)
(cddr exp))
; env-make? : exp -> boolean
(define (env-make? exp)
(tagged-list? 'env-make exp))
; env-make->id : env-make-exp -> env-id
(define (env-make->id exp)
(cadr exp))
; env-make->fields : env-make-exp -> list[symbol]
(define (env-make->fields exp)
(map car (cddr exp)))
; env-make->values : env-make-exp -> list[exp]
(define (env-make->values exp)
(map cadr (cddr exp)))
; env-get? : exp -> boolen
(define (env-get? exp)
(tagged-list? 'env-get exp))
; env-get->id : env-get-exp -> env-id
(define (env-get->id exp)
(cadr exp))
; env-get->field : env-get-exp -> symbol
(define (env-get->field exp)
(caddr exp))
; env-get->env : env-get-exp -> exp
(define (env-get->env exp)
(cadddr exp))
; set-cell!? : set-cell!-exp -> boolean
(define (set-cell!? exp)
(tagged-list? 'set-cell! exp))
; set-cell!->cell : set-cell!-exp -> exp
(define (set-cell!->cell exp)
(cadr exp))
; set-cell!->value : set-cell!-exp -> exp
(define (set-cell!->value exp)
(caddr exp))
; cell? : exp -> boolean
(define (cell? exp)
(tagged-list? 'cell exp))
; cell->value : cell-exp -> exp
(define (cell->value exp)
(cadr exp))
; cell-get? : exp -> boolean
(define (cell-get? exp)
(tagged-list? 'cell-get exp))
; cell-get->cell : cell-exp -> exp
(define (cell-get->cell exp)
(cadr exp))
;; Syntax manipulation.
;; ; substitute-var : alist[var,exp] ref-exp -> exp
;; (define (substitute-var env var)
;; (let ((sub (assq var env)))
;; (if sub
;; (cadr sub)
;; var)))
;;
;; ; substitute : alist[var,exp] exp -> exp
;; (define (substitute env exp)
;;
;; (define (substitute-with env)
;; (lambda (exp)
;; (substitute env exp)))
;;
;; (cond
;; ; Core forms:
;; ((null? env) exp)
;; ((const? exp) exp)
;; ((prim? exp) exp)
;; ((ref? exp) (substitute-var env exp))
;; ((lambda? exp) `(lambda ,(lambda->formals exp)
;; ,@(map (lambda (body-exp)
;; ;; TODO: could be more efficient
;; (substitute
;; (assq-remove-keys env (lambda->formals exp))
;; body-exp))
;; (lambda->exp exp))))
;; ((set!? exp) `(set! ,(substitute-var env (set!->var exp))
;; ,(substitute env (set!->exp exp))))
;; ((if? exp) `(if ,(substitute env (if->condition exp))
;; ,(substitute env (if->then exp))
;; ,(substitute env (if->else exp))))
;;
;; ; Sugar:
;; ((let? exp) `(let ,(azip (let->bound-vars exp)
;; (map (substitute-with env) (let->args exp)))
;; ,(substitute (assq-remove-keys env (let->bound-vars exp))
;; (car (let->exp exp)))))
;; ((letrec? exp) (let ((new-env (assq-remove-keys env (letrec->bound-vars exp))))
;; `(letrec ,(azip (letrec->bound-vars exp)
;; (map (substitute-with new-env)
;; (letrec->args exp)))
;; ,(substitute new-env (car (letrec->exp exp))))))
;; ((begin? exp) (cons 'begin (map (substitute-with env) (begin->exps exp))))
;;
;; ; IR (1):
;; ((cell? exp) `(cell ,(substitute env (cell->value exp))))
;; ((cell-get? exp) `(cell-get ,(substitute env (cell-get->cell exp))))
;; ((set-cell!? exp) `(set-cell! ,(substitute env (set-cell!->cell exp))
;; ,(substitute env (set-cell!->value exp))))
;;
;; ; IR (2):
;; ((closure? exp) `(closure ,(substitute env (closure->lam exp))
;; ,(substitute env (closure->env exp))))
;; ((env-make? exp) `(env-make ,(env-make->id exp)
;; ,@(azip (env-make->fields exp)
;; (map (substitute-with env)
;; (env-make->values exp)))))
;; ((env-get? exp) `(env-get ,(env-get->id exp)
;; ,(env-get->field exp)
;; ,(substitute env (env-get->env exp))))
;;
;; ; Application:
;; ((app? exp) (map (substitute-with env) exp))
;; (else (error "unhandled expression type in substitution: " exp))))
;;
;; Macro expansion
(define (macro? exp) (assoc (car exp) *defined-macros*))
(define (macro-expand exp)
(let ((macro (assoc (car exp) *defined-macros*)))
;; assumes ER macro
(if macro
((cdr macro)
exp
(lambda (sym) ;; TODO: not good enough, need to actually rename, and keep same results if
sym) ;; the same symbol is renamed more than once
(lambda (sym-a sym-b) ;; TODO: the compare function from exrename.
(eq? sym-a sym-b))) ;; this may need to be more sophisticated
exp))) ;; TODO: error instead??
; expand : exp -> exp
(define (expand exp)
(cond
((const? exp) exp)
((prim? exp) exp)
((ref? exp) exp)
((quote? exp) exp)
((lambda? exp) `(lambda ,(lambda->formals exp)
,@(map expand (lambda->exp exp))))
((define? exp) (if (define-lambda? exp)
(expand (define->lambda exp))
`(define ,(expand (define->var exp))
,@(expand (define->exp exp)))))
((set!? exp) `(set! ,(expand (set!->var exp))
,(expand (set!->exp exp))))
((if? exp) `(if ,(expand (if->condition exp))
,(expand (if->then exp))
,(if (if-else? exp)
(expand (if->else exp))
;; Insert default value for missing else clause
;; FUTURE: append the empty (unprinted) value
;; instead of #f
#f)))
((app? exp)
(cond
;; TODO: could check for a define-syntax here and load into memory
;; if found. would then want to continue expanding. may need to
;; return some value such as #t or nil as a placeholder, since the
;; define-syntax form would not be carried forward in the compiled code
;; ((define-syntax? exp) ...)
((macro? exp)
(expand ;; Could expand into another macro
(macro-expand exp)))
(else
(map expand exp))))
(else
(error "unknown exp: " exp))))
; TODO: eventually, merge below functions with above *defined-macros* defs and
;; replace both with a lib of (define-syntax) constructs
; 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))
; letrec=>lets+sets : letrec-exp -> exp
(define (letrec=>lets+sets exp)
(if (letrec? exp)
(let* ((bindings (letrec->bindings exp))
(namings (map (lambda (b) (list (car b) #f)) bindings))
(names (letrec->bound-vars exp))
(sets (map (lambda (binding)
(cons 'set! binding))
bindings))
(args (letrec->args exp)))
`(let ,namings
(begin ,@(append sets (letrec->exp exp)))))))
;; NOTE: chibi uses the following macro. turns vars into defines?
;;(define-syntax letrec
;; (er-macro-transformer
;; (lambda (expr rename compare)
;; ((lambda (defs)
;; `((,(rename 'lambda) () ,@defs ,@(cddr expr))))
;; (map (lambda (x) (cons (rename 'define) x)) (cadr expr))))))
;;
; 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))
; JAE - should be fine until CPS phase
((pair? exps)
`((lambda ()
,@exps)))))
;((pair? exps) `(let (($_ ,(car exps)))
; ,(dummy-bind (cdr exps))))))
(dummy-bind (begin->exps exp)))
;; Top-level analysis
; Separate top-level defines (globals) from other expressions
;
; This function extracts out non-define statements, and adds them to
; a "main" after the defines.
;
(define (isolate-globals exp)
(let loop ((top-lvl exp)
(globals '())
(exprs '()))
(cond
((null? top-lvl)
(append
(reverse globals)
(expand
`((begin ,@(reverse exprs))))))
(else
(cond
((define? (car top-lvl))
(cond
;; Global is redefined, convert it to a (set!) at top-level
((has-global? globals (define->var (car top-lvl)))
(loop (cdr top-lvl)
globals
(cons
`(set! ,(define->var (car top-lvl))
,@(define->exp (car top-lvl)))
exprs)))
;; Form cannot be properly converted to CPS later on, so split it up
;; into two parts - use the define to initialize it to false (CPS is fine),
;; and place the expression into a top-level (set!), which can be
;; handled by the existing CPS conversion.
((and (list? (car (define->exp (car top-lvl))))
(not (lambda? (car (define->exp (car top-lvl))))))
(loop (cdr top-lvl)
(cons
`(define ,(define->var (car top-lvl)) #f)
globals)
(cons
`(set! ,(define->var (car top-lvl))
,@(define->exp (car top-lvl)))
exprs)))
;; First time we've seen this define, add it and keep going
(else
(loop (cdr top-lvl)
(cons (car top-lvl) globals)
exprs))))
(else
(loop (cdr top-lvl)
globals
(cons (car top-lvl) exprs))))))))
; Has global already been found?
;
; NOTE:
; Linear search may get expensive (n^2), but with a modest set of
; define statements hopefully it will be acceptable. If not, will need
; to use a faster data structure (EG: map or hashtable)
(define (has-global? exp var)
(call/cc
(lambda (return)
(for-each
(lambda (e)
(if (and (define? e)
(equal? (define->var e) var))
(return #t)))
exp)
#f)))
; Compute list of global variables based on expression in top-level form
; EG: (def, def, expr, ...)
(define (global-vars exp)
(let ((globals '()))
(for-each
(lambda (e)
(if (define? e)
(set! globals (cons (define->var e) globals))))
exp)
globals))
;; Remove global variables that are not used by the rest of the program.
;; Many improvements can be made, including:
;;
;; TODO: remove unused locals
(define (filter-unused-variables asts lib-exports)
(define (do-filter code)
(let ((all-fv (apply ;; More efficient way to do this?
append ;; Could use delete-duplicates
(map
(lambda (ast)
(if (define? ast)
(let ((var (define->var ast)))
;; Do not keep global that refers to itself
(filter
(lambda (v)
(not (equal? v var)))
(free-vars (define->exp ast))))
(free-vars ast)))
code))))
(filter
(lambda (ast)
(or (not (define? ast))
(member (define->var ast) all-fv)
(member (define->var ast) lib-exports)))
code)))
;; Keep filtering until no more vars are removed
(define (loop code)
(let ((new-code (do-filter code)))
(if (> (length code) (length new-code))
(loop new-code)
new-code)))
(loop asts))
;; Syntactic analysis.
; free-vars : exp -> sorted-set[var]
(define (free-vars ast . opts)
(define bound-only?
(and (not (null? opts))
(car opts)))
(define (search exp)
(cond
; Core forms:
((const? exp) '())
((prim? exp) '())
((quote? exp) '())
((ref? exp) (if bound-only? '() (list exp)))
((lambda? exp)
(difference (reduce union (map search (lambda->exp exp)) '())
(lambda-formals->list exp)))
((if? exp) (union (search (if->condition exp))
(union (search (if->then exp))
(search (if->else exp)))))
((define? exp) (union (list (define->var exp))
(search (define->exp exp))))
((set!? exp) (union (list (set!->var exp))
(search (set!->exp exp))))
; Application:
((app? exp) (reduce union (map search exp) '()))
(else (error "unknown expression: " exp))))
(search ast))
;; Mutable variable analysis and elimination.
;; Mutables variables analysis and elimination happens
;; on a desugared Intermediate Language (1).
;; Mutable variable analysis turns mutable variables
;; into heap-allocated cells:
;; For any mutable variable mvar:
;; (lambda (... mvar ...) body)
;; =>
;; (lambda (... $v ...)
;; (let ((mvar (cell $v)))
;; body))
;; (set! mvar value) => (set-cell! mvar value)
;; mvar => (cell-get mvar)
; mutable-variables : list[symbol]
(define mutable-variables '())
(define (clear-mutables)
(set! 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
; Core forms:
((const? exp) (void))
((prim? exp) (void))
((ref? exp) (void))
((quote? exp) (void))
((lambda? exp) (begin
(map analyze-mutable-variables (lambda->exp exp))
(void)))
((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))))
; Sugar:
((let? exp) (begin
(map analyze-mutable-variables (map cadr (let->bindings exp)))
(map analyze-mutable-variables (let->exp exp))
(void)))
((letrec? exp) (begin
(map analyze-mutable-variables (map cadr (letrec->bindings exp)))
(map analyze-mutable-variables (letrec->exp exp))
(void)))
((begin? exp) (begin
(map analyze-mutable-variables (begin->exps exp))
(void)))
; Application:
((app? exp) (begin
(map analyze-mutable-variables exp)
(void)))
(else (error "unknown expression type: " exp))))
; wrap-mutables : exp -> exp
(define (wrap-mutables exp globals)
(define (wrap-mutable-formals formals body-exp)
(if (not (pair? formals))
body-exp
(if (is-mutable? (car formals))
`((lambda (,(car formals))
,(wrap-mutable-formals (cdr formals) body-exp))
(cell ,(car formals)))
(wrap-mutable-formals (cdr formals) body-exp))))
(cond
; Core forms:
((const? exp) exp)
((ref? exp) (if (and (not (member exp globals))
(is-mutable? exp))
`(cell-get ,exp)
exp))
((prim? exp) exp)
((quote? exp) exp)
((lambda? exp) `(lambda ,(lambda->formals exp)
,(wrap-mutable-formals (lambda-formals->list exp)
(wrap-mutables (car (lambda->exp exp)) globals)))) ;; Assume single expr in lambda body, since after CPS phase
((set!? exp) `(,(if (member (set!->var exp) globals)
'set-global!
'set-cell!)
,(set!->var exp)
,(wrap-mutables (set!->exp exp) globals)))
((if? exp) `(if ,(wrap-mutables (if->condition exp) globals)
,(wrap-mutables (if->then exp) globals)
,(wrap-mutables (if->else exp) globals)))
; Application:
((app? exp) (map (lambda (e) (wrap-mutables e globals)) exp))
(else (error "unknown expression type: " exp))))
;; Alpha conversion
;; (aka alpha renaming)
;;
;; This phase is intended to rename identifiers to preserve lexical scoping
;;
;; TODO: does not properly handle renaming builtin functions, would probably need to
;; pass that renaming information downstream
(define (alpha-convert ast globals return-unbound)
;; Initialize top-level variables
(define (initialize-top-level-vars ast fv)
(if (> (length fv) 0)
;; Free variables found, set initial values
`((lambda ,fv ,ast)
,@(map (lambda (_) #f) fv))
ast))
;; Find any defined variables in the given code block
(define (find-defined-vars ast)
(filter
(lambda (expr)
(not (null? expr)))
(map
(lambda (expr)
(if (define? expr)
(define->var expr)
'()))
ast)))
;; Take a list of identifiers and generate a list of
;; renamed pairs, EG: (var . renamed-var)
(define (make-a-lookup vars)
(map
(lambda (a) (cons a (gensym a)))
vars))
;; Wrap any defined variables in a lambda, so they can be initialized
(define (initialize-defined-vars ast vars)
(if (> (length vars) 0)
`(((lambda ,vars ,@ast)
,@(map (lambda (_) #f) vars)))
ast))
;; Perform actual alpha conversion
(define (convert ast renamed)
;(write `(DEBUG convert ,ast))
;(write (newline))
(cond
((const? ast) ast)
((quote? ast) ast)
((ref? ast)
(let ((renamed (assoc ast renamed)))
(cond
(renamed
(cdr renamed))
(else ast))))
((define? ast)
;; Only internal defines at this point, of form: (define ident value)
`(set! ,@(map (lambda (a) (convert a renamed)) (cdr ast))))
((set!? ast)
;; Without define, we have no way of knowing if this was a
;; define or a set prior to this phase. But no big deal, since
;; the set will still work in either case, so no need to check
`(set! ,@(map (lambda (a) (convert a renamed)) (cdr ast))))
((if? ast)
;; Add a failsafe here in case macro expansion added more
;; incomplete if expressions.
;; FUTURE: append the empty (unprinted) value instead of #f
(if (if-else? ast)
`(if ,@(map (lambda (a) (convert a renamed)) (cdr ast)))
(convert (append ast '(#f)) renamed)))
((prim-call? ast)
(let ((converted
(cons (car ast)
(map (lambda (a) (convert a renamed))
(cdr ast)))))
(if (precompute-prim-app? converted)
(eval converted) ;; OK, evaluate at compile time
converted)))
((lambda? ast)
(let* ((args (lambda-formals->list ast))
(ltype (lambda-formals-type ast))
(a-lookup (map (lambda (a) (cons a (gensym a))) args))
(body (lambda->exp ast))
(define-vars (find-defined-vars body))
(defines-a-lookup (make-a-lookup define-vars))
)
`(lambda
,(list->lambda-formals
(map (lambda (p) (cdr p)) a-lookup)
ltype)
,@(initialize-defined-vars
(convert
body
(append a-lookup defines-a-lookup renamed))
(map (lambda (p) (cdr p)) defines-a-lookup)))))
((app? ast)
(map (lambda (a) (convert a renamed)) ast))
(else
(error "unhandled expression: " ast))))
(let* ((fv (difference (free-vars ast) globals))
;; Only find set! and lambda vars
(bound-vars (union globals (free-vars ast #t)))
;; vars never bound in prog, but could be built-in
(unbound-vars (difference fv bound-vars))
;; vars we know nothing about - error!
(unknown-vars (difference unbound-vars (built-in-syms)))
)
(cond
((> (length unknown-vars) 0)
(let ((unbound-to-return (list)))
(if (member 'eval unknown-vars)
(set! unbound-to-return (cons 'eval unbound-to-return)))
(if (or (member 'read unknown-vars)
(member 'read-all unknown-vars))
(set! unbound-to-return (cons 'read unbound-to-return)))
(if (and (> (length unbound-to-return) 0)
(= (length unknown-vars) (length unbound-to-return)))
(return-unbound unbound-to-return)
;; TODO: should not report above (eval read) as errors
(error "Unbound variable(s)" unknown-vars))))
((define? ast)
;; Deconstruct define so underlying code can assume internal defines
(let ((body (car ;; Only one member by now
(define->exp ast))))
;(write `(DEBUG body ,body))
(cond
((lambda? body)
(let* ((args (lambda-formals->list body))
(ltype (lambda-formals-type body))
(a-lookup (map (lambda (a) (cons a (gensym a))) args))
(define-vars (find-defined-vars (lambda->exp body)))
(defines-a-lookup (make-a-lookup define-vars))
)
;; Any internal defines need to be initialized within the lambda,
;; so the lambda formals are preserved. So we need to deconstruct
;; the defined lambda and then reconstruct it, with #f placeholders
;; for any internal definitions.
;;
;; Also, initialize-top-level-vars cannot be used directly due to
;; the required splicing.
`(define
,(define->var ast)
(lambda
,(list->lambda-formals
(map (lambda (p) (cdr p)) a-lookup)
ltype)
,@(convert (let ((fv* (union
define-vars
(difference fv (built-in-syms))))
(ast* (lambda->exp body)))
(if (> (length fv*) 0)
`(((lambda ,fv* ,@ast*)
,@(map (lambda (_) #f) fv*)))
ast*))
(append a-lookup defines-a-lookup))))))
(else
`(define
,(define->var ast)
,@(convert (initialize-top-level-vars
(define->exp ast)
(difference fv (built-in-syms)))
(list)))))))
(else
(convert (initialize-top-level-vars
ast
(difference fv (built-in-syms)))
(list))))))
;; CPS conversion
;;
;; This is a port of code from the 90-minute Scheme->C Compiler by Marc Feeley
;;
;; Convert intermediate code to continuation-passing style, to allow for
;; first-class continuations and call/cc
;;
(define (cps-convert ast)
(define (cps ast cont-ast)
(cond
((const? ast)
(list cont-ast ast))
((ref? ast)
(list cont-ast ast))
((quote? ast)
(list cont-ast ast))
((set!? ast)
(cps-list (cddr ast) ;; expr passed to set
(lambda (val)
(list cont-ast
`(set! ,(cadr ast) ,@val))))) ;; cadr => variable
((if? ast)
(let ((xform
(lambda (cont-ast)
(cps-list (list (cadr ast))
(lambda (test)
(list 'if
(car test)
(cps (caddr ast)
cont-ast)
(cps (cadddr ast)
cont-ast)))))))
(if (ref? cont-ast) ; prevent combinatorial explosion
(xform cont-ast)
(let ((k (gensym 'k)))
(list (list 'lambda
(list k)
(xform k))
cont-ast)))))
((prim-call? ast)
(cps-list (cdr ast) ; args to primitive function
(lambda (args)
(list cont-ast
`(,(car ast) ; op
,@args)))))
((lambda? ast)
(let ((k (gensym 'k))
(ltype (lambda-formals-type ast)))
(list cont-ast
`(lambda
,(list->lambda-formals
(cons k (cadr ast)) ; lam params
(if (equal? ltype 'args:varargs)
'args:fixed-with-varargs ;; OK? promote due to k
ltype))
,(cps-seq (cddr ast) k)))))
;
; TODO: begin is expanded already by desugar code... better to do it here?
; ((seq? ast)
; (cps-seq (ast-subx ast) cont-ast))
((app? ast)
(let ((fn (app->fun ast)))
(cond
((lambda? fn)
(cps-list (app->args ast)
(lambda (vals)
(cons (list
'lambda
(lambda->formals fn)
(cps-seq (cddr fn) ;(ast-subx fn)
cont-ast))
vals))))
(else
(cps-list ast ;(ast-subx ast)
(lambda (args)
(cons (car args)
(cons cont-ast
(cdr args)))))))))
(else
(error "unknown ast" ast))))
(define (cps-list asts inner)
(define (body x)
(cps-list (cdr asts)
(lambda (new-asts)
(inner (cons x new-asts)))))
(cond ((null? asts)
(inner '()))
((or (const? (car asts))
(ref? (car asts)))
(body (car asts)))
(else
(let ((r (gensym 'r))) ;(new-var 'r)))
(cps (car asts)
`(lambda (,r) ,(body r)))))))
(define (cps-seq asts cont-ast)
(cond ((null? asts)
(list cont-ast #f))
((null? (cdr asts))
(cps (car asts) cont-ast))
(else
(let ((r (gensym 'r)))
(cps (car asts)
`(lambda
(,r)
,(cps-seq (cdr asts) cont-ast)))))))
;; Remove dummy symbol inserted into define forms converted to CPS
(define (remove-unused ast)
(list (car ast) (cadr ast) (cadddr ast)))
(let* ((global-def? (define? ast)) ;; No internal defines by this phase
(ast-cps
(if global-def?
(remove-unused
`(define ,(define->var ast)
,@(let ((k (gensym 'k))
(r (gensym 'r)))
(cps (car (define->exp ast)) 'unused))))
(cps ast '%halt))))
ast-cps))
;; Closure-conversion.
;;
;; Closure conversion eliminates all of the free variables from every
;; lambda term.
;;
;; The code below is based on a fusion of a port of the 90-min-scc code by
;; Marc Feeley and the closure conversion code in Matt Might's scheme->c
;; compiler.
(define (pos-in-list x lst)
(let loop ((lst lst) (i 0))
(cond ((not (pair? lst)) #f)
((eq? (car lst) x) i)
(else
(loop (cdr lst) (+ i 1))))))
(define (closure-convert exp globals)
(define (convert exp self-var free-var-lst)
(define (cc exp)
(cond
((const? exp) exp)
((quote? exp) exp)
((ref? exp)
(let ((i (pos-in-list exp free-var-lst)))
(if i
`(%closure-ref
,self-var
,(+ i 1))
exp)))
((or
(tagged-list? '%closure-ref exp)
(tagged-list? '%closure exp)
(prim-call? exp))
`(,(car exp)
,@(map cc (cdr exp)))) ;; TODO: need to splice?
((set!? exp) `(set! ,(set!->var exp)
,(cc (set!->exp exp))))
((lambda? exp)
(let* ((new-self-var (gensym 'self))
(body (lambda->exp exp))
(new-free-vars
(difference
(difference (free-vars body) (lambda-formals->list exp))
globals)))
`(%closure
(lambda
,(list->lambda-formals
(cons new-self-var (lambda-formals->list exp))
(lambda-formals-type exp))
,(convert (car body) new-self-var new-free-vars)) ;; TODO: should this be a map??? was a list in 90-min-scc.
,@(map (lambda (v) ;; TODO: splice here?
(cc v))
new-free-vars))))
((if? exp) `(if ,@(map cc (cdr exp))))
((cell? exp) `(cell ,(cc (cell->value exp))))
((cell-get? exp) `(cell-get ,(cc (cell-get->cell exp))))
((set-cell!? exp) `(set-cell! ,(cc (set-cell!->cell exp))
,(cc (set-cell!->value exp))))
((app? exp)
(let ((fn (car exp))
(args (map cc (cdr exp))))
(if (lambda? fn)
(let* ((body (lambda->exp fn))
(new-free-vars
(difference
(difference (free-vars body) (lambda-formals->list fn))
globals))
(new-free-vars? (> (length new-free-vars) 0)))
(if new-free-vars?
; Free vars, create a closure for them
(let* ((new-self-var (gensym 'self)))
`((%closure
(lambda
,(list->lambda-formals
(cons new-self-var (lambda-formals->list fn))
(lambda-formals-type fn))
,(convert (car body) new-self-var new-free-vars))
,@(map (lambda (v) (cc v))
new-free-vars))
,@args))
; No free vars, just create simple lambda
`((lambda ,(lambda->formals fn)
,@(map cc body))
,@args)))
(let ((f (cc fn)))
`((%closure-ref ,f 0)
,f
,@args)))))
(else
(error "unhandled exp: " exp))))
(cc exp))
`(lambda ()
,(convert exp #f '())))
;; Library section
;; A quicky-and-dirty (for now) implementation of r7rs libraries
;; TODO: relocate this somewhere else, once it works. Ideally
;; somewhere accessible to the interpreter
(define (library? ast)
(tagged-list? 'define-library ast))
(define (lib:name ast) (cadr ast))
;; Convert name (as list of symbols) to a mangled string
(define (lib:name->string name)
(apply string-append (map mangle name)))
;; Helper function that returns an empty list as a default value
(define (lib:result result)
(if result result '()))
(define (lib:exports ast)
(lib:result
(and-let* ((code (assoc 'export (cddr ast))))
(cdr code))))
(define (lib:imports ast)
(lib:result
(and-let* ((code (assoc 'import (cddr ast))))
(cdr code))))
(define (lib:body ast)
(lib:result
(and-let* ((code (assoc 'begin (cddr ast))))
(cdr code))))
;; TODO: include, include-ci, cond-expand
;; resolve library filename from an import
(define (lib:import->filename import)
(string-append
(apply
string-append
(map
(lambda (i)
(string-append "/" (symbol->string i)))
import))
".sld"))
;; Resolve, EG: (libs lib2) ==> lib2.o
;; Thing is though, what if a library includes another library? now the
;; program needs to link to both .o files
(define (lib:import->obj-file import)
(string-append (symbol->string (car (reverse import))) ".o"))
;; TODO: for a program import set, resolve each to their .o files, then
;; process each import recursively to get the .o files that each one of those
;; libs requires. will probably need to prune duplicates from completed list.
;; longer-term, do we want to look at file timestamps to see if files need to
;; be recompiled?
(define (lib:imports->objs imports basedir)
(map
(lambda (i)
(append (lib:imports->objs (lib:read-imports i basedir))
(lib:import->obj-file i)))
imports))
(define (lib:read-imports import basedir)
(let* ((dir (string-append basedir (lib:import->filename import)))
(fp (open-input-file dir))
(lib (read-all fp))
(imports (lib:imports (car lib))))
(close-input-port fp)
imports))
;; Read export list for a given import
(define (lib:import->export-list import basedir)
(let* ((dir (string-append basedir (lib:import->filename import)))
(fp (open-input-file dir))
(lib (read-all fp))
(exports (lib:exports (car lib))))
(close-input-port fp)
exports))
;; Take a list of imports and resolve it to the imported vars
(define (lib:resolve-imports imports basedir)
(apply
append
(map
(lambda (import)
(lib:import->export-list import basedir))
imports)))
;; END Library section
; Suitable definitions for the cell functions:
;(define (cell value) (lambda (get? new-value)
; (if get? value (set! value new-value))))
;(define (set-cell! c v) (c #f v))
;(define (cell-get c) (c #t #t))