mcf/eforth.ss
#lang scheme/base

(require
 "mem.ss"
 "eforth-tools.ss"
 scheme/control
 (for-syntax scheme/base))

;; Dr. C.H. Ting's eForth, a highly portable forth, written in terms
;; of the primitives:

;;   BYE ?RX TX! !IO doLIT doLIST EXIT EXECUTE next ?branch branch ! @
;;   C! C@ RP@ RP! R> R@ >R SP@ SP! DROP DUP SWAP OVER 0< AND OR XOR
;;   UM+ $NEXT D$ $USER $COLON $CODE


;; Completely functional implementation is difficult due to the memory
;; array nature of Forth, so we use an abstract array in terms of mem@
;; and mem! (see "mem.ss" for default), which can be implemented
;; functionally if necessary.  It would be interesting to see how much
;; of the indirection can be optimized away later.

;; Step 1: implement the primitives in Scheme.  This implementation
;;         will serve as a reflective compiler in Staapl.

;; Step 2: bootstrap the Forth code.

;; Step 3: find a translation to Staapl primitives for the 18F for
;;         running part of the machine on the 18F.



;; Stacks are impemented in-memory, not as abstract entities.  This is
;; closest to real implementation.

(define *SP* #x0000)  ;; Stack Pointer
(define *RP* #x0001)  ;; Return/Retain Stack Pointer
(define *IP* #x0002)  ;; Interpreter Pointer

(define (stack-push sp-addr val)
  (notrace
   (let ((sp (sub1 (mem@ sp-addr))))
     (mem! sp val)
     (mem! sp-addr sp))))
(define (stack-pop sp-addr)
  (notrace
   (let* ((sp (mem@ sp-addr))
          (val (mem@ sp)))
     (mem! sp-addr (add1 sp))
     val)))

;; Parameter access.
(define (lit . ns) (for ((n ns)) (stack-push *SP* n)))
(define (pop)   (stack-pop *SP*))
(define (top)   (notrace (mem@ (mem@ *SP*))))
(define-syntax locals
  (lambda (stx)
    (syntax-case stx ()
      ((_ formals . body)
       (syntax-case (reverse (syntax->list #'formals)) ()
         ((rf ...)
          #`(let* ((rf (pop)) ...) . body)))))))


;; DEBUG
(define (.S [n 10])
  (notrace
   (for ((val (reverse
               (for/list ((i (in-range n)))
                         (mem@ (+ (mem@ *SP*) i))))))
        (printf " ~a" val))
   (newline)))

;; Testing order of locals
;; (define (ab) (locals (a b) (printf "a=~a b=~a\n" a b)))

       



(define (not-implemented name) (error 'not-implemented "~a" name))
(define-syntax CODE
  (syntax-rules ()
    ((_ name)        (CODE name (not-implemented 'name)))
    ((_ name . body) (define name (lambda () . body)))))



(CODE @     (locals (addr) (lit (mem@ addr))) ($NEXT))
(CODE !     (locals (val addr) (mem! addr val)) ($NEXT))

(define C! !)
(define C@ @)

(CODE DROP  (pop) ($NEXT))
(CODE SWAP  (locals (a b) (lit b a)) ($NEXT))
(CODE DUP   (locals (a)   (lit a a)) ($NEXT))
(CODE OVER  (locals (a b) (lit a b a)) ($NEXT))

  
(CODE SP@   (lit (mem@ *SP*)) ($NEXT))
(CODE RP@   (lit (mem@ *RP*)) ($NEXT))
(CODE SP!   (mem! *SP* (pop)) ($NEXT))
(CODE RP!   (mem! *RP* (pop)) ($NEXT))

(define (binop op) (locals (a b) (lit (op a b))))

(CODE AND   (binop bitwise-and) ($NEXT))
(CODE OR    (binop bitwise-ior) ($NEXT))
(CODE XOR   (binop bitwise-xor) ($NEXT))

(CODE UM+   (locals (a b)
               (let ((sum (+ a b)))
                 (lit sum
                      (arithmetic-shift sum (- word-size)))))
            ($NEXT))
      

(CODE R>    (lit (stack-pop *RP*)) ($NEXT))
(CODE R@    (lit (mem@ *RP*)) ($NEXT))
(CODE >R    (stack-push *RP* (pop)) ($NEXT))

(CODE 0<    (locals (a) (lit (if (< a 0) -1 0))) ($NEXT))

(CODE BYE   (printf "BYE\n") (abort (void)))

(CODE ?RX   (let ((c (read-char)))
              (if (eof-object? c)
                  (lit 0)
                  (lit (char->integer c) -1)))
            ($NEXT))

(CODE TX!   (write-char (integer->char (pop))) ($NEXT))

(CODE !IO   ($NEXT)) ;; Init serial port.

  

;; Inner interpreter.
(define (*IP++) (stack-pop *IP*))
(define (IP! ip) (mem! *IP* ip))


(CODE doLIT  (lit (*IP++)) ($NEXT))

           
(CODE doLIST (locals (addr)
                (stack-push *RP* (mem@ *IP*))
                (mem! *IP* addr))
             ($NEXT))

(CODE EXIT (mem! *IP* (stack-pop *RP*)) ($NEXT))

;; The jump/goto machine instruction normally present in NEXT
;; dereferences the numeric code to a thunk and executes it in tail
;; position.  Here continue will start executing primitive machine
;; code represented by number, first mapped to a thunk.

;; The difference with a real machine is that a primitive is usually a
;; list of concrete machine instructions, while we have an extra
;; indirection here that maps a single numeric representation to a
;; closure.  This indirection is necessary to serialize memory images.

(define (goto xt) (continue (mem@ xt)))
(define ($NEXT) (goto (*IP++)))
(CODE EXECUTE (goto (pop)))

(CODE branch  (let ((xt (*IP++))) (mem! *IP* xt)) ($NEXT))
(CODE ?branch (locals (flag) (let ((ip (*IP++))) (when (zero? flag) (IP! ip)))) ($NEXT))

;; TODO
   
(CODE next  (let*
                ((ip (*IP++))
                 (count (sub1 (stack-pop *RP*))))
              (unless (zero? count)
                (IP! ip)
                (stack-push *RP* count)))
            ($NEXT))
                


(CODE D$)
(CODE $USER)
(CODE $COLON)
(CODE $CODE)



;; The instruction table: map from numbers -> thunks.  The ordering
;; here is quite arbitrary except for keeping BYE = 0 for safety.

(define instruction-table
  (list->vector
   (list
    BYE ?RX TX! !IO doLIT doLIST EXIT EXECUTE next ?branch branch ! @
    C! C@ RP@ RP! R> R@ >R SP@ SP! DROP DUP SWAP OVER 0< AND OR XOR UM+
    D$ $USER $COLON $CODE)))

(define (continue ins)
  (let ((primitive (vector-ref instruction-table ins)))
    (primitive)))


;; BOOT

;; For the interpreter to work we need to wrap all primitives in high
;; level words for $NEXT indirection to work.  No other dict structure
;; is necessary though (only for outer interpreter).

(define *DICT* #x100)

(define (instruction->xt ins)
  (+ *DICT*
     (prompt
      (for ((i (in-range (vector-length instruction-table)))
            (p instruction-table))
           (when (eq? ins p) (abort i)))
      0)))
  
(define (compile: . lst)
  (for/list ((ins lst))
     (if (number? ins)
         ins (instruction->xt ins))))
         
(define (hello)
  (append
   (compile: doLIT 3 >R)
   (apply append (for/list ((c "Staapl eForth 1.0\n"))
                    (compile: doLIT (char->integer c) TX!)))
   (compile: next #x1003)
   (compile: BYE)
   ))
         

;; Initialize the machine
(define (boot)
  (newline)
  (mem! *SP* #x0000)  ;; Top of data stack - 1
  (mem! *RP* #xFF00)  ;; Top of return stack - 1.
  (mem! *IP* #x1000)  ;; Threaded boot code.

  (apply mem! *DICT*
         (for/list ((i (in-range
                        (vector-length instruction-table))))
                   i)) ;; Primitive wrapper words.

  (apply mem! #x1000 (hello))

  (prompt ($NEXT)))

(boot)