;;; -*- Mode: Lisp; Package: Prolog; Base: 8. ; -*-
;;; (C) Copyright 1983,1984,1985, Uppsala University

;; This file contains code to replace micro-coded primitives of LM-Prolog.

;To go away when we switch to lexical closures.
#-LEXICAL
(defsubst %invoke (x) (apply (car x) (cdr x)))

(defmacro %dereference (x)
  (cond ((symbolp x)
         `(cond ((value-cell-p ,x) (contents ,x)) (t ,x)))
	((and (consp x)
	      (memq (car x)
		    '(quote function prolog-cons prolog-list prolog-list*)))
	 x)
        (t `(let ((x1 ,x))
              (cond ((value-cell-p x1) (contents x1)) (t x1))))))

(defmacro %reference (x)
  (cond ((symbolp x)
         `(cond ((value-cell-p ,x)
                 (%make-pointer dtp-external-value-cell-pointer ,x))
                (t ,x)))
	((and (consp x)
	      (memq (car x)
		    '(quote function prolog-cons prolog-list prolog-list*)))
	 x)
        (t `(let ((x1 ,x))
              (cond ((value-cell-p x1)
                     (%make-pointer dtp-external-value-cell-pointer x1))
                    (t x1))))))

#-3600
(progn 'compile
(defun %cell (name)
  (let ((cell (%make-pointer dtp-locative (cons-in-area nil name *prolog-work-area*))))
    (setf (contents cell) cell)))

(defun %cell0 ()
  (let ((cell (%make-pointer dtp-locative (SI:%MAKE-LIST NIL *prolog-work-area* 1))))
    (setf (contents cell) cell)))

)

#+3600
(progn 'compile
(defun %prolog-list (l &rest elements)
  (without-interrupts
   (let ((cons (si:%allocate-list-block l (or *prolog-work-area* default-cons-area))))
     (do ((cell cons (1+ cell))
	  (l l (1- l))
	  (elements elements (cdr elements)))
	 ((cond ((= l 1)
		 (si:%p-store-cdr-and-contents cell (%reference (car elements)) cdr-nil)
		 t)))		 
       (si:%p-store-cdr-and-contents cell (%reference (car elements)) cdr-next))
     (%make-pointer dtp-list cons))))

(defun %prolog-list* (l &rest elements)
  (without-interrupts
   (let ((cons (si:%allocate-list-block l (or *prolog-work-area* default-cons-area))))
     (do ((cell cons (1+ cell))
	  (l l (1- l))
	  (elements elements (cdr elements)))
	 ((cond ((= l 2)
		 (si:%p-store-cdr-and-contents cell (%reference (car elements)) cdr-normal)
		 (si:%p-store-cdr-and-contents (1+ cell) (%reference (cadr elements)) cdr-nil)
		 t)))		 
       (si:%p-store-cdr-and-contents cell (%reference (car elements)) cdr-next))
     (%make-pointer dtp-list cons))))

(defun prolog-cons (x y)
  (without-interrupts
   (let ((cons (si:%allocate-list-block 2 (or *prolog-work-area* default-cons-area))))
     (si:%p-store-cdr-and-contents cons (%reference x) cdr-normal)
     (si:%p-store-cdr-and-contents (1+ cons) (%reference y) cdr-nil)
     (%make-pointer dtp-list cons))))

(defun %cell (name)
  (without-interrupts
   (let* ((cons (si:%allocate-list-block 2 (or *prolog-work-area* default-cons-area)))
	  (pointer (%make-pointer dtp-locative cons)))
     (si:%p-store-cdr-and-contents cons pointer cdr-normal)
     (si:%p-store-cdr-and-contents (1+ cons) name cdr-nil)
     pointer)))

(defun %cell0 ()
  (without-interrupts
   (let* ((cons (si:%allocate-list-block 1 (or *prolog-work-area* default-cons-area)))
	  (pointer (%make-pointer dtp-locative cons)))
     (si:%p-store-cdr-and-contents cons pointer cdr-nil)
     pointer)))

)

(defsubst %uvar-p (x) (memq (%data-type x) '(#.dtp-locative  #.dtp-instance)))

(defsubst make-unbound (value-cell) (%p-store-contents value-cell value-cell))

;Code depends on this returning NIL.
(deffun %untrail (mark)
  (cond ((> *trail* mark)
         (let ((item (array-pop *trail-array*)))
           (cond ((value-cell-p item) (make-unbound item))
                 (t (invoke item))))
         (%untrail mark))))

(defsubst nth-element (index vector)
  (%p-contents-offset vector index))

(defsubst %unify-term-with-term (x y) (unify x y))

(defmacro bind-cell (x y)
  (cond ((atom x) `(let () (setf (contents ,x) (%reference ,y)) (trail ,x)))
	(t `(let ((bind-cell-temp ,x))
	      (setf (contents bind-cell-temp) (%reference ,y))
	      (trail bind-cell-temp)))))

(DEFINE-DISPATCH-TABLE %CONSTRUCT (TEMPLATE) ((CDR TEMPLATE)) ((CAR TEMPLATE))
		       8. ':initial-value #+3600 #'prog1 #-3600 #'list*)

(DEFINE-DISPATCH-ENTRY %CONSTRUCT 1 (TARGS)
  (let ((cell (%cell (REST1 TARGS))))
    (setf (nth-element (FIRST TARGS) *vector*) cell)
    cell))

(DEFINE-DISPATCH-ENTRY %CONSTRUCT 2 (TARGS)
  (nth-element (FIRST TARGS) *vector*))

(ALTER-DISPATCH-ENTRY %CONSTRUCT 3 '%CONSTRUCT3)

(defsubst %construct3 (TARGS)
  ;;this could be more clever about CDR-coding
  (prolog-cons (%construct (FIRST TARGS)) (%construct (REST1 TARGS))))
  
(ALTER-DISPATCH-ENTRY %CONSTRUCT 4 '%CONSTRUCT4)

(defsubst %construct4 (TARGS)
  (let ((cell (%cell (REST1 TARGS))))
    (setf (nth-element (FIRST TARGS) *vector*) cell)
    (make-instance-in-area *prolog-work-area* 'read-only-variable
                           ':cell cell)))

(ALTER-DISPATCH-ENTRY %CONSTRUCT 5 '%CONSTRUCT5)

(defsubst %construct5 (TARGS)
  (let ((cell (nth-element (FIRST TARGS) *vector*)))
    (cond ((value-cell-p cell)
	   (make-instance-in-area *prolog-work-area* 'read-only-variable
				  ':cell cell))
	  (t cell))))

(alter-dispatch-entry %construct 6 '%construct6)

(defsubst %construct6 (targs)
  (make-instance-in-area *prolog-work-area* 'read-only-variable
			 ':cell (%cell targs)))

(alter-dispatch-entry %construct 7 '%cell)

(define-dispatch-table %unify-term-with-template (term template) (term (cdr template))
		       ((car template))
		       8.)

(alter-dispatch-entry %unify-term-with-template 0 'unify-without-occur-check)

(define-dispatch-entry %unify-term-with-template 1 (term targs)
  (setf (nth-element (first targs) *vector*) term)
  t)

(define-dispatch-entry %unify-term-with-template 2 (term targs)
  (unify term (%dereference (nth-element (first targs) *vector*))))

(define-dispatch-entry %unify-term-with-template 3 (term targs)
  (cond ((consp term)
	 (and (%unify-term-with-template (first term) (first targs))
	      (%unify-term-with-template (rest1 term) (rest1 targs))))
	((%uvar-p term) ;;otherwise don't bother to construct the term 
	 (bind-cell-check term (%construct3 targs)))))

(define-dispatch-entry %unify-term-with-template 4 (term targs)
  (cond ((value-cell-p term)
	 (bind-cell term (%construct4 targs)))
	(t nil)))

(define-dispatch-entry %unify-term-with-template 5 (term targs)
  (let ((old-value (%dereference (nth-element (first targs) *vector*))))
    (cond ((value-cell-p term)
	   (cond ((value-cell-p old-value)
		  (bind-cell term
			     (make-instance-in-area *prolog-work-area*
						    'read-only-variable
						    ':cell old-value)))
		 (t (bind-cell term old-value))))
	  ((value-cell-p old-value) nil)
	  (t (unify old-value term)))))

(define-dispatch-entry %unify-term-with-template 6 (term targs)
  (cond ((value-cell-p term)
	 (bind-cell term (%construct6 targs)))))

(define-dispatch-entry %unify-term-with-template 7 (ignore ignore)
  t)

;;Unifyers come in varying degree of specialization...

;;VARIABLE and not-PREVENT
(defun bind-cell-without-occur-check (x y)
  (cond ((eq x y))
	((value-cell-p x) (bind-cell x y))
	((value-cell-p y) (bind-cell y x))
	((send x ':unify y))))

;;VARIABLE and PREVENT
(defun bind-cell-with-occur-check (x y)
  (cond ((eq x y))
	((value-cell-p x) (and (occur-check x y) (bind-cell x y)))
	((value-cell-p y) (bind-cell y x))
	((send x ':unify y))))

;;NOT-LIST and NOT-VARIABLE and NOT-PREVENT
(defsubst %uvar-constant (x y)
  (select (%data-type x)
    (dtp-locative (bind-cell x y))
    (dtp-instance (send x ':unify y))
    (otherwise (equal x y))))

;;NOT-LIST and NOT-VARIABLE and PREVENT
(defsubst %uvar-constant-occur-check (x y)
  (select (%data-type x)
    (dtp-locative (and (occur-check x y) (bind-cell x y)))
    (dtp-instance (send x ':unify y))
    (otherwise (equal x y))))

;;NOT-LIST and NOT-LIST
(defsubst %uvar (x y) 
  (cond ((= dtp-locative (%data-type x)) (bind-cell x y))
        ((= dtp-locative (%data-type y)) (bind-cell y x))
        ((= dtp-instance (%data-type x)) (send x ':unify y))
        ((= dtp-instance (%data-type y)) (send y ':unify x))
        ((equal x y))))

;;? AND ? and IGNORE
(deffun unify-without-occur-check (x y)
  (cond ((eq x y))
	((consp x)
	 (cond ((consp y)
		(and (unify-without-occur-check (car x) (car y))
		     (unify-without-occur-check (cdr x) (cdr y))))
	       ((%uvar-constant y x))))
	((consp y)
	 (%uvar-constant x y))
	((%uvar x y))))

;;? AND ? and PREVENT
(deffun unify-with-occur-check (x y)
  (cond ((eq x y))
	((consp x)
	 (cond ((consp y)
		(and (unify-with-occur-check (car x) (car y))
		     (unify-with-occur-check (cdr x) (cdr y))))
	       ((%uvar-constant-occur-check y x))))
	((consp y)
	 (%uvar-constant-occur-check x y))
        ((%uvar x y))))

;;? AND ? and HANDLE
;;Can't tail recurse due to WITH-CONS-BOUND-TO-CONS.
(defun unify-handle-circularity (x y)
  (cond ((eq x y))
	((consp x)
	 (cond ((consp y)
		(WITH-CONS-PARTS x
		  (WITH-CONS-PARTS y
		    (let ((first-x (first x))
			  (rest-x (rest1 x))
			  (first-y (first y))
			  (rest-y (rest1 y)))
		      (cond ((and (eq first-x first-y) (eq rest-x rest-y)))
			    (t (WITH-CONS-BOUND-TO-CONS x y
				 (and (unify-handle-circularity first-x first-y)
				      (unify-handle-circularity
					(%DEREFERENCE rest-x)
					(%DEREFERENCE rest-y))))))))))
	       ((%uvar-constant y x))))
	((consp y)
	 (%uvar-constant x y))
        ((%uvar x y))))

(defun install-circularity-mode-ignore ()
  (setf #'bind-cell-check 'bind-cell-without-occur-check)
  (setf #'unify 'unify-without-occur-check))

(defun install-circularity-mode-prevent ()
  (setf #'bind-cell-check 'bind-cell-with-occur-check)
  (setf #'unify 'unify-with-occur-check))

(defun install-circularity-mode-handle ()
  (setf #'bind-cell-check 'bind-cell-without-occur-check)
  (setf #'unify 'unify-handle-circularity))

(install-circularity-mode-ignore)

(deffun occur-check (x y)
  (cond ((consp y)
         (and (occur-check x (car y)) (occur-check x (cdr y))))
        ((typep y 'prolog-flavor)
         (multiple-value-bind (ordinary-term prolog-flavor-p)
             (send y ':ordinary-term)
         (and (not prolog-flavor-p) (occur-check x ordinary-term))))
        (t (neq x y))))

(defsubst occurs-in (x y) (not (occur-check x y)))
