#lang eopl

(require "cps-out-lang.rkt")          ; for tfexp?
(provide (all-defined-out))           ; too many things to list

;;;;;;;;;;;;;;;; expressed values ;;;;;;;;;;;;;;;;

;;; an expressed value is either a number, a boolean or a procval.

(define-datatype expval expval?
  (num-val
   (value number?))
  (bool-val
   (boolean boolean?))
  (proc-val 
   (proc proc?)))

;;; extractors:

(define expval->num
  (lambda (v)
    (cases expval v
      (num-val (num) num)
      (else (expval-extractor-error 'num v)))))

(define expval->bool
  (lambda (v)
    (cases expval v
      (bool-val (bool) bool)
      (else (expval-extractor-error 'bool v)))))

(define expval->proc
  (lambda (v)
    (cases expval v
      (proc-val (proc) proc)
      (else (expval-extractor-error 'proc v)))))

(define expval-extractor-error
  (lambda (variant value)
    (eopl:error 'expval-extractors "Looking for a ~s, found ~s"
                variant value)))

;;;;;;;;;;;;;;;; continuations ;;;;;;;;;;;;;;;;

;; the interpreter is tail-recursive, so it really doesn't do
;; anything with the continuation.  So all we need is one
;; continuation value.

(define-datatype continuation continuation?
  (end-cont)                     
  )

;;;;;;;;;;;;;;;; procedures ;;;;;;;;;;;;;;;;

(define-datatype proc proc?
  (procedure
   (vars (list-of symbol?))
   (body tfexp?)
   (env environment?)))

;;;;;;;;;;;;;;;; environment structures ;;;;;;;;;;;;;;;;

;;; represent environment as a list of bindings.
;;; binding ::= ('let    (list-of id) (list-of expval)) 
;;;           | ('letrec (list-of id) (list-of bvar) (list-of expression))

;;; The first binding for extend-env*, the second is for
;;; extend-env-rec**. 

;;; this representation is designed to make the printed representation
;;; of the environment more readable.

(define empty-env
  (lambda ()
    '()))

(define empty-env? 
  (lambda (x) (null? x)))

(define extend-env*
  (lambda (syms vals old-env)
    (cons (list 'let syms vals) old-env)))

(define extend-env-rec**
  (lambda (p-names b-varss p-bodies saved-env)
    (cons 
     (list 'letrec p-names b-varss p-bodies)
     saved-env)))

(define apply-env
  (lambda (env search-sym)
    (if (null? env) 
        (eopl:error 'apply-env "No binding for ~s" search-sym)
        (let* ((binding (car env))
               (saved-env (cdr env)))
          (let ((pos (list-index search-sym (cadr binding))))
            (if pos
                (case (car binding)
                  ((let)
                   (list-ref (caddr binding) pos))
                  ((letrec)
                   (let ((bvars (caddr binding))
                         (bodies (cadddr binding)))
                     (proc-val
                      (procedure
                       (list-ref bvars pos)
                       (list-ref bodies pos)
                       env)))))
                (apply-env saved-env search-sym)))))))

;; returns position of sym in los, else #f
(define list-index
  (lambda (sym los)
    (let loop ((pos 0) (los los))
      ;; los is at position pos of the original los
      (cond
        ((null? los) #f)
        ((eqv? sym (car los)) pos)
        (else (loop (+ pos 1) (cdr los)))))))

;; not precise, but will do.
(define environment?
  (list-of
   (lambda (p)
     (and 
      (pair? p)
      (or (eqv? (car p) 'let) (eqv? (car p) 'letrec))))))


;;;;;;;;;;;;;;;; initial environment ;;;;;;;;;;;;;;;;

;; init-env : () -> environment

;; (init-env) builds an environment in which i is bound to the
;; expressed value 1, v is bound to the expressed value 5, and x is
;; bound to the expressed value 10.  

(define init-env 
  (let ((extend-env1
         (lambda (sym val env)
           (extend-env* (list sym) (list val) env))))    
    (lambda ()
      (extend-env1
       'i (num-val 1)
       (extend-env1
        'v (num-val 5)
        (extend-env1
         'x (num-val 10)
         (empty-env)))))))

;; exercise:  Improve this code by getting rid of extend-env1.
