#lang racket/base
(require racket/match
         macro-debugger/model/deriv
         racket/struct
         "util.rkt")
(provide deriv->refs)

;; ========

;; phase : (parameterof nat)
(define phase (make-parameter 0))
(define (add-disappeared-uses?) #t)

;; ========

;; deriv->refs : *Deriv* -> Refs
;; *Deriv* = Deriv | LDeriv | BRule | ModRule | ... (anything from deriv.rkt)
(define (deriv->refs deriv0)

  ;; refs : (listof Refs), mutable
  (define refs null)

  (define (recur . args)
    (let check ([arg args])
      (cond [(syntax? arg) (error 'deriv->refs "internal error on ~s" arg)]
            [(list? arg) (for-each check arg)]
            [else (void)]))
    (for ([arg (in-list args)])
      (if (list? arg)
          (apply recur arg)
          (analyze-deriv arg))))
  (define (recur/phase-up . args)
    (parameterize ((phase (add1 (phase))))
      (apply recur args)))
  (define (add-refs! rs)
    (set! refs (append rs refs)))
  (define (add! ids [mode 'reference])
    (let ([p (phase)])
      (add-refs! (for/list ([id (in-list ids)])
                   (ref p id mode (identifier-binding id p))))))
  (define (add/binding! id binding mode)
    (add-refs! (list (ref (phase) id mode binding))))

  ;; analyze/quote-syntax : stx -> void
  ;; Current approach: estimate that an identifier in a syntax template
  ;; may be used at (sub1 (phase)) or (phase).
  ;; FIXME: Allow for more conservative choices, too.
  ;; FIXME: #%top, #%app, #%datum, etc?
  ;; FIXME: Track tentative (in quote-syntax) references separately?
  (define (analyze/quote-syntax qs-stx)
    (let ([phases (for/list ([offset '(0 1 -1 2 -2)]) (+ (phase) offset))]
          [stx (syntax-case qs-stx ()
                 [(_quote-syntax x) #'x])])
      (define (add*! id)
        (add-refs! (for/list ([p (in-list phases)])
                     (ref p id 'quote-syntax (identifier-binding id p)))))
      (let loop ([stx stx])
        (let ([d (if (syntax? stx) (syntax-e stx) stx)])
          (cond [(identifier? stx) (add*! stx)]
                [(pair? d)
                 (loop (car d))
                 (loop (cdr d))]
                [(vector? d)
                 (map loop (vector->list d))]
                [(prefab-struct-key d)
                 (map loop (struct->list d))]
                [(box? d)
                 (loop (unbox d))]
                [else
                 (void)])))))

  (define (analyze-deriv deriv)
    ;; Handle common base (ie, resolves) part of derivs, if applicable
    (match deriv
      [(base z1 z2 resolves ?1)
       (add! resolves)
       (when (and (syntax? z2) (add-disappeared-uses?))
         (let ([uses (syntax-property z2 'disappeared-use)])
           (add! (let loop ([x uses] [onto null])
                   (cond [(identifier? x) (cons x onto)]
                         [(pair? x) (loop (car x) (loop (cdr x) onto))]
                         [else onto]))
                 'disappeared-use)))]
      [_
       (void)])
    ;; Handle individual variants
    (match deriv
      [(lift-deriv z1 z2 first lift-stx second)
       (recur first second)]
      [(tagrule z1 z2 tagged-stx next)
       (recur next)]
      [(lift/let-deriv z1 z2 first lift-stx second)
       (recur first second)]
      [(mrule z1 z2 rs ?1 me1 locals me2 ?2 etx next)
       (recur locals next)]
      [(local-exn exn)
       (void)]
      [(local-expansion z1 z2 for-stx? me1 inner lifted me2 opaque)
       ((if for-stx? recur/phase-up recur) inner)]
      [(local-lift expr ids)
       (void)]
      [(local-lift-end decl)
       (void)]
      [(local-lift-require req expr mexpr)
       (void)]
      [(local-lift-provide prov)
       (void)]
      [(local-bind names ?1 renames bindrhs)
       (recur bindrhs)]
      [(local-value name ?1 resolves bound? binding)
       #|
       Beware: in one common case, local-member-name, the binding of name is
       mutated (because used as binder in class body), so original binding is lost!
       Use binding instead.
       |#
       (when (and bound? (pair? binding))
         (add/binding! name binding 'syntax-local-value))]
      [(track-origin before after)
       (void)]
      [(local-remark contents)
       (void)]
      [(p:variable z1 z2 rs ?1)
       (void)]
      [(p:module z1 z2 rs ?1 locals tag rename check tag2 ?3 body shift)
       (recur locals check body)]
      [(p:#%module-begin z1 z2 rs ?1 me body ?2 subs)
       (recur body subs)]
      [(p:define-syntaxes z1 z2 rs ?1 prep rhs locals)
       (recur prep locals)
       (recur/phase-up rhs)]
      [(p:define-values z1 z2 rs ?1 rhs)
       (recur rhs)]
      [(p:begin-for-syntax z1 z2 rs ?1 prep body locals)
       (recur prep locals)
       (recur/phase-up body)]
      [(p:#%expression z1 z2 rs ?1 inner untag)
       (recur inner)]
      [(p:if z1 z2 rs ?1 test then else)
       (recur test then else)]
      [(p:wcm z1 z2 rs ?1 key mark body)
       (recur key mark body)]
      [(p:set! _ _ _ _ id-resolves ?2 rhs)
       (add! id-resolves)
       (recur rhs)]
      [(p:set!-macro _ _ _ _ deriv)
       (recur deriv)]
      [(p:#%app _ _ _ _ lderiv)
       (recur lderiv)]
      [(p:begin _ _ _ _ lderiv)
       (recur lderiv)]
      [(p:begin0 _ _ _ _ first lderiv)
       (recur first lderiv)]
      [(p:lambda _ _ _ _ renames body)
       (recur body)]
      [(p:case-lambda _ _ _ _ renames+bodies)
       (recur renames+bodies)]
      [(p:let-values _ _ _ _ renames rhss body)
       (recur rhss body)]
      [(p:letrec-values _ _ _ _ renames rhss body)
       (recur rhss body)]
      [(p:letrec-syntaxes+values _ _ _ _ srenames prep sbindrhss vrenames vrhss body tag)
       (recur prep sbindrhss vrhss body)]
      [(p:provide _ _ _ _ inners ?2)
       (recur inners)]
      [(p:require _ _ _ _ locals)
       (recur locals)]
      [(p:submodule _ _ _ _ exp)
       (recur exp)]
      [(p:submodule* _ _ _ _)
       (void)]
      [(p:#%stratified-body _ _ _ _ bderiv)
       (recur bderiv)]
      [(p:stop _ _ _ _) (void)]
      [(p:unknown _ _ _ _) (void)]
      [(p:#%top _ _ _ _)
       (void)]
      [(p:#%datum _ _ _ _) (void)]
      [(p:quote _ _ _ _) (void)]
      [(p:quote-syntax z1 z2 _ _)
       (when z2 (analyze/quote-syntax z2))]
      [(p:#%variable-reference _ _ _ _)
       (void)]
      [(lderiv _ _ ?1 derivs)
       (recur derivs)]
      [(bderiv _ _ pass1 trans pass2)
       (recur pass1 pass2)]
      [(b:error ?1)
       (void)]
      [(b:expr _ head)
       (recur head)]
      [(b:splice _ head ?1 tail ?2)
       (recur head)]
      [(b:defvals _ head ?1 rename ?2)
       (recur head)]
      [(b:defstx _ head ?1 rename ?2 prep bindrhs)
       (recur head prep bindrhs)]
      [(bind-syntaxes rhs locals)
       (recur/phase-up rhs)
       (recur locals)]
      [(clc ?1 renames body)
       (recur body)]
      [(module-begin/phase pass1 pass2 pass3)
       (recur pass1 pass2 pass3)]
      [(mod:prim head rename prim)
       (recur head prim)]
      [(mod:splice head rename ?1 tail)
       (recur head)]
      [(mod:lift head locals renames tail)
       (recur head locals)]
      [(mod:lift-end tail)
       (void)]
      [(mod:cons head locals)
       (recur head locals)]
      [(mod:skip)
       (void)]
      ;; Shouldn't occur in module expansion.
      ;; (Unless code calls 'expand' at compile-time; weird, but possible.)
      [(ecte _ _ locals first second locals2)
       (recur locals first second locals2)]
      [(bfs:lift lderiv lifts)
       (recur lderiv)]
      [#f
       (void)]))

  (analyze-deriv deriv0)
  refs)
