|
|
- (* 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)) *)
|