Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

385 line
7.5 KiB

5 年之前
  1. # swank.rb --- swank server for Ruby.
  2. #
  3. # This is my first Ruby program and looks probably rather strange. Some
  4. # people write Scheme interpreters when learning new languages, I
  5. # write swank backends.
  6. #
  7. # Only a few things work.
  8. # 1. Start the server with something like: ruby -r swank -e swank
  9. # 2. Use M-x slime-connect to establish a connection
  10. require "socket"
  11. def swank(port=4005)
  12. accept_connections port, false
  13. end
  14. def start_swank(port_file)
  15. accept_connections false, port_file
  16. end
  17. def accept_connections(port, port_file)
  18. server = TCPServer.new("localhost", port || 0)
  19. puts "Listening on #{server.addr.inspect}\n"
  20. if port_file
  21. write_port_file server.addr[1], port_file
  22. end
  23. socket = begin server.accept ensure server.close end
  24. begin
  25. serve socket.to_io
  26. ensure
  27. socket.close
  28. end
  29. end
  30. def write_port_file(port, filename)
  31. File.open(filename, File::CREAT|File::EXCL|File::WRONLY) do |f|
  32. f.puts port
  33. end
  34. end
  35. def serve(io)
  36. main_loop(io)
  37. end
  38. def main_loop(io)
  39. c = Connection.new(io)
  40. while true
  41. catch :swank_top_level do
  42. c.dispatch(read_packet(io))
  43. end
  44. end
  45. end
  46. class Connection
  47. def initialize(io)
  48. @io = io
  49. end
  50. def dispatch(event)
  51. puts "dispatch: %s\n" % event.inspect
  52. case event[0]
  53. when :":emacs-rex"
  54. emacs_rex *event[1..4]
  55. else raise "Unhandled event: #{event.inspect}"
  56. end
  57. end
  58. def send_to_emacs(obj)
  59. payload = write_sexp_to_string(obj)
  60. @io.write("%06x" % payload.length)
  61. @io.write payload
  62. @io.flush
  63. end
  64. def emacs_rex(form, pkg, thread, id)
  65. proc = $rpc_entries[form[0]]
  66. args = form[1..-1];
  67. begin
  68. raise "Undefined function: #{form[0]}" unless proc
  69. value = proc[*args]
  70. rescue Exception => exc
  71. begin
  72. pseudo_debug exc
  73. ensure
  74. send_to_emacs [:":return", [:":abort"], id]
  75. end
  76. else
  77. send_to_emacs [:":return", [:":ok", value], id]
  78. end
  79. end
  80. def pseudo_debug(exc)
  81. level = 1
  82. send_to_emacs [:":debug", 0, level] + sldb_info(exc, 0, 20)
  83. begin
  84. sldb_loop exc
  85. ensure
  86. send_to_emacs [:":debug-return", 0, level, :nil]
  87. end
  88. end
  89. def sldb_loop(exc)
  90. $sldb_context = [self,exc]
  91. while true
  92. dispatch(read_packet(@io))
  93. end
  94. end
  95. def sldb_info(exc, start, _end)
  96. [[exc.to_s,
  97. " [%s]" % exc.class.name,
  98. :nil],
  99. sldb_restarts(exc),
  100. sldb_backtrace(exc, start, _end),
  101. []]
  102. end
  103. def sldb_restarts(exc)
  104. [["Quit", "SLIME top-level."]]
  105. end
  106. def sldb_backtrace(exc, start, _end)
  107. bt = []
  108. exc.backtrace[start.._end].each_with_index do |frame, i|
  109. bt << [i, frame]
  110. end
  111. bt
  112. end
  113. def frame_src_loc(exc, frame)
  114. string = exc.backtrace[frame]
  115. match = /([^:]+):([0-9]+)/.match(string)
  116. if match
  117. file,line = match[1..2]
  118. [:":location", [:":file", file], [:":line", line.to_i], :nil]
  119. else
  120. [:":error", "no src-loc for frame: #{string}"]
  121. end
  122. end
  123. end
  124. $rpc_entries = Hash.new
  125. $rpc_entries[:"swank:connection-info"] = lambda do ||
  126. [:":pid", $$,
  127. :":package", [:":name", "ruby", :":prompt", "ruby> "],
  128. :":lisp-implementation", [:":type", "Ruby",
  129. :":name", "ruby",
  130. :":version", RUBY_VERSION]]
  131. end
  132. def swank_interactive_eval(string)
  133. eval(string,TOPLEVEL_BINDING).inspect
  134. end
  135. $rpc_entries[:"swank:interactive-eval"] = \
  136. $rpc_entries[:"swank:interactive-eval-region"] = \
  137. $rpc_entries[:"swank:pprint-eval"] = lambda { |string|
  138. swank_interactive_eval string
  139. }
  140. $rpc_entries[:"swank:throw-to-toplevel"] = lambda {
  141. throw :swank_top_level
  142. }
  143. $rpc_entries[:"swank:backtrace"] = lambda do |from, to|
  144. conn, exc = $sldb_context
  145. conn.sldb_backtrace(exc, from, to)
  146. end
  147. $rpc_entries[:"swank:frame-source-location"] = lambda do |frame|
  148. conn, exc = $sldb_context
  149. conn.frame_src_loc(exc, frame)
  150. end
  151. #ignored
  152. $rpc_entries[:"swank:buffer-first-change"] = \
  153. $rpc_entries[:"swank:operator-arglist"] = lambda do
  154. :nil
  155. end
  156. $rpc_entries[:"swank:simple-completions"] = lambda do |prefix, pkg|
  157. swank_simple_completions prefix, pkg
  158. end
  159. # def swank_simple_completions(prefix, pkg)
  160. def read_packet(io)
  161. header = read_chunk(io, 6)
  162. len = header.hex
  163. payload = read_chunk(io, len)
  164. #$deferr.puts payload.inspect
  165. read_sexp_from_string(payload)
  166. end
  167. def read_chunk(io, len)
  168. buffer = io.read(len)
  169. raise "short read" if buffer.length != len
  170. buffer
  171. end
  172. def write_sexp_to_string(obj)
  173. string = ""
  174. write_sexp_to_string_loop obj, string
  175. string
  176. end
  177. def write_sexp_to_string_loop(obj, string)
  178. if obj.is_a? String
  179. string << "\""
  180. string << obj.gsub(/(["\\])/,'\\\\\1')
  181. string << "\""
  182. elsif obj.is_a? Array
  183. string << "("
  184. max = obj.length-1
  185. obj.each_with_index do |e,i|
  186. write_sexp_to_string_loop e, string
  187. string << " " unless i == max
  188. end
  189. string << ")"
  190. elsif obj.is_a? Symbol or obj.is_a? Numeric
  191. string << obj.to_s
  192. elsif obj == false
  193. string << "nil"
  194. elsif obj == true
  195. string << "t"
  196. else raise "Can't write: #{obj.inspect}"
  197. end
  198. end
  199. def read_sexp_from_string(string)
  200. stream = StringInputStream.new(string)
  201. reader = LispReader.new(stream)
  202. reader.read
  203. end
  204. class LispReader
  205. def initialize(io)
  206. @io = io
  207. end
  208. def read(allow_consing_dot=false)
  209. skip_whitespace
  210. c = @io.getc
  211. case c
  212. when ?( then read_list(true)
  213. when ?" then read_string
  214. when ?' then read_quote
  215. when nil then raise EOFError.new("EOF during read")
  216. else
  217. @io.ungetc(c)
  218. obj = read_number_or_symbol
  219. if obj == :"." and not allow_consing_dot
  220. raise "Consing-dot in invalid context"
  221. end
  222. obj
  223. end
  224. end
  225. def read_list(head)
  226. list = []
  227. loop do
  228. skip_whitespace
  229. c = @io.readchar
  230. if c == ?)
  231. break
  232. else
  233. @io.ungetc(c)
  234. obj = read(!head)
  235. if obj == :"."
  236. error "Consing-dot not implemented" # would need real conses
  237. end
  238. head = false
  239. list << obj
  240. end
  241. end
  242. list
  243. end
  244. def read_string
  245. string = ""
  246. loop do
  247. c = @io.getc
  248. case c
  249. when ?"
  250. break
  251. when ?\\
  252. c = @io.getc
  253. case c
  254. when ?\\, ?" then string << c
  255. else raise "Invalid escape char: \\%c" % c
  256. end
  257. else
  258. string << c
  259. end
  260. end
  261. string
  262. end
  263. def read_quote
  264. [:quote, read]
  265. end
  266. def read_number_or_symbol
  267. token = read_token
  268. if token.empty?
  269. raise EOFError.new
  270. elsif /^[0-9]+$/.match(token)
  271. token.to_i
  272. elsif /^[0-9]+\.[0-9]+$/.match(token)
  273. token.to_f
  274. else
  275. token.intern
  276. end
  277. end
  278. def read_token
  279. token = ""
  280. loop do
  281. c = @io.getc
  282. if c.nil?
  283. break
  284. elsif terminating?(c)
  285. @io.ungetc(c)
  286. break
  287. else
  288. token << c
  289. end
  290. end
  291. token
  292. end
  293. def skip_whitespace
  294. loop do
  295. c = @io.getc
  296. case c
  297. when ?\s, ?\n, ?\t then next
  298. when nil then break
  299. else @io.ungetc(c); break
  300. end
  301. end
  302. end
  303. def terminating?(char)
  304. " \n\t()\"'".include?(char)
  305. end
  306. end
  307. class StringInputStream
  308. def initialize(string)
  309. @string = string
  310. @pos = 0
  311. @max = string.length
  312. end
  313. def pos() @pos end
  314. def getc
  315. if @pos == @max
  316. nil
  317. else
  318. c = @string[@pos]
  319. @pos += 1
  320. c
  321. end
  322. end
  323. def readchar
  324. getc or raise EOFError.new
  325. end
  326. def ungetc(c)
  327. if @pos > 0 && @string[@pos-1] == c
  328. @pos -= 1
  329. else
  330. raise "Invalid argument: %c [at %d]" % [c, @pos]
  331. end
  332. end
  333. end