;;;;
;;;; DDE Support for XLISP-STAT
;;;; Copyright (c) 1999, by Luke Tierney
;;;; You may give out copies of this software; for conditions see the file
;;;; COPYING included with this distribution.
;;;;
(in-package "SYSTEM")
;;;;
;;;; Client Functions
;;;;
(export '(dde-request dde-poke dde-execute))
(defun dde-execute (con cmd &key timeout)
(if timeout
(dde-client-transaction con :data cmd :timeout timeout)
(dde-client-transaction con :data cmd)))
(defun dde-request (con item &key binary timeout)
(if timeout
(dde-client-transaction con :type :request :item item :binary binary
:timeout timeout)
(dde-client-transaction con :type :request :item item :binary binary)))
(defun dde-poke (con item value &key timeout)
(let ((vstring (if (stringp value) value (format nil "~s" value))))
(if timeout
(dde-client-transaction con :type :poke :item item :data vstring
:timeout timeout)
(dde-client-transaction con :type :poke :item item :data vstring))))
;;**** A little example:
(defun dde-eval (e)
(let* ((c (dde-connect "XLISP-STAT"))
(success (if c (dde-execute c (format nil "~s" e)) nil))
(v (if success (dde-request c "value") nil)))
(when c (dde-disconnect c))
(if success
(read-from-string v)
(error "evaluation failed"))))
;;;;
;;;; Server Support
;;;;
(defconstant *dde-servers* (make-hash-table :test 'equal))
(defconstant *dde-conversations* (make-hash-table))
(defparameter *dde-debug* nil)
;;**** need to be able to remove service too
(defun dde-add-server (server)
(let ((service (string-upcase (send server :name)))
(old (gethash service *dde-servers*)))
(when (or old (dde-name-service service))
(setf (gethash service *dde-servers*) server)
t)))
(defun dde-find-server (name)
(values (gethash (string-upcase name) *dde-servers*)))
;;**** could use a convention about getting back error info from executes
(defun dde-server-callback (type fmt hconv hsz1 hsz2 data dw1 dw1)
(dde-debug "Server args: ~s~%" (list type fmt hconv hsz1 hsz2 data dw1 dw1))
(ignore-errors
(case type
(:connect
(let ((server (gethash hsz2 *dde-servers*)))
(and server (send server :has-topic hsz1))))
(:connect-confirm
(let* ((server (gethash hsz2 *dde-servers*))
(conv (send server :make-conversation hsz1)))
(setf (gethash hconv *dde-conversations*) conv)))
(:wildconnect
(let ((val nil))
(flet ((servs (servname server)
(let ((topics (send server :topics)))
(dolist (topic topics)
(push (list servname topic) val)))))
(maphash #'servs *dde-servers*)
val)))
(t (let ((conv (gethash hconv *dde-conversations*)))
(case type
(:execute (send conv :execute data))
(:request (send conv :request hsz2))
(:poke (send conv :poke hsz2 data))
(:disconnect
(remhash hconv *dde-conversations*)
(send conv :disconnect))))))))
(defun dde-debug (fmt &rest args)
(when *dde-debug* (apply #'format *debug-io* fmt args)))
;;;;
;;;; Standard Server
;;;;
(defproto dde-server-proto '(name topics))
(defmeth dde-server-proto :isnew (name)
(setf (slot-value 'name) name))
(defmeth dde-server-proto :name () (slot-value 'name))
(defmeth dde-server-proto :has-topic (topic)
(if (assoc topic (slot-value 'topics) :test #'equal) t nil))
(defmeth dde-server-proto :topics ()
(mapcar #'first (slot-value 'topics)))
(defmeth dde-server-proto :add-topic (topic factory)
(let* ((topic (string-upcase topic))
(entry (assoc topic (slot-value 'topics) :test #'equal)))
(if entry
(setf (second entry) factory)
(push (list topic factory) (slot-value 'topics)))))
(defmeth dde-server-proto :make-conversation (topic)
(let ((confac (second (assoc topic (slot-value 'topics) :test #'equal))))
(if (objectp confac)
(send confac :new self topic)
(funcall confac self topic))))
;;;;
;;;; Standard Conversation
;;;;
(defproto dde-conversation-proto '(server topic value))
(defmeth dde-conversation-proto :isnew (server topic)
(setf (slot-value 'server) server)
(setf (slot-value 'topic) topic))
;; Using the following modified readtable allows commands to be
;; enclosed in [...]. This seems to be necessary to properly handle
;; execute transactions sent by Excel.
(defconstant *dde-readtable* (copy-readtable nil))
(set-macro-character #\[ #'(lambda (x y) (values)) t *dde-readtable*)
(set-macro-character #\] #'(lambda (x y) (values)) t *dde-readtable*)
(defmeth dde-conversation-proto :execute (cmd)
(let ((*readtable* *dde-readtable*)
(eof (cons nil nil)))
(with-input-from-string (s cmd)
(do ((expr (read s nil eof) (read s nil eof)))
((eq expr eof))
(setf (slot-value 'value) (eval expr))))
t))
(defmeth dde-conversation-proto :request (item)
(when (equal item "VALUE")
(format nil "~s" (slot-value 'value))))
(defmeth dde-conversation-proto :poke (item data) nil)
(defmeth dde-conversation-proto :disconnect () nil)
;;;;
;;;; Initialize the Standard Server
;;;;
(let ((server (send dde-server-proto :new "XLISP-STAT")))
(send server :add-topic "XLISP-STAT" dde-conversation-proto)
(send server :add-topic "SYSTEM" dde-conversation-proto)
(setf (gethash "XLISP-STAT" system::*dde-servers*) server))
;;;;
;;;; Command Line Conversation Prototype
;;;;
(defproto cmdline-conversation-proto nil nil dde-conversation-proto)
;;**** get this internally?
(defconstant *banner*
(format nil "XLISP-PLUS version 3.04~%~
Portions Copyright (c) 1988, by David Betz.~%~
Modified by Thomas Almy and others.~%~
XLISP-STAT Release ~d.~d.~d.~%~
Copyright (c) 1989-1999, by Luke Tierney.~%"
xls-major-release
xls-minor-release
xls-subminor-release))
(defmeth cmdline-conversation-proto :isnew (server topic)
(call-next-method server topic)
(setf (slot-value 'value)
(format nil "~a~%~a" *banner* (make-prompt-string))))
(defun make-prompt-string ()
(if (eq (find-package "USER") *package*)
"> "
(format nil "~a> " (package-name *package*))))
(defun read-eval-print-from-string (string)
(with-input-from-string (*standard-input* string)
(with-output-to-string (*standard-output*)
(let ((*debug-io* *standard-output*))
(let ((eof (cons nil nil)))
(do ((expr (read *standard-input* nil eof)
(read *standard-input* nil eof)))
((eq expr eof))
(setf +++ ++ ++ + + - - expr)
(multiple-value-bind (values error)
(ignore-errors
(multiple-value-list (eval expr)))
(cond
(error (format t "~&Error: ~a~%" error))
(t (setf *** ** ** * * (first values))
(format t "~{~&~s~%~}" values))))
(format t "~&~a" (make-prompt-string))))))))
(defmeth cmdline-conversation-proto :execute (cmd)
(setf (slot-value 'value) (read-eval-print-from-string cmd)))
(defmeth cmdline-conversation-proto :request (item)
(when (equal item "VALUE")
(let ((value (slot-value 'value)))
(setf (slot-value 'value) "")
value)))
;;;;
;;;; Add Command Line Handler to Server
;;;;
(send (dde-find-server "XLISP-STAT")
:add-topic "CMDLINE" cmdline-conversation-proto)
syntax highlighted by Code2HTML, v. 0.9.1