git.fiddlerwoaroof.com
src/ops-rhs.lisp
5092d8a5
 ;;; ****************************************************************
 ;;; OPS5 Interpreter ***********************************************
 ;;; ****************************************************************
 ;;; This Common Lisp version of OPS5 is in the public domain.  It is based
 ;;; in part on based on a Franz Lisp implementation done by Charles L. Forgy
 ;;; at Carnegie-Mellon University, which was placed in the public domain by
 ;;; the author in accordance with CMU policies.  Ported to Common Lisp by 
 ;;; George Wood and Jim Kowalski. CMU Common Lisp modifications by
 ;;; Dario Guise, Skef Wholey, Michael Parzen, and Dan Kuokka. 
 ;;; Modified to work in CLtL1, CLtL2 and X3J13 compatible lisps by 
 ;;; Mark Kantrowitz on 14-OCT-92.
 ;;; 
 ;;; This code is made available is, and without warranty of any kind by the
 ;;; authors or by Carnegie-Mellon University.
 ;;;
 
 ;;;; This file contains all the functions necessary for RHS actions
 ;;;; including $actions.
 
 (in-package "OPS")
 ;; see ops.lisp
983cb74c
                                         ;; (shadow '(remove write))
                                         ;; (export '(remove write make modify crlf))
5092d8a5
 
 ;;; External global variables
 
6c4d2158
 (defvar *size-result-array* nil)
 (defvar *in-rhs* nil)
 (defvar *current-wm* nil)
 (defvar *max-wm* nil)
 (defvar *action-count* nil)
 (defvar *critical* nil)
5092d8a5
 
 
 ;;; Internal global variables
 
6c4d2158
 (defvar *wmpart-list* nil)
 (defvar *wm-filter* nil)
 (defvar *wm* nil)
 (defvar *old-wm* nil)
 (defvar *result-array* nil)
 (defvar *variable-memory* nil)
 (defvar *last* nil)
 (defvar *max-index* nil)
 (defvar *next-index* nil)
 (defvar *data-matched* nil)
 (defvar *ce-variable-memory* nil)
 (defvar *rest* nil)
 (defvar *build-trace* nil)
5092d8a5
 
 
 ;;;; Functions for RHS evaluation
 
 (defun rhs-init ()
   ;; if the size of result-array changes, change the line in i-g-v which
   ;; sets the value of *size-result-array*
   (setq *size-result-array* 255.)	;255 /256 set by gdw
   (setq *result-array* (make-array 256 :initial-element nil)) ;jgk
   (setq *in-rhs* nil)
   (setq *build-trace* nil)
   (setq *max-wm* (setq *current-wm* 0.))
   (setq *action-count* 0.)
   (setq *critical* nil)
   (setq *wmpart-list* nil))
 
 
 (defun eval-rhs (pname data)
   (when *ptrace*
     (let ((port (trace-file)))
       (format port "~&~A. ~A" 
983cb74c
               *cycle-count* pname)
5092d8a5
       (time-tag-print data port)))
   (let ((node (gethash pname *topnode-table*)))
     (setq *data-matched* data
983cb74c
           *p-name* pname
           *last* nil)
5092d8a5
     (init-var-mem (var-part node))
     (init-ce-var-mem (ce-var-part node))
     (begin-record pname data)
     (let ((*in-rhs* t))
       (eval (rhs-part node)))
     (end-record))) 
 
 (defun eval-args (z)
   (prog (r)
983cb74c
      (rhs-tab 1.)
    la   (and (atom z) (return nil))
      (setq r (pop z))
      (when (eq r '^)
        (rhs-tab (car z))
        (setq r (cadr z))
        (setq z (cddr z)))
      (cond ((eq r '//)
             ($value (car z))
             (setq z (cdr z)))
            (t ($change r)))
      (go la))) 
5092d8a5
 
 ;;;; RHS actions
 ;;;; Some of these can be called at the top level.
 
 (defmacro make (&body z)
   `(ops-make ',z))
 
 (defmacro remove (&body z)
   `(ops-remove ',z))
 
 (defmacro modify (&body z)
   `(ops-modify ',z))
 
 (defmacro openfile (&body z)
   `(ops-openfile ',z))
 
 (defmacro closefile (&body z)
   `(ops-closefile ',z))
 
 (defmacro default (&body z)
   `(ops-default ',z))
 
 (defmacro write (&body z)
   `(ops-write ',z))
 
 (defmacro crlf (&body z)
   `(ops-crlf ',z))
 
 (defmacro tabto (&body z)
   `(ops-tabto ',z))
 
 (defmacro rjust (&body z)
   `(ops-rjust ',z))
 
 (defmacro call (&body z)
   `(ops-call ',z))
 
 (defmacro bind (&body z)
   `(ops-bind ',z))
 
 (defmacro cbind (&body z)
   `(ops-cbind ',z))
 
 (defmacro build (&body z)
   `(ops-build ',z))
 
 (defmacro substr (&body l)
   `(ops-substr ',l))
 
 (defmacro compute (&body z)
   `(ops-compute ',z))
 
 (defmacro litval (&body z)
   `(ops-litval ',z))
 
 (defmacro accept (&body z)
   `(ops-accept ',z))
 
 (defmacro acceptline (&body z)
   `(ops-acceptline ',z))
 
 (defmacro arith (&body z)
   `(ops-arith ',z))
 
 
 (defun ops-make (z)
   ($reset)
   (eval-args z)
   ($assert)) 
 
 (defun ops-remove (z)
   (prog (old)
983cb74c
      (when (not *in-rhs*)
        (return (top-level-remove z)))
    top  (and (atom z) (return nil))
      (setq old (get-ce-var-bind (car z)))
      (when (null old)
        (%warn '|remove: argument not an element variable| (car z))
        (return nil))
      (remove-from-wm old)
      (setq z (cdr z))
      (go top))) 
5092d8a5
 
 (defun ops-modify (z)
   (prog (old)
983cb74c
      (cond ((not *in-rhs*)
             (%warn '|cannot be called at top level| 'modify)
             (return nil)))
      (setq old (get-ce-var-bind (car z)))
      (cond ((null old)
             (%warn '|modify: first argument must be an element variable|
                    (car z))
             (return nil)))
      (remove-from-wm old)
      (setq z (cdr z))
      ($reset)
    copy (and (atom old) (go fin))
      ($change (car old))
      (setq old (cdr old))
      (go copy)
    fin  (eval-args z)
      ($assert))) 
5092d8a5
 
 (defun ops-bind (z)
   (prog (val)
983cb74c
      (cond ((not *in-rhs*)
             (%warn '|cannot be called at top level| 'bind)
             (return nil)))
      (cond ((< (length z) 1.)
             (%warn '|bind: wrong number of arguments to| z)
             (return nil))
            ((not (symbolp (car z)))
             (%warn '|bind: illegal argument| (car z))
             (return nil))
            ((= (length z) 1.) (setq val (gensym)))
            (t ($reset)
               (eval-args (cdr z))
               (setq val ($parameter 1.))))
      (make-var-bind (car z) val))) 
5092d8a5
 
 (defun ops-cbind (z)
   (cond ((not *in-rhs*)
983cb74c
          (%warn '|cannot be called at top level| 'cbind))
         ((not (= (length z) 1.))
          (%warn '|cbind: wrong number of arguments| z))
         ((not (symbolp (car z)))
          (%warn '|cbind: illegal argument| (car z)))
         ((null *last*)
          (%warn '|cbind: nothing added yet| (car z)))
         (t (make-ce-var-bind (car z) *last*)))) 
5092d8a5
 
 
 (defun ops-call (z)
   (let ((f (car z)))
     ($reset)
     (eval-args (cdr z))
     (funcall f))) 
 
 
 (defun halt () 
   (cond ((not *in-rhs*)
983cb74c
          (%warn '|cannot be called at top level| 'halt))
         (t (setq *halt-flag* t)))) 
5092d8a5
 
 (defun ops-build (z)
   (prog (r)
983cb74c
      (cond ((not *in-rhs*)
             (%warn '|cannot be called at top level| 'build)
             (return nil)))
      ($reset)
      (build-collect z)
      (setq r (unflat (use-result-array)))
      (and *build-trace* (funcall *build-trace* r))
      (compile-production (car r) (cdr r)))) 
5092d8a5
 
 (defun ops-compute (z) ($value (ari z))) 
 
983cb74c
                                         ;; arith is the obsolete form of compute
5092d8a5
 (defun ops-arith (z) ($value (ari z))) 
 
 ;;; Should change the division in this function to use / instead of floor
 (defun ari (x)
   (cond ((atom x)
983cb74c
          (%warn '|bad syntax in arithmetic expression | x)
          0.)
         ((atom (cdr x)) (ari-unit (car x)))
         ((eq (cadr x) '+)
          (+ (ari-unit (car x)) (ari (cddr x))))
         ;;"plus" changed to "+" by gdw
         ((eq (cadr x) '-)
          (- (ari-unit (car x)) (ari (cddr x))))
         ((eq (cadr x) '*)
          (* (ari-unit (car x)) (ari (cddr x))))
         ((eq (cadr x) '//)
          ;; was (floor (ari-unit (car x)) (ari (cddr x))) ;@@@ quotient? /
          ;; but changed to / by mk 10-15-92
          (/ (ari-unit (car x)) (ari (cddr x))))
         ((eq (cadr x) 'quotient)
          ;; for backward compatability
          (floor (ari-unit (car x)) (ari (cddr x))))
         ;;@@@ kluge only works for integers
         ;;@@@ changed to floor by jcp (from round)
         ((eq (cadr x) '\\)
          (mod (floor (ari-unit (car x))) (floor (ari (cddr x)))))
         (t (%warn '|bad syntax in arithmetic expression | x) 0.))) 
5092d8a5
 
 (defun ari-unit (a)
   (prog (r)
983cb74c
      (cond ((consp  a) (setq r (ari a)))	;dtpr\consp gdw
            (t (setq r ($varbind a))))
      (cond ((not (numberp r))
             (%warn '|bad value in arithmetic expression| a)
             (return 0.))
            (t (return r))))) 
5092d8a5
 
 (defun ops-substr (l)
   (prog (k elm start end)
983cb74c
      (cond ((not (= (length l) 3.))
             (%warn '|substr: wrong number of arguments| l)
             (return nil)))
      (setq elm (get-ce-var-bind (car l)))
      (cond ((null elm)
             (%warn '|first argument to substr must be a ce var|
                    l)
             (return nil)))
      (setq start ($varbind (cadr l)))
      (setq start ($litbind start))
      (cond ((not (numberp start))
             (%warn '|second argument to substr must be a number|
                    l)
             (return nil)))
      ;;###	(comment |if a variable is bound to INF, the following|
      ;;	 |will get the binding and treat it as INF is|
      ;;	 |always treated.  that may not be good|)
      (setq end ($varbind (caddr l)))
      (cond ((eq end 'inf) (setq end (length elm))))
      (setq end ($litbind end))
      (cond ((not (numberp end))
             (%warn '|third argument to substr must be a number|
                    l)
             (return nil)))
      ;;###	(comment |this loop does not check for the end of elm|
      ;;         |instead it relies on cdr of nil being nil|
      ;;         |this may not work in all versions of lisp|)
      (setq k 1.)
    la   (cond ((> k end) (return nil))
               ((not (< k start)) ($value (car elm))))
      (setq elm (cdr elm))
      (setq k (1+ k))
      (go la))) 
5092d8a5
 
 (defun genatom nil ($value (gensym))) 
 
 (defun ops-litval (z)
   (prog (r)
983cb74c
      (cond ((not (= (length z) 1.))
             (%warn '|litval: wrong number of arguments| z)
             ($value 0) 
             (return nil))
            ((numberp (car z)) ($value (car z)) (return nil)))
      (setq r ($litbind ($varbind (car z))))
      (cond ((numberp r) ($value r) (return nil)))
      (%warn '|litval: argument has no literal binding| (car z))
      ($value 0)))
 
 
 
 ;; rhs-tab implements the tab ('^') function in the rhs.  it has
 ;; four responsibilities:
 ;;	- to move the array pointers
 ;;	- to watch for tabbing off the left end of the array
 ;;	  (ie, to watch for pointers less than 1)
 ;;	- to watch for tabbing off the right end of the array
 ;;	- to write nil in all the slots that are skipped
 ;; the last is necessary if the result array is not to be cleared
 ;; after each use; if rhs-tab did not do this, $reset
 ;; would be much slower.
5092d8a5
 
 (defun rhs-tab (z) ($tab ($varbind z)))
 
 
 (defun time-tag-print (data port)
   (when (not (null data))
     (time-tag-print (cdr data) port)
     (princ '| | port)
     (princ (creation-time (car data)) port)))
 
 (defun init-var-mem (vlist)
   (prog (v ind r)
983cb74c
      (setq *variable-memory* nil)
    top  (and (atom vlist) (return nil))
      (setq v (car vlist))
      (setq ind (cadr vlist))
      (setq vlist (cddr vlist))
      (setq r (gelm *data-matched* ind))
      (setq *variable-memory* (cons (cons v r) *variable-memory*))
      (go top))) 
5092d8a5
 
 (defun init-ce-var-mem (vlist)
   (prog (v ind r)
983cb74c
      (setq *ce-variable-memory* nil)
    top  (and (atom vlist) (return nil))
      (setq v (car vlist))
      (setq ind (cadr vlist))
      (setq vlist (cddr vlist))
      (setq r (nth (1- ind) *data-matched*)) ; was ce-gelm
      (setq *ce-variable-memory*
            (cons (cons v r) *ce-variable-memory*))
      (go top))) 
5092d8a5
 
 (defun make-ce-var-bind (var elem)
   (push (cons var elem)
983cb74c
         *ce-variable-memory*)) 
5092d8a5
 
 (defun make-var-bind (var elem)
   (push (cons var elem) 
983cb74c
         *variable-memory*)) 
5092d8a5
 
 (defun get-ce-var-bind (x)
   (if (numberp x)
       (get-num-ce x)
       (let ((r (assoc x *ce-variable-memory*)))
983cb74c
         (when r 
           (cdr r))))) 
5092d8a5
 
 (defun get-num-ce (x)
   (prog (r l d)
983cb74c
      (setq r *data-matched*)
      (setq l (length r))
      (setq d (- l x))
      (and (> 0. d) (return nil))
    la   (cond ((null r) (return nil))
               ((> 1. d) (return (car r))))
      (setq d (1- d))
      (setq r (cdr r))
      (go la))) 
5092d8a5
 
 (defun build-collect (z)
   (prog (r)
983cb74c
    la   (and (atom z) (return nil))
      (setq r (car z))
      (setq z (cdr z))
      (cond ((consp  r)	;dtpr\consp gdw
             ($value '\()
             (build-collect r)
             ($value '\)))
            ((eq r '\\) ($change (car z)) (setq z (cdr z)))
            (t ($value r)))
      (go la))) 
5092d8a5
 
 (defun unflat (x)
   (setq *rest* x)
   (unflat*)) 
 
 (defun unflat* ()
   (if (atom *rest*)
       nil
       (let ((c (pop *rest*)))
983cb74c
         (cond ((eq c '\() (cons (unflat*) (unflat*)))
               ((eq c '\)) nil)
               (t (cons c (unflat*))))))) 
5092d8a5
 
 ;;;; $Functions.
 ;;;; These functions provide an interface to the result array.
 ;;;; The result array is used to organize attribute values into their
 ;;;; correct slot.
 
 (defun $litbind (x)
   (if (symbolp x)
       (or (literal-binding-of x)
983cb74c
           x)
5092d8a5
       x)) 
 
 (defun $varbind (x)
   (if *in-rhs*
       ;; If we're in the RHS, lookup the binding. 
       (let ((binding (assoc x *variable-memory*)))
983cb74c
         (if binding
             (cdr binding)
             x))
5092d8a5
       ;; Otherwise just return it unevaluated.
       x))
 
 (defun $change (x)
   (if (consp  x)			;dtpr\consp gdw
       (eval-function x)	
       ($value ($varbind x)))) 
 
 (defun $reset nil
   (setq *max-index* 0.)
   (setq *next-index* 1.)) 
 
 (defun $tab (z)
   (prog (edge next)
983cb74c
      (setq next ($litbind z))
      (when (floatp next)
        (setq next (floor next)))
      (when (or (not (numberp next)) 
                (> next *size-result-array*)
                (> 1. next))		; ( '| |)
        (%warn '|illegal index after ^| next)
        (return *next-index*))
      (setq edge (- next 1.))
      (cond ((> *max-index* edge) (go ok)))
    clear (when (== *max-index* edge) (go ok))
      (setf (aref *result-array* edge) nil)
      (decf edge)
      (go clear)
    ok   (setq *next-index* next)
      (return next))) 
5092d8a5
 
 (defun $value (v)
   (cond ((> *next-index* *size-result-array*)
983cb74c
          (%warn '|index too large| *next-index*))
         (t
          (and (> *next-index* *max-index*)
               (setq *max-index* *next-index*))
          (setf (aref *result-array* *next-index*) v)
          (incf *next-index*)))) 
5092d8a5
 
 (defun $assert nil
   (setq *last* (use-result-array))
   (add-to-wm *last* nil))
 
 (defun $parametercount ()
   *max-index*)
 
 (defun $parameter (k)
   (cond ((or (not (numberp k)) (> k *size-result-array*) (< k 1.))
983cb74c
          (%warn '|illegal parameter number | k)
          nil)
         ((> k *max-index*) nil)
         (t (aref *result-array* k))))
5092d8a5
 
 (defun $ifile (x) 
   (when (symbolp x)
     (gethash x *inputfile-table*)))
 
 (defun $ofile (x) 
   (when (symbolp x) 
     (gethash x *outputfile-table*)))
 
 ;;; 
 
 (defun use-result-array ()
   "Use-result-array returns the contents of the result array as a list."
   ;; is *max-index* acting like a fill-pointer? Then we can't just use
   ;; coerce, unless we change *result-array* to use a fill pointer.
   ;; Also, note that index 0 of the array is ignored.
   (prog (k r)
983cb74c
      (setq k *max-index*)
      (setq r nil)
    top  (and (== k 0.) (return r))
      (setq r (cons (aref *result-array* k) r))
      (decf k)
      (go top))) 
5092d8a5
 
 (defun eval-function (form)
   (if (not *in-rhs*)
       (%warn '|functions cannot be used at top level| (car form))
       (eval form)))
 
 ;;;; WM maintaining functions
 
 ;;; The order of operations in the following two functions is critical.
 ;;; add-to-wm order: (1) change wm (2) record change (3) match 
 ;;; remove-from-wm order: (1) record change (2) match (3) change wm
 ;;; (back will not restore state properly unless wm changes are recorded
 ;;; before the cs changes that they cause)  (match will give errors if 
 ;;; the thing matched is not in wm at the time)
 
 (defun add-to-wm (wme override)
   (prog (fa z part timetag port)
983cb74c
      (setq *critical* t)
      (setq *current-wm* (1+ *current-wm*))
      (and (> *current-wm* *max-wm*) (setq *max-wm* *current-wm*))
      (setq *action-count* (1+ *action-count*))
      (setq fa (wm-hash wme))
      (or (member fa *wmpart-list*)
          (setq *wmpart-list* (cons fa *wmpart-list*)))
      (setq part (gethash fa *wmpart*-table*))
      (cond (override (setq timetag override))
            (t (setq timetag *action-count*)))
      (setq z (cons wme timetag))
      (setf (gethash fa *wmpart*-table*) (cons z part))
      (record-change '=>wm *action-count* wme)
      (match 'new wme)
      (setq *critical* nil)
      (cond ((and *in-rhs* *wtrace*)
             (setq port (trace-file))
             (terpri port)
             (princ '|=>wm: | port)
             (ppelm wme port))))) 
5092d8a5
 
 ;;; remove-from-wm uses eq, not equal to determine if wme is present
 
 (defun remove-from-wm (wme)
   (prog (fa z part timetag port)
983cb74c
      (setq fa (wm-hash wme))
      (setq part (gethash fa *wmpart*-table*))
      (setq z (assoc wme part))
      (or z (return nil))
      (setq timetag (cdr z))
      (cond ((and *wtrace* *in-rhs*)
             (setq port (trace-file))
             (terpri port)
             (princ '|<=wm: | port)
             (ppelm wme port)))
      (setq *action-count* (1+ *action-count*))
      (setq *critical* t)
      (setq *current-wm* (1- *current-wm*))
      (record-change '<=wm timetag wme)
      (match nil wme)
      (setf (gethash fa *wmpart*-table*) (delete z part :test #'eq))
      (setq *critical* nil))) 
5092d8a5
 
 ;;; mapwm maps down the elements of wm, applying fn to each element
 ;;; each element is of form (datum . creation-time)
 
 (defun mapwm (fn)
   (dolist (wmpl *wmpart-list*)
     (mapc fn (gethash wmpl *wmpart*-table*)))
983cb74c
   #+(or)
   (prog (wmpl part)
      (setq wmpl *wmpart-list*)
    lab1 (cond ((atom wmpl) (return nil)))
      (setq part (gethash (car wmpl) *wmpart*-table*))
      (setq wmpl (cdr wmpl))
      (mapc fn part)
      (go lab1))) 
5092d8a5
 
 (defun ops-wm (a) 
   (mapc #'(lambda (z) (terpri) (ppelm z *standard-output*)) 
983cb74c
         (get-wm a))
5092d8a5
   nil) 
 
 (defun creation-time (wme)
   (cdr (assoc wme (gethash (wm-hash wme) *wmpart*-table*)))) 
 
 (defun get-wm (z)
   (setq *wm-filter* z)
   (setq *wm* nil)
   (mapwm #'(lambda (elem) 
983cb74c
              (when (or (null *wm-filter*)
                        (member (cdr elem) *wm-filter*)) ;test #'equal
                (push (car elem) *wm*))))
5092d8a5
   (prog2 nil *wm* (setq *wm* nil))) 
 
 (defun wm-hash (x)
   (cond ((not x) '<default>)
983cb74c
         ((not (car x)) (wm-hash (cdr x)))
         ((symbolp (car x)) (car x))
         (t (wm-hash (cdr x))))) 
5092d8a5
 
 (defun refresh ()
   (setq *old-wm* nil)
   (mapwm #'refresh-collect)
   (mapc #'refresh-del *old-wm*)
   (mapc #'refresh-add *old-wm*)
   (setq *old-wm* nil)) 
 
 (defun refresh-collect (x)
   (push x *old-wm*)) 
 
 (defun refresh-del (x) (remove-from-wm (car x))) 
 
 (defun refresh-add (x) (add-to-wm (car x) (cdr x))) 
 
 ;;; *EOF*