
;; Simple interpreter of section 3.6 -- 
;; based on online code 3-6*.scm
;; recursive procedures

;;;;;;;;;;;;;;;; top level and tests ;;;;;;;;;;;;;;;;

(define run
  (lambda (string)
    (eval-program (scan&parse string))))

;;;;;;;;;;;;;;;; grammatical specification ;;;;;;;;;;;;;;;;

(define the-lexical-spec
  '((whitespace (whitespace) skip)
    (comment ("%" (arbno (not #\newline))) skip)
    (identifier
      (letter (arbno (or letter digit "_" "-" "?")))
      symbol)
    (number (digit (arbno digit)) number)))

(define the-grammar
  '((program (expression) a-program)
    (expression (number) lit-exp)
    (expression (identifier) var-exp)   
    (expression
      (primitive "(" (separated-list expression ",") ")")
      primapp-exp)
    (expression
      ("if" expression "then" expression "else" expression)
      if-exp)
    (expression
      ("let" (arbno  identifier "=" expression) "in" expression)
      let-exp)
    (expression
     ("proc" "(" (separated-list identifier ",") ")" expression)
     proc-exp)
    (expression
     ("(" expression (arbno expression) ")")
     app-exp)
    (expression                         ; new for 3-6
      ("letrec"
        (arbno identifier "(" (separated-list identifier ",") ")"
          "=" expression)
        "in" expression)
      letrec-exp)

    (primitive ("+")     add-prim)
    (primitive ("-")     subtract-prim)
    (primitive ("*")     mult-prim)
    (primitive ("add1")  incr-prim)
    (primitive ("sub1")  decr-prim)
    (primitive ("zero?") zero-test-prim)

    ))

(sllgen:make-define-datatypes the-lexical-spec the-grammar)

(define show-the-datatypes
  (lambda () (sllgen:list-define-datatypes the-lexical-spec the-grammar)))

(define scan&parse
  (sllgen:make-string-parser the-lexical-spec the-grammar))

(define just-scan
  (sllgen:make-string-scanner the-lexical-spec the-grammar))

(define read-eval-print 
  (sllgen:make-rep-loop  "--> "
    (lambda (pgm) (eval-program pgm))   ; wrapped to avoid load
                                        ; dependency 
    (sllgen:make-stream-parser 
      the-lexical-spec
      the-grammar)))

;;;;;;;;;;;;;;;; the interpreter ;;;;;;;;;;;;;;;;

(define eval-program 
  (lambda (pgm)
    (cases program pgm
      (a-program (body)
        (eval-expression body (init-env))))))

(define eval-expression 
  (lambda (exp env)
    (cases expression exp
      (lit-exp (datum) datum)
      (var-exp (id) (apply-env env id))
      (primapp-exp (prim rands)
        (let ((args (eval-rands rands env)))
          (apply-primitive prim args)))
      (if-exp (test-exp true-exp false-exp) 
        (if (true-value? (eval-expression test-exp env))
          (eval-expression true-exp env)
          (eval-expression false-exp env)))
      (let-exp (ids rands body)  
          (eval-expression body (extend-env ids (eval-rands rands env) env)))
      (proc-exp (ids body)
          (closure ids body env))
      (app-exp (rator rands)
          (apply-procval (eval-expression rator env)
                                (eval-rands rands env)))
      (letrec-exp (proc-names idss bodies letrec-body) 
        (eval-expression letrec-body
          (extend-env-recursively proc-names idss bodies env)))
      )))

(define eval-rands
  (lambda (rands env)
    (map (lambda (x) (eval-rand x env)) rands)))

(define eval-rand
  (lambda (rand env)
    (eval-expression rand env)))

;; alternative definition:  (cvarela 2003/09/25)

(define eval-rand eval-expression)


(define apply-primitive
  (lambda (prim args)
    (cases primitive prim
      (add-prim  () (+ (car args) (cadr args)))
      (subtract-prim () (- (car args) (cadr args)))
      (mult-prim  () (* (car args) (cadr args)))
      (incr-prim  () (+ (car args) 1))
      (decr-prim  () (- (car args) 1))
      (zero-test-prim () (if (zero? (car args)) 1 0))
      )))

;; alternative definition allowing a variable number of arguments for
;; primitive operators +,-,*:  (gunduz 2003/09/29)

(define apply-primitive
  (lambda (prim args)
    (cases primitive prim
      (add-prim  () (apply + args))
      (subtract-prim () (apply - args))
      (mult-prim  () (apply * args))
      (incr-prim  () (+ (car args) 1))
      (decr-prim  () (- (car args) 1))
      (zero-test-prim () (if (zero? (car args)) 1 0))
      )))

(define init-env 
  (lambda ()
    (extend-env
      '(i v x)
      '(1 5 10)
      (empty-env))))
               
;;;;;;;;;;;;;;;; booleans ;;;;;;;;;;;;;;;;
;;;;;;;;; Number representation;;;;

(define true-value?
  (lambda (x)
    (not (zero? x))))

;;;;;;;;;;;;;;;;;;;;; Procedure Values ;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;; Procedural Representation ;;;;;;;;;;;;;;;;;

(define closure
  (lambda (ids body env)
    (lambda (args)
      (eval-expression body (extend-env ids args env)))))

(define apply-procval
  (lambda (proc args)
    (if (not (procedure? proc))
	(eopl:error 'apply-procval 
		    "Attempt to apply non-procedure ~s" proc)
	(proc args))))

;;;;;;;;;;;;;;;; environments ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;; procedural representation ;;;;;;

(define environment? procedure?)

(define apply-env
  (lambda (env sym)
    (env sym)))

(define empty-env
  (lambda ()
    (lambda (sym)
      (eopl:error 'empty-env "No binding for: ~s" sym))))

(define extend-env
  (lambda (ids vals env)
    (lambda (sym)
      (let ((pos (rib-find-position sym ids)))
        (if (number? pos)
          (list-ref vals pos)
          (apply-env env sym))))))

(define extend-env-recursively
  (lambda (proc-names idss bodies old-env)
    (letrec
      ((rec-env
         (lambda (sym)
           (let ((pos (rib-find-position sym proc-names)))
             (if (number? pos)
               (closure
                 (list-ref idss pos)
                 (list-ref bodies pos)
                 rec-env)
               (apply-env old-env sym))))))
      rec-env)))

(define rib-find-position 
  (lambda (sym los)
    (list-find-position sym los)))

(define list-find-position
  (lambda (sym los)
    (list-index (lambda (sym1) (eqv? sym1 sym)) los)))

; or simply:

(define rib-find-position list-find-position)

(define list-index
  (lambda (pred ls)
    (cond
      ((null? ls) #f)
      ((pred (car ls)) 0)
      (else (let ((list-index-r (list-index pred (cdr ls))))
              (if (number? list-index-r)
                (+ list-index-r 1)
                #f))))))


;; e.g., in defined language:

(run
"
  (proc (x,y) +(x,y) 2 5)
")

(run
"
  let f = proc (y,z) +(y,-(z,5))
  in  (f 2 28)
")

(run
"
  let curry = proc (f) proc (x) proc (y) (f x y)
       plus = proc (x,y) +(x,y)
  in (((curry plus) 2) 3)
")

(run
"
  let a = 3
  in let p = proc (x) +(x,a)
          a = 5
      in *(a,(p 2))
")

(run
"
  let makemult = proc (maker,x)
                            if x
                            then +(4,(maker maker sub1(x)))
                            else 0
  in let times4 = proc (x) (makemult makemult x)
      in (times4 3)
")

;; Exercise 3.21

(run
"
let fact = proc (maker,x)
                            if  x
                            then *(x,(maker maker sub1(x)))
                            else 1
  in let factorial = proc (x) (fact fact x)
      in (factorial 3)
")

;; Exercise 3.22

(run
"
let evenR = proc(even1,odd1,x)
                  if x
                  then (odd1 odd1 even1 sub1(x))
                  else 1
     oddR = proc(odd1,even1,x)
                  if x
                  then (even1 even1 odd1 sub1(x))
                  else 0
in let even =proc(x) (evenR evenR oddR x)
        odd = proc(x) (oddR oddR evenR x)
    in (even 6)
")

;; Using letrec:

(run
"
  letrec
    fact(x) = if zero?(x) then 1 else *(x,(fact sub1(x)))
  in (fact 6)
")

(run
"
  letrec
    even(x) = if zero?(x) then 1 else (odd sub1(x))
    odd(x)  = if zero?(x) then 0 else (even sub1(x))
  in (odd 6)
")
