1 ;;; riece-ruby.el --- interact with Ruby interpreter
2 ;; Copyright (C) 1998-2005 Daiki Ueno
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
6 ;; Keywords: IRC, riece
8 ;; This file is part of Riece.
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)
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.
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.
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:
31 ;; (riece-ruby-execute "sleep 30"); returns immediately
34 ;; (riece-ruby-execute "1 + 1")
37 ;; (riece-ruby-execute "\"")
40 ;; (riece-ruby-inspect "rubyserv0")
41 ;; => ((OK nil) nil "running")
43 ;; (riece-ruby-inspect "rubyserv1")
44 ;; => ((OK nil) "2" "finished")
46 ;; (riece-ruby-inspect "rubyserv2")
47 ;; => ((OK nil) "(eval):1: unterminated string meets end of file" "exited")
51 (defgroup riece-ruby nil
52 "Interact with the Ruby interpreter."
55 (defcustom riece-ruby-command "ruby"
56 "Command name for Ruby interpreter."
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.")
64 (defvar riece-ruby-process nil
65 "Process object of the Ruby interpreter.")
67 (defvar riece-ruby-lock nil
68 "Lock for waiting server response.
69 Local to the process buffer.")
70 (defvar riece-ruby-response nil
72 Local to the process buffer.")
73 (defvar riece-ruby-data nil
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
82 Local to the process buffer.")
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.")
96 (defun riece-ruby-substitute-variables (program alist)
97 (setq program (copy-sequence program))
99 (let ((pointer program))
101 (setq pointer (memq (car (car alist)) program))
103 (setcar pointer (cdr (car alist))))))
104 (setq alist (cdr alist)))
105 (apply #'concat program))
107 (defun riece-ruby-escape-data (data)
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) "")
114 index (+ (match-end 0)
115 (* (- (match-end 0) (match-beginning 0)) 2))))
118 (defun riece-ruby-unescape-data (data)
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) "\""))
124 index (- (match-end 0) 2)))
127 (defun riece-ruby-reset-process-buffer ()
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)))
140 (defun riece-ruby-send-eval (program)
141 (let* ((string (riece-ruby-escape-data program))
142 (length (- (length string) 998))
145 (while (< index length)
146 (setq data (cons (substring string index (setq index (+ index 998)))
148 (setq data (cons (substring string index) data)
149 data (nreverse data))
150 (process-send-string riece-ruby-process "EVAL\r\n")
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")))
157 (defun riece-ruby-send-poll (name)
158 (process-send-string riece-ruby-process
159 (concat "POLL " name "\r\n")))
161 (defun riece-ruby-send-exit (name)
162 (process-send-string riece-ruby-process
163 (concat "EXIT " name "\r\n")))
165 (defun riece-ruby-filter (process input)
167 (set-buffer (process-buffer process))
168 (goto-char (point-max))
170 (goto-char (process-mark process))
172 (while (looking-at ".*\r\n")
173 (if (looking-at "OK\\( \\(.*\\)\\)?\r")
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")
183 (setq riece-ruby-escaped-data nil
185 (list 'ERR (string-to-number (match-string 2))
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")
193 (setq riece-ruby-status-alist (cons (cons (match-string 1)
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)))
204 (funcall (cdr entry) (car entry) (match-string 2))))
205 (if (looking-at "# exit \\(.*\\)\r")
206 (riece-ruby-run-exit-handler (match-string 1))))))))
208 (set-marker (process-mark process) (point-marker))))
210 (defun riece-ruby-run-exit-handler (name)
211 (let ((entry (assoc name riece-ruby-exit-handler-alist)))
214 (funcall (cdr entry) (car entry))
215 (setq riece-ruby-exit-handler-alist
216 (delq entry riece-ruby-exit-handler-alist))))))
218 (defun riece-ruby-sentinel (process status)
219 (kill-buffer (process-buffer process)))
221 (defun riece-ruby-execute (program)
222 (unless (and riece-ruby-process
223 (eq (process-status riece-ruby-process) 'run))
224 (let (selective-display
225 (coding-system-for-write 'binary)
226 (coding-system-for-read 'binary))
227 (setq riece-ruby-process
228 (start-process "riece-ruby" (generate-new-buffer " *Ruby*")
230 (if (file-name-absolute-p riece-ruby-server-program)
231 riece-ruby-server-program
233 riece-ruby-server-program
236 (symbol-file 'riece-ruby-execute)))))))
237 (set-process-filter riece-ruby-process #'riece-ruby-filter)
238 (set-process-sentinel riece-ruby-process #'riece-ruby-sentinel)))
240 (set-buffer (process-buffer riece-ruby-process))
241 (riece-ruby-reset-process-buffer)
242 (make-local-variable 'riece-ruby-lock)
243 (setq riece-ruby-lock t)
244 (riece-ruby-send-eval program)
245 (while riece-ruby-lock
246 (accept-process-output riece-ruby-process))
247 (if (eq (car riece-ruby-response) 'ERR)
248 (error "Couldn't execute: %S" (cdr riece-ruby-response)))
249 (cdr (assoc "name" riece-ruby-status-alist))))
251 (defun riece-ruby-inspect (name)
253 (set-buffer (process-buffer riece-ruby-process))
254 (riece-ruby-reset-process-buffer)
255 (make-local-variable 'riece-ruby-lock)
256 (setq riece-ruby-lock t)
257 (riece-ruby-send-poll name)
258 (while riece-ruby-lock
259 (accept-process-output riece-ruby-process))
260 (list riece-ruby-response
262 riece-ruby-status-alist)))
264 (defun riece-ruby-clear (name)
266 (set-buffer (process-buffer riece-ruby-process))
267 (riece-ruby-reset-process-buffer)
268 (riece-ruby-send-exit name))
269 (let ((entry (assoc name riece-ruby-property-alist)))
271 (delq entry riece-ruby-property-alist))))
273 (defun riece-ruby-set-exit-handler (name handler)
274 (let ((entry (assoc name riece-ruby-exit-handler-alist)))
276 (setcdr entry handler)
277 (setq riece-ruby-exit-handler-alist
278 (cons (cons name handler)
279 riece-ruby-exit-handler-alist)))
280 ;;check if the program already exited
281 (riece-ruby-inspect name)))
283 (defun riece-ruby-set-output-handler (name handler)
284 (let ((entry (assoc name riece-ruby-output-handler-alist)))
286 (setcdr entry handler)
287 (setq riece-ruby-output-handler-alist
288 (cons (cons name handler)
289 riece-ruby-output-handler-alist)))))
291 (defun riece-ruby-set-property (name property value)
292 (let ((entry (assoc name riece-ruby-property-alist))
295 (setq entry (list name)
296 riece-ruby-property-alist (cons entry riece-ruby-property-alist)))
297 (if (setq property-entry (assoc property (cdr entry)))
298 (setcdr property-entry value)
299 (setcdr entry (cons (cons property value) (cdr entry))))))
301 (defun riece-ruby-property (name property)
302 (cdr (assoc property (cdr (assoc name riece-ruby-property-alist)))))
304 (provide 'riece-ruby)
306 ;;; riece-ruby.el ends here