* riece-ruby.el (riece-ruby-set-output-handler): New function.
[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 (defvar riece-ruby-command "ruby"
52   "Command name for Ruby interpreter.")
53
54 (defvar riece-ruby-server-program "server.rb")
55
56 (defvar riece-ruby-process nil)
57
58 (defvar riece-ruby-lock nil)
59 (defvar riece-ruby-response nil)
60 (defvar riece-ruby-data nil)
61 (defvar riece-ruby-escaped-data nil)
62 (defvar riece-ruby-status-alist nil)
63
64 (defvar riece-ruby-output-handler-alist nil)
65 (defvar riece-ruby-exit-handler-alist nil)
66
67 (defun riece-ruby-substitute-variables (program variable value)
68   (setq program (copy-sequence program))
69   (let ((pointer program))
70     (while pointer
71       (setq pointer (memq variable program))
72       (if pointer
73           (setcar pointer value)))
74     program))
75
76 (defun riece-ruby-escape-data (data)
77   (let ((index 0))
78     (while (string-match "[%\r\n]+" data index)
79       (setq data (replace-match
80                   (mapconcat (lambda (c) (format "%%%02X" c))
81                              (match-string 0 data) "")
82                   nil nil data)
83             index (+ (match-end 0)
84                      (* (- (match-end 0) (match-beginning 0)) 2))))
85     data))
86
87 (defun riece-ruby-unescape-data (data)
88   (let ((index 0))
89     (while (string-match "%\\([0-9A-F][0-9A-F]\\)" data index)
90       (setq data (replace-match
91                   (read (concat "\"\\x" (match-string 1 data) "\""))
92                   nil nil data)
93             index (- (match-end 0) 2)))
94     data))
95
96 (defun riece-ruby-reset-process-buffer ()
97   (save-excursion
98     (set-buffer (process-buffer riece-ruby-process))
99     (buffer-disable-undo)
100     (make-local-variable 'riece-ruby-response)
101     (setq riece-ruby-response nil)
102     (make-local-variable 'riece-ruby-data)
103     (setq riece-ruby-data nil)
104     (make-local-variable 'riece-ruby-escaped-data)
105     (setq riece-ruby-escaped-data nil)
106     (make-local-variable 'riece-ruby-status-alist)
107     (setq riece-ruby-status-alist nil)))
108
109 (defun riece-ruby-send-eval (program)
110   (let* ((string (riece-ruby-escape-data program))
111          (length (- (length string) 998))
112          (index 0)
113          data)
114     (while (< index length)
115       (setq data (cons (substring string index (setq index (+ index 998)))
116                        data)))
117     (setq data (cons (substring string index) data)
118           data (nreverse data))
119     (process-send-string riece-ruby-process "EVAL\r\n")
120     (while data
121       (process-send-string riece-ruby-process
122                            (concat "D " (car data) "\r\n"))
123       (setq data (cdr data)))
124     (process-send-string riece-ruby-process "END\r\n")))
125
126 (defun riece-ruby-send-poll (name)
127   (process-send-string riece-ruby-process
128                        (concat "POLL " name "\r\n")))
129
130 (defun riece-ruby-send-exit (name)
131   (process-send-string riece-ruby-process
132                        (concat "EXIT " name "\r\n")))
133
134 (defun riece-ruby-filter (process input)
135   (save-excursion
136     (set-buffer (process-buffer process))
137     (goto-char (point-max))
138     (insert input)
139     (goto-char (process-mark process))
140     (beginning-of-line)
141     (while (looking-at ".*\r\n")
142       (if (looking-at "OK\\( \\(.*\\)\\)?\r")
143           (progn
144             (if riece-ruby-escaped-data
145                 (setq riece-ruby-data (mapconcat #'riece-ruby-unescape-data
146                                                  riece-ruby-escaped-data "")))
147             (setq riece-ruby-escaped-data nil
148                   riece-ruby-response (list 'OK (match-string 2))
149                   riece-ruby-lock nil))
150         (if (looking-at "ERR \\([0-9]+\\)\\( \\(.*\\)\\)?\r")
151             (progn
152               (setq riece-ruby-escaped-data nil
153                     riece-ruby-response
154                     (list 'ERR (string-to-number (match-string 2))
155                           (match-string 3))
156                     riece-ruby-lock nil))
157           (if (looking-at "D \\(.*\\)\r")
158               (setq riece-ruby-escaped-data (cons (match-string 1)
159                                                   riece-ruby-escaped-data))
160             (if (looking-at "S \\(.*\\) \\(.*\\)\r")
161                 (progn
162                   (setq riece-ruby-status-alist (cons (cons (match-string 1)
163                                                             (match-string 2))
164                                                       riece-ruby-status-alist))
165                   (if (member (car (car riece-ruby-status-alist))
166                               '("finished" "exited"))
167                       (riece-ruby-run-exit-handler
168                        (cdr (car riece-ruby-status-alist)))))
169               (if (looking-at "# output \\(.*\\) \\(.*\\)\r")
170                   (let ((entry (assoc (match-string 1)
171                                       riece-ruby-output-handler-alist)))
172                     (if entry
173                         (funcall (cdr entry) (match-string 2))))
174                 (if (looking-at "# exit \\(.*\\)\r")
175                     (riece-ruby-run-exit-handler (match-string 1))))))))
176       (forward-line))
177     (set-marker (process-mark process) (point-marker))))
178
179 (defun riece-ruby-run-exit-handler (name)
180   (let ((entry (assoc name riece-ruby-exit-handler-alist)))
181     (if entry
182         (progn
183           (funcall (cdr entry))
184           (setq riece-ruby-exit-handler-alist (delq entry))))))
185
186 (defun riece-ruby-sentinel (process status)
187   (kill-buffer (process-buffer process)))
188
189 (defun riece-ruby-execute (program)
190   (unless (and riece-ruby-process
191                (eq (process-status riece-ruby-process) 'run))
192     (let (selective-display
193           (coding-system-for-write 'binary)
194           (coding-system-for-read 'binary))
195       (setq riece-ruby-process
196             (start-process "riece-ruby" (generate-new-buffer " *Ruby*")
197                            riece-ruby-command
198                            (if (file-name-absolute-p riece-ruby-server-program)
199                                riece-ruby-server-program
200                              (expand-file-name
201                               riece-ruby-server-program
202                               (file-name-directory
203                                (symbol-file 'riece-ruby-execute))))))
204       (set-process-filter riece-ruby-process #'riece-ruby-filter)
205       (set-process-sentinel riece-ruby-process #'riece-ruby-sentinel)))
206   (save-excursion
207     (set-buffer (process-buffer riece-ruby-process))
208     (riece-ruby-reset-process-buffer)
209     (make-local-variable 'riece-ruby-lock)
210     (setq riece-ruby-lock t)
211     (riece-ruby-send-eval program)
212     (while riece-ruby-lock
213       (accept-process-output riece-ruby-process))
214     (if (eq (car riece-ruby-response) 'ERR)
215         (error "Couldn't execute: %S" (cdr riece-ruby-response)))
216     (cdr (assoc "name" riece-ruby-status-alist))))
217
218 (defun riece-ruby-inspect (name)
219   (save-excursion
220     (set-buffer (process-buffer riece-ruby-process))
221     (riece-ruby-reset-process-buffer)
222     (make-local-variable 'riece-ruby-lock)
223     (setq riece-ruby-lock t)
224     (riece-ruby-send-poll name)
225     (while (null riece-ruby-response)
226       (accept-process-output riece-ruby-process))
227     (list riece-ruby-response
228           riece-ruby-data
229           riece-ruby-status-alist)))
230
231 (defun riece-ruby-clear (name)
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-exit name)
238     (while (null riece-ruby-response)
239       (accept-process-output riece-ruby-process))))
240
241 (defun riece-ruby-set-exit-handler (name handler)
242   (let ((entry (assoc name riece-ruby-exit-handler-alist)))
243     (if entry
244         (setcdr entry handler)
245       (setq riece-ruby-exit-handler-alist
246             (cons (cons name handler)
247                   riece-ruby-exit-handler-alist)))
248     ;;check if the program already exited
249     (riece-ruby-inspect)))
250
251 (defun riece-ruby-set-output-handler (name handler)
252   (let ((entry (assoc name riece-ruby-output-handler-alist)))
253     (if entry
254         (setcdr entry handler)
255       (setq riece-ruby-output-handler-alist
256             (cons (cons name handler)
257                   riece-ruby-output-handler-alist)))))
258
259 (provide 'riece-ruby)
260
261 ;;; riece-ruby.el ends here