Mercurial > dotfiles
comparison .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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:c30d68fbd368 |
|---|---|
| 1 ;;; Interface between Emacs Lisp and Python - Lisp part. -*- emacs-lisp -*- | |
| 2 ;;; Copyright © 2001, 2002, 2003 Progiciels Bourbeau-Pinard inc. | |
| 3 ;;; François Pinard <pinard@iro.umontreal.ca>, 2001. | |
| 4 | |
| 5 ;;; This program is free software; you can redistribute it and/or modify | |
| 6 ;;; it under the terms of the GNU General Public License as published by | |
| 7 ;;; the Free Software Foundation; either version 2, or (at your option) | |
| 8 ;;; any later version. | |
| 9 ;;; | |
| 10 ;;; This program is distributed in the hope that it will be useful, | |
| 11 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 ;;; GNU General Public License for more details. | |
| 14 ;;; | |
| 15 ;;; You should have received a copy of the GNU General Public License | |
| 16 ;;; along with this program; if not, write to the Free Software Foundation, | |
| 17 ;;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ | |
| 18 | |
| 19 ;;; Portability stunts. | |
| 20 | |
| 21 (defvar pymacs-use-hash-tables | |
| 22 (and (fboundp 'make-hash-table) (fboundp 'gethash) (fboundp 'puthash)) | |
| 23 "Set to t if hash tables are available.") | |
| 24 | |
| 25 (eval-and-compile | |
| 26 | |
| 27 (if (fboundp 'multibyte-string-p) | |
| 28 (defalias 'pymacs-multibyte-string-p 'multibyte-string-p) | |
| 29 (defun pymacs-multibyte-string-p (string) | |
| 30 "Tell XEmacs if STRING should be handled as multibyte." | |
| 31 (not (equal (find-charset-string string) '(ascii)))))) | |
| 32 | |
| 33 (defalias 'pymacs-report-error (symbol-function 'error)) | |
| 34 | |
| 35 ;;; Published variables and functions. | |
| 36 | |
| 37 (defvar pymacs-load-path nil | |
| 38 "List of additional directories to search for Python modules. | |
| 39 The directories listed will be searched first, in the order given.") | |
| 40 | |
| 41 (defvar pymacs-trace-transit '(5000 . 30000) | |
| 42 "Keep the communication buffer growing, for debugging. | |
| 43 When this variable is nil, the `*Pymacs*' communication buffer gets erased | |
| 44 before each communication round-trip. Setting it to `t' guarantees that | |
| 45 the full communication is saved, which is useful for debugging. | |
| 46 It could also be given as (KEEP . LIMIT): whenever the buffer exceeds LIMIT | |
| 47 bytes, it is reduced to approximately KEEP bytes.") | |
| 48 | |
| 49 (defvar pymacs-forget-mutability nil | |
| 50 "Transmit copies to Python instead of Lisp handles, as much as possible. | |
| 51 When this variable is nil, most mutable objects are transmitted as handles. | |
| 52 This variable is meant to be temporarily rebound to force copies.") | |
| 53 | |
| 54 (defvar pymacs-mutable-strings nil | |
| 55 "Prefer transmitting Lisp strings to Python as handles. | |
| 56 When this variable is nil, strings are transmitted as copies, and the | |
| 57 Python side thus has no way for modifying the original Lisp strings. | |
| 58 This variable is ignored whenever `forget-mutability' is set.") | |
| 59 | |
| 60 (defvar pymacs-timeout-at-start 30 | |
| 61 "Maximum reasonable time, in seconds, for starting the Pymacs helper. | |
| 62 A machine should be pretty loaded before one needs to increment this.") | |
| 63 | |
| 64 (defvar pymacs-timeout-at-reply 5 | |
| 65 "Expected maximum time, in seconds, to get the first line of a reply. | |
| 66 The status of the Pymacs helper is checked at every such timeout.") | |
| 67 | |
| 68 (defvar pymacs-timeout-at-line 2 | |
| 69 "Expected maximum time, in seconds, to get another line of a reply. | |
| 70 The status of the Pymacs helper is checked at every such timeout.") | |
| 71 | |
| 72 (defvar pymacs-dreadful-zombies nil | |
| 73 "If zombies should trigger hard errors, whenever they get called. | |
| 74 If `nil', calling a zombie will merely produce a diagnostic message.") | |
| 75 | |
| 76 (defun pymacs-load (module &optional prefix noerror) | |
| 77 "Import the Python module named MODULE into Emacs. | |
| 78 Each function in the Python module is made available as an Emacs function. | |
| 79 The Lisp name of each function is the concatenation of PREFIX with | |
| 80 the Python name, in which underlines are replaced by dashes. If PREFIX is | |
| 81 not given, it defaults to MODULE followed by a dash. | |
| 82 If NOERROR is not nil, do not raise error when the module is not found." | |
| 83 (interactive | |
| 84 (let* ((module (read-string "Python module? ")) | |
| 85 (default (concat (car (last (split-string module "\\."))) "-")) | |
| 86 (prefix (read-string (format "Prefix? [%s] " default) | |
| 87 nil nil default))) | |
| 88 (list module prefix))) | |
| 89 (message "Pymacs loading %s..." module) | |
| 90 (let ((lisp-code (pymacs-call "pymacs_load_helper" module prefix))) | |
| 91 (cond (lisp-code (let ((result (eval lisp-code))) | |
| 92 (message "Pymacs loading %s...done" module) | |
| 93 result)) | |
| 94 (noerror (message "Pymacs loading %s...failed" module) nil) | |
| 95 (t (pymacs-report-error "Pymacs loading %s...failed" module))))) | |
| 96 | |
| 97 (defun pymacs-eval (text) | |
| 98 "Compile TEXT as a Python expression, and return its value." | |
| 99 (interactive "sPython expression? ") | |
| 100 (let ((value (pymacs-serve-until-reply "eval" `(princ ,text)))) | |
| 101 (when (interactive-p) | |
| 102 (message "%S" value)) | |
| 103 value)) | |
| 104 | |
| 105 (defun pymacs-exec (text) | |
| 106 "Compile and execute TEXT as a sequence of Python statements. | |
| 107 This functionality is experimental, and does not appear to be useful." | |
| 108 (interactive "sPython statements? ") | |
| 109 (let ((value (pymacs-serve-until-reply "exec" `(princ ,text)))) | |
| 110 (when (interactive-p) | |
| 111 (message "%S" value)) | |
| 112 value)) | |
| 113 | |
| 114 (defun pymacs-call (function &rest arguments) | |
| 115 "Return the result of calling a Python function FUNCTION over ARGUMENTS. | |
| 116 FUNCTION is a string denoting the Python function, ARGUMENTS are separate | |
| 117 Lisp expressions, one per argument. Immutable Lisp constants are converted | |
| 118 to Python equivalents, other structures are converted into Lisp handles." | |
| 119 (pymacs-serve-until-reply | |
| 120 "eval" `(pymacs-print-for-apply ',function ',arguments))) | |
| 121 | |
| 122 (defun pymacs-apply (function arguments) | |
| 123 "Return the result of calling a Python function FUNCTION over ARGUMENTS. | |
| 124 FUNCTION is a string denoting the Python function, ARGUMENTS is a list of | |
| 125 Lisp expressions. Immutable Lisp constants are converted to Python | |
| 126 equivalents, other structures are converted into Lisp handles." | |
| 127 (pymacs-serve-until-reply | |
| 128 "eval" `(pymacs-print-for-apply ',function ',arguments))) | |
| 129 | |
| 130 ;;; Integration details. | |
| 131 | |
| 132 ;; Python functions and modules should ideally look like Lisp functions and | |
| 133 ;; modules. This page tries to increase the integration seamlessness. | |
| 134 | |
| 135 (defadvice documentation (around pymacs-ad-documentation activate) | |
| 136 ;; Integration of doc-strings. | |
| 137 (let* ((reference (pymacs-python-reference function)) | |
| 138 (python-doc (when reference | |
| 139 (pymacs-eval (format "doc_string(%s)" reference))))) | |
| 140 (if (or reference python-doc) | |
| 141 (setq ad-return-value | |
| 142 (concat | |
| 143 "It interfaces to a Python function.\n\n" | |
| 144 (when python-doc | |
| 145 (if raw python-doc (substitute-command-keys python-doc))))) | |
| 146 ad-do-it))) | |
| 147 | |
| 148 (defun pymacs-python-reference (object) | |
| 149 ;; Return the text reference of a Python object if possible, else nil. | |
| 150 (when (functionp object) | |
| 151 (let* ((definition (indirect-function object)) | |
| 152 (body (and (pymacs-proper-list-p definition) | |
| 153 (> (length definition) 2) | |
| 154 (eq (car definition) 'lambda) | |
| 155 (cddr definition)))) | |
| 156 (when (and body (listp (car body)) (eq (caar body) 'interactive)) | |
| 157 ;; Skip the interactive specification of a function. | |
| 158 (setq body (cdr body))) | |
| 159 (when (and body | |
| 160 ;; Advised functions start with a string. | |
| 161 (not (stringp (car body))) | |
| 162 ;; Python trampolines hold exactly one expression. | |
| 163 (= (length body) 1)) | |
| 164 (let ((expression (car body))) | |
| 165 ;; EXPRESSION might now hold something like: | |
| 166 ;; (pymacs-apply (quote (pymacs-python . N)) ARGUMENT-LIST) | |
| 167 (when (and (pymacs-proper-list-p expression) | |
| 168 (= (length expression) 3) | |
| 169 (eq (car expression) 'pymacs-apply) | |
| 170 (eq (car (cadr expression)) 'quote)) | |
| 171 (setq object (cadr (cadr expression)))))))) | |
| 172 (when (eq (car-safe object) 'pymacs-python) | |
| 173 (format "python[%d]" (cdr object)))) | |
| 174 | |
| 175 ;; The following functions are experimental -- they are not satisfactory yet. | |
| 176 | |
| 177 (defun pymacs-file-handler (operation &rest arguments) | |
| 178 ;; Integration of load-file, autoload, etc. | |
| 179 ;; Emacs might want the contents of some `MODULE.el' which does not exist, | |
| 180 ;; while there is a `MODULE.py' or `MODULE.pyc' file in the same directory. | |
| 181 ;; The goal is to generate a virtual contents for this `MODULE.el' file, as | |
| 182 ;; a set of Lisp trampoline functions to the Python module functions. | |
| 183 ;; Python modules can then be loaded or autoloaded as if they were Lisp. | |
| 184 (cond ((and (eq operation 'file-readable-p) | |
| 185 (let ((module (substring (car arguments) 0 -3))) | |
| 186 (or (pymacs-file-force operation arguments) | |
| 187 (file-readable-p (concat module ".py")) | |
| 188 (file-readable-p (concat module ".pyc")))))) | |
| 189 ((and (eq operation 'load) | |
| 190 (not (pymacs-file-force | |
| 191 'file-readable-p (list (car arguments)))) | |
| 192 (file-readable-p (car arguments))) | |
| 193 (let ((lisp-code (pymacs-call "pymacs_load_helper" | |
| 194 (substring (car arguments) 0 -3) | |
| 195 nil))) | |
| 196 (unless lisp-code | |
| 197 (pymacs-report-error "Python import error")) | |
| 198 (eval lisp-code))) | |
| 199 ((and (eq operation 'insert-file-contents) | |
| 200 (not (pymacs-file-force | |
| 201 'file-readable-p (list (car arguments)))) | |
| 202 (file-readable-p (car arguments))) | |
| 203 (let ((lisp-code (pymacs-call "pymacs_load_helper" | |
| 204 (substring (car arguments) 0 -3) | |
| 205 nil))) | |
| 206 (unless lisp-code | |
| 207 (pymacs-report-error "Python import error")) | |
| 208 (insert (prin1-to-string lisp-code)))) | |
| 209 (t (pymacs-file-force operation arguments)))) | |
| 210 | |
| 211 (defun pymacs-file-force (operation arguments) | |
| 212 ;; Bypass the file handler. | |
| 213 (let ((inhibit-file-name-handlers | |
| 214 (cons 'pymacs-file-handler | |
| 215 (and (eq inhibit-file-name-operation operation) | |
| 216 inhibit-file-name-handlers))) | |
| 217 (inhibit-file-name-operation operation)) | |
| 218 (apply operation arguments))) | |
| 219 | |
| 220 ;(add-to-list 'file-name-handler-alist '("\\.el\\'" . pymacs-file-handler)) | |
| 221 | |
| 222 ;;; Gargabe collection of Python IDs. | |
| 223 | |
| 224 ;; Python objects which have no Lisp representation are allocated on the | |
| 225 ;; Python side as `python[INDEX]', and INDEX is transmitted to Emacs, with | |
| 226 ;; the value to use on the Lisp side for it. Whenever Lisp does not need a | |
| 227 ;; Python object anymore, it should be freed on the Python side. The | |
| 228 ;; following variables and functions are meant to fill this duty. | |
| 229 | |
| 230 (defvar pymacs-used-ids nil | |
| 231 "List of received IDs, currently allocated on the Python side.") | |
| 232 | |
| 233 (defvar pymacs-weak-hash nil | |
| 234 "Weak hash table, meant to find out which IDs are still needed.") | |
| 235 | |
| 236 (defvar pymacs-gc-wanted nil | |
| 237 "Flag if it is time to clean up unused IDs on the Python side.") | |
| 238 | |
| 239 (defvar pymacs-gc-running nil | |
| 240 "Flag telling that a Pymacs garbage collection is in progress.") | |
| 241 | |
| 242 (defvar pymacs-gc-timer nil | |
| 243 "Timer to trigger Pymacs garbage collection at regular time intervals. | |
| 244 The timer is used only if `post-gc-hook' is not available.") | |
| 245 | |
| 246 (defun pymacs-schedule-gc (&optional xemacs-list) | |
| 247 (unless pymacs-gc-running | |
| 248 (setq pymacs-gc-wanted t))) | |
| 249 | |
| 250 (defun pymacs-garbage-collect () | |
| 251 ;; Clean up unused IDs on the Python side. | |
| 252 (when pymacs-use-hash-tables | |
| 253 (let ((pymacs-gc-running t) | |
| 254 (pymacs-forget-mutability t) | |
| 255 (ids pymacs-used-ids) | |
| 256 used-ids unused-ids) | |
| 257 (while ids | |
| 258 (let ((id (car ids))) | |
| 259 (setq ids (cdr ids)) | |
| 260 (if (gethash id pymacs-weak-hash) | |
| 261 (setq used-ids (cons id used-ids)) | |
| 262 (setq unused-ids (cons id unused-ids))))) | |
| 263 (setq pymacs-used-ids used-ids | |
| 264 pymacs-gc-wanted nil) | |
| 265 (when unused-ids | |
| 266 (pymacs-apply "free_python" unused-ids))))) | |
| 267 | |
| 268 (defun pymacs-defuns (arguments) | |
| 269 ;; Take one argument, a list holding a number of items divisible by 3. The | |
| 270 ;; first argument is an INDEX, the second is a NAME, the third is the | |
| 271 ;; INTERACTION specification, and so forth. Register Python INDEX with a | |
| 272 ;; function with that NAME and INTERACTION on the Lisp side. The strange | |
| 273 ;; calling convention is to minimise quoting at call time. | |
| 274 (while (>= (length arguments) 3) | |
| 275 (let ((index (nth 0 arguments)) | |
| 276 (name (nth 1 arguments)) | |
| 277 (interaction (nth 2 arguments))) | |
| 278 (fset name (pymacs-defun index interaction)) | |
| 279 (setq arguments (nthcdr 3 arguments))))) | |
| 280 | |
| 281 (defun pymacs-defun (index interaction) | |
| 282 ;; Register INDEX on the Lisp side with a Python object that is a function, | |
| 283 ;; and return a lambda form calling that function. If the INTERACTION | |
| 284 ;; specification is nil, the function is not interactive. Otherwise, the | |
| 285 ;; function is interactive, INTERACTION is then either a string, or the | |
| 286 ;; index of an argument-less Python function returning the argument list. | |
| 287 (let ((object (pymacs-python index))) | |
| 288 (cond ((null interaction) | |
| 289 `(lambda (&rest arguments) | |
| 290 (pymacs-apply ',object arguments))) | |
| 291 ((stringp interaction) | |
| 292 `(lambda (&rest arguments) | |
| 293 (interactive ,interaction) | |
| 294 (pymacs-apply ',object arguments))) | |
| 295 (t `(lambda (&rest arguments) | |
| 296 (interactive (pymacs-call ',(pymacs-python interaction))) | |
| 297 (pymacs-apply ',object arguments)))))) | |
| 298 | |
| 299 (defun pymacs-python (index) | |
| 300 ;; Register on the Lisp side a Python object having INDEX, and return it. | |
| 301 ;; The result is meant to be recognised specially by `print-for-eval', and | |
| 302 ;; in the function position by `print-for-apply'. | |
| 303 (let ((object (cons 'pymacs-python index))) | |
| 304 (when pymacs-use-hash-tables | |
| 305 (puthash index object pymacs-weak-hash) | |
| 306 (setq pymacs-used-ids (cons index pymacs-used-ids))) | |
| 307 object)) | |
| 308 | |
| 309 ;;; Generating Python code. | |
| 310 | |
| 311 ;; Many Lisp expressions cannot fully be represented in Python, at least | |
| 312 ;; because the object is mutable on the Lisp side. Such objects are allocated | |
| 313 ;; somewhere into a vector of handles, and the handle index is used for | |
| 314 ;; communication instead of the expression itself. | |
| 315 | |
| 316 (defvar pymacs-lisp nil | |
| 317 "Vector of handles to hold transmitted expressions.") | |
| 318 | |
| 319 (defvar pymacs-freed-list nil | |
| 320 "List of unallocated indices in Lisp.") | |
| 321 | |
| 322 ;; When the Python GC is done with a Lisp object, a communication occurs so to | |
| 323 ;; free the object on the Lisp side as well. | |
| 324 | |
| 325 (defun pymacs-allocate-lisp (expression) | |
| 326 ;; This function allocates some handle for an EXPRESSION, and return its | |
| 327 ;; index. | |
| 328 (unless pymacs-freed-list | |
| 329 (let* ((previous pymacs-lisp) | |
| 330 (old-size (length previous)) | |
| 331 (new-size (if (zerop old-size) 100 (+ old-size (/ old-size 2)))) | |
| 332 (counter new-size)) | |
| 333 (setq pymacs-lisp (make-vector new-size nil)) | |
| 334 (while (> counter 0) | |
| 335 (setq counter (1- counter)) | |
| 336 (if (< counter old-size) | |
| 337 (aset pymacs-lisp counter (aref previous counter)) | |
| 338 (setq pymacs-freed-list (cons counter pymacs-freed-list)))))) | |
| 339 (let ((index (car pymacs-freed-list))) | |
| 340 (setq pymacs-freed-list (cdr pymacs-freed-list)) | |
| 341 (aset pymacs-lisp index expression) | |
| 342 index)) | |
| 343 | |
| 344 (defun pymacs-free-lisp (indices) | |
| 345 ;; This function is triggered from Python side for Lisp handles which lost | |
| 346 ;; their last reference. These references should be cut on the Lisp side as | |
| 347 ;; well, or else, the objects will never be garbage-collected. | |
| 348 (while indices | |
| 349 (let ((index (car indices))) | |
| 350 (aset pymacs-lisp index nil) | |
| 351 (setq pymacs-freed-list (cons index pymacs-freed-list) | |
| 352 indices (cdr indices))))) | |
| 353 | |
| 354 (defun pymacs-print-for-apply (function arguments) | |
| 355 ;; This function prints a Python expression calling FUNCTION, which is a | |
| 356 ;; string naming a Python function, or a Python reference, over all its | |
| 357 ;; ARGUMENTS, which are Lisp expressions. | |
| 358 (let ((separator "") | |
| 359 argument) | |
| 360 (if (eq (car-safe function) 'pymacs-python) | |
| 361 (princ (format "python[%d]" (cdr function))) | |
| 362 (princ function)) | |
| 363 (princ "(") | |
| 364 (while arguments | |
| 365 (setq argument (car arguments) | |
| 366 arguments (cdr arguments)) | |
| 367 (princ separator) | |
| 368 (setq separator ", ") | |
| 369 (pymacs-print-for-eval argument)) | |
| 370 (princ ")"))) | |
| 371 | |
| 372 (defun pymacs-print-for-eval (expression) | |
| 373 ;; This function prints a Python expression out of a Lisp EXPRESSION. | |
| 374 (let (done) | |
| 375 (cond ((not expression) | |
| 376 (princ "None") | |
| 377 (setq done t)) | |
| 378 ((eq expression t) | |
| 379 (princ "True") | |
| 380 (setq done t)) | |
| 381 ((numberp expression) | |
| 382 (princ expression) | |
| 383 (setq done t)) | |
| 384 ((stringp expression) | |
| 385 (when (or pymacs-forget-mutability | |
| 386 (not pymacs-mutable-strings)) | |
| 387 (let* ((multibyte (pymacs-multibyte-string-p expression)) | |
| 388 (text (if multibyte | |
| 389 (encode-coding-string expression 'utf-8) | |
| 390 (copy-sequence expression)))) | |
| 391 (set-text-properties 0 (length text) nil text) | |
| 392 (princ (mapconcat 'identity | |
| 393 (split-string (prin1-to-string text) "\n") | |
| 394 "\\n")) | |
| 395 (when (and multibyte | |
| 396 (not (equal (find-charset-string text) '(ascii)))) | |
| 397 (princ ".decode('UTF-8')"))) | |
| 398 (setq done t))) | |
| 399 ((symbolp expression) | |
| 400 (let ((name (symbol-name expression))) | |
| 401 ;; The symbol can only be transmitted when in the main oblist. | |
| 402 (when (eq expression (intern-soft name)) | |
| 403 (princ "lisp[") | |
| 404 (prin1 name) | |
| 405 (princ "]") | |
| 406 (setq done t)))) | |
| 407 ((vectorp expression) | |
| 408 (when pymacs-forget-mutability | |
| 409 (let ((limit (length expression)) | |
| 410 (counter 0)) | |
| 411 (princ "(") | |
| 412 (while (< counter limit) | |
| 413 (unless (zerop counter) | |
| 414 (princ ", ")) | |
| 415 (pymacs-print-for-eval (aref expression counter)) | |
| 416 (setq counter (1+ counter))) | |
| 417 (when (= limit 1) | |
| 418 (princ ",")) | |
| 419 (princ ")") | |
| 420 (setq done t)))) | |
| 421 ((eq (car-safe expression) 'pymacs-python) | |
| 422 (princ "python[") | |
| 423 (princ (cdr expression)) | |
| 424 (princ "]") | |
| 425 (setq done t)) | |
| 426 ((pymacs-proper-list-p expression) | |
| 427 (when pymacs-forget-mutability | |
| 428 (princ "[") | |
| 429 (pymacs-print-for-eval (car expression)) | |
| 430 (while (setq expression (cdr expression)) | |
| 431 (princ ", ") | |
| 432 (pymacs-print-for-eval (car expression))) | |
| 433 (princ "]") | |
| 434 (setq done t)))) | |
| 435 (unless done | |
| 436 (let ((class (cond ((vectorp expression) "Vector") | |
| 437 ((and pymacs-use-hash-tables | |
| 438 (hash-table-p expression)) | |
| 439 "Table") | |
| 440 ((bufferp expression) "Buffer") | |
| 441 ((pymacs-proper-list-p expression) "List") | |
| 442 (t "Lisp")))) | |
| 443 (princ class) | |
| 444 (princ "(") | |
| 445 (princ (pymacs-allocate-lisp expression)) | |
| 446 (princ ")"))))) | |
| 447 | |
| 448 ;;; Communication protocol. | |
| 449 | |
| 450 (defvar pymacs-transit-buffer nil | |
| 451 "Communication buffer between Emacs and Python.") | |
| 452 | |
| 453 ;; The principle behind the communication protocol is that it is easier to | |
| 454 ;; generate than parse, and that each language already has its own parser. | |
| 455 ;; So, the Emacs side generates Python text for the Python side to interpret, | |
| 456 ;; while the Python side generates Lisp text for the Lisp side to interpret. | |
| 457 ;; About nothing but expressions are transmitted, which are evaluated on | |
| 458 ;; arrival. The pseudo `reply' function is meant to signal the final result | |
| 459 ;; of a series of exchanges following a request, while the pseudo `error' | |
| 460 ;; function is meant to explain why an exchange could not have been completed. | |
| 461 | |
| 462 ;; The protocol itself is rather simple, and contains human readable text | |
| 463 ;; only. A message starts at the beginning of a line in the communication | |
| 464 ;; buffer, either with `>' for the Lisp to Python direction, or `<' for the | |
| 465 ;; Python to Lisp direction. This is followed by a decimal number giving the | |
| 466 ;; length of the message text, a TAB character, and the message text itself. | |
| 467 ;; Message direction alternates systematically between messages, it never | |
| 468 ;; occurs that two successive messages are sent in the same direction. The | |
| 469 ;; first message is received from the Python side, it is `(version VERSION)'. | |
| 470 | |
| 471 (defun pymacs-start-services () | |
| 472 ;; This function gets called automatically, as needed. | |
| 473 (let ((buffer (get-buffer-create "*Pymacs*"))) | |
| 474 (with-current-buffer buffer | |
| 475 (buffer-disable-undo) | |
| 476 (set-buffer-multibyte nil) | |
| 477 (set-buffer-file-coding-system 'raw-text) | |
| 478 (save-match-data | |
| 479 ;; Launch the Pymacs helper. | |
| 480 (let ((process | |
| 481 (apply 'start-process "pymacs" buffer | |
| 482 (let ((python (getenv "PYMACS_PYTHON"))) | |
| 483 (if (or (null python) (equal python "")) | |
| 484 "python" | |
| 485 python)) | |
| 486 "-c" (concat "import sys;" | |
| 487 " from Pymacs.pymacs import main;" | |
| 488 " main(*sys.argv[1:])") | |
| 489 (mapcar 'expand-file-name pymacs-load-path)))) | |
| 490 (cond ((fboundp 'set-process-query-on-exit-flag) | |
| 491 (set-process-query-on-exit-flag process nil)) | |
| 492 ((fboundp 'process-kill-without-query-process) | |
| 493 (process-kill-without-query process))) | |
| 494 ;; Receive the synchronising reply. | |
| 495 (while (progn | |
| 496 (goto-char (point-min)) | |
| 497 (not (re-search-forward "<\\([0-9]+\\)\t" nil t))) | |
| 498 (unless (accept-process-output process pymacs-timeout-at-start) | |
| 499 (pymacs-report-error | |
| 500 "Pymacs helper did not start within %d seconds" | |
| 501 pymacs-timeout-at-start))) | |
| 502 (let ((marker (process-mark process)) | |
| 503 (limit-position (+ (match-end 0) | |
| 504 (string-to-number (match-string 1))))) | |
| 505 (while (< (marker-position marker) limit-position) | |
| 506 (unless (accept-process-output process pymacs-timeout-at-start) | |
| 507 (pymacs-report-error | |
| 508 "Pymacs helper probably was interrupted at start"))))) | |
| 509 ;; Check that synchronisation occurred. | |
| 510 (goto-char (match-end 0)) | |
| 511 (let ((reply (read (current-buffer)))) | |
| 512 (if (and (pymacs-proper-list-p reply) | |
| 513 (= (length reply) 2) | |
| 514 (eq (car reply) 'version)) | |
| 515 (unless (string-equal (cadr reply) "0.23") | |
| 516 (pymacs-report-error | |
| 517 "Pymacs Lisp version is 0.23, Python is %s" | |
| 518 (cadr reply))) | |
| 519 (pymacs-report-error "Pymacs got an invalid initial reply"))))) | |
| 520 (when pymacs-use-hash-tables | |
| 521 (if pymacs-weak-hash | |
| 522 ;; A previous Pymacs session occurred in *this* Emacs session. Some | |
| 523 ;; IDs may hang around, which do not correspond to anything on the | |
| 524 ;; Python side. Python should not recycle such IDs for new objects. | |
| 525 (when pymacs-used-ids | |
| 526 (let ((pymacs-transit-buffer buffer) | |
| 527 (pymacs-forget-mutability t)) | |
| 528 (pymacs-apply "zombie_python" pymacs-used-ids))) | |
| 529 (setq pymacs-weak-hash (make-hash-table :weakness 'value))) | |
| 530 (if (boundp 'post-gc-hook) | |
| 531 (add-hook 'post-gc-hook 'pymacs-schedule-gc) | |
| 532 (setq pymacs-gc-timer (run-at-time 20 20 'pymacs-schedule-gc)))) | |
| 533 ;; If nothing failed, only then declare that Pymacs has started! | |
| 534 (setq pymacs-transit-buffer buffer))) | |
| 535 | |
| 536 (defun pymacs-terminate-services () | |
| 537 ;; This function is mainly provided for documentation purposes. | |
| 538 (interactive) | |
| 539 (garbage-collect) | |
| 540 (pymacs-garbage-collect) | |
| 541 (when (or (not pymacs-used-ids) | |
| 542 (yes-or-no-p "\ | |
| 543 Killing the Pymacs helper might create zombie objects. Kill? ")) | |
| 544 (cond ((boundp 'post-gc-hook) | |
| 545 (remove-hook 'post-gc-hook 'pymacs-schedule-gc)) | |
| 546 ((timerp pymacs-gc-timer) | |
| 547 (cancel-timer pymacs-gc-timer))) | |
| 548 (when pymacs-transit-buffer | |
| 549 (kill-buffer pymacs-transit-buffer)) | |
| 550 (setq pymacs-gc-running nil | |
| 551 pymacs-gc-timer nil | |
| 552 pymacs-transit-buffer nil | |
| 553 pymacs-lisp nil | |
| 554 pymacs-freed-list nil))) | |
| 555 | |
| 556 (defun pymacs-serve-until-reply (action inserter) | |
| 557 ;; This function builds a Python request by printing ACTION and | |
| 558 ;; evaluating INSERTER, which itself prints an argument. It then | |
| 559 ;; sends the request to the Pymacs helper, and serves all | |
| 560 ;; sub-requests coming from the Python side, until either a reply or | |
| 561 ;; an error is finally received. | |
| 562 (unless (and pymacs-transit-buffer | |
| 563 (buffer-name pymacs-transit-buffer) | |
| 564 (get-buffer-process pymacs-transit-buffer)) | |
| 565 (pymacs-start-services)) | |
| 566 (when pymacs-gc-wanted | |
| 567 (pymacs-garbage-collect)) | |
| 568 (let ((inhibit-quit t) | |
| 569 done value) | |
| 570 (while (not done) | |
| 571 (let ((form (pymacs-round-trip action inserter))) | |
| 572 (setq action (car form)) | |
| 573 (when (eq action 'free) | |
| 574 (pymacs-free-lisp (cadr form)) | |
| 575 (setq form (cddr form) | |
| 576 action (car form))) | |
| 577 (let* ((pair (pymacs-interruptible-eval (cadr form))) | |
| 578 (success (cdr pair))) | |
| 579 (setq value (car pair)) | |
| 580 (cond ((eq action 'eval) | |
| 581 (if success | |
| 582 (setq action "return" | |
| 583 inserter `(pymacs-print-for-eval ',value)) | |
| 584 (setq action "raise" | |
| 585 inserter `(let ((pymacs-forget-mutability t)) | |
| 586 (pymacs-print-for-eval ,value))))) | |
| 587 ((eq action 'expand) | |
| 588 (if success | |
| 589 (setq action "return" | |
| 590 inserter `(let ((pymacs-forget-mutability t)) | |
| 591 (pymacs-print-for-eval ,value))) | |
| 592 (setq action "raise" | |
| 593 inserter `(let ((pymacs-forget-mutability t)) | |
| 594 (pymacs-print-for-eval ,value))))) | |
| 595 ((eq action 'return) | |
| 596 (if success | |
| 597 (setq done t) | |
| 598 (pymacs-report-error "%s" value))) | |
| 599 ((eq action 'raise) | |
| 600 (if success | |
| 601 (pymacs-report-error "Python: %s" value) | |
| 602 (pymacs-report-error "%s" value))) | |
| 603 (t (pymacs-report-error "Protocol error: %s" form)))))) | |
| 604 value)) | |
| 605 | |
| 606 (defun pymacs-round-trip (action inserter) | |
| 607 ;; This function produces a Python request by printing and | |
| 608 ;; evaluating INSERTER, which itself prints an argument. It sends | |
| 609 ;; the request to the Pymacs helper, awaits for any kind of reply, | |
| 610 ;; and returns it. | |
| 611 (with-current-buffer pymacs-transit-buffer | |
| 612 ;; Possibly trim the beginning of the transit buffer. | |
| 613 (cond ((not pymacs-trace-transit) | |
| 614 (erase-buffer)) | |
| 615 ((consp pymacs-trace-transit) | |
| 616 (when (> (buffer-size) (cdr pymacs-trace-transit)) | |
| 617 (let ((cut (- (buffer-size) (car pymacs-trace-transit)))) | |
| 618 (when (> cut 0) | |
| 619 (save-excursion | |
| 620 (goto-char cut) | |
| 621 (unless (memq (preceding-char) '(0 ?\n)) | |
| 622 (forward-line 1)) | |
| 623 (delete-region (point-min) (point)))))))) | |
| 624 ;; Send the request, wait for a reply, and process it. | |
| 625 (let* ((process (get-buffer-process pymacs-transit-buffer)) | |
| 626 (status (process-status process)) | |
| 627 (marker (process-mark process)) | |
| 628 (moving (= (point) marker)) | |
| 629 send-position reply-position reply) | |
| 630 (save-excursion | |
| 631 (save-match-data | |
| 632 ;; Encode request. | |
| 633 (setq send-position (marker-position marker)) | |
| 634 (let ((standard-output marker)) | |
| 635 (princ action) | |
| 636 (princ " ") | |
| 637 (eval inserter)) | |
| 638 (goto-char marker) | |
| 639 (unless (= (preceding-char) ?\n) | |
| 640 (princ "\n" marker)) | |
| 641 ;; Send request text. | |
| 642 (goto-char send-position) | |
| 643 (insert (format ">%d\t" (- marker send-position))) | |
| 644 (setq reply-position (marker-position marker)) | |
| 645 (process-send-region process send-position marker) | |
| 646 ;; Receive reply text. | |
| 647 (while (and (eq status 'run) | |
| 648 (progn | |
| 649 (goto-char reply-position) | |
| 650 (not (re-search-forward "<\\([0-9]+\\)\t" nil t)))) | |
| 651 (unless (accept-process-output process pymacs-timeout-at-reply) | |
| 652 (setq status (process-status process)))) | |
| 653 (when (eq status 'run) | |
| 654 (let ((limit-position (+ (match-end 0) | |
| 655 (string-to-number (match-string 1))))) | |
| 656 (while (and (eq status 'run) | |
| 657 (< (marker-position marker) limit-position)) | |
| 658 (unless (accept-process-output process pymacs-timeout-at-line) | |
| 659 (setq status (process-status process)))))) | |
| 660 ;; Decode reply. | |
| 661 (if (not (eq status 'run)) | |
| 662 (pymacs-report-error "Pymacs helper status is `%S'" status) | |
| 663 (goto-char (match-end 0)) | |
| 664 (setq reply (read (current-buffer)))))) | |
| 665 (when (and moving (not pymacs-trace-transit)) | |
| 666 (goto-char marker)) | |
| 667 reply))) | |
| 668 | |
| 669 (defun pymacs-interruptible-eval (expression) | |
| 670 ;; This function produces a pair (VALUE . SUCCESS) for EXPRESSION. | |
| 671 ;; A cautious evaluation of EXPRESSION is attempted, and any | |
| 672 ;; error while evaluating is caught, including Emacs quit (C-g). | |
| 673 ;; Any Emacs quit also gets forward as a SIGINT to the Pymacs handler. | |
| 674 ;; With SUCCESS being true, VALUE is the expression value. | |
| 675 ;; With SUCCESS being false, VALUE is an interruption diagnostic. | |
| 676 (condition-case info | |
| 677 (cons (let ((inhibit-quit nil)) (eval expression)) t) | |
| 678 (quit (setq quit-flag t) | |
| 679 (interrupt-process pymacs-transit-buffer) | |
| 680 (cons "*Interrupted!*" nil)) | |
| 681 (error (cons (prin1-to-string info) nil)))) | |
| 682 | |
| 683 (defun pymacs-proper-list-p (expression) | |
| 684 ;; Tell if a list is proper, id est, that it is `nil' or ends with `nil'. | |
| 685 (cond ((not expression)) | |
| 686 ((consp expression) (not (cdr (last expression)))))) | |
| 687 | |
| 688 (provide 'pymacs) |
