structure Stream :> STREAM = struct type fileId = int type fileOffset = int type pos = fileId * fileOffset type ppos = string * int * int option type fileInfo = fileId * string * string exception UngetcError exception InvalidFileInfo fun ppos2str (pos, line, col) = let val % = Int.toString in case col of SOME col => pos ^ ":" ^ %line ^ ":" ^ %col | NONE => pos ^ ":" ^ %line end type t = fileId * string * fileOffset * string fun convert (fid, fname, _, contents) = (fid, fname, contents) fun calcFilePos s offset = let fun calc s cur offset (line, col) = if cur = offset then (line, col) else calc s (cur + 1) offset (if String.sub (s, cur) = #"\n" then (line + 1, 1) else (line, col + 1)) in calc s 0 offset (1, 1) end fun printPos (_, fname, contents) (_, pos) = let val (line, col) = calcFilePos contents pos val line = Int.toString line val col = Int.toString col in print $ fname ^ ":" ^ line ^ ":" ^ col ^ ": " end fun getchar (S as (fid, fname, off, contents)) = if off < String.size contents then (SOME $ String.sub (contents, off), (fid, fname, off + 1, contents)) else (NONE, S) fun ungetc (fid, fname, off, contents) = if off = 0 then raise UngetcError else (fid, fname, off - 1, contents) fun getPosAfterCharRead (fid, _, off, _) = (fid, off - 1) fun pos2pposWithFI (id, pos) (id', fname, contents) = if id <> id' then raise InvalidFileInfo else let val (line, col) = calcFilePos contents pos in (fname, line, SOME col) end fun pos2ppos pos stream = pos2pposWithFI pos (convert stream) fun pposWithoutCol (fname, line, SOME _) = (fname, line, NONE) | pposWithoutCol (_, _, NONE) = raise Unreachable fun getPos (id, _, off, _) = (id, off) fun getSubstr startOff endOff (_, _, _, contents) = String.substring (contents, startOff, endOff - startOff) fun create fname = let open TextIO val h = openIn fname val contents = inputAll h val () = closeIn h in (0, fname, 0, contents) end fun isFirstOnLine (_, offset) ((_, _, _, contents) : t) = let fun returnToNL ~1 = true | returnToNL offset = let val chr = String.sub (contents, offset) in if chr = #"\n" then true else if Char.isSpace chr then returnToNL (offset - 1) else false end in returnToNL (offset - 1) end end