; Stuff from MCE

(define (expression-type exp)
  (cond ((null? exp) 'null)
	((number? exp) 'number)
	((boolean? exp) 'boolean)
	((string? exp) 'string)
	;((vector? exp) 'vector)
	((variable? exp) 'variable)
	((quoted? exp) 'quote)
	((assignment? exp) 'assignment)
	((definition? exp) 'define)
	((if? exp) 'if)
	((lambda? exp) 'lambda)
	((let? exp) 'let)
	((begin? exp) 'begin)
	((cond? exp) 'cond)
	((application? exp) 'application)
	(else (error "Error: Unknown expression type --- " exp))))

(define variable? symbol?)

(define (tagged-list? exp tag)
  (if (tagged? exp)
      (eq? (type-tag exp) tag)
      #f))

(define (quoted? exp)
  (tagged-list? exp 'quote))

(define (text-of-quotation exp) 
  (cadr exp))

(define (assignment? exp)
  (tagged-list? exp 'set!))
(define (assignment-variable exp) (cadr exp))
(define (assignment-value exp) (caddr exp))

(define (desugar-define exp)
  (if (pair? (cadr exp))
      `(define ,(caadr exp) (lambda ,(cdadr exp) ,@(cddr exp)))
      exp))

(define (definition? exp)
  (tagged-list? exp 'define))

(define (definition-variable exp)
  (if (symbol? (cadr exp))
      (cadr exp)
      (caadr exp)))

(define (definition-value exp)
  (if (symbol? (cadr exp))
      (caddr exp)
      (make-lambda (cdadr exp)
		   (cddr exp))))

(define (make-lambda parameters body)
    (cons 'lambda (cons parameters body)))

(define (lambda? exp) (tagged-list? exp 'lambda))
(define (lambda-parameters exp) (cadr exp))
(define (lambda-body exp) (cddr exp))

(define (if? exp) (tagged-list? exp 'if))
(define (if-predicate exp) (cadr exp))
(define (if-consequent exp) (caddr exp))
(define (if-alternative exp)
  (if (not (null? (cdddr exp)))
      (cadddr exp)
      #f))
(define (make-if predicate consequent alternative)
    (list 'if predicate consequent alternative))

(define (begin? exp) (tagged-list? exp 'begin))
(define (begin-actions exp) (cdr exp))

(define (sequence->exp seq)
  (cond ((null? seq) seq)
        ((null? (cdr seq)) (car seq))
        (else (make-begin seq))))
(define (make-begin seq) (cons 'begin seq))

(define (application? exp) (pair? exp))
(define (operator exp) (car exp))
(define (operands exp) (cdr exp))

(define (cond? exp) (tagged-list? exp 'cond))
(define (cond-clauses exp) (cdr exp))
(define (cond-else-clause? clause)
  (eq? (cond-predicate clause) 'else))
(define (cond-predicate clause) (car clause))
(define (cond-actions clause) (cdr clause))
(define (cond->if exp)
  (expand-clauses (cond-clauses exp)))

(define (expand-clauses clauses)
  (if (null? clauses)
      'false                          ; no else clause
      (let ((first (car clauses))
            (rest (cdr clauses)))
        (if (cond-else-clause? first)
            (if (null? rest)
                (sequence->exp (cond-actions first))
                (error "ELSE clause isn't last -- COND->IF"
                       clauses))
            (make-if (cond-predicate first)
                     (sequence->exp (cond-actions first))
                     (expand-clauses rest))))))

(define (let? exp) (tagged-list? exp 'let))
(define (let-body exp) (cddr exp))
(define (let-parameters exp) (map car (cadr exp)))
(define (let-arguments exp) (map cadr (cadr exp)))

(define (make-frame variables values) (cons variables values))
(define frame-variables car)
(define frame-values cdr)

(define (add-binding-to-frame! var val frame)
  (set-car! frame (cons var (car frame)))
  (set-cdr! frame (cons val (cdr frame))))

(define first-frame car)
(define enclosing-environment cdr)

(define (extend-environment vars vals base-env)
  (if (= (length vars) (length vals))
      (cons (make-frame vars vals) base-env)
      (if (< (length vars) (length vals))
          (error "Too many arguments supplied" vars vals)
          (error "Too few arguments supplied" vars vals))))

(define (lookup-variable-value var env)
  (define (env-loop env)
    (define (scan vars vals)
      (cond ((null? vars)
	     (env-loop (enclosing-environment env)))
	    ((eq? var (car vars))
	     (car vals))
	    (else (scan (cdr vars) (cdr vals)))))
    (if (eq? env the-empty-environment)
	(error "Unbound variable" var)
	(let ((frame (first-frame env)))
	  (scan (frame-variables frame)
		(frame-values frame)))))
  (env-loop env))

(define (set-variable-value! var val env)
  (define (env-loop env)
    (define (scan vars vals)
      (cond ((null? vars)
	     (env-loop (enclosing-environment env)))
	    ((eq? var (car vars))
	     (set-car! vals val))
	    (else (scan (cdr vars) (cdr vals)))))
    (if (eq? env the-empty-environment)
	(error "Unbound variable -- SET!" var)
	(let ((frame (first-frame env)))
	  (scan (frame-variables frame)
		(frame-values frame)))))
  (env-loop env))

(define (define-variable-in-frame! var val frame)
  (define (scan vars vals)
    (cond ((null? vars)
	   (add-binding-to-frame! var val frame))
	  ((eq? var (car vars))
	   (set-car! vals val))
	  (else (scan (cdr vars) (cdr vals)))))
  (scan (frame-variables frame)
	(frame-values frame)))

(define (define-variable! var val env)
  (define-variable-in-frame! var val (first-frame env)))

; Looks up the type of var in the given frame
; Returns #f if there is no binding
; Note that this is the only way #f can be returned, since #f is not a type.
(define (lookup-in-frame var frame)
  (define (scan vars vals)
    (cond ((null? vars)
	   #f)
	  ((eq? var (car vars))
	   (car vals))
	  (else (scan (cdr vars) (cdr vals)))))
  (scan (frame-variables frame)
	(frame-values frame)))

(define the-empty-environment '())
(define the-global-environment
  (extend-environment '() '() the-empty-environment))
