summaryrefslogtreecommitdiff
path: root/stream.sml
blob: cc08d66743c3939e6ecf6d2dd8480c9ed1c18acd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
structure Stream :> STREAM = struct 
  type fileId = int
  type fileOffset = int

  datatype pos = Pos of string * int * int

  type t = {
    id: fileOffset,
    fname: string,
    off: fileOffset,
    contents: string,

    (* offset * line * col *)
    cache: fileOffset * int * int
  }

  exception EOF
  exception UngetcError

  val updateStream = fn z =>
    let
      fun from id fname off contents cache =
        { id, fname, off, contents, cache }
      fun to f { id, fname, off, contents, cache } =
        f id fname off contents cache
    in
      FRU.makeUpdate5 (from, from, to)
    end
    z

  fun ppos (Pos (fname, line, col)) out =
      Printf out `fname `":" I line `":" I col %
  val Ppos = fn z => bind A1 ppos z

  fun getchar (S as { contents, off, ... }: t) =
    (String.sub (contents, off), updateStream S s#off (off + 1) %)
      handle Subscript => (chr 0, S)

  fun ungetc ({ off = 0, ... }: t) =
      raise UngetcError
    | ungetc stream = updateStream stream u#off (fn off => off - 1) %

  fun getSubstr startOff endOff ({ contents, ... }: t) =
      String.substring (contents, startOff, endOff - startOff)

  fun getLine (S as { contents, off, ... }: t) =
  let
    fun find off =
      if off = size contents then
        NONE
      else
        if String.sub (contents, off) = #"\n" then
          SOME off
        else
          find (off + 1)
  in
    case find off of
      SOME off' =>
        (SOME $ getSubstr off off' S, updateStream S s#off off' %)
    | NONE => (NONE, S)
  end

  fun getFname ({ fname, ... }: t) = fname

  fun createFromInstream fname instream =
  let
    open TextIO
    val contents = inputAll instream
    val () = closeIn instream
  in
    { id = 0, fname, off = 0, contents, cache = (0, 1, 1) }
  end

  fun create fname = createFromInstream fname (TextIO.openIn fname)

  fun createFromString s = createFromInstream s (TextIO.openString s)

  fun getOffset ({ off, ... }: t) = off

  fun isFirstOnLine ({ contents, ... }: t) off =
  let
    fun check (~1) = true
      | check off =
        case String.sub (contents, off) of
          #"\n" => true
        | #" " => check (off - 1)
        | #"\t" => check (off - 1)
        | _ => false
  in
    check (off - 1)
  end

  fun getPosRaw off (S as { cache = (prevOff, line, col), fname,
    contents, ... }: t) =
  let
    fun calcPos curOff (line, col) =
      if curOff = off then
        (line, col)
      else
        calcPos (curOff + 1)
            (if String.sub (contents, curOff) = #"\n" then (line + 1, 1)
               else (line, col + 1))
    val (line, col) = calcPos prevOff (line, col)
  in
    assert $ off >= prevOff;
    (Pos (fname, line, col), updateStream S s#cache (off, line, col) %)
  end

  fun getPos (S as { off, ... }: t) =
    getPosRaw off S

  fun getPosDisc s = #1 $ getPos s

  fun EOFpos (S as { contents, ... }: t) =
    getPosRaw (String.size contents) S

  fun getPosAfterChar stream =
    getPosRaw (getOffset stream -1) stream
end