;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; 6.821 Problem Set #4
;;;
;;;
;;; FLEX and FLAT interpreters:
;;; FLEX = (CBV FL) - recursion
;;; FLAT = (CBV FL) - recursion - (free vars in procedures) + tuples
;;;
;;; In both languages, all primitive operators are accessed as 
;;; primops, e.g., (primop + ...), (primop left ...), etc.
;;;
;;; Author: Brian
;;; Created: 10/1/94
;;; Adapted from Lyn's ps4.fx (1992) and fl-naming.scm (1994)
;;; Revisions:
;;;	10/4: Fixed left and right primops.
;;;	10/7: Changed parse-common to use parse in all cases.
;;;	10/11: Added symbol to keyword list.
;;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;; PROBLEM SET CODE begins with DATATYPES: 
;;;	ignore the initial patching for #u


;;;----------------------------------------------------------------------------
;;; Magic for handling unit.  This should really be in a separate file, or
;;; be part of Scheme+.

(define-structure (unit-obj (print-procedure 
			     (lambda (state struct) 
			       (unparse-string state "#u")))))

;; THE-UNIT is the unique instance of the UNIT-OBJ structure
(define the-unit (make-unit-obj))

(define (unit? obj) (eq? obj the-unit))

;;; Changing parser to handle #u
(define parse-object/unit
  (let ((discard-char (access discard-char 
			      (->environment (find-package '(runtime parser))))))
    (lambda ()
      (discard-char)
      the-unit)))

(parser-table/set-entry! system-global-parser-table
			 '("#u" "#U")
			 parse-object/unit)

;;; Constructors for handling UNIT

(define unit->sexp  
  (make-constructor
   (lambda () #u)
   (lambda (sexp succ fail)
     (if (unit? sexp)
	 (succ)
	 (fail)))))

(define a-unit unit->sexp)


;;;----------------------------------------------------------------------------
;;; SYM-SEXP is a synonym for SYMBOL->SEXP (and should replace it in 
;;; future versions of Scheme+)

(define sym->sexp (make-sexp-constructor 'sym symbol?))




;;;----------------------------------------------------------------------------
;;; DATATYPES

(define-datatype exp
  ($lit exp)
  ($var-ref sym)
  ($proc sym exp)	; In FLAT, sym can be only free var in exp
  ($call exp exp)
  ($if exp exp exp)
  ($let (listof sym) (listof exp) exp)	; FLAT only
  ($pair exp exp)
  ($primop primitive (listof exp))
  ;; Tuples
  ($tuple (listof exp))			; FLAT only
  ($tuple-ref exp int)			; FLAT only
  ($tuple? exp)				; FLAT only
  ($tuple-length exp)			; FLAT only
  ($tuple-append exp exp)		; FLAT only
  ;; Top-level FLAT program -- only used by the LIFTer
  ($program-flat (listof sym) (listof exp) exp)
  )

;;; NOTE: LET expressions are represented by 
;;;   * a list of the indentifiers
;;;   * a list of the expressions for those identifiers
;;;   * a body expression
;;; A LET expression could also have been represented by 
;;;   * a list of bindings that contain both an identifier and an expression
;;;   * a body expression
;;; 

; Expressible Values
(define-datatype exp-val
  (val->exp-val val)
  (error->exp-val string)
  )

(define-datatype value
  (unit->val)
  (int->val  int)
  (bool->val bool)
  (sym->val  sym)
  (procedure->val (-> (value) exp-val))
  (pair->val value value)
  (tuple->val     (listof value))	; FLAT only 
  )



;;;----------------------------------------------------------------------------
;;; EVALUATOR

;;; Curried evaluator performs all dispatches on syntactic types first.
;;; Eval: exp -> env -> exp-val

;;; In this implementation, FLEX-EVAL and FLAT-EVAL are the same.
;;; However, FLEX-EVAL should be given an EXP parsed by FLEX-PARSE
;;; and FLAT-EVAL should be given an EXP parsed by FLAT-PARSE.

(define (flex-eval exp) (eval-exp exp))
(define (flat-eval exp) (eval-exp exp))

(define (flex-eval-empty exp) ((flex-eval exp) the-empty-environment))
(define (flat-eval-empty exp) ((flat-eval exp) the-empty-environment))

(define (eval-exp exp)
  (match exp 
    (($lit xval)            (eval-literal xval))
    (($var-ref v)           (eval-var-ref v))
    (($proc formal body)    (eval-proc formal body))
    (($call rator rand)     (eval-call rator rand))
    (($if test then else)   (eval-if test then else))
    (($let names exps body) (eval-let names exps body))
    (($pair left right)     (eval-pair left right))
    (($primop prim args)    (eval-primop prim args))
    (($tuple exps)          (eval-tuple exps))
    (($tuple-ref exp index) (eval-tuple-ref exp index))
    (($tuple? exp)	    (eval-tuple? exp))
    (($tuple-length exp)    (eval-tuple-length exp))
    (($tuple-append exp1 exp2)       (eval-tuple-append exp1 exp2))
    (($program-flat names exps body) (eval-program names exps body))
    (_ (error 
	(string-append
	 "FLEX/FLAT-EVAL doesn't know how to handle:\n"
	 (write-sexp-to-string (flat-unparse exp)))))
    ))

(define (eval-literal exp-val)
  (lambda (env) exp-val))

(define (eval-var-ref v)
  (lambda (env) (lookup v env)))

(define (eval-proc formal body)
  (let ((body-meaning (eval-exp body)))
    (lambda (env)
      (val->exp-val
       (procedure->val 
	(lambda (val) 
	  (body-meaning (extend-env formal val env))))))))

(define (eval-call rator rand)
  (let ((rator-meaning (eval-exp rator))
	(rand-meaning  (eval-exp rand)))
    (lambda (env)
      (with-procedure (rator-meaning env)
	(lambda (p) 
	  (with-value (rand-meaning env) 
	    p))))))

(define (eval-if test then else)
  (let ((test-meaning (eval-exp test))
	(then-meaning (eval-exp then))
	(else-meaning (eval-exp else)))
    (lambda (env)
      (with-boolean (test-meaning env)
	(lambda (b) 
	  (if b (then-meaning env) (else-meaning env)))))))

(define (eval-let names exps body)
  (let ((exp-meanings (map eval-exp exps))
	(body-meaning (eval-exp body)))
    (lambda (env)
      (with-values (map (lambda (m) (m env)) exp-meanings)
	(lambda (values)
	  (body-meaning (extend-env-by-list 
			 names
			 values
			 env)))))))

(define (eval-pair left right)
  (let ((left-meaning  (eval-exp left))
	(right-meaning (eval-exp right)))
    (lambda (env)
      (with-value (left-meaning env)
	(lambda (left)
	  (with-value (right-meaning env)
	    (lambda (right)
	      (val->exp-val
	       (pair->val left right)))))))))

(define (eval-primop prim args)
  (match prim
    ((make-primitive name n proc)
     (let ((arg-meanings (map eval-exp args)))
       (lambda (env)
	 (with-values (map (lambda (m) (m env)) arg-meanings)
	   (lambda (vals)
	     (apply proc vals))))))))

(define (eval-tuple components)
  (let ((component-meanings (map eval-exp components)))
    (lambda (env)
      (with-values (map (lambda (m) (m env)) component-meanings)
	(lambda (values)
	  (val->exp-val (tuple->val values)))))))

(define (eval-tuple-ref tuple-exp index)
  (let ((tuple-meaning (eval-exp tuple-exp)))
    (lambda (env)
      (with-tuple (tuple-meaning env)
	(lambda (elts)
	  (if (and (>= index 0)
		   (< index (length elts)))
	      (val->exp-val (list-ref elts index))
	      (error->exp-val "TUPLE-REF: Index out of range")))))))

(define (eval-tuple? exp)
  (let ((tuple-meaning (eval-exp exp)))
    (lambda (env)
      (with-value (tuple-meaning env)
	(lambda (v) 
	  (val->exp-val
	   (bool->val
	    (match v
	      ((tuple->val _) #t)
	      (_ #f)))))))))

(define (eval-tuple-length exp)
  (let ((tuple-meaning (eval-exp exp)))
    (lambda (env)
      (with-tuple (tuple-meaning env)
	(lambda (elts) 
	  (val->exp-val
	   (int->val (length elts))))))))

(define (eval-tuple-append tuple-exp1 tuple-exp2)
  (let ((tuple1-meaning (eval-exp tuple-exp1))
	(tuple2-meaning (eval-exp tuple-exp2)))
    (lambda (env)
      (with-tuple (tuple1-meaning env)
	(lambda (elts1)
	  (with-tuple (tuple2-meaning env)
	    (lambda (elts2)
	      (val->exp-val
	       (tuple->val (append elts1 elts2))))))))))

(define (eval-program names exps body)
  (let ((exp-meanings (map eval-exp exps))
	(body-meaning (eval-exp body)))
    (lambda (env)
      (letrec ((new-env
		(lambda (var)
		  (letrec ((loop 
			    (lambda (vs ms)
			      (cond 
			       ((null? vs) (lookup var env))
			       ((same-var? var (car vs))
				((car ms) new-env))
			       (else (loop (cdr vs) (cdr ms)))))))
		    (loop names exp-meanings)))))
	;; Ensure that they are all defined
	(with-values (map (lambda (a-meaning) (a-meaning new-env))
			  exp-meanings)
	  (lambda (ignore)
	    (body-meaning new-env)
	    ))))))


;;;----------------------------------------------------------------------------
;;; ENVIRONMENTS

;;; This injects the output so that normal and unbound lookups both give 
;;; EXP-VALUEs 
(define extend-env 
  (lambda (var1 value env)
    (lambda (var2) 
      (if (same-var? var1 var2)
	  (val->exp-val value)
          (lookup var2 env)))))

(define lookup
  (lambda (var env) (env var)))

(define the-empty-environment
  (lambda (var) (error->exp-val
                 (string-append "Unbound variable: " 
                                (symbol->string var)))))

(define same-var? eq?) 

(define (extend-env-by-list vars vals env)
  (if (null? vars)
      env
      (extend-env (car vars) (car vals) 
		  (extend-env-by-list (cdr vars) (cdr vals) env))))



;;;----------------------------------------------------------------------------
;;; Auxiliary procedures

;; with-value: (-> (exp-val (-> (value) exp-val)) exp-val)
(define (with-value exp-val return)
  (match exp-val
    ((val->exp-val val) (return val))
    ((error->exp-val _) exp-val)
    ))

;; NOTE: This conflicts with Scheme's WITH-VALUES!
;;
;; with-values: (-> (exp-vals (-> (values) exp-val)) exp-val)
(define (with-values exp-vals return)
  (if (null? exp-vals)
      (return '())
      (with-value (car exp-vals)
        (lambda (val)
          (with-values (cdr exp-vals)
	    (lambda (vals)
	      (return (cons val vals))))))))

;; with-integer: (-> (exp-val (-> (int) exp-val)) exp-val)
(define (with-integer exp-val return)
  (with-value exp-val
    (lambda (val)
      (match val
        ((int->val n) (return n))
        (_  (error-with-val 
	     "Non-integer occurs in position where a integer is expected: "
	     val))
	))))

;; with-boolean: (-> (exp-val (-> (bool) exp-val)) exp-val)
(define (with-boolean exp-val return)
  (with-value exp-val
    (lambda (val)
      (match val
        ((bool->val n) (return n))
        (_  (error-with-val 
	     "Non-integer occurs in position where a integer is expected: "
	     val))
	))))

;; with-procedure: (-> (exp-val (-> (procedure) exp-val)) exp-val)
(define (with-procedure exp-val return)
  (with-value exp-val
    (lambda (val)
      (match val
        ((procedure->val p) (return p))
        (_  (error-with-val 
	     "Non-procedure occurs in position where a procedure is expected: "
	     val))
	))))

;; with-tuple: 
;; 	(-> (exp-val (-> ((listof exp-val)) exp-val)) exp-val)
(define (with-tuple exp-val return)
  (with-value exp-val
    (lambda (val)
      (match val
	((tuple->val exps) (return exps))
	(_ (error-with-val
	    "Non-tuple occurs where a tuple is expected: "
	    val))
	))))

;;;----------------------------------------------------------------------------
;;; Primitives: stored in a table to use for looking up primops and 
;;;	building initial flk environment.  

(define-datatype primitive
  (make-primitive sym n-args proc))

(define *flk-primitives-table* '())

(define (add-prim! primitive)
  (set! *flk-primitives-table*
	(cons primitive *flk-primitives-table*))
  unspecific)

(define (lookup-primop sym succ fail)
  (let loop ((lib *flk-primitives-table*))
    (if (null? lib)
	(fail)
	(match (car lib)
	  ((make-primitive prim-name _ _) 
	   (if (eq? sym prim-name)
	       (succ (car lib))
	       (loop (cdr lib))))
	  (_ (loop (cdr lib)))))))


(define (define-general-primitive sym nargs proc)
  (add-prim! (make-primitive sym nargs proc)))

(define (define-typed-primitive sym scheme-proc arg-list return-construtor)
  (define-general-primitive 
   sym
   (length arg-list)
   (add-types sym scheme-proc arg-list return-construtor)))

(define (define-predicate sym obj->value)
  (define-general-primitive
   sym 1
   (lambda (val)
     (val->exp-val
      (bool->val 
       (match val
	 ((obj->value _) #t)
	 (_ #f)))))))

(define (define-logical-primitive sym n-args scheme-proc)
  (define-general-primitive
   sym n-args
   (add-types sym
	      scheme-proc
	      (make-list n-args bool->val)
	      bool->val)))

(define (define-arithop-primitive sym scheme-proc)
  (define-general-primitive
   sym 2
   (add-types sym scheme-proc (list int->val int->val) int->val)))

(define (define-arithop-error-at-0 sym scheme-proc)
  (define-general-primitive
    sym 2
    (add-type-checks sym
		     (lambda (x y)
		       (if (= y 0)
			   'divide-by-zero-error
			   (scheme-proc x y)))
		     (list int->val int->val)
		     (lambda (result)
		       (if (eq? result 'divide-by-zero-error)
			   (error->exp-val "Divide by zero")
			   (val->exp-val (int->val result)))))))

(define (define-arithop-relate sym scheme-proc)
  (define-general-primitive
    sym 2
    (add-types sym scheme-proc (list int->val int->val) bool->val)))


(define (add-types sym scheme-proc arg-list result->val)
  (add-type-checks sym
		   scheme-proc
		   arg-list
		   (lambda (x) (val->exp-val (result->val x)))))

(define (add-type-checks prim-name scheme-proc arg-types result->exp-val)
  (lambda arg-vals
    (define (check-types types vals return)
      (if (null? types)	      ;;; Assume NARGS-checking is done by EVAL-PRIMOP
	  (return '())
	  (match (car vals)
	    (((car types) fst)		;; Types is a list of constructors
	     (check-types (cdr types) 
                          (cdr vals)
			  (lambda (rest)
			    (return (cons fst rest)))))
	    (_ (error->exp-val
		(string-append "Type error in application of primitive: "
			       (symbol->string prim-name)))))))
    (check-types arg-types 
		 arg-vals 
		 (lambda (untagged-args)
		   (result->exp-val (apply scheme-proc untagged-args))))))


;;;----------------------------------------------------------------------------
;;; Primitive Handlers

(define (fl/unit? val)
  (val->exp-val
   (bool->val
    (match val
      ((unit->val) #t)
      (_ #f)))))

(define (fl/pair? val)
  (val->exp-val
   (bool->val
    (match val
      ((pair->val _ _) #t)
      (_ #f)))))

(define (fl/pair-selector op)
  (lambda (val)
    (match val
      ;; left and right are values, so inject 
      ((pair->val left right) (val->exp-val (op left right)))
      (_ (error-with-val "pair selector applied to non-pair" exp-val)))))
     
(define fl/left  (fl/pair-selector (lambda (left right) left)))
(define fl/right (fl/pair-selector (lambda (left right) right)))


;;;----------------------------------------------------------------------------
;;; Put primitives in the table

;; Predicates
(define-general-primitive 'unit? 1 fl/unit?)
(define-predicate 'boolean?   bool->val)
(define-predicate 'integer?   int->val)
(define-predicate 'symbol?    sym->val)
(define-predicate 'procedure? procedure->val)
(define-general-primitive 'pair? 1 fl/pair?)

;; Logical Primitives
(define-logical-primitive 'not?   1 not)
(define-logical-primitive 'and?   2 (lambda (x y) (and x y)))
(define-logical-primitive 'or?    2 (lambda (x y) (or  x y)))
(define-logical-primitive 'bool=? 2 (lambda (x y) (if x y (not y))))

;; Arithmetic Primitives
(define-arithop-primitive  '+   +)
(define-arithop-primitive  '-   -)
(define-arithop-primitive  '*   *)
(define-arithop-error-at-0 '/   quotient)
(define-arithop-error-at-0 'rem remainder)

;; Arithmetic Relations
(define-arithop-relate '=  =)
(define-arithop-relate '/= (lambda (x y) (not (= x y))))
(define-arithop-relate '<  <)
(define-arithop-relate '<= <=)
(define-arithop-relate '>  >)
(define-arithop-relate '>= >=)

;; Symbols
(define-typed-primitive 'sym=? eq? (list sym->val sym->val) bool->val)

;; Pairs
(define-general-primitive 'left  1 fl/left)
(define-general-primitive 'right 1 fl/right)



;;;----------------------------------------------------------------------------
;;; SYMBOL SETS

(define the-empty-set '())

(define set-empty? null?)

(define (set->list set) set)

(define (list->set lst) lst)

(define set-member? 
  (lambda (elt set)
    (cond ((null? set) #f)
	  ((eq? elt (car set)) #t)
	  (else (set-member? elt (cdr set))))))

(define set-adjoin 
  (lambda (elt set)
    (if (set-member? elt set)
	set
	(cons elt set))))

(define set-choose car)

(define set-rest cdr)

(define set-singleton (lambda (elt) (list elt)))

(define set-union 
  (lambda (s1 s2)
    (cond ((set-empty? s1) s2)
	  ((set-member? (set-choose s1) s2)
	   (set-union (set-rest s1) s2))
	  (else (set-adjoin (set-choose s1)
			    (set-union (set-rest s1) s2))))))

(define set-intersection
  (lambda (s1 s2)
    (cond ((set-empty? s1) the-empty-set)
	  ((set-member? (set-choose s1) s2)
	   (set-adjoin (set-choose s1)
		       (set-intersection (set-rest s1) s2)))
	  (else (set-intersection (set-rest s1) s2)))))

(define set-difference 
  (lambda (s1 s2)
    (cond ((set-empty? s1) the-empty-set)
	  ((set-member? (set-choose s1) s2)
	   (set-difference (set-rest s1) s2))
	  (else (set-adjoin (set-choose s1)
			    (set-difference (set-rest s1) s2))))))

(define mapunion 
  (lambda (proc lst)
    (if (null? lst)
	'()
	(set-union (proc (car lst))
		   (mapunion proc (cdr lst))))))

(define (set-subset? s1 s2)
  (every? (lambda (elt) (set-member? elt s2))
	  (set->list s1)))

;;;----------------------------------------------------------------------------
;;; PARSING

(define (parse-common parse sexp language-string)
  (match sexp
    ((unit->sexp)                      ($lit (val->exp-val (unit->val))))
    ((bool->sexp b)                    ($lit (val->exp-val (bool->val b))))
    ((int->sexp i)                     ($lit (val->exp-val (int->val i))))
    (`(SYMBOL ,(sym->sexp s))          ($lit (val->exp-val (sym->val s))))
    ((sym->sexp s)                     ($var-ref s))
    (`(PROC ,(sym->sexp formal) ,body) ($proc formal (parse body)))
    (`(CALL ,rator ,rand)              ($call (parse rator) (parse rand)))
    (`(IF ,test ,then ,else)           ($if (parse test) 
					    (parse then)
					    (parse else)))
    (`(PAIR ,left ,right)              ($pair (parse left) (parse right)))
    (`(PRIMOP ,(sym->sexp op) ,@args)  (parse-primop op args parse))
    (_ (error (string-append "PARSE: Unknown " language-string " expression!")
	      sexp))
    ))

(define (parse-primop op args parser)
  (lookup-primop op
    (lambda (prim)
      (match prim
	((make-primitive name n proc)
	 (if (= n (length args))
	     ($primop prim (map parser args))
	     (error "PARSE: Primop applied to wrong number of arguments: " 
		    (list op args))))))
    (lambda () (error "PARSE: Unknown primop!" op))))

(define (flex-parse sexp)
  (if (flex-sugar? sexp)
      (flex-parse (flex-desugar sexp))
      (parse-common flex-parse sexp "FLEX")))

(define (flat-parse-program sexp)
  (match sexp
    (`(PROGRAM ,(list->sexp bindings) ,body)
     ;; Bindings are mutually recursive and at top-level so okay if they
     ;; have free variables.
     (let ((old-flag check-free-variables?))
       (set! check-free-variables? #f)
       (let ((bound-expressions
	      (map (compose flat-parse binding->val) bindings)))
	        (set! check-free-variables? old-flag)
		($program-flat (map binding->var bindings)
			       bound-expressions
			       (flat-parse body)))))
    (_ (flat-parse sexp))))

(define (flat-parse sexp)
  (if (flat-sugar? sexp)
      (flat-parse (flat-desugar sexp))
      (match sexp
	;; Check free-variables restriction
	(`(PROC ,(sym->sexp formal) ,body) (flat-parse-proc formal body))
	;; Let is primitive in FLAT
	(`(LET ,bindings ,body)            (flat-parse-let bindings body))
	;; Tuples
	(`(TUPLE ,@components)             ($tuple (map flat-parse components)))
	(`(TUPLE-REF ,tuple ,(int->sexp index))
	 ($tuple-ref (flat-parse tuple) index))
	(`(TUPLE? ,exp)                    ($tuple? (flat-parse exp)))
	(`(TUPLE-LENGTH ,exp)              ($tuple-length (flat-parse exp)))
	(`(TUPLE-APPEND ,exp1 ,exp2)       ($tuple-append (flat-parse exp1)
							  (flat-parse exp2)))
	(_ (parse-common flat-parse sexp "FLAT"))
	)))

(define (flat-parse-proc formal body)
  ;; Embed restriction check for abstractions in parser
  (let ((abst ($proc formal (flat-parse body))))
    (if (or (flat? abst) (not check-free-variables?))
	abst
	(error 
	 (string-append
	  "FLAT-PARSE: Not a legal FLAT abstraction\n(contains the free variables "
	  (string-append
	   (with-output-to-string
	     (lambda () (display (list->sexp (set->list (free-vars abst))))))
	   (string-append
	    "):\n"
	    (with-output-to-string
	      (lambda () (display (flat-unparse abst)))))))))))

(define check-free-variables? #t)

(define (flat-parse-let bindings body)
  ($let (map binding->var bindings)
	(map (compose flat-parse binding->val) bindings)
	(flat-parse body)))

;;;----------------------------------------------------------------------------
;;; UNPARSING

(define (make-unparser unparse language-name)
  (lambda (exp)
    (match exp
      (($lit (val->exp-val (unit->val)))   (unit->sexp))
      (($lit (val->exp-val (bool->val b))) (bool->sexp b))
      (($lit (val->exp-val (int->val i)))  (int->sexp i))
      (($lit (val->exp-val (sym->val s)))  `(symbol ,s))
      (($var-ref s)          (sym->sexp s))
      (($proc formal body)   `(PROC ,(sym->sexp formal) ,(unparse body)))
      (($call rator rand)    `(CALL ,(unparse rator) ,(unparse rand)))
      (($if test then else)  `(IF ,(unparse test) ,(unparse then) ,(unparse else)))
      (($pair left right)    (unparse-pair (unparse left) (unparse right)))
      (($primop (make-primitive name _ _) args) `(PRIMOP ,name ,@(map unparse args)))
      (_ (error
	  (string-append "UNPARSE -- unknown " language-name " expression.")))
      )))

(define (unparse-pair left right)
  (match right
    (`#u            `(list ,left))
    (`(LIST ,@elts) `(list ,left ,@elts))
    (_              `(PAIR ,left ,right))))

(define flex-unparse (make-unparser (lambda (exp) (flex-unparse exp))
				    "FLEX"))

(define flat-unparse 
  (let ((recur (make-unparser (lambda (exp) (flat-unparse exp))
			      "FLAT")))
    (lambda (exp)
      (match exp
	(($let names exps body) `(LET ,(map list names (map flat-unparse exps))
				   ,(flat-unparse body)))
	(($tuple components)       `(TUPLE ,@(map flat-unparse components)))
	(($tuple-ref tuple index)  `(TUPLE-REF ,(flat-unparse tuple) 
					       ,index))
	(($tuple? exp)             `(TUPLE? ,(flat-unparse exp)))
	(($tuple-length exp)       `(TUPLE-LENGTH ,(flat-unparse exp)))
	(($tuple-append exp1 exp2) `(TUPLE-APPEND ,(flat-unparse exp1) 
						  ,(flat-unparse exp2)))
	(($program-flat names exps body)
	 `(PROGRAM ,(map list names (map flat-unparse exps))
		   ,(flat-unparse body)))
	(_ (recur exp))))))


;;;----------------------------------------------------------------------------
;;; DESUGARING
;;;
;;; Build an environment mapping sugar keyword to a sexp-transform.

(define (flex-sugar? sexp) (sugar? sexp flex-keywords))
(define (flat-sugar? sexp)
  ;; LET is in the FLAT-kernel
  (match sexp
    (`(LET ,(a-list _) ,_) #f)
    (_ (sugar? sexp flat-keywords))))

(define flex-keywords '(proc call if pair primop symbol))
(define flat-keywords (append 
		       flex-keywords
		       '(let tuple tuple-ref tuple? tuple-length tuple-append)))

(define (flex-desugar sexp) (desugar sexp))
(define (flat-desugar sexp) (desugar sexp))

(define *sugar-keywords* '())

(define *sugar-env* 
  (lambda (keyword)
    (error "Syntax Error: unbound sugar keyword" keyword)))

(define (sugar? sexp keywords)
  (match sexp
    (`(lambda ,(a-list _) ,_) #t)	;; curried abstraction
    (`(,(a-symbol sym) ,@_) (or (memq sym *sugar-keywords*)
				(not (memq sym keywords))))
    (`(,operator ,@operands) #t)	;; application 
    (_ #f)))

(define (desugar sexp)
  ;; The standard environment is handled differently than in desugaring rules
  ((lookup (keyword sexp) *sugar-env*) sexp))

(define (keyword sexp)
  (match sexp
    (`(,(a-symbol keyword) ,@_) (if (memq keyword *sugar-keywords*)
				    keyword
				    implicit-call-tag))
    (`(,operator ,@operands)  implicit-call-tag)
    (_ (error "KEYWORD: unrecognized syntax" sexp))
    ))

(define (define-sugar keyword transformer)
  (if (null? (memq keyword *sugar-keywords*))
      (set! *sugar-keywords* (cons keyword *sugar-keywords*)))
  ;; Extend environment 
  (let ((old-env *sugar-env*))
    (set! *sugar-env* (lambda (sym)
			(if (eq? sym keyword)
			    transformer
			    (lookup sym old-env))))))

(define-sugar 'list
  (lambda (sexp)
    (match sexp
      (`(LIST) #u)
      (`(LIST ,first ,@rest)
       `(PAIR ,first (LIST ,@rest)))
      (_ (error "DESUGAR-LIST: invalid syntax" sexp)))))

(define-sugar 'quote
  (lambda (sexp)
    (match sexp
      (`(QUOTE ,item)
       (match item
	 ((bool->sexp b) item)
	 ((int->sexp  n) item)
	 ((sym->sexp s) `(SYMBOL ,s))
	 ((list->sexp lst) 
	  `(LIST ,@(map (lambda (elt) `(QUOTE ,elt)) lst)))
	 (_ (error "DESUGAR-QUOTE: invalid syntax" sexp))))
      (_ (error "DESUGAR-QUOTE: invalid syntax" sexp)))))

(define-sugar 'lambda
  (lambda (sexp)
    (match sexp
      (`(LAMBDA () ,body) 
       `(PROC ,(fresh-var) ,body))
      (`(LAMBDA (,a-formal) ,body)
       `(PROC ,a-formal ,body))
      (`(LAMBDA (,first ,@rest) ,body)
       `(PROC ,first
	      (LAMBDA (,@rest)
		,body)))
      (_ (error "DESUGAR-LAMBDA: invalid syntax" sexp)))))
		 
(define implicit-call-tag (list '*implicit-call*))

(define-sugar implicit-call-tag
  (lambda (sexp)
    (match sexp
      (`(,operator)
       `(CALL ,operator #u))
      (`(,operator ,one-arg)
       `(CALL ,operator ,one-arg))
      (`(,operator ,first-arg ,@rest)
       `((CALL ,operator ,first-arg) ,@rest))
      (_ (error "DESUGAR-IMPLICIT-CALL: invalid syntax" sexp)))))

(define-sugar 'cond
  (lambda (sexp)
    (match sexp
      (`(COND) #u)
      (`(COND (ELSE ,default))
       default)
      (`(COND (ELSE ,default) ,@rest)
       (error "DESUGAR-COND: else not last clause" sexp))
      (`(COND (,test ,consequent) ,@rest)
       `(IF ,test ,consequent (COND ,@rest)))
      (_ (error "DESUGAR-COND: invalid syntax" sexp)))))
	
(define-sugar 'and
  (lambda (sexp)
    (match sexp
      (`(AND) (bool->sexp #t))
      (`(AND ,first ,@rest)
       `(IF ,first (AND ,@rest) #f))
      (_ (error "DESUGAR-AND: invalid syntax" sexp)))))

(define-sugar 'or
  (lambda (sexp)
    (match sexp
      (`(OR) (bool->sexp #f))
      (`(OR ,first ,@rest)
       `(IF ,first #t (OR ,@rest)))
      (_ (error "DESUGAR-OR: invalid syntax" sexp)))))

;; Only in FLEX
(define-sugar 'let
  (lambda (sexp)
    (match sexp
      (`(LET (,@bindings) ,body)
       ;; Syntax of bindings enforced by binding selectors
       `((LAMBDA ,(list->sexp (map binding->var bindings)) ,body)
	 ,@(map binding->val bindings)))
      (_ (error "DESUGAR-LET: invalid syntax" sexp)))))

(define binding->var
  (lambda (sexp)
    (match sexp
      (`(,(sym->sexp var) ,_) var)
      (_ (error "BINDING->VAR: Not a binding!" sexp)))))

(define binding->val
  (lambda (sexp)
    (match sexp
      (`(,_ ,val) val)
      (_ (error "BINDING->VAL: Not a binding!" sexp)))))



;;;----------------------------------------------------------------------------
; FRESH-VAR
; Generate a new variable.

; Fresh variables are of the form `[VAR-n]', where n is the next integer
; from the counter maintained by FRESH-VAR.  The name is surrounded 
; by square brackets ---  these are illegal in FL identifiers but not 
; in FLK identifiers. (No check is performed here to ensure this 
; constraint holds on FL identifiers; but we are helped by the fact that  
; the Scheme reader doesn't recognize '[' and ']'.)

(define fresh-var
  (let ((counter 1))
    (lambda ()
      (let ((val counter))
	(set! counter (+ counter 1))
	(string->symbol (string-append "[var-" (number->string val) "]"))))))


;;;----------------------------------------------------------------------------
;;; FREE VARIABLES

(define (free-vars exp)
  (match exp
    (($lit _) the-empty-set)
    (($var-ref id) (set-singleton id))
    (($proc formal body) (set-difference (free-vars body)
					 (set-singleton formal)))
    (($call rator rand) (set-union (free-vars rator)
				   (free-vars rand)))
    (($if test consequent alternate)
     (set-union (free-vars test)
		(set-union (free-vars consequent)
			   (free-vars alternate))))
    (($primop op args) (mapunion free-vars args))
    (($pair exp1 exp2) (set-union (free-vars exp1) (free-vars exp2)))
    (($let ids exps body)
     (set-union (mapunion free-vars exps)
		(set-difference (free-vars body)
				(list->set ids))))
    (($tuple components)       (mapunion free-vars components))
    (($tuple-ref tuple _)      (free-vars tuple))
    (($tuple? exp)             (free-vars exp))
    (($tuple-length exp)       (free-vars exp))
    (($tuple-append exp1 exp2) (set-union (free-vars exp1) (free-vars exp2)))
    (($program-flat ids exps body)
     (set-difference (set-union (mapunion free-vars exps) 
				(free-vars body))
		     (list->set ids)))
    ))

;;; Checks that a FLAT expression is legal ---
;;; i.e., all abstractions are closed

(define (non-scoped? exp) (flat? exp))

(define (flat? exp)
  (match exp
    (($lit _) #t)
    (($var-ref _) #t)
    (($proc formal body)
     (and (flat? body)
	  (set-subset? (free-vars body) (set-singleton formal))))
    (($call rator rand) (and (flat? rator) (flat? rand)))
    (($if test consequent alternate) (and (flat? test) 
					  (flat? consequent)
					  (flat? alternate)))
    (($primop op-name args)    (every? flat? args))
    (($pair exp1 exp2)         (and (flat? exp1) (flat? exp2)))
    (($let ids exps body)      (and (every? flat? exps) (flat? body)))
    (($tuple components)       (every? flat? components))
    (($tuple-ref tuple _)      (flat? tuple))
    (($tuple? exp)             (flat? exp))
    (($tuple-length exp)       (flat? exp))
    (($tuple-append exp1 exp2) (and (flat? exp1) (flat? exp2)))
    (($program-flat ids exps body) #f)	;; Kludge...
    ))
  

;;;----------------------------------------------------------------------------
;;; READ-EVAL-PRINT LOOP

(define (make-repl prompt parse eval)
  (lambda ()
    (let loop ((env the-empty-environment))
      (newline)
      (newline)
      (write-string prompt)
      (let ((sexp (read)))
	(newline)
	(cond ((eq? sexp 'quit) 'done)
	      ((define-sexp? sexp) 
	       (let ((exp-meaning (eval (parse (definition-value sexp))))
		     (name (definition-name sexp)))
		 (match (exp-meaning env)
		   ((val->exp-val v)
		    (begin
		      (display
		       (string-append ";Updating " (symbol->string name) 
				      " --> "      (value->string v)))
		      (loop (extend-env name v env))))
		   (error-val
		    (begin
		      (display (unparse-exp-value error-val))
		      (loop env))))))
	      (else (display (unparse-exp-value ((eval (parse sexp)) env)))
		    (loop env)))))))

(define flex-repl (make-repl "flex=> " flex-parse flex-eval))

(define flat-repl (make-repl "flat=> " flat-parse-program flat-eval))

(define false-symbol (string->symbol "#f"))

(define (unparse-exp-value exp-val)
  (match exp-val
    ((error->exp-val string) 
     (string->sexp (string-append "[FLEX/FLAT Error: " string "]")))
    ((val->exp-val v) (unparse-value v))))

(define (unparse-value val)
  (match val
    ((unit->val)   #u)
    ((int->val  n) (int->sexp n))
    ((bool->val b) (if b (bool->sexp b) false-symbol))
    ((sym->val s) `',(sym->sexp s))
    ((pair->val left right) (unparse-pair (unparse-value left)
					  (unparse-value right)))
    ((procedure->val p) `procedure)
    ((tuple->val vals) `(tuple ,@(map unparse-value vals)))
    ))


(define (error-with-val error-string val)
  (error->exp-val
   (string-append error-string "\n\t" (value->string val))))
    
(define (value->string val)
  (with-output-to-string
    (lambda () (display (unparse-value val)))))



;;;----------------------------------------------------------------------------
;;; UTILITIES

(define (every? pred lst)
  (if (null? lst)
      #t
      (and (pred (car lst))
	   (every? pred (cdr lst)))))

(define (compose f g)
  (lambda (x) (f (g x))))
 
(define (define-sexp? sexp)
  (match sexp
    (`(define ,(a-symbol name) ,value-exp) #t)
    (_ #f)))

(define (definition-name def)
  (match def
    (`(define ,(a-symbol name) ,value-exp) name)
    (_ (error (string-append "DEFINITION-NAME -- not a definition")))))

(define (definition-value def)
  (match def
    (`(define ,(a-symbol name) ,value-exp) value-exp)
    (_ (error (string-append "DEFINITION-VALUE -- not a definition")))))


;;;----------------------------------------------------------------------------
;;; TESTING

(define (test-translate translator)
  (begin
    (analyze-it translator (get-input "test=> "))
    #u))

(define (analyze-it translator flex-exp)
  (let ((flat-exp (translator flex-exp))
	(validinput (set-empty? (free-vars flex-exp))))
    (newline) 
    (newline) 
    (write-string "Input expression (FLEX): ")
    (pp (flex-unparse flex-exp))
    (newline) 
    (warn-on-unbound-vars "INPUT" validinput)
    (newline)
    (write-string "Translated expression (FLAT): ")
    (pp (flat-unparse flat-exp))
    (newline)
    (warn-on-bogus-translator validinput flat-exp)
    flat-exp))

(define (get-input prompt)
  (newline)
  (write-string prompt)
  (flex-parse (read)))

(define (test-loop translator)
  (letrec 
      ((testloop
	(lambda ()
	  (begin
	    (newline)
	    (let ((flex-exp (get-input "testloop=> ")))
	      (let ((flat-exp (analyze-it translator flex-exp)))
		(begin
		  (newline)
		  (write-string "Input expression value: ")
		  (pp (unparse-exp-value (flex-eval-empty flex-exp)))
		  (newline)
		  (newline)
		  (write-string "Translated expression value: ")
		  (pp (unparse-exp-value (flat-eval-empty flat-exp)))
		  (newline)
		  (testloop))))))))
    (testloop)))

(define (warn-on-unbound-vars string validinput)
  (if (not validinput)
      (begin
	(newline)
	(write-string "----------------------------------------------------\n")
	(write-string (string-append  
		       "*** " string
		       " EXPRESSION CONTAINS UNBOUND VARIABLES! ***\n"))
	(write-string "----------------------------------------------------\n")
	)
      #u))

(define (warn-on-bogus-translator validinput flat-exp)
  (if (and validinput (not (non-scoped? flat-exp)))
      (begin
	(newline)
	(write-string "----------------------------------------------\n")
	(write-string "*** TRANSLATOR DOESN'T WORK ON THIS CASE! ***\n")
	(write-string "(Some PROCs contain free variables!)\n")
	(write-string "----------------------------------------------\n")
	)
      #u))


;;;----------------------------------------------------------------------------
;;; LIFTer

;lift: flat-exp -> flat-exp
(define (lift flat-exp)
  (walk flat-exp (lambda (new-exp ids procs)
		   ($program-flat ids procs new-exp))))

(define (walk exp return)
  (match exp
    (($proc formal body) 
     (let ((id (fresh-var)))
       (walk body
	     (lambda (new-body ids procs)
	       (return ($var-ref id)
		       (cons id ids)
		       (cons ($proc formal new-body) procs))))))
    (($call rator rand)   (walk-list (list rator rand)     $call return))
    (($if test consq alt) (walk-list (list test consq alt) $if   return))
    (($pair left right)   (walk-list (list left right)     $pair return))
    (($let ids exps body) (walk-list (cons body exps)
				     (lambda new-list
				       ($let ids 
					     (cdr new-list)
					     (car new-list)))
				     return))
    (($primop prim exps)  (walk-list exps
				     (lambda new-exps
				       ($primop prim new-exps))
				     return))
    (($tuple exps)        (walk-list exps 
				     (lambda new-exps ($tuple new-exps))
				     return))
    (($tuple-ref exp index) (walk-list (list exp)
				       (lambda (new-exp)
					 ($tuple-ref new-exp index))
				       return))
    (($tuple? exp)       (walk-list (list exp) $tuple? return))
    (($tuple-length exp) (walk-list (list exp) $tuple-length return))
    (($tuple-append exp1 exp2) (walk-list (list exp1 exp2)
					  $tuple-append
					  return))
    (_ (return exp '() '()))
    ))

(define (walk-list listof-exps constructor return)
  (let loop ((listof-exps listof-exps)
	     (return (lambda (new-list ids procs)
		       (return (apply constructor new-list) ids procs))))
    (if (null? listof-exps)
	(return '() '() '())
	(walk (car listof-exps)
	      (lambda (new-car car-ids car-procs)
		(loop (cdr listof-exps) 
		      (lambda (new-cdr cdr-ids cdr-procs)
			(return (cons   new-car   new-cdr)
				(append car-ids   cdr-ids)
				(append car-procs cdr-procs)))))))))

(define (translate-and-lift translator)
  (lambda (sexp)
    (flat-unparse (lift (translator (flex-parse sexp))))))

(define (lift-flat sexp)
  (flat-unparse (lift (flat-parse sexp))))


;; eg. (lift-flat '(call (proc x x) 3))
;; ==> (program (([var-4] (proc x x))) (call [var-4] 3))

(define (lift-loop-on-flex)
  (let loop ()
    (newline)
    (newline)
    (write-string "lift-flex=> ")
    (let* ((sexp (read))
	   (flex-exp   (flex-parse sexp))
	   (validinput (set-empty? (free-vars flex-exp)))
	   (lifted-exp (lift flex-exp))
	   (validlift  (set-empty? (free-vars lifted-exp)))
	   )
      (newline)
      (newline)
      (write-string "Input expression: ")
      (pp (flex-unparse flex-exp))
      (warn-on-unbound-vars "INTPUT" validinput)
      (newline)
      (newline)
      (write-string "Lifted expression: ")
      (pp (flat-unparse lifted-exp))
      (warn-on-unbound-vars "LIFTED" validlift)
      (newline)
      (newline)
      (write-string "Input expression value: ")
      (pp (unparse-exp-value (flex-eval-empty flex-exp)))
      (newline)
      (newline)
      (write-string "Lifted expression value: ")
      (pp (unparse-exp-value (flat-eval-empty lifted-exp)))
      (loop))))

(define (lift-loop-with-translate translator)
  (let loop ()
    (newline)
    (newline)
    (write-string "lift-trans=> ")
    (let* ((sexp (read))
	   (flex-exp   (flex-parse sexp))
	   (validinput (set-empty? (free-vars flex-exp)))
	   (flat-exp   (translator flex-exp))
	   (lifted-exp (lift flat-exp))
	   (validlift  (set-empty? (free-vars lifted-exp)))
	   )
      (newline)
      (newline)
      (write-string "Input expression: ")
      (pp (flex-unparse flex-exp))
      (warn-on-unbound-vars "INTPUT" validinput)
      (newline)
      (newline)
      (write-string "Translated expression: ")
      (pp (flat-unparse flat-exp))
      (warn-on-bogus-translator validinput flat-exp)
      (newline)
      (newline)
      (write-string "Lifted expression: ")
      (pp (flat-unparse lifted-exp))
      (warn-on-unbound-vars "LIFTED" validlift)
      (newline)
      (newline)
      (write-string "Input expression value: ")
      (pp (unparse-exp-value (flex-eval-empty flex-exp)))
      (newline)
      (newline)
      (write-string "Translated expression value: ")
      (pp (unparse-exp-value (flat-eval-empty flat-exp)))
      (newline)
      (newline)
      (write-string "Lifted expression value: ")
      (pp (unparse-exp-value (flat-eval-empty lifted-exp)))
      (loop))))
      

