prometheus/hermes-impl.scm

163 lines
5.7 KiB
Scheme
Raw Normal View History

2024-09-16 11:43:15 +00:00
(import
(scheme)
(srfi-1) ;;List library
)
(define (make-hermes-object)
(let ((msg (make-messages)))
(letrec ((self (lambda (name . args)
(messages-handle msg self name args))))
(messages-add! msg '%get-handler
(lambda (_ resend name receiver args visited)
(get-handler msg self name receiver args visited))
#f)
(messages-add! msg 'add-message!
(lambda (_ resend name handler . parentl)
(messages-add! msg name handler
(if (null? parentl)
#f
(car parentl))))
#f)
(messages-add! msg 'delete-message!
(lambda (_ resend name)
(messages-delete! msg name))
#f)
self)))
(define (alist-set! alist name value)
(cond
((assq name alist)
=> (lambda (entry)
(set-cdr! entry value)
alist))
(else
(alist-cons name value alist))))
(define-record-type messages
(%make-messages alist parents)
messages?
(alist messages-alist set-messages-alist!)
(parents messages-parents set-messages-parents!))
(define (make-messages)
(%make-messages '() '()))
(define (messages-add! msg name handler parent?)
(set-messages-alist! msg
(alist-set! (messages-alist msg)
name handler))
(if parent?
(set-messages-parents! msg
(alist-set! (messages-parents msg)
name handler))))
(define (messages-delete! msg name)
(set-messages-alist! msg
(alist-delete! name
(messages-alist msg)
eq?))
(set-messages-parents! msg
(alist-delete! name
(messages-parents msg)
eq?)))
;;; Do a direct lookup (as opposed to asking the parents) for a
;;; message handler in the record.
(define (messages-direct-lookup msg name)
(cond
((assq name (messages-alist msg))
=> cdr)
(else
#f)))
;;; Ask the parents in the messages record for handlers. This returns
;;; two values as explained above. To enable the
;;; AMBIGUOUS-MESSAGE-SEND error, the parent list is searched
;;; completely even when a handler is found.
(define (messages-parent-lookup msg self name receiver args visited)
(let loop ((alis (messages-parents msg))
(handler #f)
(found #f))
(if (null? alis)
(if handler
(values handler found)
(values 'message-not-understood #f))
(receive (new new-found)
(((cdar alis)
receiver
(lambda args
(error "Parent slots must not use resend."
receiver name args)))
'%get-handler name receiver args (cons self visited))
(case new
((message-not-understood)
(loop (cdr alis)
handler
found))
((ambiguous-message-send)
(values 'ambiguous-message-send
#f))
(else
(if (and handler
(and (not (eq? found new-found))))
(values 'ambiguous-message-send
#f)
(loop (cdr alis)
new
new-found))))))))
;;; Handle a single message, checking for errors.
(define (messages-handle msg self name args)
(receive (handler found)
(get-handler msg self name self args '())
(run-with-error-checking handler self name args)))
;;; Return the appropriate handler procedure.
(define (get-handler msg self name receiver args visited)
(if (memq self visited)
(values 'message-not-understood #f)
(cond
((messages-direct-lookup msg name)
=> (lambda (handler)
(values (lambda ()
(apply handler
receiver
(make-resender msg self receiver visited)
args))
self)))
(else
(messages-parent-lookup msg self name receiver args visited)))))
;;; Create a resender for the message.
(define (make-resender msg self receiver visited)
(lambda (target name . args)
(receive (handler found)
(cond
((eq? target #f) ; ask parents
(messages-parent-lookup msg self name receiver args visited))
((or (eq? target self) ; ask this object
(eq? target #t)) ; hystorical syntax
(get-handler msg self name receiver args visited))
((symbol? target) ; ask named parent
((self target) '%get-handler name receiver args (cons self visited)))
(else ; ask that object
(target '%get-handler name receiver args (cons self visited))))
(run-with-error-checking handler self name args))))
;;; Signal the appropriate errors, if handler is not a procedure.
;;; Else, call that handler.
(define (run-with-error-checking handler self name args)
(case handler
((message-not-understood)
(if (eq? name 'message-not-understood)
(error "Message MESSAGE-NOT-UNDERSTOOD not understood"
self args)
(self 'message-not-understood name args)))
((ambiguous-message-send)
(if (eq? name 'ambiguous-message-send)
(error "Message AMBIGUOUS-MESSAGE-SEND is ambiguous"
self args)
(self 'ambiguous-message-send name args)))
(else
(handler))))