#lang whalesong (require "../selfhost-lang.rkt")

(require "expression-structs.rkt"
         "analyzer-structs.rkt"
         "arity-structs.rkt"
         "lexical-structs.rkt"
         "il-structs.rkt"
         "compiler-structs.rkt"
         ; racket/list
         )

(require "compiler-helper.rkt")



(provide collect-all-lambdas-with-bodies
         collect-lam-applications
         extract-static-knowledge
         ensure-prefix)

;; Holds helper functions we use for different analyses.

;; Given a lambda body, collect all the applications that exist within
;; it.  We'll use this to determine what procedures can safely be
;; transformed into primitives.
(: collect-lam-applications (Lam CompileTimeEnvironment -> (Listof CompileTimeEnvironmentEntry)))
(define (collect-lam-applications lam cenv)
  
  (let loop 
    ([exp  (Lam-body lam)]  ; : Expression
     [cenv  cenv]           ; : CompileTimeEnvironment
     [acc  '()])            ; : (Listof CompileTimeEnvironmentEntry)
    
    (cond
      [(Top? exp)
       (loop (Top-code exp)
             (cons (Top-prefix exp) cenv)
             acc)]
      
      [(Module? exp)
       (loop (Module-code exp)
             (cons (Module-prefix exp) cenv)
             acc)]
      
      [(Constant? exp)
       acc]
      
      [(LocalRef? exp)
       acc]
      
      [(ToplevelRef? exp)
       acc]
      
      [(ToplevelSet? exp)
       (loop (ToplevelSet-value exp) cenv acc)]
      
      [(Branch? exp)
       (define acc-1 (loop (Branch-predicate exp) cenv acc))
       (define acc-2 (loop (Branch-consequent exp) cenv acc-1))
       (define acc-3 (loop (Branch-alternative exp) cenv acc-2))
       acc-3]
      
      [(Lam? exp)
       acc]
      
      [(CaseLam? exp)
       acc]
      
      [(EmptyClosureReference? exp)
       acc]
      
      [(Seq? exp)
       (foldl (lambda  (e   ; [e : Expression]
                        acc ; [acc : (Listof CompileTimeEnvironmentEntry)]
                        )
                (loop e cenv acc))
              acc
              (Seq-actions exp))]
      
      [(Splice? exp)
       (foldl (lambda  (e   ; [e : Expression]
                        acc ; [acc : (Listof CompileTimeEnvironmentEntry)]
                        )
                (loop e cenv acc))
              acc
              (Splice-actions exp))]
      
      [(Begin0? exp)
       (foldl (lambda (e   ; [e : Expression]
                       acc ; [acc : (Listof CompileTimeEnvironmentEntry)]
                       )
                (loop e cenv acc))
              acc
              (Begin0-actions exp))]
      
      [(App? exp)
       (define new-cenv
         (append (build-list (length (App-operands exp)) (lambda (i #;[i : Natural]) '?))
                 cenv))
       (foldl (lambda (e #;[e : Expression]
                         acc #;[acc : (Listof CompileTimeEnvironmentEntry)])
                (loop e new-cenv acc))
              (cons (extract-static-knowledge (App-operator exp) new-cenv)
                    (loop (App-operator exp) new-cenv acc))
              (App-operands exp))]
      
      [(Let1? exp)
       (define acc-1 (loop (Let1-rhs exp) (cons '? cenv) acc))
       (define acc-2 (loop (Let1-body exp) 
                           (cons (extract-static-knowledge (Let1-rhs exp) (cons '? cenv)) 
                                 cenv)
                           acc-1))
       acc-2]
      
      [(LetVoid? exp)
       (loop (LetVoid-body exp) 
             (append (build-list (LetVoid-count exp) (lambda (i #;[i : Natural]) '?))
                     cenv)
             acc)]
      
      [(InstallValue? exp)
       (loop (InstallValue-body exp) cenv acc)]
      
      [(BoxEnv? exp)
       (loop (BoxEnv-body exp) cenv acc)]
      
      [(LetRec? exp)
       (let ([n (length (LetRec-procs exp))])
         (let ([new-cenv (append (map (lambda (p #;[p : Lam]) 
                                        (extract-static-knowledge 
                                         p 
                                         (append (build-list (length (LetRec-procs exp))
                                                             (lambda (i #;[i : Natural]) '?))
                                                 (drop cenv n))))
                                      (LetRec-procs exp))
                                 (drop cenv n))])
           (loop (LetRec-body exp) new-cenv acc)))]
      
      [(WithContMark? exp)
       (define acc-1 (loop (WithContMark-key exp) cenv acc))
       (define acc-2 (loop (WithContMark-value exp) cenv acc-1))
       (define acc-3 (loop (WithContMark-body exp) cenv acc-2))
       acc-3]
      
      [(ApplyValues? exp)
       (define acc-1 (loop (ApplyValues-proc exp) cenv acc))
       (define acc-2 (loop (ApplyValues-args-expr exp) cenv acc-1))
       acc-2]
      
      [(DefValues? exp)
       (loop (DefValues-rhs exp) cenv acc)]
      
      [(PrimitiveKernelValue? exp)
       acc]
      
      [(VariableReference? exp)
       (loop (VariableReference-toplevel exp) cenv acc)]
      
      [(Require? exp)
       acc])))





(: extract-static-knowledge (Expression CompileTimeEnvironment ->  
                                        CompileTimeEnvironmentEntry))
;; Statically determines what we know about the expression, given the compile time environment.
;; We should do more here eventually, including things like type inference or flow analysis, so that
;; we can generate better code.
(define (extract-static-knowledge exp cenv)
  (cond
    [(Lam? exp)
     ;(log-debug "known to be a lambda")
     (make-StaticallyKnownLam (Lam-name exp)
                              (Lam-entry-label exp)
                              (if (Lam-rest? exp)
                                  (make-ArityAtLeast (Lam-num-parameters exp))
                                  (Lam-num-parameters exp)))]
    [(and (LocalRef? exp) 
          (not (LocalRef-unbox? exp)))
     (let ([entry (list-ref cenv (LocalRef-depth exp))])
       ;(log-debug (format "known to be ~s" entry))
       entry)]
    
    [(EmptyClosureReference? exp)
     (make-StaticallyKnownLam (EmptyClosureReference-name exp)
                              (EmptyClosureReference-entry-label exp)
                              (if (EmptyClosureReference-rest? exp)
                                  (make-ArityAtLeast (EmptyClosureReference-num-parameters exp))
                                  (EmptyClosureReference-num-parameters exp)))]
    [(ToplevelRef? exp)
     ;(log-debug (format "toplevel reference of ~a" exp))
     ;(when (ToplevelRef-constant? exp)
     ;  (log-debug (format "toplevel reference ~a should be known constant" exp)))
     (let ([name ; : (U Symbol False GlobalBucket ModuleVariable)
            (list-ref (Prefix-names (ensure-prefix (list-ref cenv (ToplevelRef-depth exp))))
                      (ToplevelRef-pos exp))])
       (cond
         [(ModuleVariable? name)
          ;(log-debug (format "toplevel reference is to ~s" name))
          name]
         [(GlobalBucket? name)
          '?]
         [else
          ;(log-debug (format "nothing statically known about ~s" exp))
          '?]))]
    
    [(Constant? exp)
     (make-Const (ensure-const-value (Constant-v exp)))]
    
    [(PrimitiveKernelValue? exp)
     exp]
    
    [else
     ;(log-debug (format "nothing statically known about ~s" exp))
     '?]))









(: collect-all-lambdas-with-bodies (Expression -> (Listof lam+cenv)))
;; Finds all the lambdas in the expression.
(define (collect-all-lambdas-with-bodies exp)
  (let loop      ; : (Listof lam+cenv)
    ([exp  exp]  ; : Expression
     [cenv '()]) ; : CompileTimeEnvironment
    
    (cond
      [(Top? exp)
       (loop (Top-code exp) (cons (Top-prefix exp) cenv))]
      [(Module? exp)
       (loop (Module-code exp) (cons (Module-prefix exp) cenv))]
      [(Constant? exp)
       '()]
      [(LocalRef? exp)
       '()]
      [(ToplevelRef? exp)
       '()]
      [(ToplevelSet? exp)
       (loop (ToplevelSet-value exp) cenv)]
      [(Branch? exp)
       (append (loop (Branch-predicate exp) cenv)
               (loop (Branch-consequent exp) cenv)
               (loop (Branch-alternative exp) cenv))]
      [(Lam? exp)
       (cons (make-lam+cenv exp (extract-lambda-cenv exp cenv))
             (loop (Lam-body exp) 
                   (extract-lambda-cenv exp cenv)))]
      [(CaseLam? exp)
       (cons (make-lam+cenv exp cenv)
             (apply append (map (lambda (lam #;[lam : (U Lam EmptyClosureReference)])
                                  (loop lam cenv))
                                (CaseLam-clauses exp))))]
      
      [(EmptyClosureReference? exp)
       '()]
      
      [(Seq? exp)
       (apply append (map (lambda (e #;[e : Expression]) (loop e cenv))
                          (Seq-actions exp)))]
      [(Splice? exp)
       (apply append (map (lambda (e #;[e : Expression]) (loop e cenv))
                          (Splice-actions exp)))]
      [(Begin0? exp)
       (apply append (map (lambda (e #;[e : Expression]) (loop e cenv))
                          (Begin0-actions exp)))]
      [(App? exp)
       (let ([new-cenv (append (build-list (length (App-operands exp)) (lambda (i #;[i : Natural]) '?))
                               cenv)])
         (append (loop (App-operator exp) new-cenv)
                 (apply append (map (lambda (e #;[e : Expression]) (loop e new-cenv)) (App-operands exp)))))]
      [(Let1? exp)
       (append (loop (Let1-rhs exp)
                     (cons '? cenv))
               (loop (Let1-body exp) 
                     (cons (extract-static-knowledge (Let1-rhs exp) (cons '? cenv)) 
                           cenv)))]
      [(LetVoid? exp)
       (loop (LetVoid-body exp) 
             (append (build-list (LetVoid-count exp) (lambda (i #;[i : Natural]) '?))
                     cenv))]
      [(InstallValue? exp)
       (loop (InstallValue-body exp) cenv)]
      [(BoxEnv? exp)
       (loop (BoxEnv-body exp) cenv)]
      [(LetRec? exp)
       (let ([n (length (LetRec-procs exp))])
         (let ([new-cenv (append (map (lambda (p #;[p : Lam]) 
                                        (extract-static-knowledge 
                                         p 
                                         (append (build-list (length (LetRec-procs exp))
                                                             (lambda (i #;[i : Natural]) '?))
                                                 (drop cenv n))))
                                      (LetRec-procs exp))
                                 (drop cenv n))])
           (append (apply append 
                          (map (lambda (lam #;[lam : Lam])
                                 (loop lam new-cenv))
                               (LetRec-procs exp)))
                   (loop (LetRec-body exp) new-cenv))))]
      [(WithContMark? exp)
       (append (loop (WithContMark-key exp) cenv)
               (loop (WithContMark-value exp) cenv)
               (loop (WithContMark-body exp) cenv))]
      [(ApplyValues? exp)
       (append (loop (ApplyValues-proc exp) cenv)
               (loop (ApplyValues-args-expr exp) cenv))]
      [(DefValues? exp)
       (append (loop (DefValues-rhs exp) cenv))]
      [(PrimitiveKernelValue? exp)
       '()]
      [(VariableReference? exp)
       (loop (VariableReference-toplevel exp) cenv)]
      [(Require? exp)
       '()]
      [else (error 'here (list exp cenv))]
      )))



(: extract-lambda-cenv (Lam CompileTimeEnvironment -> CompileTimeEnvironment))
;; Given a Lam and the ambient environment, produces the compile time environment for the
;; body of the lambda.
(define (extract-lambda-cenv lam cenv)
  (append (map (lambda (d #;[d : Natural])
                 (list-ref cenv d))
               (Lam-closure-map lam))
          (build-list (if (Lam-rest? lam)
                          (add1 (Lam-num-parameters lam))
                          (Lam-num-parameters lam))
                      (lambda (i #;[i : Natural]) '?))))










(: ensure-prefix (CompileTimeEnvironmentEntry -> Prefix))
(define (ensure-prefix x)
  (if (Prefix? x)
      x
      (error 'ensure-prefix "Not a prefix: ~s" x)))
