Add jupyter-return-delayed-thunk

This commit is contained in:
Nathaniel Nicandro 2020-12-20 06:34:04 -06:00
parent 3db7446f3a
commit 13935fe38f

View file

@ -47,15 +47,20 @@
(cl-defstruct jupyter-delayed value) (cl-defstruct jupyter-delayed value)
(defconst jupyter-io-nil (make-jupyter-delayed :value (lambda () nil)))
(defvar jupyter-io-cache (make-hash-table :weakness 'key))
(defun jupyter-return-delayed (value) (defun jupyter-return-delayed (value)
"Return an I/O value wrapping VALUE." "Return an I/O value wrapping VALUE."
(declare (indent 0)) (declare (indent 0))
(make-jupyter-delayed :value (lambda () value))) (make-jupyter-delayed :value (lambda () value)))
(defmacro jupyter-return-delayed-thunk (&rest body)
"Return an I/O value that evaluates BODY."
(declare (debug (body)) (indent 0))
`(make-jupyter-delayed :value (lambda () ,@body)))
(defconst jupyter-io-nil (jupyter-return-delayed nil))
(defvar jupyter-io-cache (make-hash-table :weakness 'key))
(defvar jupyter-current-io (defvar jupyter-current-io
(lambda (content) (lambda (content)
(error "Unhandled I/O: %s" content)) (error "Unhandled I/O: %s" content))
@ -107,11 +112,10 @@ Return the result of evaluating BODY."
The result of the returned action is the result of the I/O action The result of the returned action is the result of the I/O action
BODY evaluates to." BODY evaluates to."
(declare (indent 1) (debug (form body))) (declare (indent 1) (debug (form body)))
`(make-jupyter-delayed `(jupyter-return-delayed-thunk
:value (lambda () (let ((jupyter-current-io ,io))
(let ((jupyter-current-io ,io)) (jupyter-mlet* ((result (progn ,@body)))
(jupyter-mlet* ((result (progn ,@body))) result))))
result)))))
(defmacro jupyter-run-with-io (io &rest body) (defmacro jupyter-run-with-io (io &rest body)
"Return the result of evaluating the I/O value BODY evaluates to. "Return the result of evaluating the I/O value BODY evaluates to.
@ -146,11 +150,10 @@ returned action is the result of the last action in IO-ACTIONS."
"Return an I/O action that performs IO-A then IO-B. "Return an I/O action that performs IO-A then IO-B.
The result of the returned action is the result of IO-B." The result of the returned action is the result of IO-B."
(declare (indent 1)) (declare (indent 1))
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (jupyter-mlet* ((_ io-a)
(jupyter-mlet* ((_ io-a) (result io-b))
(result io-b)) result)))
result))))
;;; Publisher/subscriber ;;; Publisher/subscriber
@ -296,18 +299,16 @@ Ex. Subscribe to a publisher and unsubscribe after receiving two
(jupyter-publish x))) (jupyter-publish x)))
(reverse msgs)) ; => '(1 2)" (reverse msgs)) ; => '(1 2)"
(declare (indent 0)) (declare (indent 0))
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (funcall jupyter-current-io (list 'subscribe sub))
(funcall jupyter-current-io (list 'subscribe sub)) nil))
nil)))
(defun jupyter-publish (value) (defun jupyter-publish (value)
"Return an I/O action that submits VALUE to publish as content." "Return an I/O action that submits VALUE to publish as content."
(declare (indent 0)) (declare (indent 0))
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (funcall jupyter-current-io (jupyter-content value))
(funcall jupyter-current-io (jupyter-content value)) nil))
nil)))
;;; Working with requests ;;; Working with requests
@ -319,21 +320,20 @@ Evaluate IO-REQ, an IO action that results in a sent request, and
wait for that request to become idle. Signal a wait for that request to become idle. Signal a
`jupyter-timeout-before-idle' error if TIMEOUT seconds elapses `jupyter-timeout-before-idle' error if TIMEOUT seconds elapses
and the request has not become idle yet." and the request has not become idle yet."
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (jupyter-mlet* ((req io-req))
(jupyter-mlet* ((req io-req)) (or (jupyter-wait-until-idle req timeout)
(or (jupyter-wait-until-idle req timeout) (signal 'jupyter-timeout-before-idle (list req)))
(signal 'jupyter-timeout-before-idle (list req))) req)))
req))))
(defun jupyter-messages (io-req &optional timeout) (defun jupyter-messages (io-req &optional timeout)
"Return an IO action that returns the messages of IO-REQ. "Return an IO action that returns the messages of IO-REQ.
IO-REQ is an IO action that evaluates to a sent request. TIMEOUT IO-REQ is an IO action that evaluates to a sent request. TIMEOUT
has the same meaning as in `jupyter-idle'." has the same meaning as in `jupyter-idle'."
(make-jupyter-delayed (let ((idle-req (jupyter-idle io-req timeout)))
:value (lambda () (jupyter-return-delayed-thunk
(jupyter-mlet* ((req (jupyter-idle io-req timeout))) (jupyter-mlet* ((req idle-req))
(jupyter-request-messages req))))) (jupyter-request-messages req)))))
(defun jupyter-find-message (msg-type msgs) (defun jupyter-find-message (msg-type msgs)
"Return a message whose type is MSG-TYPE in MSGS." "Return a message whose type is MSG-TYPE in MSGS."
@ -347,14 +347,13 @@ has the same meaning as in `jupyter-idle'."
"Return an IO action that returns the reply message of IO-REQ. "Return an IO action that returns the reply message of IO-REQ.
IO-REQ is an IO action that evaluates to a sent request. TIMEOUT IO-REQ is an IO action that evaluates to a sent request. TIMEOUT
has the same meaning as in `jupyter-idle'." has the same meaning as in `jupyter-idle'."
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (jupyter-mlet* ((msgs (jupyter-messages io-req timeout)))
(jupyter-mlet* ((msgs (jupyter-messages io-req timeout))) (cl-find-if
(cl-find-if (lambda (msg)
(lambda (msg) (let ((type (jupyter-message-type msg)))
(let ((type (jupyter-message-type msg))) (string-suffix-p "_reply" type)))
(string-suffix-p "_reply" type))) msgs))))
msgs)))))
(defun jupyter-message-subscribed (io-req cbs) (defun jupyter-message-subscribed (io-req cbs)
"Return an IO action that subscribes CBS to a request's message publisher. "Return an IO action that subscribes CBS to a request's message publisher.
@ -366,19 +365,18 @@ an alist mapping message types to callback functions like
The returned IO action returns the sent request after subscribing The returned IO action returns the sent request after subscribing
the callbacks." the callbacks."
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (jupyter-mlet* ((req io-req))
(jupyter-mlet* ((req io-req)) (jupyter-run-with-io
(jupyter-run-with-io (jupyter-request-message-publisher req)
(jupyter-request-message-publisher req) (jupyter-subscribe
(jupyter-subscribe (jupyter-subscriber
(jupyter-subscriber (lambda (msg)
(lambda (msg) (when-let*
(when-let* ((msg-type (jupyter-message-type msg))
((msg-type (jupyter-message-type msg)) (fn (car (alist-get msg-type cbs nil nil #'string=))))
(fn (car (alist-get msg-type cbs nil nil #'string=)))) (funcall fn msg))))))
(funcall fn msg)))))) req)))
req))))
(defun jupyter-client-subscribed (io-req) (defun jupyter-client-subscribed (io-req)
"Return an IO action that subscribes a client to a request's message publisher. "Return an IO action that subscribes a client to a request's message publisher.
@ -388,22 +386,21 @@ of evaluation of the action.
The returned IO action returns the sent request after subscribing The returned IO action returns the sent request after subscribing
the client." the client."
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (lambda () (jupyter-with-client jupyter-current-client
(jupyter-with-client jupyter-current-client (let ((client jupyter-current-client))
(let ((client jupyter-current-client)) (jupyter-mlet* ((req io-req))
(jupyter-mlet* ((req io-req)) (when (string= (jupyter-request-type req)
(when (string= (jupyter-request-type req) "execute_request")
"execute_request") (jupyter-server-mode-set-client client))
(jupyter-server-mode-set-client client)) (jupyter-run-with-io
(jupyter-run-with-io (jupyter-request-message-publisher req)
(jupyter-request-message-publisher req) (jupyter-subscribe
(jupyter-subscribe (jupyter-subscriber
(jupyter-subscriber (lambda (msg)
(lambda (msg) (let ((channel (plist-get msg :channel)))
(let ((channel (plist-get msg :channel))) (jupyter-handle-message client channel msg))))))
(jupyter-handle-message client channel msg)))))) req)))))
req))))))
;;; Request ;;; Request
@ -458,28 +455,26 @@ the client."
(ch (if (member type '("input_reply" "input_request")) (ch (if (member type '("input_reply" "input_request"))
"stdin" "stdin"
"shell"))) "shell")))
(make-jupyter-delayed (jupyter-return-delayed-thunk
:value (let ((req (jupyter-generate-request
(lambda () jupyter-current-client
(let ((req (jupyter-generate-request :type type
jupyter-current-client :content content
:type type :client jupyter-current-client
:content content ;; Anything sent to stdin is a reply not a request
:client jupyter-current-client ;; so consider the "request" completed.
;; Anything sent to stdin is a reply not a request :idle-p (string= ch "stdin")
;; so consider the "request" completed. :inhibited-handlers ih)))
:idle-p (string= ch "stdin") (setf (jupyter-request-message-publisher req)
:inhibited-handlers ih))) (jupyter-message-publisher req))
(setf (jupyter-request-message-publisher req) (jupyter-mlet*
(jupyter-message-publisher req)) ((_ (jupyter-do
(jupyter-mlet* (jupyter-subscribe
((_ (jupyter-do (jupyter-request-message-publisher req))
(jupyter-subscribe (jupyter-publish
(jupyter-request-message-publisher req)) (list 'send ch type content
(jupyter-publish (jupyter-request-id req)))))))
(list 'send ch type content req))))
(jupyter-request-id req)))))))
req)))))
(cl-defun jupyter-request (type &rest content) (cl-defun jupyter-request (type &rest content)
"Return an IO action that sends a `jupyter-request'. "Return an IO action that sends a `jupyter-request'.