main.bend

bend-toon-codec@0.1.0.0 · 138 lines · 13 definitions

1import Base
2import bend-toon-codec@0.1.0.0/src/json.bend as I
3import bend-toon-codec@0.1.0.0/src/encode.bend as Enc
4import bend-toon-codec@0.1.0.0/src/decode.bend as Dec
5
6# bend-toon: TOON (Token-Oriented Object Notation, spec v3.3) for Bend 2.
7#
8#   import bend-toon@0.1.0/main.bend as Toon
9#
10#   Toon.encode(value, Toon.EncodeOpts{2, Toon.Comma{}})   -> String
11#   Toon.decode(text, Toon.DecodeOpts{2, True{}})           -> Result<String, Json>
12#
13# The types below are this file's own, so a caller can name them (Bend does
14# not expose the types of a module's imports). They have the shape of the
15# Json in cswap-bend's src/json.bend, constructor for constructor, and the
16# codec works on an internal copy of the same shape (src/json.bend).
17# README.md has the API, the value model and the conformance report.
18
19# JSON values. Arrays and objects are chains inside Json itself: an array is
20# JArr{JCons{a, JCons{b, JNil{}}}}, an object is
21# JObj{JField{"k", v, JField{"k2", v2, JNil{}}}}. Numbers keep their decimal
22# text (JNum{"1.50"}); encode writes the canonical form.
23type Json is Data:
24  JNull{}
25  JBool{b: Bool}
26  JNum{text: String}
27  JStr{s: String}
28  JArr{items: Json}
29  JObj{fields: Json}
30  JNil{}
31  JCons{head: Json, tail: Json}
32  JField{key: String, val: Json, tail: Json}
33
34# Delimiter of arrays and tabular rows.
35type Delim is Data:
36  Comma{}
37  Tab{}
38  Pipe{}
39
40# Spaces per indent level, the document delimiter, and key folding: 0 is off
41# (the default), n of 2 or more folds chains of single-key objects into dotted
42# keys of at most n segments (`a.b.c: 1`), 4294967295 folds whole chains.
43type EncodeOpts is Data:
44  EncodeOpts{indent: U32, delim: Delim, fold: U32}
45
46# Spaces per indent level (used to compute line depth), strict mode (counts,
47# indentation, blank lines, duplicate keys), and path expansion (an unquoted
48# key `a.b.c` becomes nested objects; quoted keys stay literal).
49type DecodeOpts is Data:
50  DecodeOpts{indent: U32, strict: Bool, expand: Bool}
51
52# Indent 2, comma.
53def encode_default() -> EncodeOpts:
54  EncodeOpts{2, Comma{}, 0}
55
56# Indent 2, strict.
57def decode_default() -> DecodeOpts:
58  DecodeOpts{2, True{}, False{}}
59
60# ---- conversion to and from the codec's own value type -----------------------
61
62def to_inner(j: Json) -> I.Json:
63  match j:
64    case JNull{}:
65      I.JNull{}
66    case JBool{b}:
67      I.JBool{b}
68    case JNum{t}:
69      I.JNum{t}
70    case JStr{s}:
71      I.JStr{s}
72    case JArr{x}:
73      I.JArr{to_inner(x)}
74    case JObj{x}:
75      I.JObj{to_inner(x)}
76    case JNil{}:
77      I.JNil{}
78    case JCons{h, t}:
79      I.JCons{to_inner(h), to_inner(t)}
80    case JField{k, v, t}:
81      I.JField{k, to_inner(v), to_inner(t)}
82
83def of_inner(j: I.Json) -> Json:
84  match j:
85    case I.JNull{}:
86      JNull{}
87    case I.JBool{b}:
88      JBool{b}
89    case I.JNum{t}:
90      JNum{t}
91    case I.JStr{s}:
92      JStr{s}
93    case I.JArr{x}:
94      JArr{of_inner(x)}
95    case I.JObj{x}:
96      JObj{of_inner(x)}
97    case I.JNil{}:
98      JNil{}
99    case I.JCons{h, t}:
100      JCons{of_inner(h), of_inner(t)}
101    case I.JField{k, v, t}:
102      JField{k, of_inner(v), of_inner(t)}
103
104def of_inner_result(r: Result<&2, &2, String, I.Json>) -> Result<&2, &2, String, Json>:
105  match r:
106    case Fail{e}:
107      Fail{e}
108    case Done{v}:
109      Done{of_inner(v)}
110
111def inner_delim(d: Delim) -> Enc.Delim:
112  match d:
113    case Comma{}:
114      Enc.Comma{}
115    case Tab{}:
116      Enc.Tab{}
117    case Pipe{}:
118      Enc.Pipe{}
119
120# ---- API ---------------------------------------------------------------------
121
122# TOON text of a value, LF line endings, no trailing newline. NaN and Infinity
123# (a JNum whose text is not a number) encode as null.
124def encode(j: Json, o: EncodeOpts) -> String:
125  match o:
126    case EncodeOpts{indent, d, fold}:
127      Enc.encode(to_inner(j), Enc.EOpts{indent, inner_delim(d), fold})
128
129# The value of TOON text, or the first error with its line number.
130def decode(s: String, o: DecodeOpts) -> Result<&2, &2, String, Json>:
131  match o:
132    case DecodeOpts{indent, strict, expand}:
133      of_inner_result(Dec.decode(s, Dec.DOpts{indent, strict, expand}))
134
135# JSON-model equality: numbers by value (`1.0` equals `1`), objects by ordered
136# keys.
137def eq(a: Json, b: Json) -> Bool:
138  I.eq(to_inner(a), to_inner(b))