;; Copyright (C) 2006 Dan Muresan
;;
;; This file is part of cpscm (http://www.omnigia.com/scheme/cpscm/home).
;;
;; cpscm is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.
;;
;; cpscm is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with cpscm; if not, write to the Free Software
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

(require-library 'analysis) (import analysis)
(require-library 'r5rs-bootstrap) (import r5rs-bootstrap)
(require-library 'alexpander-cpscm)

(define cpscm-name "cpscm")
(define (__cpscm name) (s+ cpscm-name "__" name))

(module cpscm
  (prog->is
   trampoline?
   cpscm-compiler
   sexp->cps def->cps lambda->cps
   cpscm-int-defs cpscm-defs cpscm-defs-cpsed)
                  
(def-in-module trampoline? (make-parameter #t))

(define (cpscm-int-vars)
  `(
    (define %cpscm:vals-marker #f)
    (define %cpscm:winders '())
    (define %cpscm:err-hnd #f)
    (define %cpscm:debug-trampolines? #f)
    (define %cpscm:err-cont #f)
    ))

(define (cpscm-int-defs)
  `(
    (define (,make-promise proc)
      (let ((result-ready? #f) (result #f))
        (lambda ()
          (if result-ready? result
              (let ((x (proc)))
                (if result-ready? result
                    (begin (set! result-ready? #t)
                           (set! result x)
                           result)))))))
    (set! %cpscm:vals-marker (list 'values))
    (define (%cpscm:pack-vals things)
      (if (or (null? things) (not (null? (cdr things))))
          `(,%cpscm:vals-marker . ,things)
          (car things)))
    (define (%cpscm:unpack-vals vals)
      (if (and (pair? vals) (eq? (car vals) %cpscm:vals-marker))
          (cdr vals) (list vals)))
    (define (%cpscm:switch-winders ccwinders)
      ;; @param a reversed list of winders to be popped
      ;; @param b reversed list of winders to be pushed
      (define (switch a b)
        (define (run-all to-apply)
          (if (null? to-apply) #t
              (begin ((car to-apply)) (run-all (cdr to-apply)))))
        ;; apply cdr's of (reverse! a), then car's of b
        (run-all (append-reverse! (map cdr a) (map car b)))
        (set! %cpscm:winders ccwinders))
      (define (loop a b)
        (if (or (null? a) (null? b) (not (eq? (car a) (car b))))
            (switch a b)
            (loop (cdr a) (cdr b))))
      (loop (reverse %cpscm:winders) (reverse ccwinders)))
    (define (dynamic-wind before thunk after)
      (define result #f)
      (before)
      (set! %cpscm:winders (cons (cons before after) %cpscm:winders))
      (set! result (thunk))
      (set! %cpscm:winders (cdr %cpscm:winders))
      (after)
      result)
    (define (with-failure-continuation hnd thunk)
      (define old-hnd (current-error-handler))
      (define (new-hnd e ek) (current-error-handler old-hnd) (hnd e ek))
      (current-error-handler new-hnd)
      (thunk))
    (define with/fc with-failure-continuation)
    (define (%cpscm:default-err-hnd e ek)
      (display "Error: ") (display e) (newline))
    (set! %cpscm:err-hnd %cpscm:default-err-hnd)
    (define (current-error-handler . hnd)
      (if (null? hnd) %cpscm:err-hnd
            (set! %cpscm:err-hnd (car hnd))))
    (call-with-current-continuation
     (lambda (exit)
       (call-with-values
           (lambda ()
             (call-with-current-continuation
              (lambda (cc) (set! %cpscm:err-cont cc) (exit #t))))
         (lambda (e ek) ((current-error-handler) e ek)))))
    (define (error e)
      (call-with-current-continuation
       (lambda (ek)
         (%cpscm:err-cont e ek))))
    ))
(define (cpscm-defs)
  (append (r5rs-bootstrap-defs) (cpscm-int-defs)))

(define (cpscm-defs-cpsed)
  `(,@(cpscm-int-vars)
    (define (call-with-current-continuation k f)
      (define ccwinders %cpscm:winders)
      (f k (lambda (_ . xs)
            (%cpscm:switch-winders
             (lambda (_) (%cpscm:pack-vals k xs))
             ccwinders))))
    (define (apply k f . args)
      (%cpscm:normal-apply %cpscm:normal-apply f k args))
    (define (call-with-values k producer consummer)
      (producer
       (lambda (vals)
         (%cpscm:unpack-vals (lambda (vs) (apply k consummer vs)) vals))))
    (define (values k . things) (%cpscm:pack-vals k things))
    ))

;; Converts a function body to CPS.
;; @param k name of final continuation
;; @param t list of defs then expressions
(define (body->cps k body)
  (receive (defs bexpr) (sep-int-defs body)
    (define substs (sexp->anf (wrap-begin bexpr)))
    `(,@(map def->cps defs) ,(output-cps k (reverse! substs)))))

;; Outputs a CPS form based on an ANF list of substitutions
(define (output-cps k substs)
  (define never-cps `(set! %cpscm:native))
  ;; CPS-transforms lambda forms, leaves other forms alone
  (define (maybe-lambda->cps x)
    (if (lambda-form? x)
        (lambda->cps x) x))
  (if (null? (cdr substs))
      `(,k . ,(map maybe-lambda->cps (cddar substs)))
      (let loop ((substs substs))
        (let* ((csubst (car substs)) (result (car csubst)))
          (define f (maybe-lambda->cps (second csubst)))
          (define cc
            (if (null? (cddr substs))
                (let ((finsubst (second substs)))
                  (if (eq? (third finsubst) result) k
                      `(lambda (,result)
                         (,k . ,(map maybe-lambda->cps (cddr finsubst))))))
                `(lambda (,result) ,(loop (cdr substs)))))
          (define args (map maybe-lambda->cps (cddr csubst)))
          (cond
           ((eq? f 'begin)
            `(,cc ,(last csubst)))
           ((and (eq? f 'apply) (equal? (last args) '(quote ())))
            `(,(car args) ,cc ,@(drop-right (cdr args) 1)))
           ((memq f never-cps)
            `(,cc (,f ,@args)))
           (else `(,f ,cc ,@args)))))))

;; CPS-transforms lambda forms
(define (lambda->cps form)
  (define args (second form))
  (define k (xgensym "k"))
  `(lambda (,k . ,args) ,@(body->cps k (cddr form))))

;; CPS-transforms definitions
;; Only handles right-hand-sides that are values.
(define (def->cps form)
  (wmatch form
          ((or ('define (f . args) . body)
               ('define f ('lambda args . body)))
           (let ((form `(lambda ,args . ,body)))
             `(define ,f ,(lambda->cps form))))
          (('define x v) `(define ,x ,(sexp->cps v)))))

(define (sexp->cps sexp)
  (cond
   ((define? sexp) (def->cps sexp))
   (else
    (wmatch sexp
            (('quote x) sexp)
            ((f . args) `(,(lambda->cps `(lambda () ,sexp)) (lambda (x) x)))
            (_ sexp)))))

;; Converts a program to "intermediate Scheme" form.
;; Includes CPS conversion step.
(define (prog->is prog)
  (map
   (compose simplify-sexp sexp->cps simplify-sexp rewrite-int-defs expand-extra
            (lambda (sexp)
              (if (define? sexp)
                  (cout ";; compiling "
                        (second (def-fun->lambda-form sexp)) "\n"))
              sexp))
   (expand-program prog)))

(define/opt (cpscm-compiler compiler ((print pretty-print)))
  (lambda (from . to)
    (define compiled
      (compiler
       (if (string? from) (call-with-input-file from port->sexp-list)
           from)))
    (define (print-compiled p)
      (for-each (lambda (sexp) (print sexp p) (newline p) (newline p))
                compiled))
    (or (null? to) (set! to (car to)))
    (cond
     ((null? to) compiled)
     ((string? to) (call-with-output-file to print-compiled))
     ((port? to) (print-compiled to))
     (else (error 'cpscm-compiler)))))
)
