(* swank-mlworks.sml -- SWANK server for MLWorks
|
|
*
|
|
* This code has been placed in the Public Domain.
|
|
*)
|
|
|
|
(* This is an experiment to see how the interfaces/modules would look
|
|
* in a language with a supposedly "good" module system.
|
|
*
|
|
* MLWorks is probably the only SML implementation that tries to
|
|
* support "interactive programming". Since MLWorks wasn't maintained
|
|
* the last 15 or so years, big chunks of the SML Basis Library are
|
|
* missing or not the way as required by the standard. That makes it
|
|
* rather hard to do anything; it also shows that MLWorks hasn't been
|
|
* "used in anger" for a long time.
|
|
*)
|
|
|
|
structure Swank = struct
|
|
|
|
structure Util = struct
|
|
fun utf8ToString (v:Word8Vector.vector) : string = Byte.bytesToString v
|
|
fun stringToUtf8 s = Byte.stringToBytes s
|
|
end
|
|
|
|
structure Map = struct
|
|
datatype ('a, 'b) map = Alist of {list: ('a * 'b) list ref,
|
|
eq: ('a * 'a) -> bool}
|
|
|
|
fun stringMap () =
|
|
Alist {list = ref [],
|
|
eq = (fn (x:string,y:string) => x = y)}
|
|
|
|
|
|
fun lookup (Alist {list, eq}, key) =
|
|
let fun search [] = NONE
|
|
| search ((key', value) :: xs) =
|
|
if eq (key', key) then SOME value
|
|
else search xs
|
|
in search (!list)
|
|
end
|
|
|
|
fun put (Alist {list, eq}, key, value) =
|
|
let val l = (key, value) :: (!list)
|
|
in list := l
|
|
end
|
|
|
|
end
|
|
|
|
structure CharBuffer = struct
|
|
local
|
|
structure C = CharArray
|
|
datatype buffer = B of {array : C.array ref,
|
|
index: int ref}
|
|
in
|
|
|
|
fun new hint = B {array = ref (C.array (hint, #"\000")),
|
|
index = ref 0}
|
|
|
|
fun append (buffer as B {array, index}, char) =
|
|
let val a = !array
|
|
val i = !index
|
|
val len = C.length a
|
|
in if i < len then
|
|
(C.update (a, i, char);
|
|
index := i + 1;
|
|
())
|
|
else let val aa = C.array (2 * len, #"\000")
|
|
fun copy (src, dst) =
|
|
let val len = C.length src
|
|
fun loop i =
|
|
if i = len then ()
|
|
else (C.update (dst, i, C.sub (src, i));
|
|
loop (i + 1))
|
|
in loop 0 end
|
|
in copy (a, aa);
|
|
C.update (aa, i, char);
|
|
array := aa;
|
|
index := i + 1;
|
|
()
|
|
end
|
|
end
|
|
|
|
fun toString (B {array, index}) =
|
|
let val a = !array
|
|
val i = !index
|
|
in CharVector.tabulate (i, fn i => C.sub (a, i)) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
structure Sexp = struct
|
|
structure Type = struct
|
|
datatype sexp = Int of int
|
|
| Str of string
|
|
| Lst of sexp list
|
|
| Sym of string
|
|
| QSym of string * string
|
|
| T
|
|
| Nil
|
|
| Quote
|
|
end
|
|
open Type
|
|
|
|
exception ReadError
|
|
|
|
fun fromUtf8 v =
|
|
let val len = Word8Vector.length v
|
|
val index = ref 0
|
|
fun getc () =
|
|
case getc' () of
|
|
SOME c => c
|
|
| NONE => raise ReadError
|
|
and getc' () =
|
|
let val i = !index
|
|
in if i = len then NONE
|
|
else (index := i + 1;
|
|
SOME (Byte.byteToChar (Word8Vector.sub (v, i))))
|
|
end
|
|
and ungetc () = index := !index - 1
|
|
and sexp () : sexp =
|
|
case getc () of
|
|
#"\"" => string (CharBuffer.new 100)
|
|
| #"(" => lst ()
|
|
| #"'" => Lst [Quote, sexp ()]
|
|
| _ => (ungetc(); token ())
|
|
and string buf : sexp =
|
|
case getc () of
|
|
#"\"" => Str (CharBuffer.toString buf)
|
|
| #"\\" => (CharBuffer.append (buf, getc ()); string buf)
|
|
| c => (CharBuffer.append (buf, c); string buf)
|
|
and lst () =
|
|
let val x = sexp ()
|
|
in case getc () of
|
|
#")" => Lst [x]
|
|
| #" " => let val Lst y = lst () in Lst (x :: y) end
|
|
| _ => raise ReadError
|
|
end
|
|
and token () =
|
|
let val tok = token' (CharBuffer.new 50)
|
|
val c0 = String.sub (tok, 0)
|
|
in if Char.isDigit c0 then (case Int.fromString tok of
|
|
SOME i => Int i
|
|
| NONE => raise ReadError)
|
|
else
|
|
Sym (tok)
|
|
end
|
|
and token' buf : string =
|
|
case getc' () of
|
|
NONE => CharBuffer.toString buf
|
|
| SOME #"\\" => (CharBuffer.append (buf, getc ());
|
|
token' buf)
|
|
| SOME #" " => (ungetc (); CharBuffer.toString buf)
|
|
| SOME #")" => (ungetc (); CharBuffer.toString buf)
|
|
| SOME c => (CharBuffer.append (buf, c); token' buf)
|
|
in
|
|
sexp ()
|
|
end
|
|
|
|
fun toString sexp =
|
|
case sexp of
|
|
(Str s) => "\"" ^ String.toCString s ^ "\""
|
|
| (Lst []) => "nil"
|
|
| (Lst xs) => "(" ^ String.concatWith " " (map toString xs) ^ ")"
|
|
| Sym (name) => name
|
|
| QSym (pkg, name) => pkg ^ ":" ^ name
|
|
| Quote => "quote"
|
|
| T => "t"
|
|
| Nil => "nil"
|
|
| Int i => Int.toString i
|
|
|
|
fun toUtf8 sexp = Util.stringToUtf8 (toString sexp)
|
|
end
|
|
|
|
structure Net = struct
|
|
local
|
|
structure S = Socket
|
|
structure I = INetSock
|
|
structure W = Word8Vector
|
|
|
|
fun createSocket (port) =
|
|
let val sock : S.passive I.stream_sock = I.TCP.socket ()
|
|
val SOME localhost = NetHostDB.fromString "127.0.0.1"
|
|
in
|
|
S.Ctl.setREUSEADDR (sock, true);
|
|
S.bind (sock, I.toAddr (localhost, port));
|
|
S.listen (sock, 2);
|
|
sock
|
|
end
|
|
|
|
fun addrToString sockAddr =
|
|
let val (ip, port) = I.fromAddr sockAddr
|
|
in NetHostDB.toString ip ^ ":" ^ Int.toString port
|
|
end
|
|
|
|
exception ShortRead of W.vector
|
|
exception InvalidHexString of string
|
|
in
|
|
|
|
fun acceptConnection port =
|
|
let val sock = createSocket port
|
|
val addr = S.Ctl.getSockName sock
|
|
val _ = print ("Listening on: " ^ addrToString addr ^ "\n")
|
|
val (peer, addr) = S.accept sock
|
|
in
|
|
S.close sock;
|
|
print ("Connection from: " ^ addrToString addr ^ "\n");
|
|
peer
|
|
end
|
|
|
|
fun receivePacket socket =
|
|
let val v = S.recvVec (socket, 6)
|
|
val _ = if W.length v = 6 then ()
|
|
else raise ShortRead v
|
|
val s = Util.utf8ToString v
|
|
val _ = print ("s = " ^ s ^ "\n")
|
|
val len =
|
|
case StringCvt.scanString (Int.scan StringCvt.HEX) s of
|
|
SOME len => len
|
|
| NONE => raise InvalidHexString s
|
|
val _ = print ("len = " ^ Int.toString len ^ "\n")
|
|
val payload = S.recvVec (socket, len)
|
|
val plen = W.length payload
|
|
val _ = print ("plen = " ^ Int.toString plen ^ "\n")
|
|
val _ = if plen = len then ()
|
|
else raise ShortRead payload
|
|
in
|
|
payload
|
|
end
|
|
|
|
fun nibbleToHex i:string = Int.fmt StringCvt.HEX i
|
|
|
|
fun loadNibble i pos =
|
|
Word32.toInt (Word32.andb (Word32.>> (Word32.fromInt i,
|
|
Word.fromInt (pos * 4)),
|
|
0wxf))
|
|
|
|
fun hexDigit i pos = nibbleToHex (loadNibble i pos)
|
|
|
|
fun lenToHex i =
|
|
concat [hexDigit i 5,
|
|
hexDigit i 4,
|
|
hexDigit i 3,
|
|
hexDigit i 2,
|
|
hexDigit i 1,
|
|
hexDigit i 0]
|
|
|
|
fun sendPacket (payload:W.vector, socket) =
|
|
let val len = W.length payload
|
|
val header = Util.stringToUtf8 (lenToHex len)
|
|
val packet = W.concat [header, payload]
|
|
in print ("len = " ^ Int.toString len ^ "\n"
|
|
^ "header = " ^ lenToHex len ^ "\n"
|
|
^ "paylad = " ^ Util.utf8ToString payload ^ "\n");
|
|
S.sendVec (socket, {buf = packet, i = 0, sz = NONE})
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
structure Rpc = struct
|
|
open Sexp.Type
|
|
|
|
val funTable : (string, sexp list -> sexp) Map.map
|
|
= Map.stringMap ()
|
|
|
|
fun define name f = Map.put (funTable, name, f)
|
|
|
|
exception UnknownFunction of string
|
|
fun call (name, args) =
|
|
(print ("call: " ^ name ^ "\n");
|
|
case Map.lookup (funTable, name) of
|
|
SOME f => f args
|
|
| NONE => raise UnknownFunction name)
|
|
|
|
|
|
local fun getpid () =
|
|
Word32.toInt (Posix.Process.pidToWord (Posix.ProcEnv.getpid ()))
|
|
in
|
|
fun connectionInfo [] =
|
|
Lst [Sym ":pid", Int (getpid ()),
|
|
Sym ":lisp-implementation", Lst [Sym ":type", Str "MLWorks",
|
|
Sym ":name", Str "mlworks",
|
|
Sym ":version", Str "2.x"],
|
|
Sym ":machine", Lst [Sym ":instance", Str "",
|
|
Sym ":type", Str "",
|
|
Sym ":version", Str ""],
|
|
Sym ":features", Nil,
|
|
Sym ":package", Lst [Sym ":name", Str "root",
|
|
Sym ":prompt", Str "-"]]
|
|
end
|
|
|
|
fun nyi _ = Nil
|
|
|
|
local structure D = Shell.Dynamic
|
|
in
|
|
fun interactiveEval [Str string] =
|
|
let val x = D.eval string
|
|
in Str (concat [D.printValue x, " : ", D.printType (D.getType x)])
|
|
end
|
|
end
|
|
|
|
val _ =
|
|
(define "swank:connection-info" connectionInfo;
|
|
define "swank:swank-require" nyi;
|
|
define "swank:interactive-eval" interactiveEval;
|
|
())
|
|
end
|
|
|
|
structure EventLoop = struct
|
|
open Sexp.Type
|
|
|
|
fun execute (sexp, pkg) =
|
|
(print ("sexp = " ^ (Sexp.toString sexp) ^ "\n");
|
|
case sexp of
|
|
Lst (Sym name :: args) => Rpc.call (name, args))
|
|
|
|
fun emacsRex (sexp, pkg, id as Int _, sock) =
|
|
let val result = (Lst [Sym (":ok"), execute (sexp, pkg)]
|
|
handle exn => (Lst [Sym ":abort",
|
|
Str (exnName exn ^ ": "
|
|
^ exnMessage exn)]))
|
|
val reply = Lst [Sym ":return", result, id]
|
|
in Net.sendPacket (Sexp.toUtf8 reply, sock)
|
|
end
|
|
|
|
fun dispatch (Lst ((Sym key) :: args), sock) =
|
|
case key of
|
|
":emacs-rex" => let val [sexp, pkg, _, id] = args
|
|
in emacsRex (sexp, pkg, id, sock)
|
|
end
|
|
|
|
fun processRequests socket:unit =
|
|
let val sexp = Sexp.fromUtf8 (Net.receivePacket socket)
|
|
in print ("request: "
|
|
^ Util.utf8ToString (Sexp.toUtf8 sexp)
|
|
^ "\n");
|
|
dispatch (sexp, socket);
|
|
processRequests socket
|
|
end
|
|
|
|
end
|
|
|
|
(* val _ = EventLoop.processRequests (Net.acceptConnection 4005) *)
|
|
val _ = ()
|
|
end
|
|
|
|
(* (Swank.EventLoop.processRequests (Swank.Net.acceptConnection 4005)) *)
|