Fixed.
[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, Ruby
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 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 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 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-escape-data (data)
97   (let ((index 0))
98     (while (string-match "[%\r\n]+" data index)
99       (setq data (replace-match
100                   (mapconcat (lambda (c) (format "%%%02X" c))
101                              (match-string 0 data) "")
102                   nil nil data)
103             index (+ (match-end 0)
104                      (* (- (match-end 0) (match-beginning 0)) 2))))
105     data))
106
107 (defun riece-ruby-unescape-data (data)
108   (let ((index 0))
109     (while (string-match "%\\([0-9A-F][0-9A-F]\\)" data index)
110       (setq data (replace-match
111                   (read (concat "\"\\x" (match-string 1 data) "\""))
112                   nil nil data)
113             index (- (match-end 0) 2)))
114     data))
115
116 (defun riece-ruby-reset-process-buffer ()
117   (save-excursion
118     (set-buffer (process-buffer riece-ruby-process))
119     (buffer-disable-undo)
120     (make-local-variable 'riece-ruby-response)
121     (setq riece-ruby-response nil)
122     (make-local-variable 'riece-ruby-data)
123     (setq riece-ruby-data nil)
124     (make-local-variable 'riece-ruby-escaped-data)
125     (setq riece-ruby-escaped-data nil)
126     (make-local-variable 'riece-ruby-status-alist)
127     (setq riece-ruby-status-alist nil)))
128
129 (defun riece-ruby-send-eval (program)
130   (let* ((string (riece-ruby-escape-data program))
131          (length (- (length string) 998))
132          (index 0)
133          data)
134     (while (< index length)
135       (setq data (cons (substring string index (setq index (+ index 998)))
136                        data)))
137     (setq data (cons (substring string index) data)
138           data (nreverse data))
139     (process-send-string riece-ruby-process "EVAL\r\n")
140     (while data
141       (process-send-string riece-ruby-process
142                            (concat "D " (car data) "\r\n"))
143       (setq data (cdr data)))
144     (process-send-string riece-ruby-process "END\r\n")))
145
146 (defun riece-ruby-send-poll (name)
147   (process-send-string riece-ruby-process
148                        (concat "POLL " name "\r\n")))
149
150 (defun riece-ruby-send-exit (name)
151   (process-send-string riece-ruby-process
152                        (concat "EXIT " name "\r\n")))
153
154 (defun riece-ruby-filter (process input)
155   (save-excursion
156     (set-buffer (process-buffer process))
157     (goto-char (point-max))
158     (insert input)
159     (goto-char (process-mark process))
160     (beginning-of-line)
161     (while (looking-at ".*\r\n")
162       (if (looking-at "OK\\( \\(.*\\)\\)?\r")
163           (progn
164             (if riece-ruby-escaped-data
165                 (setq riece-ruby-data (mapconcat #'riece-ruby-unescape-data
166                                                  riece-ruby-escaped-data "")))
167             (setq riece-ruby-escaped-data nil
168                   riece-ruby-response (list 'OK (match-string 2))
169                   riece-ruby-lock nil))
170         (if (looking-at "ERR \\([0-9]+\\)\\( \\(.*\\)\\)?\r")
171             (progn
172               (setq riece-ruby-escaped-data nil
173                     riece-ruby-response
174                     (list 'ERR (string-to-number (match-string 2))
175                           (match-string 3))
176                     riece-ruby-lock nil))
177           (if (looking-at "D \\(.*\\)\r")
178               (setq riece-ruby-escaped-data (cons (match-string 1)
179                                                   riece-ruby-escaped-data))
180             (if (looking-at "S \\([^ ]*\\) \\(.*\\)\r")
181                 (progn
182                   (setq riece-ruby-status-alist (cons (cons (match-string 1)
183                                                             (match-string 2))
184                                                       riece-ruby-status-alist))
185                   (if (member (car (car riece-ruby-status-alist))
186                               '("finished" "exited"))
187                       (riece-ruby-run-exit-handler
188                        (cdr (car riece-ruby-status-alist)))))
189               (if (looking-at "# output \\([^ ]*\\) \\(.*\\)\r")
190                   (let ((entry (assoc (match-string 1)
191                                       riece-ruby-output-handler-alist)))
192                     (if entry
193                         (funcall (cdr entry) (car entry) (match-string 2))))
194                 (if (looking-at "# exit \\(.*\\)\r")
195                     (riece-ruby-run-exit-handler (match-string 1))))))))
196       (forward-line))
197     (set-marker (process-mark process) (point-marker))))
198
199 (defun riece-ruby-run-exit-handler (name)
200   (let ((entry (assoc name riece-ruby-exit-handler-alist)))
201     (if entry
202         (progn
203           (setq riece-ruby-exit-handler-alist
204                 (delq entry riece-ruby-exit-handler-alist))
205           (funcall (cdr entry) (car entry))
206           (riece-ruby-clear name)))))
207
208 (defun riece-ruby-sentinel (process status)
209   (kill-buffer (process-buffer process)))
210
211 (defun riece-ruby-execute (program)
212   "Schedule an execution of a Ruby PROGRAM.
213 Return a string name assigned by the server."
214   (unless (and riece-ruby-process
215                (eq (process-status riece-ruby-process) 'run))
216     (let (selective-display
217           (coding-system-for-write 'binary)
218           (coding-system-for-read 'binary))
219       (setq riece-ruby-process
220             (start-process "riece-ruby" (generate-new-buffer " *Ruby*")
221                            riece-ruby-command
222                            (if (file-name-absolute-p riece-ruby-server-program)
223                                riece-ruby-server-program
224                              (expand-file-name
225                               riece-ruby-server-program
226                               (file-name-directory
227                                (locate-library
228                                 (symbol-file 'riece-ruby-execute)))))))
229       (process-kill-without-query riece-ruby-process)
230       (set-process-filter riece-ruby-process #'riece-ruby-filter)
231       (set-process-sentinel riece-ruby-process #'riece-ruby-sentinel)))
232   (save-excursion
233     (set-buffer (process-buffer riece-ruby-process))
234     (riece-ruby-reset-process-buffer)
235     (make-local-variable 'riece-ruby-lock)
236     (setq riece-ruby-lock t)
237     (riece-ruby-send-eval program)
238     (while riece-ruby-lock
239       (accept-process-output riece-ruby-process))
240     (if (eq (car riece-ruby-response) 'ERR)
241         (error "Couldn't execute: %S" (cdr riece-ruby-response)))
242     (cdr (assoc "name" riece-ruby-status-alist))))
243
244 (defun riece-ruby-inspect (name)
245   "Inspect a result of program execution distinguished by NAME.
246 Return a three element list.
247 The car is protocol response line which looks like:
248   \(ERR 103 \"Not implemented\").
249 The cadr is data from the server, that is, the result of the program.
250 The caddr is status from the server."
251   (save-excursion
252     (set-buffer (process-buffer riece-ruby-process))
253     (riece-ruby-reset-process-buffer)
254     (make-local-variable 'riece-ruby-lock)
255     (setq riece-ruby-lock t)
256     (riece-ruby-send-poll name)
257     (while riece-ruby-lock
258       (accept-process-output riece-ruby-process))
259     (list riece-ruby-response
260           riece-ruby-data
261           riece-ruby-status-alist)))
262
263 (defun riece-ruby-clear (name)
264   "Clear a result of program execution distinguished by NAME.
265 Note that riece-ruby-clear is automatically called iff an exit-handler
266 is specified.  Otherwise, it should be called explicitly."
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   "Set an exit-handler HANDLER for the program distinguished by NAME.
281 An exit-handler is called when the program is finished or exited abnormally.
282 An exit-handler is called with an argument same as NAME.
283 Note that riece-ruby-clear is automatically called iff an exit-handler
284 is specified.  Otherwise, it should be called explicitly."
285   (let ((entry (assoc name riece-ruby-exit-handler-alist)))
286     (if handler
287         (progn
288           (if entry
289               (setcdr entry handler)
290             (setq riece-ruby-exit-handler-alist
291                   (cons (cons name handler)
292                         riece-ruby-exit-handler-alist)))
293           ;;check if the program already exited
294           (riece-ruby-inspect name))
295       (if entry
296           (setq riece-ruby-exit-handler-alist
297                 (delq entry riece-ruby-exit-handler-alist))))))
298
299 (defun riece-ruby-set-output-handler (name handler)
300   "Set an output-handler HANDLER for the program distinguished by NAME.
301 An output-handler is called when the program sends any output by using
302 `output' method in the Ruby program.
303 An output-handler is called with two argument.  The first argument is
304 the same as NAME.  The second argument is output string."
305   (let ((entry (assoc name riece-ruby-output-handler-alist)))
306     (if handler
307         (progn
308           (if entry
309               (setcdr entry handler)
310             (setq riece-ruby-output-handler-alist
311                   (cons (cons name handler)
312                         riece-ruby-output-handler-alist))))
313       (if entry
314           (setq riece-ruby-output-handler-alist
315                 (delq entry riece-ruby-output-handler-alist))))))
316
317 (defun riece-ruby-set-property (name property value)
318   "Set given PROPERTY/VALUE pair to the program distinguished by NAME."
319   (let ((entry (assoc name riece-ruby-property-alist))
320         property-entry)
321     (unless entry
322       (setq entry (list name)
323             riece-ruby-property-alist (cons entry riece-ruby-property-alist)))
324     (if (setq property-entry (assoc property (cdr entry)))
325         (setcdr property-entry value)
326       (setcdr entry (cons (cons property value) (cdr entry))))))
327
328 (defun riece-ruby-property (name property)
329   "Return the value of PROPERTY set to the program distinguished by NAME."
330   (cdr (assoc property (cdr (assoc name riece-ruby-property-alist)))))
331
332 (defun riece-ruby-substitute-variables (program alist)
333   "Substitute symbols in PROGRAM by looking up ALIST.
334 Return a string concatenating elements in PROGRAM."
335   (setq program (copy-sequence program))
336   (while alist
337     (let ((pointer program))
338       (while pointer
339         (setq pointer (memq (car (car alist)) program))
340         (if pointer
341             (setcar pointer (cdr (car alist))))))
342     (setq alist (cdr alist)))
343   (apply #'concat program))
344
345 (provide 'riece-ruby)
346
347 ;;; riece-ruby.el ends here