#|

Your task is to extend the interpreter below with `cond'
and the implement the function `contains-five?' in your
interpreter:

(define (contains-five? a-lon)
  (cond
    [(null? a-lon) #f]
    [else (cond
            [(= (car a-lon) 5) #t]
            [else (contains-five? (cdr a-lon))])]))

and test it with these function calls (already translated below).

(contains-five? '())
(contains-five? (cons 1 '()))
(contains-five? (cons 5 '()))
(contains-five? (cons 3 (cons 4 (cons 5 (cons 6 (cons 7 '()))))))

Be sure to read over the entire assignment and think about
the organization and that data that the program uses
before trying to modify the program.

NB: if you use set!, you are either doing it wrong or
making it more complicated than you have to.

Step 1: add boolean values to scheme-val and add tests for
        booleans to the evaluate test suite
        
Step 2: add conditional expressions to scheme-exp. You
        may assume that cond expressions always have
        two clauses and always have an `else' clause
        as the question in the second clause.
        NOTE: use `kond' instead of `cond' in your
        define-struct (to avoid collisions with
        built in names)

Step 3: make some examples of cond expressions and
        add tests to the evaluator test suite and
        the subst test suite.
        
Step 4: extend the evaluate and subst functions to
        handle cond expressions.
        
Step 5: translate the contains-five definition above
        to a scheme-def.
        
Step 6: test your scheme-def and your evaluator by
        using the example calls to contains-five?
        shown above (already translated below).
        
|#

#|

DATA DEFINITIONS

A scheme-exp is either:
  - (make-num= scheme-exp scheme-exp)

  - (make-kons scheme-exp scheme-exp)
  - (make-hd scheme-exp)
  - (make-tl scheme-exp)
  - (make-null-test scheme-exp)

  - (make-app symbol scheme-exp)
  - symbol

  - scheme-val
  
A scheme-val is:
  - number
  - (cons scheme-val scheme-val)
  - '()
  
A scheme-def is:
  - (make-def symbol symbol scheme-exp)

A list-of-scheme-defs is either:
  - empty
  - (cons scheme-def list-of-scheme-defs)
|#

(define-struct num= (lhs rhs))
(define-struct kons (hd tl))
(define-struct hd (arg))
(define-struct tl (arg))
(define-struct null-test (arg))
(define-struct app (rator rand))
(define-struct def (name param body))

;; evaluate : scheme-exp list-of-scheme-defs -> scheme-val
(define (evaluate a-se a-losd)
  (cond
    [(num=? a-se) (= (evaluate (num=-lhs a-se) a-losd)
                     (evaluate (num=-rhs a-se) a-losd))]
    [(kons? a-se) (cons (evaluate (kons-hd a-se) a-losd)
                        (evaluate (kons-tl a-se) a-losd))]
    [(hd? a-se) (car (evaluate (hd-arg a-se) a-losd))]
    [(tl? a-se) (cdr (evaluate (tl-arg a-se) a-losd))]
    [(app? a-se)
     (let ([def (lookup-def (app-rator a-se) a-losd)])
       (evaluate
        (subst (def-param def)
               (evaluate (app-rand a-se) a-losd)
               (def-body def))
        a-losd))]
    [(symbol? a-se) (error 'evaluate "free variable")]
   
    [(null-test? a-se)
     (null? (evaluate (null-test-arg a-se) a-losd))]
    [else ;; scheme-val
     a-se]))

;; lookup-def : symbol list-of-scheme-defs -> def
;; finds the def for name in a-losd, or calls error if there isn't one
(define (lookup-def name a-losd)
  (cond
    [(null? a-losd) (error 'lookup-def "not found: ~a" name)]
    [else (if (eq? name (def-name (car a-losd)))
              (car a-losd)
              (lookup-def name (cdr a-losd)))]))

;; subst : number or cons symbol scheme-exp -> scheme-exp
;; substitutes val for var in body
(define (subst var val body)
  (cond
    [(num=? body) (make-num=
                   (subst var val (num=-lhs body))
                   (subst var val (num=-rhs body)))]
    
    [(kons? body) (make-kons (subst var val (kons-hd body))
                             (subst var val (kons-tl body)))]
    [(hd? body) (make-hd (subst var val (hd-arg body)))]
    [(tl? body) (make-tl (subst var val (tl-arg body)))]
    [(app? body)
     (make-app (app-rator body)
               (subst var val (app-rand body)))]
    [(symbol? body) 
     (if (eq? var body)
         val
         body)]
    [(null-test? body) 
     (make-null-test (subst var val (null-test-arg body)))]
    [else ;; scheme-val
     body]))

;; EXAMPLES AS TESTS
(subst 'x 1 2)
2

(subst 'x 1 'x)
1

(subst 'x 1 'y)
'y

(subst 'x 1 #t)
#t

(subst 'x 1 (make-num= 'x 'y))
(make-num= 1 'y)

(subst 'x 1 (make-kons 'x 'y))
(make-kons 1 'y)

(subst 'x 1 '())
'()

(subst 'x 1 (make-hd 'x))
(make-hd 1)

(subst 'x 1 (make-tl 'x))
(make-hd 1)

(subst 'x 1 (make-tl 'x))
(make-tl 1)

(subst 'x 1 (make-app 'f 'x))
(make-app 'f 1)

(subst 'x 1 (make-null-test 'x))
(make-null-test 1)

(define defs
  (cons 
   (make-def 'f 'x (make-kons 1 'x))
   (cons
    (make-def 'g 'x (make-kons 3 (make-tl 'x)))
    (cons
     (make-def 'h 'x (make-num= 'x 1))
     '()))))

(lookup-def 'f defs)
(make-def 'f 'x (make-kons 1 'x))

(lookup-def 'g defs)
(make-def 'g 'x (make-kons 3 (make-tl 'x)))

(evaluate 2 defs)
2

(evaluate (make-num= 1 2) defs)
#f

(evaluate (make-num= 1 1) defs)
#t

(evaluate (make-kons 1 '()) defs)
(cons 1 '())

(evaluate (make-hd (make-kons 1 '())) defs)
1

(evaluate (make-tl (make-kons 1 '())) defs)
'()

(evaluate (make-null-test (make-kons 1 '())) defs)
#f

(evaluate (make-null-test '()) defs)
#t

(evaluate (make-app 'h '1) defs)
#t

(define cf-test1 (make-app 'contains-five? '()))
(define cf-test2 (make-app 'contains-five? (make-kons 1 '())))
(define cf-test3 (make-app 'contains-five? (make-kons 5 '())))
(define cf-test4 (make-app 'contains-five? (make-kons 3 (make-kons 4 (make-kons 5 (make-kons 6 (make-kons 7 '())))))))