Initial commit
This commit is contained in:
385
elpa/slime-20171102.1213/contrib/swank.rb
Normal file
385
elpa/slime-20171102.1213/contrib/swank.rb
Normal file
@@ -0,0 +1,385 @@
|
||||
# swank.rb --- swank server for Ruby.
|
||||
#
|
||||
# This is my first Ruby program and looks probably rather strange. Some
|
||||
# people write Scheme interpreters when learning new languages, I
|
||||
# write swank backends.
|
||||
#
|
||||
# Only a few things work.
|
||||
# 1. Start the server with something like: ruby -r swank -e swank
|
||||
# 2. Use M-x slime-connect to establish a connection
|
||||
|
||||
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
|
||||
|
||||
#ignored
|
||||
$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 swank_simple_completions(prefix, pkg)
|
||||
|
||||
def read_packet(io)
|
||||
header = read_chunk(io, 6)
|
||||
len = header.hex
|
||||
payload = read_chunk(io, len)
|
||||
#$deferr.puts payload.inspect
|
||||
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" # would need real conses
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user