;;; -*- Mode:LISP; Package:SYSTEM-INTERNALS; Base:10 -*-


(defvar *obj-node-table* nil)
(DEFVAR *OBJ-INDEX* 0)

(defresource obj-table ()
  :constructor (make-hash-table)
  :deinitializer (send object :clear-hash))

;; The *OBJ-NODE-TABLE* will have key entered for each node in an object.
;; Value:
;;   T ... object seen once
;;  -N ... object seen more than once, index in table assigned as N.
;;         must be transmited.
;;   N ... available as index N.

(defvar *only-share-symbols* nil)

(defun save-object (obj filename &KEY (circularp T) (VERBOSE T) (symbolp t))
  (let ((*obj-node-table* nil)
	(*OBJ-INDEX* 0)
	(*only-share-symbols* (not circularp)))
    (WITH-OPEN-FILE (STREAM FILENAME :DIRECTION :OUTPUT :CHARACTERS NIL :BYTE-SIZE 8)
      (IF VERBOSE
	  (FORMAT T "~&Saving object to ~S~%" filename))
      (cond ((or circularp symbolp)
	     (using-resource (x obj-table)
	       (setq *obj-node-table* x)
	       (walk-object obj)
	       (if verbose
		   (format t "~&~D shared subexpression~p.~%" *obj-index* *obj-index*))
	       (save-object-1 stream)
	       (transmit-object obj stream)))
	    ('else
	     (save-object-1 stream)
	     (transmit-object obj stream))))))

(defvar *save-object-file-prefix* "SAVED LISP OBJECT ")

(defun save-object-1 (stream)
  (princ *save-object-file-prefix* stream)
  (transmit-object *obj-index* stream))

(defvar *obj-array* nil)

(defresource obj-array (size)
  :constructor (let ((a (make-array (* 100 (ceiling size 100))
				    :fill-pointer 0 :adjustable t)))
		 (setf (fill-pointer a) size)
		 a)
  :MATCHER (NOT (< (ARRAY-LENGTH OBJECT) SIZE))
  :INITIALIZER (SETF (FILL-POINTER OBJECT) SIZE)
  :deinitializer (progn (fill object nil)
			(setf (fill-pointer object) 0)))

(defun restore-object (filename &key (verbose t))
  (with-open-file (stream filename :characters nil :byte-size 8)
    (if verbose (format t "~&Loading object from ~S~%" (send stream :truename)))
    (let ((max-index (restore-object-1 stream)))
      (if verbose (format t "~&~D shared subexpression~p.~%" max-index max-index))
      (using-resource (*obj-array* obj-array (1+ MAX-INDEX))
	(receive-object stream)))))

(defun restore-object-1 (stream)
  (do ((j 0 (1+ j))
       (n (length *save-object-file-prefix*)))
      ((= j n)
       (receive-object stream))
    (or (= (send stream :tyi) (aref *save-object-file-prefix* j))
	(ferror nil "stream doesnt begin with ~S" *save-object-file-prefix*))))

(defun walk-object (OBJ &AUX TEMP)
  (IF (SETQ TEMP (GETHASH OBJ *OBJ-NODE-TABLE*))
      (RETURN-FROM walk-object 
	(IF (EQ TEMP T)
	    (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) (- (INCF *OBJ-INDEX*))))))
  (TYPECASE OBJ
    (INTEGER NIL)
    (CHARACTER NIL)
    (SYMBOL
     (WHEN (NOT (MEMQ OBJ '(T NIL)))
       (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) T)
       (WALK-OBJECT (PACKAGE-NAME (SYMBOL-PACKAGE OBJ)))))
    (STRING
     (or *only-share-symbols* (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) T)))
    (ARRAY
     (COND ((not (= (array-rank obj) 1))
	    (WALK-OBJECT-UNHANDLED OBJ))
	   ((NAMED-STRUCTURE-P OBJ)
	    (or *only-share-symbols* (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) T))
	    (DOTIMES (J (LENGTH OBJ))
	      (WALK-OBJECT (AREF OBJ J))))
	   ((ARRAY-HAS-LEADER-P OBJ)
	    (WALK-OBJECT-UNHANDLED OBJ))
	   ((EQ (SETQ TEMP (ARRAY-TYPE OBJ)) 'ART-Q)
	    (or *only-share-symbols* (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) T))
	    (DOTIMES (J (LENGTH OBJ))
	      (walk-OBJECT (AREF OBJ J))))
	   ((EQ  TEMP 'ART-8B)
	    (or *only-share-symbols* (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) T)))
	   ('ELSE
	    (WALK-OBJECT-UNHANDLED OBJ))))
    (CONS
     (or *only-share-symbols* (SETF (GETHASH OBJ *OBJ-NODE-TABLE*) T))
     (DO ((J 0 (1+ J))
	  (L OBJ (CDR L)))
	 ((Atom L)
	  (COND ((NULL L))
		('ELSE
		 (walk-object l))))
       (walk-object (car l))))
    (INSTANCE
     (walk-OBJECT-UNHANDLED OBJ))
    (T
     (walk-OBJECT-UNHANDLED OBJ))))

(DEFUN walk-OBJECT-UNHANDLED (OBJ)
  (CERROR "transmit NIL instead"
	  "object type or makeup not handled: ~S" OBJ))


(EVAL-WHEN (EVAL COMPILE LOAD)

(DEFVAR *OPCODES*
	'((0 OBJECT-NIL)
	  (1 OBJECT-T)
	  (2 POSITIVE-INTEGER)
	  (3 NEGATIVE-INTEGER)
	  (4 SHORT-FLOAT)
	  (5 SINGLE-FLOAT)
	  (6 SYMBOL)
	  (7 LIST)
	  (8 LIST*)
	  (9 SIMPLE-ARRAY-ART-Q)
	  (10 SIMPLE-ARRAY-ART-8B)
	  (11 SIMPLE-STRING)
	  (12 SIMPLE-NAMED-STRUCTURE)
	  (13 SIMPLE-CHARACTER)
	  (14 HAIRY-CHARACTER)
	  (15 OBJECT-ASSIGN-INDEX)
	  (16 OBJECT-OF-INDEX)))
)

(DEFMACRO OPCODE (NAME)
  (OR (CAR (CAR (MEM #'(LAMBDA (X E) (EQ X (CADR E))) NAME *OPCODES*)))
      (FERROR NIL "UNKNOWN OBJECT OPCODE NAME: ~S" NAME)))

(defun transmit-object (OBJ stream &AUX TEMP1 TEMP2)
  (WHEN (AND *OBJ-NODE-TABLE* (SETQ temp1 (GETHASH OBJ *OBJ-NODE-TABLE*)))
    (cond ((eq temp1 t))
	  ((> temp1 0)
	   (send stream :tyo (opcode object-of-index))
	   (return-from transmit-object (transmiti temp1 stream)))
	  ('else
	   (send stream :tyo (opcode object-assign-index))
	   (setq temp1 (- temp1))
	   (setf (GETHASH OBJ *OBJ-NODE-TABLE*) temp1)
	   (transmiti temp1 stream))))
  (TYPECASE OBJ
    (INTEGER
     (COND ((< OBJ 0)
	    (SEND STREAM :TYO (OPCODE NEGATIVE-INTEGER))
	    (TRANSMITI (- OBJ) STREAM))
	   ('ELSE
	    (SEND STREAM :TYO (OPCODE POSITIVE-INTEGER))
	    (TRANSMITI OBJ STREAM))))
    (CHARACTER
     (SETQ TEMP1 (CHAR-BITS OBJ))
     (SETQ TEMP2 (CHAR-FONT OBJ))
       (COND ((AND (ZEROP TEMP1) (ZEROP TEMP2))
	      (SEND STREAM :TYO (OPCODE SIMPLE-CHARACTER))
	      (SEND STREAM :TYO (CHAR-CODE OBJ)))
	     ('ELSE
	      (SEND STREAM :TYO (OPCODE HAIRY-CHARACTER))
	      (SEND STREAM :TYO (CHAR-CODE OBJ))
	      (SEND STREAM :TYO TEMP1)
	      (SEND STREAM :TYO TEMP2))))
    (SYMBOL
     (COND ((NULL OBJ)
	    (SEND STREAM :TYO (OPCODE OBJECT-NIL)))
	   ((EQ OBJ T)
	    (SEND STREAM :TYO (OPCODE OBJECT-T)))
	   ('ELSE
	    (SEND STREAM :TYO (OPCODE SYMBOL))
	    (TRANSMIT-OBJECT (SYMBOL-NAME OBJ) STREAM)
	    (TRANSMIT-OBJECT (PACKAGE-NAME (SYMBOL-PACKAGE OBJ)) STREAM))))

    (STRING
     (COND ((AND (ARRAY-HAS-LEADER-P OBJ)
		 ;; allow a string with fill pointer to show up as a simple
		 ;; string on the receiving end.
		 (NOT (= (ARRAY-LEADER-LENGTH OBJ) 1)))
	    (TRANSMIT-OBJECT-UNHANDLED OBJ STREAM))
	   ('ELSE
	    (SEND STREAM :TYO (OPCODE SIMPLE-STRING))
	    (SETQ TEMP2 (LENGTH OBJ))
	    (TRANSMITI TEMP2 STREAM)
	    (SEND STREAM :STRING-OUT OBJ 0 TEMP2))))
    (ARRAY
     (COND ((not (= (array-rank obj) 1))
	    (TRANSMIT-OBJECT-UNHANDLED OBJ STREAM))
	   ((SETQ TEMP1 (NAMED-STRUCTURE-P OBJ))
	    (SETQ TEMP2 (LENGTH OBJ))
	    (SEND STREAM :TYO (OPCODE SIMPLE-NAMED-STRUCTURE))
	    (TRANSMITI TEMP2 STREAM)
	    (TRANSMIT-OBJECT TEMP1 STREAM)
	    (DOTIMES (J TEMP2)
	      (TRANSMIT-OBJECT (AREF OBJ J) STREAM)))
	   ((ARRAY-HAS-LEADER-P OBJ)
	    (TRANSMIT-OBJECT-UNHANDLED OBJ STREAM))
	   ((EQ (SETQ TEMP1 (ARRAY-TYPE OBJ)) 'ART-Q)
	    (SETQ TEMP2 (LENGTH OBJ))
	    (SEND STREAM :TYO (OPCODE SIMPLE-ARRAY-ART-Q))
	    (TRANSMITI TEMP2 STREAM)
	    (DOTIMES (J TEMP2)
	      (TRANSMIT-OBJECT (AREF OBJ J) STREAM)))
	   ((EQ TEMP1 'ART-8B)
	    (SETQ TEMP2 (LENGTH OBJ))
	    (SEND STREAM :TYO (OPCODE SIMPLE-ARRAY-ART-8B))
	    (TRANSMITI TEMP2 STREAM)
	    (SEND STREAM :STRING-OUT OBJ 0 TEMP2))
	   ('ELSE
	    (TRANSMIT-OBJECT-UNHANDLED OBJ STREAM))))
    (CONS
     (DO ((J 0 (1+ J))
	  (TEMP1 OBJ (CDR TEMP1)))
	 ((Atom TEMP1)
	  (COND ((NULL TEMP1)
		 (SEND STREAM :TYO (OPCODE LIST))
		 (TRANSMITI J STREAM)
		 (DOLIST (TEMP2 OBJ)
		   (TRANSMIT-OBJECT TEMP2 STREAM)))
		('ELSE
		 (SEND STREAM :TYO (OPCODE LIST*))
		 (TRANSMITI (1+ J) STREAM)
		 (DO ((TEMP2 OBJ (CDR TEMP2)))
		     ((ATOM TEMP2)
		      (TRANSMIT-OBJECT TEMP2 STREAM))
		   (TRANSMIT-OBJECT (CAR TEMP2) STREAM)))))))
    (INSTANCE
     (TRANSMIT-OBJECT-UNHANDLED OBJ STREAM))
    (T
     (TRANSMIT-OBJECT-UNHANDLED OBJ STREAM))))

(DEFUN RECEIVE-OBJECT (STREAM &AUX TEMP1 TEMP2 INDEX)
  (SETQ TEMP1 (SEND STREAM :TYI))
  (COND ((= TEMP1 (OPCODE OBJECT-ASSIGN-INDEX))
	 (SETQ INDEX (RECEIVEI STREAM))
	 (SETQ TEMP1 (SEND STREAM :TYI))))
  (COND ((= TEMP1 (OPCODE OBJECT-OF-INDEX))
	 (AREF *OBJ-ARRAY* (RECEIVEI STREAM)))
	((= TEMP1 (OPCODE OBJECT-NIL))
	 NIL)
	((= TEMP1 (OPCODE OBJECT-T))
	 T)
	((= TEMP1 (OPCODE POSITIVE-INTEGER))
	 (RECEIVEI STREAM))
	((= TEMP1 (OPCODE NEGATIVE-INTEGER))
	 (- (RECEIVEI STREAM)))
	((= TEMP1 (OPCODE SYMBOL))
	 (SETQ TEMP1 (INTERN (RECEIVE-OBJECT STREAM)
			     (RECEIVE-OBJECT STREAM)))
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP1))
	 TEMP1)
	((= TEMP1 (OPCODE SIMPLE-STRING))
	 (SETQ TEMP1 (RECEIVEI STREAM))
	 (SETQ TEMP2 (MAKE-ARRAY TEMP1 :TYPE 'ART-STRING))
	 (SEND STREAM :STRING-IN NIL TEMP2 0 TEMP1)
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP2))
	 TEMP2)
	((= TEMP1 (OPCODE SIMPLE-ARRAY-ART-8B))
	 (SETQ TEMP1 (RECEIVEI STREAM))
	 (SETQ TEMP2 (MAKE-ARRAY TEMP1 :TYPE 'ART-8B))
	 (SEND STREAM :STRING-IN NIL TEMP2 0 TEMP1)
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP2))
	 TEMP2)
	((= TEMP1 (OPCODE SIMPLE-NAMED-STRUCTURE))
	 (SETQ TEMP1 (RECEIVEI STREAM))
	 (SETQ TEMP2 (RECEIVE-OBJECT STREAM))
	 (SETQ TEMP2 (MAKE-ARRAY TEMP1 :NAMED-STRUCTURE-SYMBOL TEMP2
				 :TYPE 'ART-Q))
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP2))
	 (RECEIVE-OBJECT STREAM) ; THROW IT AWAY.
	 ;; SOMETHING VERY SCREWY ABOUT NAMED STRUCTURES
	 (DO ((J 1 (1+ J)))
	     ((= J TEMP1))
	   (SETF (AREF TEMP2 J) (RECEIVE-OBJECT STREAM)))
	 TEMP2)
	((= TEMP1 (OPCODE SIMPLE-ARRAY-ART-Q))
	 (SETQ TEMP1 (RECEIVEI STREAM))
	 (SETQ TEMP2 (MAKE-ARRAY TEMP1))
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP2))
	 (DOTIMES (J TEMP1)
	   (SETF (AREF TEMP2 J) (RECEIVE-OBJECT STREAM)))
	 TEMP2)
	((= TEMP1 (OPCODE SIMPLE-CHARACTER))
	 (CODE-CHAR (SEND STREAM :TYI)))
	((= TEMP1 (OPCODE HAIRY-CHARACTER))
	 (CODE-CHAR (SEND STREAM :TYI)
		    (SEND STREAM :TYI)
		    (SEND STREAM :TYI)))
	((= TEMP1 (OPCODE LIST))
	 (SETQ TEMP1 (RECEIVEI STREAM))
	 (SETQ TEMP2 (MAKE-LIST TEMP1))
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP2))
	 (DO ((TEMP1 TEMP2 (CDR TEMP1)))
	     ((NULL TEMP1))
	   (RPLACA TEMP1 (RECEIVE-OBJECT STREAM)))
	 TEMP2)
	((= TEMP1 (OPCODE LIST*))
	 (SETQ TEMP1 (RECEIVEI STREAM))
	 (SETQ TEMP2 (MAKE-LIST* TEMP1))
	 (IF INDEX (SETF (AREF *OBJ-ARRAY* INDEX) TEMP2))
	 (DO ((TEMP1 TEMP2 (CDR TEMP1)))
	     (NIL)
	   (RPLACA TEMP1 (RECEIVE-OBJECT STREAM))
	   (IF (NULL (CDR TEMP1))
	       (RETURN  (RPLACD TEMP1 (RECEIVE-OBJECT STREAM)))))
	 TEMP2)
	(T
	 (CERROR "return NIL"
		 "unknown object opcode received: ~D" temp1)
	 nil)))

    
(DEFUN TRANSMITI (POSITIVE-INTEGER STREAM &AUX LENGTH)
  (COND ((< POSITIVE-INTEGER 128)
	 (RETURN-FROM TRANSMITI (SEND STREAM :TYO POSITIVE-INTEGER)))
	((< (SETQ LENGTH (CEILING (HAULONG POSITIVE-INTEGER) 8)) 128)
	 (SEND STREAM :TYO (+ LENGTH 127)))
	('ELSE
	 (SEND STREAM :TYO 255)
	 (TRANSMITI LENGTH STREAM)))
  (DOTIMES (J LENGTH)
    (SEND STREAM :TYO (LDB (BYTE 8 (* 8 J)) POSITIVE-INTEGER))))

(DEFUN RECEIVEI (STREAM &AUX NUMBER)
  (LET ((LENGTH (SEND STREAM :TYI)))
    (COND ((< LENGTH 128)
	   (RETURN-FROM RECEIVEI LENGTH))
	  ((< (SETQ LENGTH (- LENGTH 127)) 128))
	  ('ELSE
	   (SETQ LENGTH (RECEIVEI STREAM))))
    (SETQ NUMBER 0)
    (DOTIMES (J LENGTH)
      (SETQ NUMBER (DPB (SEND STREAM :TYI)
			(BYTE 8 (* 8 J))
			NUMBER)))
    NUMBER))
	   

(DEFUN TRANSMIT-OBJECT-UNHANDLED (OBJ STREAM)
  (CERROR "transmit NIL instead"
	  "OBJECT TYPE OR MAKEUP NOT HANDLED: ~S" OBJ)
  (TRANSMIT-OBJECT NIL STREAM))


(DEFUN MAKE-LIST* (N)
  (CASEQ N
    (2 (LIST* NIL NIL))
    (3 (LIST* NIL NIL NIL))
    (4 (LIST* NIL NIL NIL NIL))
    (T (MAKE-LIST (1- N)))))


(defun hexdump (filename)
  (with-open-file (stream filename :characters nil :byte-size 8)
    (do ((j 0 (1+ j))
	 (A #(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\A #\B #\C #\D #\E #\F))
	 (c))
	((null (setq c (send stream :tyi))))
      (if (zerop (mod j 32)) (terpri))
      ; grossly slow (format t "~16,2,'0R" C)
      (SEND STANDARD-OUTPUT :TYO (AREF A (LDB #o0404 C)))
      (SEND STANDARD-OUTPUT :TYO (AREF A (LDB #o0004 C))))))


(DEFUN OBJCOMP-FILE (FILENAME)
  (LET ((FORMS)(INPUT-TRUENAME))
    (WITH-OPEN-FILE (STREAM FILENAME :DIRECTION :INPUT :ERROR :REPROMPT)
      (SETQ INPUT-TRUENAME (SEND STREAM :TRUENAME))
      (FS:READING-FROM-STREAM (X STREAM)
	(PUSH X FORMS)))
    (SAVE-OBJECT `(PROGN ,@(NREVERSE FORMS))
		 (SEND INPUT-TRUENAME :NEW-PATHNAME :TYPE "SAVE" :VERSION :NEWEST))))

