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