;;; -*- Mode:LISP; Package:COMPILER; Readtable:ZL; Base:10 -*-

#|

FOREIGN-SYMBOL.LISP

Provides a compile-time customization mechanism for solving problems
with references to symbols in undefined packages or unloaded systems.

The real-word application of this is for the cross-compiler, when Lambda
system files contain references to symbols in Falcon packages that are
not known to Lambda.  (Got that?)

For each "foreign symbol," a property list of values is associated with
the foreign symbol tag, which is generally a keyword.  For each target
computer, a "native value" for the foreign symbol can be associated with
a particular tag.  (These native values can be anything, but the
existing requirement is to obtain a different symbol as the foreign
symbol value.)

Here's an example of the application of FOREIGN-SYMBOL.  Instead of
doing the following and losing:

  (do-something-with
    (cond
      ((eq *target-computer* 'k)
        ;;array:art-q ;;WKF: 5/18/88
        #+lambda k-array:art-q #+falcon array:art-q
        'art-q)
      ((eq *target-computer* 'lambda)
        ;;etc.
        )))

...one would do something like the following:

1. Define the foreign symbols for each processor, either a) in separate
files using DEF-FOREIGN-SYMBOL, or b) centrally for multiple processors
using DEF-FOREIGN-SYMBOLS.  For example:

  a.1: Define the Lambda native symbol, e.g., in "SYS:SYS;TYPES":

   #+lambda
   (def-foreign-symbol :art-q 'ZL:ART-Q)

  a.2: Define the Falcon native symbol elsewhere:

   (def-foreign-symbol :art-q
     #+lambda 'K-ARRAY:ART-Q
     #+falcon 'ARRAY:ART-Q)

OR:

  b: Define all the versions in one file (currently SYS:SYS;DEF-FOREIGN-SYMBOLS.LISP):
   
   (def-foreign-symbols :art-q
     (:lambda 'zl:art-q)
     #+falcon-system-loaded
     (:k #+lambda 'k-array:art-q #+falcon array:art-q))

3: Refer to the foreign symbol where needed using FOREIGN-SYMBOL:

(defun foo () (print (foreign-symbol :art-q)))

Additional features:

1. FOREIGN-SYMBOL is SETF'able; this translates into DEF-FOREIGN-SYMBOL.

2. The source file of a FOREIGN-SYMBOL is recorded and checked, like DEFUN's, etc.

3. FOREIGN-SYMBOL-VALUE is also provided to provide compile-time lookups
with a further level of indirection: the SYMBOL-VALUE of the native
symbol is returned.

4. INLINE-FOREIGN-SYMBOL and INLINE-FOREIGN-SYMBOL-VALUE are equivalent
to FOREIGN-SYMBOL and FOREIGN-SYMBOL-VALUE, respectively, but they get
optimized into the current (compile-time) values.  This is for code that
is only being compiled to work on or for one processor, and that doesn't
need to get the foreign symbols dynamically, at run time.  I don't know
if anything in the system files will need this.

|#

(defvar *foreign-symbol-table* (make-hash-table)
	"Compile-time lookup table for FOREIGN-SYMBOL entries")

;;;Utility look-up function:

(defun lookup-foreign-symbol (foreign-symbol &optional error-p new-value-p &aux native-value)
  (declare (eh:error-reporter))
  (declare (values native-value value-found-p))
  (flet ((error-string ()
	  (format nil "There is no declared foreign-symbol value for ~S" foreign-symbol)))
    (cond
      ((setq native-value
	     (car-safe (getf (gethash foreign-symbol *foreign-symbol-table*) *target-computer*)))
       (values native-value t))
      (error-p  (error (error-string)))
      (new-value-p
       ;;If allowed, user can set value for undefined foreign-symbol
       (setq native-value (cerror t nil nil (error-string)))
       (def-foreign-symbol foreign-symbol native-value)
       (values native-value t))
      (t (values nil nil)))))

;;;Normal lookup interface:

(defun foreign-symbol (FOREIGN-SYMBOL &optional no-error-p new-value-p)
  "Returns the version of FOREIGN-SYMBOL appropriate to the compilation target
  computer, according to the appropriate CLAUSE in CLAUSES.  
  The second return value is T when the lookup was successful.
NO-ERROR-P non-NIL means don't signal an error when foreign symbol is undefined;
  rather, the second value returned is NIL.
If NEW-VALUE-P is also non-NIL,  the user is offfered a chance to handle an
  undefined foreign symbol by specifying a new value (in the error handler)."
  (lookup-foreign-symbol foreign-symbol (null no-error-p) new-value-p))

;;;Single-processor definition interface:

(defun def-foreign-symbol (foreign-symbol native-value &optional (target-computer *target-computer*))
  (declare (zwei:indentation 2 1))
  (setf (getf (gethash foreign-symbol *foreign-symbol-table*) target-computer)
	(ncons native-value))
  (si:record-source-file-name foreign-symbol 'foreign-symbol))

(defsetf foreign-symbol def-foreign-symbol)

;;;Multiple-processor definition interface:

(defmacro def-foreign-symbols (foreign-symbol &rest native-clauses)
  "Define native processor symbolic equivelents for FOREIGN-SYMBOL.
Each of the remaining arguments is a native processor clause, which
resembles an association list.
The first element of each clause is a processor symbol (e.g., :LAMBDA or :K).
The second element of each clause is the native equivalent symbol name (normally quoted)."
  (declare (zwei:indentation 1 1))
  `(progn
     ,@(do ((clauses native-clauses (cdr clauses))
	    (defs nil))
	   ((null clauses) (reverse defs))
	 (let ((clause (first clauses)))
	   (push (list 'def-foreign-symbol foreign-symbol
		       (second clause)
		       (second (assoc (first clause)
				      `((:lambda 'lambda-interface)
					(:falcon 'k)	;;;@@@Just a synonym.  Fix this later?
					(:k 'k)))))
		 defs)))))

;;;Lookup variant for getting symbol's value:

(defun foreign-symbol-value (foreign-symbol &optional no-error-p new-value-p)
  (symbol-value (lookup-foreign-symbol foreign-symbol (null no-error-p) new-value-p)))

(deff inline-foreign-symbol 'foreign-symbol)
(deff inline-foreign-symbol-value 'foreign-symbol-value)

(defoptimizer compile-time-foreign-symbol inline-foreign-symbol (form)
  `(quote ,(foreign-symbol (second form) (third form))))

(defoptimizer compile-time-foreign-symbol-value inline-foreign-symbol-value (form)
  (foreign-symbol-value (second form) (third form)))

#| Tests:

(def-foreign-symbols :art-q
  (:lambda 'zl:art-q)
  #+falcon-system-loaded
  (:k #+lambda 'k-array:art-q #+falcon array:art-q))

(defun foo () (print (foreign-symbol :art-q)))

(defun foo2 () (print (foreign-symbol-value :art-q)))

(defun foo3 () (print `(quote ,(foreign-symbol :art-q))))

(defun ifoo () (print (inline-foreign-symbol :art-q)))

(defun ifoo2 () (print (inline-foreign-symbol-value :art-q)))

(defun ifoo3 () (print `(quote ,(inline-foreign-symbol :art-q))))

|#

