StringStream - SML
Table of Contents
1. Introduction
I found myself looking for something analogous to Java's StringStream
,
when writing things out.
First, we need to construct a TextPrimIO.writer
for the
stream, which will just write to a string ref
buffer. This is the
lowest level to Standard ML's I/O model (hence the TextPrimIO
implements the PRIM_IO
signature).
We do not need to make the string buffer block upon writing to it, at
least I don't think so. A minimal implementation just requires the
writeVec
to write to a string buffer. For our purposes, a "string
buffer" is a reference to a string; "writing to it" amounts to updating
the reference by appending to it.
On SML/NJ, it seems that only writeVec'
is needed for an unbuffered
string stream. MLton also appears to only require writeVec'
. I should
really test this on other implementations…
(* Creates a string writer, which writes to the supplied string buffer. @param: buffer is the underlying string reference which accumulates everything the writer writes. @see: https://smlfamily.github.io/Basis/prim-io.html#SIG:PRIM_IO.writer:TY @see: https://smlfamily.github.io/Basis/mono-array-slice.html @see: https://smlfamily.github.io/Basis/mono-array.html *) fun stringWriter(buffer : string ref) : TextPrimIO.writer = let fun writeVec' (v : TextPrimIO.vector_slice) = (buffer := (!buffer)^(CharVectorSlice.vector v); (CharVectorSlice.length v)) fun writeArr' (arr : TextPrimIO.array_slice) = (buffer:=(!buffer)^(CharArraySlice.vector arr); (CharArraySlice.length arr)) fun writeVecNB' (v : TextPrimIO.vector_slice) = (buffer := (!buffer)^(CharVectorSlice.vector v); SOME (CharVectorSlice.length v)) fun writeArrNB' (arr : TextPrimIO.array_slice) = (buffer:=(!buffer)^(CharArraySlice.vector arr); SOME (CharArraySlice.length arr)) fun closing () = print ("Trying to close string stream?!?!?\n\n") in TextPrimIO.WR{ name = "<string>", chunkSize = 1, writeVec = SOME writeVec', writeArr = SOME writeArr', writeVecNB = SOME writeVecNB', writeArrNB = SOME writeArrNB', block = NONE, canOutput = NONE, getPos = NONE, setPos = NONE, endPos = NONE, verifyPos = NONE, close = closing, ioDesc = NONE} end;
Now, the Standard ML I/O model has a stream "facade" (if I may
borrow such an anachronistic term) wrapping around a
"primitive" writer
object. We use an outstream
when invoking
TextIO.output
, in order to write to the stream. We will have
IO.NO_BUF
mode for our string output stream, meaning we write directly
to the writer without buffering (thus no need for "flushing" the
stream).
We have a smart constructor for a "string stream" from a "string writer":
fun stringStream(buffer : string ref) = let val writer : TextIO.StreamIO.writer = stringWriter(buffer) in TextIO.StreamIO.mkOutstream(writer, IO.NO_BUF) end;
Now we can show an example usage:
val b1 : string ref = ref ""; val ss1 = stringStream(b1); fun ss_print (s : string) = let val cv : CharVector.vector = s in TextIO.StreamIO.output(ss1, cv) end; ss_print("foobar"); print("\n\n!b1 = "^(!b1)^"\n\n"); (* then `!b1` evaluates to "foobar" *)
Another approach to constructing a string stream would be to have its
buffer be a reference to a list of strings, but this would be mildly
inefficient since it'd be a stack (and thus a reverse
would be needed
to make the order correct).