#lang racket
(require syntax/toplevel
mred/mred
"../private/planet.rkt")
(require rackunit
(cce main))
(define (top-level->module lang-path terms)
(let* ([name (gensym 'program)])
(list
(datum->syntax
#'here
`(module ,name ,lang-path
,(datum->syntax #f `(#%module-begin ,@terms))))
(datum->syntax
#'here `(require ',name))
(datum->syntax
#'here `(current-namespace (module->namespace '',name))))))
(define-struct prog (make-ns terms results))
(define-struct result (term expand eval))
(define-struct summary ())
(define-struct (success summary) (expansion evaluation))
(define-struct (expand-failure summary) (term exn))
(define-struct (eval-failure summary) (term exn))
(provide/contract
[top-level->module (-> any/c list? (listof syntax?))]
[make-program (-> list? prog?)]
[make-program/mred (-> list? prog?)]
[make-module-program (->* [any/c list?] [list?] prog?)]
[make-module-program/mred (->* [any/c list?] [list?] prog?)]
[make-program/namespace (-> (-> namespace?) list? prog?)]
[check-expand-failure (-> prog? exn?)]
[check-expand-success (-> prog? (listof syntax?))]
[check-eval-failure (-> prog? exn?)]
[check-eval-success (-> prog? any)])
(define (check-expand-failure program)
(let* ([summary (summarize (force (prog-results program)))])
(if (expand-failure? summary)
(expand-failure-exn summary)
(summary->failure summary))))
(define (check-eval-failure program)
(let* ([summary (summarize (force (prog-results program)))])
(if (eval-failure? summary)
(eval-failure-exn summary)
(summary->failure summary))))
(define (check-expand-success program)
(let* ([summary (summarize (force (prog-results program)))])
(if (success? summary)
(success-expansion summary)
(summary->failure summary))))
(define (check-eval-success program)
(let* ([summary (summarize (force (prog-results program)))])
(if (success? summary)
(apply values (success-evaluation summary))
(summary->failure summary))))
(define (summary->failure summary)
(cond
[(success? summary)
(fail (format "The program succeeded with values: ~s"
(success-evaluation summary)))]
[(eval-failure? summary)
(fail (format "Term ~s failed to eval with error ~s"
(datum (eval-failure-term summary))
(eval-failure-exn summary)))]
[(expand-failure? summary)
(fail (format "Term ~s failed to expand with error ~s"
(datum (expand-failure-term summary))
(expand-failure-exn summary)))]))
(define (datum v) (syntax->datum (datum->syntax #f v)))
(define (summarize results)
(cond
[(findf (lambda (res) (exn? (result-expand res))) results) =>
(lambda (res)
(make-expand-failure (result-term res) (result-expand res)))]
[(findf (lambda (res) (exn? (result-eval res))) results) =>
(lambda (res)
(make-eval-failure (result-term res) (result-eval res)))]
[else (make-success (map result-expand results)
(result-eval (last results)))]))
(define (last l)
(cond
[(null? l) (error 'last "got an empty list")]
[(null? (cdr l)) (car l)]
[else (last (cdr l))]))
(define (make-program/namespace generate-namespace terms)
(make-prog generate-namespace
terms
(delay (execute generate-namespace terms))))
(define (make-program terms)
(make-program/namespace (lambda () (make-base-namespace)) terms))
(define make-module-program
(lambda (path body [rest null])
(make-program (append (top-level->module path body) rest))))
(define (make-program/mred terms)
(make-program/namespace
(lambda () (make-gui-namespace))
terms))
(define make-module-program/mred
(lambda (path body [rest null])
(make-program/mred (append (top-level->module path body) rest))))
(define (execute gen-ns terms)
(parameterize ([current-namespace (gen-ns)]
[current-custodian (make-custodian)])
(begin0
(execute-terms terms)
(custodian-shutdown-all
(current-custodian)))))
(define (execute-terms terms)
(if (null? terms)
null
(execute-term (car terms) (cdr terms))))
(define (execute-term term rest)
(let* ([stx (expand-term term)]
[vals (and (syntax? stx) (eval-term stx))])
(cons (make-result term stx vals)
(if (list? vals)
(execute-terms rest)
(map skip-term rest)))))
(define (expand-term term)
(call-with-continuation-barrier
(lambda ()
(with-handlers ([exn? identity]
[any? (non-exception-failure 'expand term)])
(expand term)))))
(define (eval-term stx)
(call-with-continuation-barrier
(lambda ()
(with-handlers ([exn? identity]
[any? (non-exception-failure
'eval (syntax->datum stx))])
(call-with-values (lambda () (eval-syntax stx)) list)))))
(define (skip-term term)
(make-result term #f #f))
(define any? (const #t))
(define ((non-exception-failure op term) non-exn)
(fail (format "Attempting to ~a term ~s raised non-exception value ~s."
op term non-exn)))