46d6a74dcffcdc2387290832068147d02a631f21
[riece] / lisp / riece-ruby.el
1 ;;; riece-ruby.el --- interact with Ruby interpreter
2 ;; Copyright (C) 1998-2005 Daiki Ueno
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Created: 1998-09-28
6 ;; Keywords: IRC, riece
7
8 ;; This file is part of Riece.
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; riece-ruby.el is a library to interact with the Ruby interpreter.
28 ;; It supports concurrent execution of Ruby programs in a single
29 ;; session.  For example:
30 ;; 
31 ;; (riece-ruby-execute "sleep 30"); returns immediately
32 ;; => "rubyserv0"
33 ;;
34 ;; (riece-ruby-execute "1 + 1")
35 ;; => "rubyserv1"
36 ;;
37 ;; (riece-ruby-execute "\"")
38 ;; => "rubyserv2"
39 ;;
40 ;; (riece-ruby-inspect "rubyserv0")
41 ;; => ((OK nil) nil "running")
42 ;;
43 ;; (riece-ruby-inspect "rubyserv1")
44 ;; => ((OK nil) "2" "finished")
45 ;;
46 ;; (riece-ruby-inspect "rubyserv2")
47 ;; => ((OK nil) "(eval):1: unterminated string meets end of file" "exited")
48
49 ;;; Code:
50
51 (defgroup riece-ruby nil
52   "Interact with the Ruby interpreter."
53   :group 'riece)
54
55 (defcustom riece-ruby-command "ruby"
56   "Command name for Ruby interpreter."
57   :type 'string
58   :group 'riece-ruby)
59
60 (defvar riece-ruby-server-program "server.rb"
61   "The server program file.  If the filename is not absolute, it is
62 assumed that the file is in the same directory of this file.")
63
64 (defvar riece-ruby-process nil
65   "Process object of the Ruby interpreter.")
66
67 (defvar riece-ruby-lock nil
68   "Lock for waiting server response.
69 Local to the process buffer.")
70 (defvar riece-ruby-response nil
71   "The server response.
72 Local to the process buffer.")
73 (defvar riece-ruby-data nil
74   "Data from server.
75 Local to the process buffer.")
76 (defvar riece-ruby-escaped-data nil
77   "Escaped data from server.  This variable is cleared every time
78 server response arrives.
79 Local to the process buffer.")
80 (defvar riece-ruby-status-alist nil
81   "Status from server.
82 Local to the process buffer.")
83
84 (defvar riece-ruby-output-handler-alist nil
85   "An alist mapping from program name to output handler.
86 Output handlers are called every time \"# output\" line arrives.
87 Use `riece-ruby-set-output-handler' to set this variable.")
88 (defvar riece-ruby-exit-handler-alist nil
89   "An alist mapping from program name to exit handler.
90 Exit handlers are called once when \"# exit\" line arrives.
91 Use `riece-ruby-set-exit-handler' to set this variable.")
92 (defvar riece-ruby-property-alist nil
93   "An alist mapping from program name to the property list.
94 Use `riece-ruby-set-property' to set this variable.")
95
96 (defun riece-ruby-substitute-variables (program alist)
97   (setq program (copy-sequence program))
98   (while alist
99     (let ((pointer program))
100       (while pointer
101         (setq pointer (memq (car (car alist)) program))
102         (if pointer
103             (setcar pointer (cdr (car alist))))))
104     (setq alist (cdr alist)))
105   (apply #'concat program))
106
107 (defun riece-ruby-escape-data (data)
108   (let ((index 0))
109     (while (string-match "[%\r\n]+" data index)
110       (setq data (replace-match
111                   (mapconcat (lambda (c) (format "%%%02X" c))
112                              (match-string 0 data) "")
113                   nil nil data)
114             index (+ (match-end 0)
115                      (* (- (match-end 0) (match-beginning 0)) 2))))
116     data))
117
118 (defun riece-ruby-unescape-data (data)
119   (let ((index 0))
120     (while (string-match "%\\([0-9A-F][0-9A-F]\\)" data index)
121       (setq data (replace-match
122                   (read (concat "\"\\x" (match-string 1 data) "\""))
123                   nil nil data)
124             index (- (match-end 0) 2)))
125     data))
126
127 (defun riece-ruby-reset-process-buffer ()
128   (save-excursion
129     (set-buffer (process-buffer riece-ruby-process))
130     (buffer-disable-undo)
131     (make-local-variable 'riece-ruby-response)
132     (setq riece-ruby-response nil)
133     (make-local-variable 'riece-ruby-data)
134     (setq riece-ruby-data nil)
135     (make-local-variable 'riece-ruby-escaped-data)
136     (setq riece-ruby-escaped-data nil)
137     (make-local-variable 'riece-ruby-status-alist)
138     (setq riece-ruby-status-alist nil)))
139
140 (defun riece-ruby-send-eval (program)
141   (let* ((string (riece-ruby-escape-data program))
142          (length (- (length string) 998))
143          (index 0)
144          data)
145     (while (< index length)
146       (setq data (cons (substring string index (setq index (+ index 998)))
147                        data)))
148     (setq data (cons (substring string index) data)
149           data (nreverse data))
150     (process-send-string riece-ruby-process "EVAL\r\n")
151     (while data
152       (process-send-string riece-ruby-process
153                            (concat "D " (car data) "\r\n"))
154       (setq data (cdr data)))
155     (process-send-string riece-ruby-process "END\r\n")))
156
157 (defun riece-ruby-send-poll (name)
158   (process-send-string riece-ruby-process
159                        (concat "POLL " name "\r\n")))
160
161 (defun riece-ruby-send-exit (name)
162   (process-send-string riece-ruby-process
163                        (concat "EXIT " name "\r\n")))
164
165 (defun riece-ruby-filter (process input)
166   (save-excursion
167     (set-buffer (process-buffer process))
168     (goto-char (point-max))
169     (insert input)
170     (goto-char (process-mark process))
171     (beginning-of-line)
172     (while (looking-at ".*\r\n")
173       (if (looking-at "OK\\( \\(.*\\)\\)?\r")
174           (progn
175             (if riece-ruby-escaped-data
176                 (setq riece-ruby-data (mapconcat #'riece-ruby-unescape-data
177                                                  riece-ruby-escaped-data "")))
178             (setq riece-ruby-escaped-data nil
179                   riece-ruby-response (list 'OK (match-string 2))
180                   riece-ruby-lock nil))
181         (if (looking-at "ERR \\([0-9]+\\)\\( \\(.*\\)\\)?\r")
182             (progn
183               (setq riece-ruby-escaped-data nil
184                     riece-ruby-response
185                     (list 'ERR (string-to-number (match-string 2))
186                           (match-string 3))
187                     riece-ruby-lock nil))
188           (if (looking-at "D \\(.*\\)\r")
189               (setq riece-ruby-escaped-data (cons (match-string 1)
190                                                   riece-ruby-escaped-data))
191             (if (looking-at "S \\([^ ]*\\) \\(.*\\)\r")
192                 (progn
193                   (setq riece-ruby-status-alist (cons (cons (match-string 1)
194                                                             (match-string 2))
195                                                       riece-ruby-status-alist))
196                   (if (member (car (car riece-ruby-status-alist))
197                               '("finished" "exited"))
198                       (riece-ruby-run-exit-handler
199                        (cdr (car riece-ruby-status-alist)))))
200               (if (looking-at "# output \\([^ ]*\\) \\(.*\\)\r")
201                   (let ((entry (assoc (match-string 1)
202                                       riece-ruby-output-handler-alist)))
203                     (if entry
204                         (funcall (cdr entry) (car entry) (match-string 2))))
205                 (if (looking-at "# exit \\(.*\\)\r")
206                     (riece-ruby-run-exit-handler (match-string 1))))))))
207       (forward-line))
208     (set-marker (process-mark process) (point-marker))))
209
210 (defun riece-ruby-run-exit-handler (name)
211   (let ((entry (assoc name riece-ruby-exit-handler-alist)))
212     (if entry
213         (progn
214           (setq riece-ruby-exit-handler-alist
215                 (delq entry riece-ruby-exit-handler-alist))
216           (funcall (cdr entry) (car entry))
217           (riece-ruby-clear name)))))
218
219 (defun riece-ruby-sentinel (process status)
220   (kill-buffer (process-buffer process)))
221
222 (defun riece-ruby-execute (program)
223   (unless (and riece-ruby-process
224                (eq (process-status riece-ruby-process) 'run))
225     (let (selective-display
226           (coding-system-for-write 'binary)
227           (coding-system-for-read 'binary))
228       (setq riece-ruby-process
229             (start-process "riece-ruby" (generate-new-buffer " *Ruby*")
230                            riece-ruby-command
231                            (if (file-name-absolute-p riece-ruby-server-program)
232                                riece-ruby-server-program
233                              (expand-file-name
234                               riece-ruby-server-program
235                               (file-name-directory
236                                (locate-library
237                                 (symbol-file 'riece-ruby-execute)))))))
238       (process-kill-without-query riece-ruby-process)
239       (set-process-filter riece-ruby-process #'riece-ruby-filter)
240       (set-process-sentinel riece-ruby-process #'riece-ruby-sentinel)))
241   (save-excursion
242     (set-buffer (process-buffer riece-ruby-process))
243     (riece-ruby-reset-process-buffer)
244     (make-local-variable 'riece-ruby-lock)
245     (setq riece-ruby-lock t)
246     (riece-ruby-send-eval program)
247     (while riece-ruby-lock
248       (accept-process-output riece-ruby-process))
249     (if (eq (car riece-ruby-response) 'ERR)
250         (error "Couldn't execute: %S" (cdr riece-ruby-response)))
251     (cdr (assoc "name" riece-ruby-status-alist))))
252
253 (defun riece-ruby-inspect (name)
254   (save-excursion
255     (set-buffer (process-buffer riece-ruby-process))
256     (riece-ruby-reset-process-buffer)
257     (make-local-variable 'riece-ruby-lock)
258     (setq riece-ruby-lock t)
259     (riece-ruby-send-poll name)
260     (while riece-ruby-lock
261       (accept-process-output riece-ruby-process))
262     (list riece-ruby-response
263           riece-ruby-data
264           riece-ruby-status-alist)))
265
266 (defun riece-ruby-clear (name)
267   (save-excursion
268     (set-buffer (process-buffer riece-ruby-process))
269     (riece-ruby-reset-process-buffer)
270     (make-local-variable 'riece-ruby-lock)
271     (setq riece-ruby-lock t)
272     (riece-ruby-send-exit name)
273     (while riece-ruby-lock
274       (accept-process-output riece-ruby-process)))
275   (let ((entry (assoc name riece-ruby-property-alist)))
276     (if entry
277         (delq entry riece-ruby-property-alist))))
278
279 (defun riece-ruby-set-exit-handler (name handler)
280   (let ((entry (assoc name riece-ruby-exit-handler-alist)))
281     (if handler
282         (progn
283           (if entry
284               (setcdr entry handler)
285             (setq riece-ruby-exit-handler-alist
286                   (cons (cons name handler)
287                         riece-ruby-exit-handler-alist)))
288           ;;check if the program already exited
289           (riece-ruby-inspect name))
290       (if entry
291           (setq riece-ruby-exit-handler-alist
292                 (delq entry riece-ruby-exit-handler-alist))))))
293
294 (defun riece-ruby-set-output-handler (name handler)
295   (let ((entry (assoc name riece-ruby-output-handler-alist)))
296     (if handler
297         (progn
298           (if entry
299               (setcdr entry handler)
300             (setq riece-ruby-output-handler-alist
301                   (cons (cons name handler)
302                         riece-ruby-output-handler-alist))))
303       (if entry
304           (setq riece-ruby-output-handler-alist
305                 (delq entry riece-ruby-output-handler-alist))))))
306
307 (defun riece-ruby-set-property (name property value)
308   (let ((entry (assoc name riece-ruby-property-alist))
309         property-entry)
310     (unless entry
311       (setq entry (list name)
312             riece-ruby-property-alist (cons entry riece-ruby-property-alist)))
313     (if (setq property-entry (assoc property (cdr entry)))
314         (setcdr property-entry value)
315       (setcdr entry (cons (cons property value) (cdr entry))))))
316
317 (defun riece-ruby-property (name property)
318   (cdr (assoc property (cdr (assoc name riece-ruby-property-alist)))))
319
320 (provide 'riece-ruby)
321
322 ;;; riece-ruby.el ends here