#lang scheme/base
;; this finds the free variables of fully-expanded mzscheme expressions
;; adapted from code by mflatt


(require syntax/kerncase
         syntax/boundmap
         (for-template scheme/base))

(provide free-vars)

;; merge : (liftof (listof id)) -> (listof id)
;;  merges lists of identifiers, removing module-identifier=?
;;  duplicates
(define (merge l)
  (cond
    [(null? l) null]
    [(null? (cdr l)) (car l)]
    [else (let ([m (make-module-identifier-mapping)])
            (for-each (lambda (ids)
                        (for-each (lambda (id)
                                    (module-identifier-mapping-put! m id #t))
                                  ids))
                      l)
            (module-identifier-mapping-map m (lambda (k v) k)))]))

;; formals->boundmap : formals-stx -> bound-map-table
;; Parses a procedure "formals" and returns the binding ids
;;  in a table
(define (formals->boundmap f)
  (let ([ids (let loop ([f f])
               (cond
                 [(identifier? f) (list f)]
                 [(pair? f) (cons (car f)
                                  (loop (cdr f)))]
                 [(null? f) null]
                 [(syntax? f) (loop (syntax-e f))]))]
        [b (make-bound-identifier-mapping)])
    (for-each (lambda (id)
                (bound-identifier-mapping-put! b id #t))
              ids)
    b))

;; free-vars : expr-stx -> (listof id)
;; Returns a list of free lambda- and let-bound identifiers in a
;;  given epression. The expression must be fully expanded.
(define (free-vars e)
  (kernel-syntax-case e #f
    [id 
     (identifier? #'id) 
     (if (eq? 'lexical (identifier-binding #'id))
         (list #'id)
         null)]
    [(#%top . id) null]
    [(quote q) null]
    [(quote-syntax q) null]
    [(#%plain-lambda formals expr ...)
     (let ([free (merge (map free-vars (syntax->list #'(expr ...))))]
           [bindings (formals->boundmap #'formals)])
       (filter (lambda (id)
                 (not (bound-identifier-mapping-get bindings id (lambda () #f))))
               free))]
    [(case-lambda [formals expr ...] ...)
     (merge (map free-vars (syntax->list
                            #'((#%plain-lambda formals expr ...) ...))))]
    [(let-values ([(id ...) rhs] ...) expr ...)
     (merge (cons (free-vars #'(#%plain-lambda (id ... ...) expr ...))
                  (map free-vars (syntax->list #'(rhs ...)))))]
    [(letrec-values ([(id ...) rhs] ...) expr ...)
     (free-vars #'(#%plain-lambda (id ... ...) rhs ... expr ...))]
    [(letrec-syntaxes+values stx-bindings ([(id ...) rhs] ...) expr ...)
     (free-vars #'(#%plain-lambda (id ... ...) rhs ... expr ...))]
    [(kw expr ...)
     (ormap (lambda (k) (free-identifier=? k #'kw))
	    (list #'if #'begin #'begin0 #'set! #'#%plain-app #'#%expression
		  #'#%variable-reference #'with-continuation-mark))
     (merge (map free-vars (syntax->list #'(expr ...))))]
    [(kw . _)
     (error 'free-vars "unknown core form: ~a" (syntax->datum #'kw))]))

