(* load "Reader" ; open Reader ; load "FileSys" ; *) structure FileSys = OS.FileSys ; type ('a, 'src) reader = 'src -> ('a * 'src) option ; (* as in StringCvt *) (* scanOut : TextIO.outstream -> (char, 'src) reader -> (unit, 'src) reader outputs the result of repeatedly reading the source *) fun scanOut ostr rdr src = (* ro outputs chars read and returns new source *) let fun ro src = case rdr src of NONE => src | SOME (ch, src') => (TextIO.output1 (ostr, ch) ; ro src') ; (* package result so that scanOut ostr is a scanner *) in SOME ((), ro src) end ; (* detabRdr : (char, 'src) reader -> (char, int * 'src) reader modified reader which expands tabs, with src additionally containing record of how many spaces a tab is worth *) fun detabRdr rdr (n, src) = case rdr src of NONE => NONE | SOME (#"\t", src') => if n = 1 then SOME (#" ", (8, src')) else SOME (#" ", (n-1, src)) | SOME (#"\n", src') => SOME (#"\n", (8, src')) | SOME (ch, src') => if n = 1 then SOME (ch, (8, src')) else SOME (ch, (n-1, src')) ; (* Reader.scanOut : TextIO.outstream -> (char, 'src) reader -> (unit, 'src) reader reads and outputs characters until source exhausted *) (* sird : ('a, int * 'src) reader -> ('a, 'src) reader simplify reader by providing initial integer value 8 and discarding final integer value *) fun sird rdr src = let (* f : ('a * ('int * 'src)) option -> ('a * 'src) option *) fun f NONE = NONE | f (SOME (a, (n, src'))) = SOME (a, src') ; in f (rdr (8, src)) end ; (* detab : string -> unit, replace file by version with tabs -> spaces *) fun detab file = (* note - opening and then removing a file is a non-portable Unix idiom *) let val istr = TextIO.openIn file ; val () = FileSys.remove file ; val ostr = TextIO.openOut file ; (* argument to scanStream must be a character scanner for any arbitrary source type *) (* css : : (char, 'src) reader -> (unit, 'src) reader *) fun css rdr = sird (scanOut ostr (detabRdr rdr)) ; val SOME () = TextIO.scanStream css istr ; val () = TextIO.closeOut ostr ; in () end ;