Remove the first line of stack trace.
[riece] / lisp / rubyserv.rb
1 # A simple IPC server executing Ruby programs.
2
3 require 'thread'
4
5 class RubyServ
6   def initialize
7     @buf = ''
8     @que = Queue.new
9     @thr = Hash.new
10   end
11
12   def dispatch(line)
13     case line.chomp
14     when /\AD /
15       @buf << $'
16     when /\A(\S+)\s*/
17       c = $1
18       r = $'
19       d = "dispatch_#{c.downcase}"
20       if respond_to?(d, true)
21         Thread.start do
22           self.send(d, c, r)
23         end
24       else
25         puts("ERR 103 Unknown command\r\n")
26       end
27     end
28   end
29
30   def dispatch_cancel(c, r)
31     puts("ERR 100 Not implemented\r\n")
32   end
33
34   def dispatch_bye(c, r)
35     puts("ERR 100 Not implemented\r\n")
36   end
37
38   def dispatch_auth(c, r)
39     puts("ERR 100 Not implemented\r\n")
40   end
41
42   def dispatch_reset(c, r)
43     puts("ERR 100 Not implemented\r\n")
44   end
45
46   def dispatch_end(c, r)
47     enq_data
48   end
49
50   def dispatch_help(c, r)
51     puts("ERR 100 Not implemented\r\n")
52   end
53
54   def dispatch_quit(c, r)
55     puts("ERR 100 Not implemented\r\n")
56   end
57
58   def dispatch_eval(c, r)
59     name, code = r.split(/\s+/, 2)
60     if @thr.include?(name) && @thr[name].alive?
61       puts("ERR 105 Parameter error: \"#{name}\" is already in use\r\n")
62       return
63     end
64     code = deq_data unless code
65     puts("OK\r\n")
66     @thr[name] = Thread.current
67     Thread.current[:rubyserv_name] = name
68     begin
69       Thread.current[:rubyserv_error] = false
70       Thread.current[:rubyserv_response] = eval(code)
71     rescue Exception => e
72       Thread.current[:rubyserv_error] = true
73       Thread.current[:rubyserv_response] = e.to_s.sub(/\A.*?\n/m, '')
74     end
75     puts("# exit #{name}\r\n")
76   end
77
78   def dispatch_poll(c, r)
79     thr = @thr[r]
80     if !thr
81       puts("ERR 105 Parameter error: no such name \"#{r}\"\r\n")
82     elsif thr.alive?
83       puts("S program running\r\n")
84       puts("OK\r\n")
85     else
86       @thr.delete(r)
87       if thr[:rubyserv_error]
88         puts("S program exited\r\n")
89       else
90         puts("S program finished\r\n")
91       end
92       if d = thr[:rubyserv_response]
93         send_data(d.to_s)
94       end
95       puts("OK\r\n")
96     end
97   end
98
99   def escape(s)
100     s.gsub(/[%\r\n]/) {|m| '%%%02X' % m[0]}
101   end
102
103   def unescape(s)
104     s.gsub(/%([0-9A-Z][0-9A-Z])/, ['\1'].pack('H*'))
105   end
106
107   def output(s)
108     puts("# output #{Thread.current[:rubyserv_name]} #{s}\r\n")
109   end
110
111   def send_data(d)
112     d = escape(d)
113     begin
114       len = [d.length, 998].min   # 998 = 1000 - "D "
115       puts("D #{d[0 ... len]}\r\n")
116       d = d[len .. -1]
117     end until d.empty?
118   end
119
120   def enq_data
121     d = unescape(@buf)
122     @buf = ''
123     @que.enq(d)
124   end
125
126   def deq_data
127     @que.deq
128   end
129 end
130
131 if $0 == __FILE__
132   serv = RubyServ.new
133   while gets
134     serv.dispatch($_)
135   end
136 end