require "socket"
def swank(port=4005)
accept_connections port, false
end
def start_swank(port_file)
accept_connections false, port_file
end
def accept_connections(port, port_file)
server = TCPServer.new("localhost", port || 0)
puts "Listening on #{server.addr.inspect}\n"
if port_file
write_port_file server.addr[1], port_file
end
socket = begin server.accept ensure server.close end
begin
serve socket.to_io
ensure
socket.close
end
end
def write_port_file(port, filename)
File.open(filename, File::CREAT|File::EXCL|File::WRONLY) do |f|
f.puts port
end
end
def serve(io)
main_loop(io)
end
def main_loop(io)
c = Connection.new(io)
while true
catch :swank_top_level do
c.dispatch(read_packet(io))
end
end
end
class Connection
def initialize(io)
@io = io
end
def dispatch(event)
puts "dispatch: %s\n" % event.inspect
case event[0]
when :":emacs-rex"
emacs_rex *event[1..4]
else raise "Unhandled event: #{event.inspect}"
end
end
def send_to_emacs(obj)
payload = write_sexp_to_string(obj)
@io.write("%06x" % payload.length)
@io.write payload
@io.flush
end
def emacs_rex(form, pkg, thread, id)
proc = $rpc_entries[form[0]]
args = form[1..-1];
begin
raise "Undefined function: #{form[0]}" unless proc
value = proc[*args]
rescue Exception => exc
begin
pseudo_debug exc
ensure
send_to_emacs [:":return", [:":abort"], id]
end
else
send_to_emacs [:":return", [:":ok", value], id]
end
end
def pseudo_debug(exc)
level = 1
send_to_emacs [:":debug", 0, level] + sldb_info(exc, 0, 20)
begin
sldb_loop exc
ensure
send_to_emacs [:":debug-return", 0, level, :nil]
end
end
def sldb_loop(exc)
$sldb_context = [self,exc]
while true
dispatch(read_packet(@io))
end
end
def sldb_info(exc, start, _end)
[[exc.to_s,
" [%s]" % exc.class.name,
:nil],
sldb_restarts(exc),
sldb_backtrace(exc, start, _end),
[]]
end
def sldb_restarts(exc)
[["Quit", "SLIME top-level."]]
end
def sldb_backtrace(exc, start, _end)
bt = []
exc.backtrace[start.._end].each_with_index do |frame, i|
bt << [i, frame]
end
bt
end
def frame_src_loc(exc, frame)
string = exc.backtrace[frame]
match = /([^:]+):([0-9]+)/.match(string)
if match
file,line = match[1..2]
[:":location", [:":file", file], [:":line", line.to_i], :nil]
else
[:":error", "no src-loc for frame: #{string}"]
end
end
end
$rpc_entries = Hash.new
$rpc_entries[:"swank:connection-info"] = lambda do ||
[:":pid", $$,
:":package", [:":name", "ruby", :":prompt", "ruby> "],
:":lisp-implementation", [:":type", "Ruby",
:":name", "ruby",
:":version", RUBY_VERSION]]
end
def swank_interactive_eval(string)
eval(string,TOPLEVEL_BINDING).inspect
end
$rpc_entries[:"swank:interactive-eval"] = \
$rpc_entries[:"swank:interactive-eval-region"] = \
$rpc_entries[:"swank:pprint-eval"] = lambda { |string|
swank_interactive_eval string
}
$rpc_entries[:"swank:throw-to-toplevel"] = lambda {
throw :swank_top_level
}
$rpc_entries[:"swank:backtrace"] = lambda do |from, to|
conn, exc = $sldb_context
conn.sldb_backtrace(exc, from, to)
end
$rpc_entries[:"swank:frame-source-location"] = lambda do |frame|
conn, exc = $sldb_context
conn.frame_src_loc(exc, frame)
end
$rpc_entries[:"swank:buffer-first-change"] = \
$rpc_entries[:"swank:operator-arglist"] = lambda do
:nil
end
$rpc_entries[:"swank:simple-completions"] = lambda do |prefix, pkg|
swank_simple_completions prefix, pkg
end
def read_packet(io)
header = read_chunk(io, 6)
len = header.hex
payload = read_chunk(io, len)
read_sexp_from_string(payload)
end
def read_chunk(io, len)
buffer = io.read(len)
raise "short read" if buffer.length != len
buffer
end
def write_sexp_to_string(obj)
string = ""
write_sexp_to_string_loop obj, string
string
end
def write_sexp_to_string_loop(obj, string)
if obj.is_a? String
string << "\""
string << obj.gsub(/(["\\])/,'\\\\\1')
string << "\""
elsif obj.is_a? Array
string << "("
max = obj.length-1
obj.each_with_index do |e,i|
write_sexp_to_string_loop e, string
string << " " unless i == max
end
string << ")"
elsif obj.is_a? Symbol or obj.is_a? Numeric
string << obj.to_s
elsif obj == false
string << "nil"
elsif obj == true
string << "t"
else raise "Can't write: #{obj.inspect}"
end
end
def read_sexp_from_string(string)
stream = StringInputStream.new(string)
reader = LispReader.new(stream)
reader.read
end
class LispReader
def initialize(io)
@io = io
end
def read(allow_consing_dot=false)
skip_whitespace
c = @io.getc
case c
when ?( then read_list(true)
when ?" then read_string
when ?' then read_quote
when nil then raise EOFError.new("EOF during read")
else
@io.ungetc(c)
obj = read_number_or_symbol
if obj == :"." and not allow_consing_dot
raise "Consing-dot in invalid context"
end
obj
end
end
def read_list(head)
list = []
loop do
skip_whitespace
c = @io.readchar
if c == ?)
break
else
@io.ungetc(c)
obj = read(!head)
if obj == :"."
error "Consing-dot not implemented" end
head = false
list << obj
end
end
list
end
def read_string
string = ""
loop do
c = @io.getc
case c
when ?"
break
when ?\\
c = @io.getc
case c
when ?\\, ?" then string << c
else raise "Invalid escape char: \\%c" % c
end
else
string << c
end
end
string
end
def read_quote
[:quote, read]
end
def read_number_or_symbol
token = read_token
if token.empty?
raise EOFError.new
elsif /^[0-9]+$/.match(token)
token.to_i
elsif /^[0-9]+\.[0-9]+$/.match(token)
token.to_f
else
token.intern
end
end
def read_token
token = ""
loop do
c = @io.getc
if c.nil?
break
elsif terminating?(c)
@io.ungetc(c)
break
else
token << c
end
end
token
end
def skip_whitespace
loop do
c = @io.getc
case c
when ?\s, ?\n, ?\t then next
when nil then break
else @io.ungetc(c); break
end
end
end
def terminating?(char)
" \n\t()\"'".include?(char)
end
end
class StringInputStream
def initialize(string)
@string = string
@pos = 0
@max = string.length
end
def pos() @pos end
def getc
if @pos == @max
nil
else
c = @string[@pos]
@pos += 1
c
end
end
def readchar
getc or raise EOFError.new
end
def ungetc(c)
if @pos > 0 && @string[@pos-1] == c
@pos -= 1
else
raise "Invalid argument: %c [at %d]" % [c, @pos]
end
end
end