#lang s-exp "lang.ss"
(require "helpers.ss")
(require "pinfo.ss")
(define (desugar-program a-program a-pinfo)
(local [
(define (reorder-tests-to-end a-program program/rev tests/rev)
(cond
[(empty? a-program)
(append (reverse program/rev) (reverse tests/rev))]
[(test-case? (first a-program))
(reorder-tests-to-end (rest a-program)
program/rev
(cons (first a-program)
tests/rev))]
[else
(reorder-tests-to-end (rest a-program)
(cons (first a-program) program/rev)
tests/rev)]))
(define (desugar-program-element an-element a-pinfo)
(cond
[(defn? an-element)
(desugar-defn an-element a-pinfo)]
[(stx-begins-with? an-element 'include)
(desugar-include an-element a-pinfo)]
[(test-case? an-element)
(desugar-test-case an-element a-pinfo)]
[(expression? an-element)
(local [(define expr+pinfo (desugar-expression an-element a-pinfo))]
(list (list (first expr+pinfo))
(second expr+pinfo)))]))
(define (desugar-defn a-defn a-pinfo)
(local [(define define-stx (first (stx-e a-defn)))]
(case-analyze-definition a-defn
(lambda (id args body)
(local [(define subexpr+pinfo (desugar-expression body a-pinfo))]
(list (list (make-stx:list (list define-stx
(make-stx:list (cons id args)
(stx-loc a-defn))
(first subexpr+pinfo))
(stx-loc a-defn)))
(second subexpr+pinfo))))
(lambda (id body)
(local [(define subexpr+pinfo (desugar-expression body a-pinfo))]
(list (list (make-stx:list (list define-stx
id
(first subexpr+pinfo))
(stx-loc a-defn)))
(second subexpr+pinfo))))
(lambda (id fields)
(list (list a-defn) a-pinfo)))))
(define (desugar-expressions exprs pinfo)
(cond
[(empty? exprs)
(list empty pinfo)]
[else
(local [(define first-desugared+pinfo
(desugar-expression (first exprs) pinfo))
(define rest-desugared+pinfo
(desugar-expressions (rest exprs)
(second first-desugared+pinfo)))]
(list (cons (first first-desugared+pinfo)
(first rest-desugared+pinfo))
(second rest-desugared+pinfo)))]))
(define (thunkify-stx an-stx)
(datum->stx (list 'lambda (list)
an-stx)
(stx-loc an-stx)))
(define (check-length! stx n error-msg)
(cond [(not (= n (length (stx-e stx))))
(syntax-error error-msg stx)]
[else
(void)]))
(define (desugar-test-case a-test-case a-pinfo)
(local [(define test-symbol-stx (first (stx-e a-test-case)))
(define test-exprs (map thunkify-stx (rest (stx-e a-test-case))))
(define desugared-exprs+pinfo (desugar-expressions test-exprs a-pinfo))]
(begin
(cond [(stx-begins-with? a-test-case 'check-expect)
(check-length! a-test-case 3
"check-expect requires two expressions. Try (check-expect test expected).")]
[(stx-begins-with? a-test-case 'check-within)
(check-length! a-test-case 4
"check-within requires three expressions. Try (check-within test expected range).")]
[(stx-begins-with? a-test-case 'check-error)
(check-length! a-test-case 3
"check-error requires two expressions. Try (check-error test message).")]
[else
(void)])
(list (list (make-stx:list (cons test-symbol-stx
(first desugared-exprs+pinfo))
(stx-loc a-test-case)))
(second desugared-exprs+pinfo)))))
(define (desugar-expression/expr+pinfo expr+pinfo)
(desugar-expression (first expr+pinfo)
(second expr+pinfo)))
(define (desugar-expression expr pinfo)
(cond
[(stx-begins-with? expr 'cond)
(desugar-expression/expr+pinfo (desugar-cond expr pinfo))]
[(stx-begins-with? expr 'case)
(desugar-expression/expr+pinfo (desugar-case expr pinfo))]
[(stx-begins-with? expr 'let)
(desugar-expression/expr+pinfo (desugar-let expr pinfo))]
[(stx-begins-with? expr 'let*)
(desugar-expression/expr+pinfo (desugar-let* expr pinfo))]
[(stx-begins-with? expr 'letrec)
(desugar-expression/expr+pinfo (desugar-letrec expr pinfo))]
[(or (stx-begins-with? expr 'quasiquote)
(stx-begins-with? expr 'unquote)
(stx-begins-with? expr 'quasiquote))
(desugar-expression/expr+pinfo (desugar-quasiquote expr pinfo))]
[(stx-begins-with? expr 'local)
(local [(define local-symbol-stx (first (stx-e expr)))
(define defns (stx-e (second (stx-e expr))))
(define body (third (stx-e expr)))
(define desugared-defns+pinfo (desugar-program defns pinfo))
(define desugared-body+pinfo (desugar-expression body (second desugared-defns+pinfo)))]
(list (make-stx:list (list local-symbol-stx
(make-stx:list (first desugared-defns+pinfo)
(stx-loc (second (stx-e expr))))
(first desugared-body+pinfo))
(stx-loc expr))
(pinfo-update-env (second desugared-body+pinfo)
(pinfo-env pinfo))))]
[(stx-begins-with? expr 'begin)
(local [(define begin-symbol-stx (first (stx-e expr)))
(define exprs (rest (stx-e expr)))
(define desugared-exprs+pinfo (desugar-expressions exprs pinfo))]
(list (make-stx:list (cons begin-symbol-stx
(first desugared-exprs+pinfo))
(stx-loc expr))
(second desugared-exprs+pinfo)))]
[(stx-begins-with? expr 'set!)
(local [(define set-symbol-stx (first (stx-e expr)))
(define id (second (stx-e expr)))
(define value (third (stx-e expr)))
(define desugared-value+pinfo (desugar-expression value pinfo))]
(list (make-stx:list (list set-symbol-stx
id
(first desugared-value+pinfo))
(stx-loc expr))
(second desugared-value+pinfo)))]
[(stx-begins-with? expr 'if)
(local [(define if-symbol-stx (first (stx-e expr)))
(define exprs (rest (stx-e expr)))
(define desugared-exprs+pinfo (desugar-expressions exprs pinfo))]
(list (make-stx:list (cons if-symbol-stx
(first desugared-exprs+pinfo))
(stx-loc expr))
(second desugared-exprs+pinfo)))]
[(stx-begins-with? expr 'and)
(local [(define and-symbol-stx (first (stx-e expr)))
(define exprs (rest (stx-e expr)))
(define desugared-exprs+pinfo (desugar-expressions exprs pinfo))]
(list (make-stx:list (cons and-symbol-stx
(first desugared-exprs+pinfo))
(stx-loc expr))
(second desugared-exprs+pinfo)))]
[(stx-begins-with? expr 'or)
(local [(define or-symbol-stx (first (stx-e expr)))
(define exprs (rest (stx-e expr)))
(define desugared-exprs+pinfo (desugar-expressions exprs pinfo))]
(list (make-stx:list (cons or-symbol-stx
(first desugared-exprs+pinfo))
(stx-loc expr))
(second desugared-exprs+pinfo)))]
[(stx-begins-with? expr 'lambda)
(local [(define lambda-symbol-stx (first (stx-e expr)))
(define args (second (stx-e expr)))
(define body (third (stx-e expr)))
(define desugared-body+pinfo (desugar-expression body pinfo))]
(list (make-stx:list (list lambda-symbol-stx
args
(first desugared-body+pinfo))
(stx-loc expr))
(second desugared-body+pinfo)))]
[(number? (stx-e expr))
(list expr pinfo)]
[(string? (stx-e expr))
(list expr pinfo)]
[(boolean? (stx-e expr))
(list expr pinfo)]
[(char? (stx-e expr))
(list expr pinfo)]
[(symbol? (stx-e expr))
(list expr pinfo)]
[(stx-begins-with? expr 'quote)
(list expr pinfo)]
[(pair? (stx-e expr))
(local [(define exprs (stx-e expr))
(define desugared-exprs+pinfo (desugar-expressions exprs pinfo))]
(list (make-stx:list (first desugared-exprs+pinfo)
(stx-loc expr))
(second desugared-exprs+pinfo)))]))
(define (processing-loop a-program a-pinfo)
(cond
[(empty? a-program)
(list empty a-pinfo)]
[else
(local [(define desugared-elts+pinfo
(desugar-program-element (first a-program) a-pinfo))
(define desugared-rest+pinfo
(processing-loop (rest a-program) (second desugared-elts+pinfo)))]
(list (append (first desugared-elts+pinfo)
(first desugared-rest+pinfo))
(second desugared-rest+pinfo)))]))]
(processing-loop (reorder-tests-to-end a-program empty empty)
a-pinfo)))
(define (desugar-include include-expr pinfo)
(cond
[(not (= (length (stx-e include-expr)) 2))
(syntax-error "Usage: (include file-path), where file-path is a string."
include-expr)]
[(not (string? (stx-e (second (stx-e include-expr)))))
(syntax-error "file-path must be a string" (second (stx-e include-expr)))]
[else
(local [(define file-path (stx-e (second (stx-e include-expr))))
(define stxs (open-input-stx file-path))]
(desugar-program stxs pinfo))]))
(define (desugar-case an-expr pinfo)
(local
[(define pinfo+val-sym (pinfo-gensym pinfo 'val))
(define updated-pinfo-1 (first pinfo+val-sym))
(define val-stx (make-stx:atom (second pinfo+val-sym) (stx-loc an-expr)))
(define pinfo+x-sym (pinfo-gensym updated-pinfo-1 'x))
(define updated-pinfo-2 (first pinfo+x-sym))
(define x-stx (make-stx:atom (second pinfo+x-sym) (stx-loc an-expr)))
(define predicate
(datum->stx (list 'lambda (list x-stx)
(list 'equal? x-stx val-stx))
(stx-loc an-expr)))
(define (loop list-of-datum answers datum-last answer-last)
(cond
[(empty? list-of-datum)
(if (and (symbol? (stx-e datum-last)) (symbol=? 'else (stx-e datum-last)))
answer-last
(make-stx:list (list (make-stx:atom 'if (stx-loc an-expr))
(make-stx:list (list (make-stx:atom 'ormap (stx-loc an-expr))
predicate
(make-stx:list (list (make-stx:atom 'quote (stx-loc an-expr))
datum-last)
(stx-loc an-expr)))
(stx-loc an-expr))
answer-last
(make-stx:list (list (make-stx:atom 'void (stx-loc an-expr)))
(stx-loc an-expr)))
(stx-loc an-expr)))]
[else
(make-stx:list (list (make-stx:atom 'if (stx-loc an-expr))
(make-stx:list (list (make-stx:atom 'ormap (stx-loc an-expr))
predicate
(make-stx:list (list (make-stx:atom 'quote (stx-loc an-expr))
(first list-of-datum))
(stx-loc an-expr)))
(stx-loc an-expr))
(first answers)
(loop (rest list-of-datum)
(rest answers)
datum-last
answer-last))
(stx-loc an-expr))]))]
(cond
[(stx-begins-with? an-expr 'case)
(deconstruct-clauses-with-else (rest (rest (stx-e an-expr)))
(lambda (else-stx)
else-stx)
(lambda (questions answers question-last answer-last)
(list (datum->stx (list 'let (list (list val-stx (second (stx-e an-expr))))
(loop questions answers question-last answer-last))
(stx-loc an-expr))
updated-pinfo-2)))]
[else
(syntax-error (format "Not a case clause: ~s" (stx-e an-expr))
an-expr)])))
(define (desugar-cond an-expr pinfo)
(local
[ (define (loop questions answers question-last answer-last)
(cond
[(empty? questions)
(datum->stx (list 'if question-last
answer-last
(list 'error ''cond
(format "cond: fell out of cond around ~s" (Loc->string (stx-loc an-expr)))))
(stx-loc an-expr))]
[else
(make-stx:list (list (make-stx:atom 'if (stx-loc an-expr))
(first questions)
(first answers)
(loop (rest questions)
(rest answers)
question-last
answer-last))
(stx-loc an-expr))]))]
(cond
[(stx-begins-with? an-expr 'cond)
(deconstruct-clauses-with-else (rest (stx-e an-expr))
(lambda (else-stx)
(make-stx:atom 'true (stx-loc else-stx)))
(lambda (questions answers question-last answer-last)
(list (loop questions answers question-last answer-last)
pinfo)))]
[else
(syntax-error (format "Not a cond clause: ~s" (stx-e an-expr))
an-expr)])))
(define (deconstruct-clauses-with-else clauses else-replacement-f f)
(local
[ (define (process-clauses clauses questions/rev answers/rev)
(cond
[(stx-begins-with? (first clauses) 'else)
(if (not (empty? (rest clauses)))
(syntax-error "else clause should be the last, but there's another clause after it" (first clauses))
(f (reverse questions/rev)
(reverse answers/rev)
(else-replacement-f (first (stx-e (first clauses))))
(second (stx-e (first clauses)))))]
[(empty? (rest clauses))
(f (reverse questions/rev)
(reverse answers/rev)
(first (stx-e (first clauses)))
(second (stx-e (first clauses))))]
[else
(process-clauses (rest clauses)
(cons (first (stx-e (first clauses))) questions/rev)
(cons (second (stx-e (first clauses))) answers/rev))]))]
(process-clauses clauses empty empty)))
(define (desugar-let a-stx pinfo)
(local [(define clauses-stx (second (stx-e a-stx)))
(define body-stx (third (stx-e a-stx)))
(define ids (map (lambda (clause)
(first (stx-e clause)))
(stx-e clauses-stx)))
(define vals (map (lambda (clause)
(second (stx-e clause)))
(stx-e clauses-stx)))
(define new-lambda-stx
(make-stx:list (list (make-stx:atom 'lambda (stx-loc a-stx))
(make-stx:list ids (stx-loc a-stx))
body-stx)
(stx-loc a-stx)))]
(begin
(check-single-body-stx! (rest (rest (stx-e a-stx))) a-stx)
(check-duplicate-identifiers! (map (lambda (a-clause)
(first (stx-e a-clause)))
(stx-e clauses-stx)))
(list (make-stx:list (cons new-lambda-stx vals)
(stx-loc a-stx))
pinfo))))
(define (desugar-let* a-stx pinfo)
(local [(define clauses-stx (second (stx-e a-stx)))
(define body-stx (third (stx-e a-stx)))
(define (loop clauses)
(cond
[(empty? clauses)
body-stx]
[else
(make-stx:list (list (make-stx:atom 'let (stx-loc (first clauses)))
(make-stx:list (list (first clauses))
(stx-loc (first clauses)))
(loop (rest clauses)))
(stx-loc (first clauses)))]))]
(begin
(check-single-body-stx! (rest (rest (stx-e a-stx))) a-stx)
(list (loop (stx-e clauses-stx))
pinfo))))
(define (desugar-letrec a-stx pinfo)
(local [(define clauses-stx (second (stx-e a-stx)))
(define body-stx (third (stx-e a-stx)))
(define define-clauses
(map (lambda (a-clause)
(local [(define name (first (stx-e a-clause)))
(define val (second (stx-e a-clause)))]
(datum->stx (list 'define name val)
(stx-loc a-clause))))
(stx-e clauses-stx)))]
(begin
(check-single-body-stx! (rest (rest (stx-e a-stx))) a-stx)
(check-duplicate-identifiers! (map (lambda (a-clause) (first (stx-e a-clause)))
(stx-e clauses-stx)))
(list (datum->stx (list 'local define-clauses body-stx)
(stx-loc a-stx))
pinfo))))
(define (desugar-quasiquote a-stx pinfo)
(local [ (define (handle-quoted a-stx depth)
(cond
[(stx:list? a-stx)
(cond [(stx-begins-with? a-stx 'quasiquote)
(begin
(check-single-body-stx! (rest (stx-e a-stx)) a-stx)
(cond
[(> depth 0)
(datum->stx (list 'list (list 'quote (first (stx-e a-stx)))
(handle-quoted (second (stx-e a-stx))
(add1 depth)))
(stx-loc a-stx))]
[else
(datum->stx (handle-quoted (second (stx-e a-stx))
(add1 depth))
(stx-loc a-stx))]))]
[(stx-begins-with? a-stx 'unquote)
(begin
(check-single-body-stx! (rest (stx-e a-stx)) a-stx)
(cond
[(> depth 1)
(datum->stx (list 'list (list 'quote (first (stx-e a-stx)))
(handle-quoted (second (stx-e a-stx))
(sub1 depth)))
(stx-loc a-stx))]
[(= depth 1)
(second (stx-e a-stx))]
[else
(syntax-error "misuse of a comma or 'unquote, not under a quasiquoting backquote" a-stx)]))]
[(stx-begins-with? a-stx 'unquote-splicing)
(cond
[(> depth 1)
(datum->stx (list 'list (list 'quote (first (stx-e a-stx)))
(handle-quoted (second (stx-e a-stx))
(sub1 depth)))
(stx-loc a-stx))]
[(= depth 1)
(syntax-error "misuse of ,@ or unquote-splicing within a quasiquoting backquote" a-stx)]
[else
(syntax-error "misuse of a ,@ or unquote-splicing, not under a quasiquoting backquote" a-stx)])]
[else
(datum->stx (cons 'append
(map
(lambda (s)
(cond
[(stx-begins-with? s 'quasiquote)
(list 'list (handle-quoted s depth))]
[(stx-begins-with? s 'unquote)
(list 'list (handle-quoted s depth))]
[(stx-begins-with? s 'unquote-splicing)
(cond
[(> depth 1)
(list 'list (handle-quoted s depth))]
[(= depth 1)
(begin
(check-single-body-stx! (rest (stx-e s)) s)
(second (stx-e s)))]
[else
(syntax-error
"misuse of ,@ or unquote-splicing within a quasiquoting backquote" a-stx)])]
[else
(list 'list (handle-quoted s depth))]))
(stx-e a-stx)))
(stx-loc a-stx))])]
[else
(cond
[(> depth 0)
(datum->stx (list 'quote a-stx) (stx-loc a-stx))]
[else
a-stx])]))]
(list (handle-quoted a-stx 0)
pinfo)))
(provide/contract
[desugar-program (program? pinfo? . -> . (list/c program? pinfo?))])