diff .elisp/pymacs.el @ 0:c30d68fbd368

Initial import from svn.
author Augie Fackler <durin42@gmail.com>
date Wed, 26 Nov 2008 10:56:09 -0600
parents
children
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/.elisp/pymacs.el
@@ -0,0 +1,688 @@
+;;; Interface between Emacs Lisp and Python - Lisp part.    -*- emacs-lisp -*-
+;;; Copyright © 2001, 2002, 2003 Progiciels Bourbeau-Pinard inc.
+;;; François Pinard <pinard@iro.umontreal.ca>, 2001.
+
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2, or (at your option)
+;;; any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software Foundation,
+;;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.  */
+
+;;; Portability stunts.
+
+(defvar pymacs-use-hash-tables
+  (and (fboundp 'make-hash-table) (fboundp 'gethash) (fboundp 'puthash))
+  "Set to t if hash tables are available.")
+
+(eval-and-compile
+
+  (if (fboundp 'multibyte-string-p)
+      (defalias 'pymacs-multibyte-string-p 'multibyte-string-p)
+    (defun pymacs-multibyte-string-p (string)
+      "Tell XEmacs if STRING should be handled as multibyte."
+      (not (equal (find-charset-string string) '(ascii))))))
+
+(defalias 'pymacs-report-error (symbol-function 'error))
+
+;;; Published variables and functions.
+
+(defvar pymacs-load-path nil
+  "List of additional directories to search for Python modules.
+The directories listed will be searched first, in the order given.")
+
+(defvar pymacs-trace-transit '(5000 . 30000)
+  "Keep the communication buffer growing, for debugging.
+When this variable is nil, the `*Pymacs*' communication buffer gets erased
+before each communication round-trip.  Setting it to `t' guarantees that
+the full communication is saved, which is useful for debugging.
+It could also be given as (KEEP . LIMIT): whenever the buffer exceeds LIMIT
+bytes, it is reduced to approximately KEEP bytes.")
+
+(defvar pymacs-forget-mutability nil
+  "Transmit copies to Python instead of Lisp handles, as much as possible.
+When this variable is nil, most mutable objects are transmitted as handles.
+This variable is meant to be temporarily rebound to force copies.")
+
+(defvar pymacs-mutable-strings nil
+  "Prefer transmitting Lisp strings to Python as handles.
+When this variable is nil, strings are transmitted as copies, and the
+Python side thus has no way for modifying the original Lisp strings.
+This variable is ignored whenever `forget-mutability' is set.")
+
+(defvar pymacs-timeout-at-start 30
+  "Maximum reasonable time, in seconds, for starting the Pymacs helper.
+A machine should be pretty loaded before one needs to increment this.")
+
+(defvar pymacs-timeout-at-reply 5
+  "Expected maximum time, in seconds, to get the first line of a reply.
+The status of the Pymacs helper is checked at every such timeout.")
+
+(defvar pymacs-timeout-at-line 2
+  "Expected maximum time, in seconds, to get another line of a reply.
+The status of the Pymacs helper is checked at every such timeout.")
+
+(defvar pymacs-dreadful-zombies nil
+  "If zombies should trigger hard errors, whenever they get called.
+If `nil', calling a zombie will merely produce a diagnostic message.")
+
+(defun pymacs-load (module &optional prefix noerror)
+  "Import the Python module named MODULE into Emacs.
+Each function in the Python module is made available as an Emacs function.
+The Lisp name of each function is the concatenation of PREFIX with
+the Python name, in which underlines are replaced by dashes.  If PREFIX is
+not given, it defaults to MODULE followed by a dash.
+If NOERROR is not nil, do not raise error when the module is not found."
+  (interactive
+   (let* ((module (read-string "Python module? "))
+          (default (concat (car (last (split-string module "\\."))) "-"))
+          (prefix (read-string (format "Prefix? [%s] " default)
+                               nil nil default)))
+     (list module prefix)))
+  (message "Pymacs loading %s..." module)
+  (let ((lisp-code (pymacs-call "pymacs_load_helper" module prefix)))
+    (cond (lisp-code (let ((result (eval lisp-code)))
+                       (message "Pymacs loading %s...done" module)
+                       result))
+          (noerror (message "Pymacs loading %s...failed" module) nil)
+          (t (pymacs-report-error "Pymacs loading %s...failed" module)))))
+
+(defun pymacs-eval (text)
+  "Compile TEXT as a Python expression, and return its value."
+  (interactive "sPython expression? ")
+  (let ((value (pymacs-serve-until-reply "eval" `(princ ,text))))
+    (when (interactive-p)
+      (message "%S" value))
+    value))
+
+(defun pymacs-exec (text)
+  "Compile and execute TEXT as a sequence of Python statements.
+This functionality is experimental, and does not appear to be useful."
+  (interactive "sPython statements? ")
+  (let ((value (pymacs-serve-until-reply "exec" `(princ ,text))))
+    (when (interactive-p)
+      (message "%S" value))
+    value))
+
+(defun pymacs-call (function &rest arguments)
+  "Return the result of calling a Python function FUNCTION over ARGUMENTS.
+FUNCTION is a string denoting the Python function, ARGUMENTS are separate
+Lisp expressions, one per argument.  Immutable Lisp constants are converted
+to Python equivalents, other structures are converted into Lisp handles."
+  (pymacs-serve-until-reply
+   "eval" `(pymacs-print-for-apply ',function ',arguments)))
+
+(defun pymacs-apply (function arguments)
+  "Return the result of calling a Python function FUNCTION over ARGUMENTS.
+FUNCTION is a string denoting the Python function, ARGUMENTS is a list of
+Lisp expressions.  Immutable Lisp constants are converted to Python
+equivalents, other structures are converted into Lisp handles."
+  (pymacs-serve-until-reply
+   "eval" `(pymacs-print-for-apply ',function ',arguments)))
+
+;;; Integration details.
+
+;; Python functions and modules should ideally look like Lisp functions and
+;; modules.  This page tries to increase the integration seamlessness.
+
+(defadvice documentation (around pymacs-ad-documentation activate)
+  ;; Integration of doc-strings.
+  (let* ((reference (pymacs-python-reference function))
+         (python-doc (when reference
+                       (pymacs-eval (format "doc_string(%s)" reference)))))
+    (if (or reference python-doc)
+        (setq ad-return-value
+              (concat
+               "It interfaces to a Python function.\n\n"
+               (when python-doc
+                 (if raw python-doc (substitute-command-keys python-doc)))))
+      ad-do-it)))
+
+(defun pymacs-python-reference (object)
+  ;; Return the text reference of a Python object if possible, else nil.
+  (when (functionp object)
+    (let* ((definition (indirect-function object))
+           (body (and (pymacs-proper-list-p definition)
+                      (> (length definition) 2)
+                      (eq (car definition) 'lambda)
+                      (cddr definition))))
+      (when (and body (listp (car body)) (eq (caar body) 'interactive))
+        ;; Skip the interactive specification of a function.
+        (setq body (cdr body)))
+      (when (and body
+                 ;; Advised functions start with a string.
+                 (not (stringp (car body)))
+                 ;; Python trampolines hold exactly one expression.
+                 (= (length body) 1))
+        (let ((expression (car body)))
+          ;; EXPRESSION might now hold something like:
+          ;;    (pymacs-apply (quote (pymacs-python . N)) ARGUMENT-LIST)
+          (when (and (pymacs-proper-list-p expression)
+                     (= (length expression) 3)
+                     (eq (car expression) 'pymacs-apply)
+                     (eq (car (cadr expression)) 'quote))
+            (setq object (cadr (cadr expression))))))))
+  (when (eq (car-safe object) 'pymacs-python)
+    (format "python[%d]" (cdr object))))
+
+;; The following functions are experimental -- they are not satisfactory yet.
+
+(defun pymacs-file-handler (operation &rest arguments)
+  ;; Integration of load-file, autoload, etc.
+  ;; Emacs might want the contents of some `MODULE.el' which does not exist,
+  ;; while there is a `MODULE.py' or `MODULE.pyc' file in the same directory.
+  ;; The goal is to generate a virtual contents for this `MODULE.el' file, as
+  ;; a set of Lisp trampoline functions to the Python module functions.
+  ;; Python modules can then be loaded or autoloaded as if they were Lisp.
+  (cond ((and (eq operation 'file-readable-p)
+              (let ((module (substring (car arguments) 0 -3)))
+                (or (pymacs-file-force operation arguments)
+                    (file-readable-p (concat module ".py"))
+                    (file-readable-p (concat module ".pyc"))))))
+        ((and (eq operation 'load)
+              (not (pymacs-file-force
+                    'file-readable-p (list (car arguments))))
+              (file-readable-p (car arguments)))
+         (let ((lisp-code (pymacs-call "pymacs_load_helper"
+                                       (substring (car arguments) 0 -3)
+                                       nil)))
+           (unless lisp-code
+             (pymacs-report-error "Python import error"))
+           (eval lisp-code)))
+        ((and (eq operation 'insert-file-contents)
+              (not (pymacs-file-force
+                    'file-readable-p (list (car arguments))))
+              (file-readable-p (car arguments)))
+         (let ((lisp-code (pymacs-call "pymacs_load_helper"
+                                       (substring (car arguments) 0 -3)
+                                       nil)))
+           (unless lisp-code
+             (pymacs-report-error "Python import error"))
+           (insert (prin1-to-string lisp-code))))
+        (t (pymacs-file-force operation arguments))))
+
+(defun pymacs-file-force (operation arguments)
+  ;; Bypass the file handler.
+  (let ((inhibit-file-name-handlers
+         (cons 'pymacs-file-handler
+               (and (eq inhibit-file-name-operation operation)
+                    inhibit-file-name-handlers)))
+        (inhibit-file-name-operation operation))
+    (apply operation arguments)))
+
+;(add-to-list 'file-name-handler-alist '("\\.el\\'" . pymacs-file-handler))
+
+;;; Gargabe collection of Python IDs.
+
+;; Python objects which have no Lisp representation are allocated on the
+;; Python side as `python[INDEX]', and INDEX is transmitted to Emacs, with
+;; the value to use on the Lisp side for it.  Whenever Lisp does not need a
+;; Python object anymore, it should be freed on the Python side.  The
+;; following variables and functions are meant to fill this duty.
+
+(defvar pymacs-used-ids nil
+  "List of received IDs, currently allocated on the Python side.")
+
+(defvar pymacs-weak-hash nil
+  "Weak hash table, meant to find out which IDs are still needed.")
+
+(defvar pymacs-gc-wanted nil
+  "Flag if it is time to clean up unused IDs on the Python side.")
+
+(defvar pymacs-gc-running nil
+  "Flag telling that a Pymacs garbage collection is in progress.")
+
+(defvar pymacs-gc-timer nil
+  "Timer to trigger Pymacs garbage collection at regular time intervals.
+The timer is used only if `post-gc-hook' is not available.")
+
+(defun pymacs-schedule-gc (&optional xemacs-list)
+  (unless pymacs-gc-running
+    (setq pymacs-gc-wanted t)))
+
+(defun pymacs-garbage-collect ()
+  ;; Clean up unused IDs on the Python side.
+  (when pymacs-use-hash-tables
+    (let ((pymacs-gc-running t)
+          (pymacs-forget-mutability t)
+          (ids pymacs-used-ids)
+          used-ids unused-ids)
+      (while ids
+        (let ((id (car ids)))
+          (setq ids (cdr ids))
+          (if (gethash id pymacs-weak-hash)
+              (setq used-ids (cons id used-ids))
+            (setq unused-ids (cons id unused-ids)))))
+      (setq pymacs-used-ids used-ids
+            pymacs-gc-wanted nil)
+      (when unused-ids
+        (pymacs-apply "free_python" unused-ids)))))
+
+(defun pymacs-defuns (arguments)
+  ;; Take one argument, a list holding a number of items divisible by 3.  The
+  ;; first argument is an INDEX, the second is a NAME, the third is the
+  ;; INTERACTION specification, and so forth.  Register Python INDEX with a
+  ;; function with that NAME and INTERACTION on the Lisp side.  The strange
+  ;; calling convention is to minimise quoting at call time.
+  (while (>= (length arguments) 3)
+    (let ((index (nth 0 arguments))
+          (name (nth 1 arguments))
+          (interaction (nth 2 arguments)))
+      (fset name (pymacs-defun index interaction))
+      (setq arguments (nthcdr 3 arguments)))))
+
+(defun pymacs-defun (index interaction)
+  ;; Register INDEX on the Lisp side with a Python object that is a function,
+  ;; and return a lambda form calling that function.  If the INTERACTION
+  ;; specification is nil, the function is not interactive.  Otherwise, the
+  ;; function is interactive, INTERACTION is then either a string, or the
+  ;; index of an argument-less Python function returning the argument list.
+  (let ((object (pymacs-python index)))
+    (cond ((null interaction)
+           `(lambda (&rest arguments)
+              (pymacs-apply ',object arguments)))
+          ((stringp interaction)
+           `(lambda (&rest arguments)
+              (interactive ,interaction)
+              (pymacs-apply ',object arguments)))
+          (t `(lambda (&rest arguments)
+                (interactive (pymacs-call ',(pymacs-python interaction)))
+                (pymacs-apply ',object arguments))))))
+
+(defun pymacs-python (index)
+  ;; Register on the Lisp side a Python object having INDEX, and return it.
+  ;; The result is meant to be recognised specially by `print-for-eval', and
+  ;; in the function position by `print-for-apply'.
+  (let ((object (cons 'pymacs-python index)))
+    (when pymacs-use-hash-tables
+      (puthash index object pymacs-weak-hash)
+      (setq pymacs-used-ids (cons index pymacs-used-ids)))
+    object))
+
+;;; Generating Python code.
+
+;; Many Lisp expressions cannot fully be represented in Python, at least
+;; because the object is mutable on the Lisp side.  Such objects are allocated
+;; somewhere into a vector of handles, and the handle index is used for
+;; communication instead of the expression itself.
+
+(defvar pymacs-lisp nil
+  "Vector of handles to hold transmitted expressions.")
+
+(defvar pymacs-freed-list nil
+  "List of unallocated indices in Lisp.")
+
+;; When the Python GC is done with a Lisp object, a communication occurs so to
+;; free the object on the Lisp side as well.
+
+(defun pymacs-allocate-lisp (expression)
+  ;; This function allocates some handle for an EXPRESSION, and return its
+  ;; index.
+  (unless pymacs-freed-list
+    (let* ((previous pymacs-lisp)
+           (old-size (length previous))
+           (new-size (if (zerop old-size) 100 (+ old-size (/ old-size 2))))
+           (counter new-size))
+      (setq pymacs-lisp (make-vector new-size nil))
+      (while (> counter 0)
+        (setq counter (1- counter))
+        (if (< counter old-size)
+            (aset pymacs-lisp counter (aref previous counter))
+          (setq pymacs-freed-list (cons counter pymacs-freed-list))))))
+  (let ((index (car pymacs-freed-list)))
+    (setq pymacs-freed-list (cdr pymacs-freed-list))
+    (aset pymacs-lisp index expression)
+    index))
+
+(defun pymacs-free-lisp (indices)
+  ;; This function is triggered from Python side for Lisp handles which lost
+  ;; their last reference.  These references should be cut on the Lisp side as
+  ;; well, or else, the objects will never be garbage-collected.
+  (while indices
+    (let ((index (car indices)))
+      (aset pymacs-lisp index nil)
+      (setq pymacs-freed-list (cons index pymacs-freed-list)
+            indices (cdr indices)))))
+
+(defun pymacs-print-for-apply (function arguments)
+  ;; This function prints a Python expression calling FUNCTION, which is a
+  ;; string naming a Python function, or a Python reference, over all its
+  ;; ARGUMENTS, which are Lisp expressions.
+  (let ((separator "")
+        argument)
+    (if (eq (car-safe function) 'pymacs-python)
+        (princ (format "python[%d]" (cdr function)))
+      (princ function))
+    (princ "(")
+    (while arguments
+      (setq argument (car arguments)
+            arguments (cdr arguments))
+      (princ separator)
+      (setq separator ", ")
+      (pymacs-print-for-eval argument))
+    (princ ")")))
+
+(defun pymacs-print-for-eval (expression)
+  ;; This function prints a Python expression out of a Lisp EXPRESSION.
+  (let (done)
+    (cond ((not expression)
+           (princ "None")
+           (setq done t))
+          ((eq expression t)
+           (princ "True")
+           (setq done t))
+          ((numberp expression)
+           (princ expression)
+           (setq done t))
+          ((stringp expression)
+           (when (or pymacs-forget-mutability
+                     (not pymacs-mutable-strings))
+             (let* ((multibyte (pymacs-multibyte-string-p expression))
+                    (text (if multibyte
+                              (encode-coding-string expression 'utf-8)
+                            (copy-sequence expression))))
+               (set-text-properties 0 (length text) nil text)
+               (princ (mapconcat 'identity
+                                 (split-string (prin1-to-string text) "\n")
+                                 "\\n"))
+               (when (and multibyte
+                          (not (equal (find-charset-string text) '(ascii))))
+                 (princ ".decode('UTF-8')")))
+             (setq done t)))
+          ((symbolp expression)
+           (let ((name (symbol-name expression)))
+             ;; The symbol can only be transmitted when in the main oblist.
+             (when (eq expression (intern-soft name))
+               (princ "lisp[")
+               (prin1 name)
+               (princ "]")
+               (setq done t))))
+          ((vectorp expression)
+           (when pymacs-forget-mutability
+             (let ((limit (length expression))
+                   (counter 0))
+               (princ "(")
+               (while (< counter limit)
+                 (unless (zerop counter)
+                   (princ ", "))
+                 (pymacs-print-for-eval (aref expression counter))
+                 (setq counter (1+ counter)))
+               (when (= limit 1)
+                 (princ ","))
+               (princ ")")
+               (setq done t))))
+          ((eq (car-safe expression) 'pymacs-python)
+           (princ "python[")
+           (princ (cdr expression))
+           (princ "]")
+           (setq done t))
+          ((pymacs-proper-list-p expression)
+           (when pymacs-forget-mutability
+             (princ "[")
+             (pymacs-print-for-eval (car expression))
+             (while (setq expression (cdr expression))
+               (princ ", ")
+               (pymacs-print-for-eval (car expression)))
+             (princ "]")
+             (setq done t))))
+    (unless done
+      (let ((class (cond ((vectorp expression) "Vector")
+                         ((and pymacs-use-hash-tables
+                               (hash-table-p expression))
+                          "Table")
+                         ((bufferp expression) "Buffer")
+                         ((pymacs-proper-list-p expression) "List")
+                         (t "Lisp"))))
+        (princ class)
+        (princ "(")
+        (princ (pymacs-allocate-lisp expression))
+        (princ ")")))))
+
+;;; Communication protocol.
+
+(defvar pymacs-transit-buffer nil
+  "Communication buffer between Emacs and Python.")
+
+;; The principle behind the communication protocol is that it is easier to
+;; generate than parse, and that each language already has its own parser.
+;; So, the Emacs side generates Python text for the Python side to interpret,
+;; while the Python side generates Lisp text for the Lisp side to interpret.
+;; About nothing but expressions are transmitted, which are evaluated on
+;; arrival.  The pseudo `reply' function is meant to signal the final result
+;; of a series of exchanges following a request, while the pseudo `error'
+;; function is meant to explain why an exchange could not have been completed.
+
+;; The protocol itself is rather simple, and contains human readable text
+;; only.  A message starts at the beginning of a line in the communication
+;; buffer, either with `>' for the Lisp to Python direction, or `<' for the
+;; Python to Lisp direction.  This is followed by a decimal number giving the
+;; length of the message text, a TAB character, and the message text itself.
+;; Message direction alternates systematically between messages, it never
+;; occurs that two successive messages are sent in the same direction.  The
+;; first message is received from the Python side, it is `(version VERSION)'.
+
+(defun pymacs-start-services ()
+  ;; This function gets called automatically, as needed.
+  (let ((buffer (get-buffer-create "*Pymacs*")))
+    (with-current-buffer buffer
+      (buffer-disable-undo)
+      (set-buffer-multibyte nil)
+      (set-buffer-file-coding-system 'raw-text)
+      (save-match-data
+        ;; Launch the Pymacs helper.
+        (let ((process
+               (apply 'start-process "pymacs" buffer
+                      (let ((python (getenv "PYMACS_PYTHON")))
+                        (if (or (null python) (equal python ""))
+                            "python"
+                          python))
+                      "-c" (concat "import sys;"
+                                   " from Pymacs.pymacs import main;"
+                                   " main(*sys.argv[1:])")
+                      (mapcar 'expand-file-name pymacs-load-path))))
+          (cond ((fboundp 'set-process-query-on-exit-flag)
+                 (set-process-query-on-exit-flag process nil))
+                ((fboundp 'process-kill-without-query-process)
+                 (process-kill-without-query process)))
+          ;; Receive the synchronising reply.
+          (while (progn
+                   (goto-char (point-min))
+                   (not (re-search-forward "<\\([0-9]+\\)\t" nil t)))
+            (unless (accept-process-output process pymacs-timeout-at-start)
+              (pymacs-report-error
+               "Pymacs helper did not start within %d seconds"
+                     pymacs-timeout-at-start)))
+          (let ((marker (process-mark process))
+                (limit-position (+ (match-end 0)
+                                   (string-to-number (match-string 1)))))
+            (while (< (marker-position marker) limit-position)
+              (unless (accept-process-output process pymacs-timeout-at-start)
+                (pymacs-report-error
+                 "Pymacs helper probably was interrupted at start")))))
+        ;; Check that synchronisation occurred.
+        (goto-char (match-end 0))
+        (let ((reply (read (current-buffer))))
+          (if (and (pymacs-proper-list-p reply)
+                   (= (length reply) 2)
+                   (eq (car reply) 'version))
+              (unless (string-equal (cadr reply) "0.23")
+                (pymacs-report-error
+                 "Pymacs Lisp version is 0.23, Python is %s"
+                 (cadr reply)))
+            (pymacs-report-error "Pymacs got an invalid initial reply")))))
+    (when pymacs-use-hash-tables
+      (if pymacs-weak-hash
+          ;; A previous Pymacs session occurred in *this* Emacs session.  Some
+          ;; IDs may hang around, which do not correspond to anything on the
+          ;; Python side.  Python should not recycle such IDs for new objects.
+          (when pymacs-used-ids
+            (let ((pymacs-transit-buffer buffer)
+                  (pymacs-forget-mutability t))
+              (pymacs-apply "zombie_python" pymacs-used-ids)))
+        (setq pymacs-weak-hash (make-hash-table :weakness 'value)))
+      (if (boundp 'post-gc-hook)
+          (add-hook 'post-gc-hook 'pymacs-schedule-gc)
+        (setq pymacs-gc-timer (run-at-time 20 20 'pymacs-schedule-gc))))
+    ;; If nothing failed, only then declare that Pymacs has started!
+    (setq pymacs-transit-buffer buffer)))
+
+(defun pymacs-terminate-services ()
+  ;; This function is mainly provided for documentation purposes.
+  (interactive)
+  (garbage-collect)
+  (pymacs-garbage-collect)
+  (when (or (not pymacs-used-ids)
+            (yes-or-no-p "\
+Killing the Pymacs helper might create zombie objects.  Kill? "))
+    (cond ((boundp 'post-gc-hook)
+           (remove-hook 'post-gc-hook 'pymacs-schedule-gc))
+          ((timerp pymacs-gc-timer)
+           (cancel-timer pymacs-gc-timer)))
+    (when pymacs-transit-buffer
+      (kill-buffer pymacs-transit-buffer))
+    (setq pymacs-gc-running nil
+          pymacs-gc-timer nil
+          pymacs-transit-buffer nil
+          pymacs-lisp nil
+          pymacs-freed-list nil)))
+
+(defun pymacs-serve-until-reply (action inserter)
+  ;; This function builds a Python request by printing ACTION and
+  ;; evaluating INSERTER, which itself prints an argument.  It then
+  ;; sends the request to the Pymacs helper, and serves all
+  ;; sub-requests coming from the Python side, until either a reply or
+  ;; an error is finally received.
+  (unless (and pymacs-transit-buffer
+               (buffer-name pymacs-transit-buffer)
+               (get-buffer-process pymacs-transit-buffer))
+    (pymacs-start-services))
+  (when pymacs-gc-wanted
+    (pymacs-garbage-collect))
+  (let ((inhibit-quit t)
+        done value)
+    (while (not done)
+      (let ((form (pymacs-round-trip action inserter)))
+        (setq action (car form))
+        (when (eq action 'free)
+          (pymacs-free-lisp (cadr form))
+          (setq form (cddr form)
+                action (car form)))
+        (let* ((pair (pymacs-interruptible-eval (cadr form)))
+               (success (cdr pair)))
+          (setq value (car pair))
+          (cond ((eq action 'eval)
+                 (if success
+                     (setq action "return"
+                           inserter `(pymacs-print-for-eval ',value))
+                   (setq action "raise"
+                         inserter `(let ((pymacs-forget-mutability t))
+                                     (pymacs-print-for-eval ,value)))))
+                ((eq action 'expand)
+                 (if success
+                     (setq action "return"
+                           inserter `(let ((pymacs-forget-mutability t))
+                                       (pymacs-print-for-eval ,value)))
+                   (setq action "raise"
+                         inserter `(let ((pymacs-forget-mutability t))
+                                     (pymacs-print-for-eval ,value)))))
+                ((eq action 'return)
+                 (if success
+                     (setq done t)
+                   (pymacs-report-error "%s" value)))
+                ((eq action 'raise)
+                 (if success
+                     (pymacs-report-error "Python: %s" value)
+                   (pymacs-report-error "%s" value)))
+                (t (pymacs-report-error "Protocol error: %s" form))))))
+    value))
+
+(defun pymacs-round-trip (action inserter)
+  ;; This function produces a Python request by printing and
+  ;; evaluating INSERTER, which itself prints an argument.  It sends
+  ;; the request to the Pymacs helper, awaits for any kind of reply,
+  ;; and returns it.
+  (with-current-buffer pymacs-transit-buffer
+    ;; Possibly trim the beginning of the transit buffer.
+    (cond ((not pymacs-trace-transit)
+           (erase-buffer))
+          ((consp pymacs-trace-transit)
+           (when (> (buffer-size) (cdr pymacs-trace-transit))
+             (let ((cut (- (buffer-size) (car pymacs-trace-transit))))
+               (when (> cut 0)
+                 (save-excursion
+                   (goto-char cut)
+                   (unless (memq (preceding-char) '(0 ?\n))
+                     (forward-line 1))
+                   (delete-region (point-min) (point))))))))
+    ;; Send the request, wait for a reply, and process it.
+    (let* ((process (get-buffer-process pymacs-transit-buffer))
+           (status (process-status process))
+           (marker (process-mark process))
+           (moving (= (point) marker))
+           send-position reply-position reply)
+      (save-excursion
+        (save-match-data
+          ;; Encode request.
+          (setq send-position (marker-position marker))
+          (let ((standard-output marker))
+            (princ action)
+            (princ " ")
+            (eval inserter))
+          (goto-char marker)
+          (unless (= (preceding-char) ?\n)
+            (princ "\n" marker))
+          ;; Send request text.
+          (goto-char send-position)
+          (insert (format ">%d\t" (- marker send-position)))
+          (setq reply-position (marker-position marker))
+          (process-send-region process send-position marker)
+          ;; Receive reply text.
+          (while (and (eq status 'run)
+                      (progn
+                        (goto-char reply-position)
+                        (not (re-search-forward "<\\([0-9]+\\)\t" nil t))))
+            (unless (accept-process-output process pymacs-timeout-at-reply)
+              (setq status (process-status process))))
+          (when (eq status 'run)
+            (let ((limit-position (+ (match-end 0)
+                                     (string-to-number (match-string 1)))))
+              (while (and (eq status 'run)
+                          (< (marker-position marker) limit-position))
+                (unless (accept-process-output process pymacs-timeout-at-line)
+                  (setq status (process-status process))))))
+          ;; Decode reply.
+          (if (not (eq status 'run))
+              (pymacs-report-error "Pymacs helper status is `%S'" status)
+            (goto-char (match-end 0))
+            (setq reply (read (current-buffer))))))
+      (when (and moving (not pymacs-trace-transit))
+        (goto-char marker))
+      reply)))
+
+(defun pymacs-interruptible-eval (expression)
+  ;; This function produces a pair (VALUE . SUCCESS) for EXPRESSION.
+  ;; A cautious evaluation of EXPRESSION is attempted, and any
+  ;; error while evaluating is caught, including Emacs quit (C-g).
+  ;; Any Emacs quit also gets forward as a SIGINT to the Pymacs handler.
+  ;; With SUCCESS being true, VALUE is the expression value.
+  ;; With SUCCESS being false, VALUE is an interruption diagnostic.
+  (condition-case info
+      (cons (let ((inhibit-quit nil)) (eval expression)) t)
+    (quit (setq quit-flag t)
+          (interrupt-process pymacs-transit-buffer)
+          (cons "*Interrupted!*" nil))
+    (error (cons (prin1-to-string info) nil))))
+
+(defun pymacs-proper-list-p (expression)
+  ;; Tell if a list is proper, id est, that it is `nil' or ends with `nil'.
+  (cond ((not expression))
+        ((consp expression) (not (cdr (last expression))))))
+
+(provide 'pymacs)