; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING.

; Generic arithmetic.

; The different kinds of numbers.

(define-enumeration numbers
  (fixnum
   bignum
   rational
   float
   complex
   not-a-number))

; Mapping numbers to their representation.

(define stob-numbers
  (make-vector stob-count (enum numbers not-a-number)))

; For now all we have are bignums (and fixnums, of course).
(vector-set! stob-numbers (enum stob bignum) (enum numbers bignum))

(define (number->representation x)
  (cond ((fixnum? x)
	 (enum numbers fixnum))
	((stob? x)
	 (vector-ref stob-numbers (header-type (stob-header x))))
	(else
	 (enum numbers not-a-number))))

;----------------
; Tables for unary and binary operations.  All entries initially return DEFAULT.

(define (make-unary-table default)
  (make-vector numbers-count
	       (lambda (x)
		 default)))
	
; (unary-table-set! <table> <type> <value>)
; (unary-table-set! <table> (<type> ...) <value>)

(define-syntax unary-table-set!
  (syntax-rules ()
    ((unary-table-set! ?table (?kind ...) ?value)
     (real-unary-table-set! ?table (list (enum numbers ?kind) ...) ?value))
    ((unary-table-set! ?table ?kind ?value)
     (real-unary-table-set! ?table (list (enum numbers ?kind)) ?value))))

(define (real-unary-table-set! table kinds value)
  (for-each (lambda (kind)
	      (vector-set! table kind value))
	    kinds))

(define (unary-dispatch table x)
  ((vector-ref table
	       (number->representation x))
     x))

(define (make-binary-table default)
  (make-vector (* numbers-count numbers-count)
	       (lambda (x y)
		 default)))

; Same as for unary tables, except that we have two indexes or lists of indexes.

(define-syntax binary-table-set!
  (syntax-rules ()
    ((binary-table-set! ?table (?kind0 ...) (?kind1 ...) ?value)
     (real-binary-table-set! ?table
			     (list (enum numbers ?kind0) ...)
			     (list (enum numbers ?kind1) ...)
			     ?value))
    ((binary-table-set! ?table (?kind0 ...) ?kind1 ?value)
     (real-binary-table-set! ?table
			     (list (enum numbers ?kind0) ...)
			     (list (enum numbers ?kind1))
			     ?value))
    ((binary-table-set! ?table ?kind0 (?kind1 ...) ?value)
     (real-binary-table-set! ?table
			     (list (enum numbers ?kind0))
			     (list (enum numbers ?kind1) ...)
			     ?value))
    ((binary-table-set! ?table ?kind0 ?kind1 ?value)
     (real-binary-table-set! ?table
			     (list (enum numbers ?kind0))
			     (list (enum numbers ?kind1))
			     ?value))))

(define (real-binary-table-set! table kinds0 kinds1 value)
  (for-each (lambda (kind0)
	      (for-each (lambda (kind1)
			  (vector-set! table
				       (+ (* kind0 numbers-count)
					  kind1)
				       value))
			kinds1))
	    kinds0))

; Does this need to be changed to get a computed goto?

(define (binary-dispatch table x y)
  ((vector-ref table
	       (+ (* (number->representation x)
		     numbers-count)
		  (number->representation y)))
     x
     y))

(define (binary-lose x y)
  unspecific-value)

;----------------
; The actual opcodes

; Predicates

(define-primitive number? (any->)
  (lambda (x)
    (not (= (number->representation x)
	    (enum numbers not-a-number))))
  return-boolean)

(define-primitive integer? (any->)
  (lambda (x)
    (let ((type (number->representation x)))
      (or (= type (enum numbers fixnum))
	  (= type (enum numbers bignum)))))
  return-boolean)

(define-primitive rational? (any->)
  (lambda (x)
    (let ((type (number->representation x)))
      (or (= type (enum numbers fixnum))
	  (= type (enum numbers bignum))
	  (= type (enum numbers rational)))))
  return-boolean)

(define-primitive real? (any->)
  (lambda (x)
    (let ((type (number->representation x)))
      (not (or (= type (enum numbers complex))
	       (= type (enum numbers not-a-number))))))
  return-boolean)

(define-primitive complex? (any->)
  (lambda (x)
    (not (= (number->representation x)
	    (enum numbers not-a-number))))
  return-boolean)

(define-primitive exact? (any->)
  (lambda (x)
    (enum-case number (number->representation x)
	       ((float)
		(goto return-boolean #f))
	       ((complex)
		(goto return-boolean (not (float? (complex-real-part x)))))
	       ((not-a-number)
		(raise-exception wrong-type-argument 0 x))
	       (else
		(goto return-boolean #t)))))

;----------------
; Arithmetic

(define-syntax define-binary-primitive
  (syntax-rules ()
    ((define-binary-primitive id table careful integer)
     (define table (make-binary-table binary-lose))
     (define-primitive id (any-> any->)
       (lambda (x y)
	 (if (and (fixnum? x)
		  (fixnum? y))
	     (goto careful
		   x
		   y
		   return
		   (lambda (x y)
		     (goto return (integer x y))))
	     (let ((r (binary-dispatch table x y)))
	       (if (vm-eq? r unspecific-value)
		   (raise-exception wrong-type-argument 0 x y)
		   (goto return r))))))
     (binary-table-set! table (fixnum bignum) (fixnum bignum) integer))))

(define-binary-primitive + add-table add-carefully integer-add)
(define-binary-primitive - subtract-table subtract-carefully integer-subtract)
(define-binary-primitive * multiply-table multiply-carefully integer-multiply)
(define-binary-primitive quotient quotient-table quotient-carefully integer-quotient)
(define-binary-primitive remainder remainder-table remainder-carefully integer-remainder)
(define-binary-primitive arithmetic-shift shift-table shift-carefully integer-shift)

; Hm.  There is no integer-divide (obviously)

(define-binary-primitive / divide-table divide-carefully integer-)












****************************************************************

How to structure all this?  It would be nice if the interpreter could be
broken into several modules.  The registers and define-primitive would
need to be separated out.

;----------------
; Tower predicates.
; These need to be changed.

(define-unary-opcode-extension integer?       &integer?  #f)
(define-unary-opcode-extension rational?      &rational? #f)
(define-unary-opcode-extension real?          &real?     #f)
(define-unary-opcode-extension complex?       &complex?  #f)
(define-unary-opcode-extension number?        &number?   #f)
(define-unary-opcode-extension exact?         &exact?    #f)

(let ((true (lambda (x) #t)))
  (unary-table-set! &integer?  (fixnum bignum)                        true)
  (unary-table-set! &rational? (fixnum bignum rational)               true)
  (unary-table-set! &real?     (fixnum bignum rational float)         true)
  (unary-table-set! &complex?  (fixnum bignum rational float complex) true)
  (unary-table-set! &number?   (fixnum bignum rational float complex) true)
  (unary-table-set! &exact?    (fixnum bignum rational)               true))

; The two parts of a complex number must have the same exactness.

(unary-table-set! &exact? (complex)
		  (lambda (z)
		    (real-part z)))

;----------------
; Imaginary operations.

(define-unary-opcode-extension real-part      &real-part (lambda (x) x))
(define-unary-opcode-extension imag-part      &imag-part (lambda (x) 0))

(unary-table-set! &real-part (complex not-a-number)
		  (lambda (x) unimplemented))

(unary-table-set! &imag-part (complex not-a-number)
		  (lambda (x) unimplemented))

;----------------
; Fractions

(define-unary-opcode-extension floor          &floor)
(define-unary-opcode-extension numerator      &numerator)
(define-unary-opcode-extension denominator    &denominator)

(define (identity x) x)

(unary-table-set! &floor       (fixnum bignum) identity)
(unary-table-set! &numerator   (fixnum bignum) identity)
(unary-table-set! &denominator (fixnum bignum) (lambda (x) 1))

;----------------
; Square root.

(define-unary-opcode-extension sqrt &sqrt)

; The bignum code could whack this.
; The VM doesn't do sqrt for positive fixnums.  I wonder why?

; For negative N, we lose if MAKE-RECTANGULAR loses.

(unary-table-set! &sqrt (fixnum bignum)
  (lambda (n)
    (if (>= n 0)
	(non-negative-integer-sqrt n)	;Dubious (JAR)
	(let ((s (non-negative-integer-sqrt (- n))))
	  (if (eq? s unimplemented)
	      s
	      (binary-dispatch &make-rectangular
			       0
			       s))))))

; Courtesy of Mr. Newton.

(define (non-negative-integer-sqrt n)
  (if (<= n 1)    ; for both 0 and 1
      n
      (let loop ((m (quotient n 2)))
	(let ((m1 (quotient n m)))
	  (cond ((< m1 m)
		 (loop (quotient (+ m m1) 2)))
		((= n (* m m))
		 m)
		(else
		 unimplemented))))))

;----------------
; Make sure this has very low priority, so that it's only tried as a
; last resort.
;
; In fact, I'll comment it out completely. -RK

;(define-method &/ (m n)
;  (if (and (integer? m) (integer? n))
;      (if (= 0 (remainder m n))
;          (quotient m n)
;          (let ((z (abs (quotient n 2))))
;            (set-exactness (quotient (if (< m 0)
;                                         (- m z)
;                                         (+ m z))
;                                     n)
;                           #f)))
;      (next-method)))

;----------------
; The rest have no useful defaults.

(define-unary-opcode-extension exact->inexact &exact->inexact)
(define-unary-opcode-extension inexact->exact &inexact->exact)

(define-binary-opcode-extension +              &+)
(define-binary-opcode-extension -              &-)
(define-binary-opcode-extension *              &*)
(define-binary-opcode-extension /              &/)
(define-binary-opcode-extension =              &=)
(define-binary-opcode-extension <              &<)
(define-binary-opcode-extension quotient       &quotient)
(define-binary-opcode-extension remainder      &remainder)
  
(define-binary-opcode-extension make-rectangular &make-rectangular)

(define-unary-opcode-extension exp  &exp)
(define-unary-opcode-extension log  &log)
(define-unary-opcode-extension sin  &sin)
(define-unary-opcode-extension cos  &cos)
(define-unary-opcode-extension tan  &tan)
(define-unary-opcode-extension asin &asin)
(define-unary-opcode-extension acos &acos)
(define-unary-opcode-extension atan &atan)

; >, <=, and >= are all extended using the table for <.

(extend-opcode! (enum op >)
		(lambda (lose)
		  (lambda (reason arg0 arg1)
		    (let ((res (binary-dispatch &< arg1 arg0)))
		      (if (eq? res unimplemented)
			  (lose reason arg0 arg1)
			  res)))))
(extend-opcode! (enum op <=)
		(lambda (lose)
		  (lambda (reason arg0 arg1)
		    (let ((res (binary-dispatch &< arg1 arg0)))
		      (if (eq? res unimplemented)
			  (lose reason arg0 arg1)
			  (not res))))))
(extend-opcode! (enum op >=)
		(lambda (lose)
		  (lambda (reason arg0 arg1)
		    (let ((res (binary-dispatch &< arg0 arg1)))
		      (if (eq? res unimplemented)
			  (lose reason arg0 arg1)
			  (not res))))))