-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjson-stream-printer.sml
143 lines (118 loc) · 4.22 KB
/
json-stream-printer.sml
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
(* json-stream-printer.sml
*
* COPYRIGHT (c) 2008 The Fellowship of SML/NJ (http://www.smlnj.org)
* All rights reserved.
*)
structure JSONStreamPrinter : sig
type printer
val null : printer -> unit
val boolean : printer * bool -> unit
val integer : printer * IntInf.int -> unit
val float : printer * real -> unit
val string : printer * string -> unit
val beginObject : printer -> unit
val objectKey : printer * string -> unit
val endObject : printer -> unit
val beginArray : printer -> unit
val endArray : printer -> unit
val new : TextIO.outstream -> printer
val new' : {strm : TextIO.outstream, pretty : bool} -> printer
val close : printer -> unit
end = struct
structure F = Format
datatype printer = P of {
strm : TextIO.outstream,
indent : int ref,
ctx : context ref,
pretty : bool
}
(* the context is used to keep track of the printing state for indentation
* and punctuation, etc.
*)
and context
= TOP (* top-most context *)
| FIRST of context (* first element of object or array; the argument *)
(* must be one of OBJECT or ARRAY. *)
| OBJECT of context (* in an object (after the first element) *)
| ARRAY of context (* in an array (after the first element) *)
| KEY of context (* after the key of a object field *)
fun new' {strm, pretty} = P{
strm = strm,
indent = ref 0,
ctx = ref TOP,
pretty = pretty
}
fun new strm = new' {strm = strm, pretty = false}
fun close (P{ctx = ref TOP, strm, ...}) = TextIO.output(strm, "\n")
| close _ = raise Fail "premature close"
fun pr (P{strm, ...}, s) = TextIO.output(strm, s)
fun indent (P{pretty = false, ...}, _) = ()
| indent (P{strm, indent, ...}, offset) = let
val tenSpaces = " "
fun prIndent n = if (n <= 10)
then TextIO.output(strm, String.extract(tenSpaces, 10-n, NONE))
else (TextIO.output(strm, tenSpaces); prIndent(n-10))
in
prIndent ((!indent+offset) * 2)
end
fun incIndent (P{indent, ...}, n) = indent := !indent + n;
fun decIndent (P{indent, ...}, n) = indent := !indent - n;
fun nl (P{pretty = false, ...}) = ()
| nl (P{strm, ...}) = TextIO.output(strm, "\n")
fun comma (P{strm, pretty = false, ...}) = TextIO.output(strm, ",")
| comma (p as P{strm, ...}) = (
TextIO.output(strm, ",\n"); indent(p, 0))
fun optComma (p as P{ctx, pretty, ...}) = (case !ctx
of FIRST ctx' => (indent(p, 0); ctx := ctx')
| OBJECT _ => comma p
| ARRAY _ => comma p
| KEY ctx' => (
pr (p, if pretty then " : " else ":");
ctx := ctx')
| _ => ()
(* end case *))
(* print a value, which may be proceeded by a comma if it is in a sequence *)
fun prVal (p, v) = (optComma p; pr(p, v))
fun null p = prVal (p, "null")
fun boolean (p, false) = prVal (p, "false")
| boolean (p, true) = prVal (p, "true")
fun integer (p, n) = prVal (p, F.format "%d" [F.LINT n])
fun float (p, f) = prVal (p, F.format "%g" [F.REAL f])
(* FIXME: need to deal with UTF-* escapes *)
fun string (p, s) = prVal (p, F.format "\"%s\"" [F.STR(String.toCString s)])
fun beginObject (p as P{ctx, ...}) = (
optComma p;
pr (p, "{"); incIndent(p, 2); nl p;
ctx := FIRST(OBJECT(!ctx)))
fun objectKey (p as P{ctx = ref(KEY _), ...}, field) =
raise Fail(concat["objectKey \"", field, "\" where value was expected"])
| objectKey (p as P{ctx, ...}, field) = (
string (p, field);
ctx := KEY(!ctx))
fun endObject (p as P{ctx, ...}) = let
fun prEnd ctx' = (
ctx := ctx';
indent(p, ~1); pr(p, "}"); decIndent (p, 2))
in
case !ctx
of OBJECT ctx' => (nl p; prEnd ctx')
| FIRST(OBJECT ctx') => prEnd ctx'
| _ => raise Fail "endObject not in object context"
(* end case *)
end
fun beginArray (p as P{ctx, ...}) = (
optComma p;
pr (p, "["); incIndent(p, 2); nl p;
ctx := FIRST(ARRAY(!ctx)))
fun endArray (p as P{ctx, ...}) = let
fun prEnd ctx' = (
ctx := ctx';
nl p; indent(p, ~1); pr(p, "]"); decIndent (p, 2))
in
case !ctx
of ARRAY ctx' => prEnd ctx'
| FIRST(ARRAY ctx') => prEnd ctx'
| _ => raise Fail "endArray not in array context"
(* end case *)
end
end