\( \newcommand\D{\mathrm{d}} \newcommand\E{\mathrm{e}} \newcommand\I{\mathrm{i}} \newcommand\bigOh{\mathcal{O}} \newcommand{\cat}[1]{\mathbf{#1}} \newcommand\curl{\vec{\nabla}\times} \newcommand{\CC}{\mathbb{C}} \newcommand{\NN}{\mathbb{N}} \newcommand{\QQ}{\mathbb{Q}} \newcommand{\RR}{\mathbb{R}} \newcommand{\ZZ}{\mathbb{Z}} \)
UP | HOME

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).

Last Updated 2022-02-03 Thu 16:24.