src/parse.bend

emerging-ezjson@1.1.0.0 · 251 lines · 0 definitions

1# src/parse: tokens to one JSON value. Commas separate elements, a colon
2# separates a key from its value, and a text has one root.
3import Base
4import emerging-ezjson@1.1.0.0/src/value.bend as V
5import emerging-ezjson@1.1.0.0/src/lex.bend as Lex
6
7# where the next token of an open array or object has to be
8type Slot is Data:
9  SValue{}
10  SComma{}
11  SKey{}
12  SColon{}
13
14# an object key: owned text, or the first `n` chars of a source suffix
15type Key is Data:
16  KOwn{s: String}
17  KCut{src: String, n: U32}
18
19# an open array (its items, reversed) or an open object (its pairs, reversed,
20# and the key awaiting its value)
21type Frame is Data:
22  FArr{items: V.Json, slot: Slot}
23  FObj{pairs: V.Json, key: Key, slot: Slot}
24
25# the parser's state: an error was seen, the finished root, the open frames
26type P is Data:
27  P{bad: Bool, root: Maybe<&2, V.Json>, stack: List<&2, Frame>}
28
29def word.ok(+raw: String) -> Bool:
30  Bool.or(String.eq(raw, "null"),
31    Bool.or(String.eq(raw, "true"),
32      Bool.or(String.eq(raw, "false"), V.num.ok(raw))))
33
34# a bare word as a value: null, true, false, else a number kept as text
35def word(+raw: String) -> V.Json:
36  Bool.pick(V.Json, String.eq(raw, "null"), V.JNull{},
37    Bool.pick(V.Json, String.eq(raw, "true"), V.JBool{True{}},
38      Bool.pick(V.Json, String.eq(raw, "false"), V.JBool{False{}}, V.JNum{raw})))
39
40def step.ready(stack: List<&2, Frame>, root: Maybe<&2, V.Json>) -> Bool:
41  match stack root:
42    case Nil{} None{}:
43      True{}
44    case Con{FArr{items, SValue{}}, rest} r:
45      True{}
46    case Con{FObj{pairs, key, SValue{}}, rest} r:
47      True{}
48    case s r:
49      False{}
50
51def put.pair(key: Key, val: V.Json, pairs: V.Json) -> V.Json:
52  match key:
53    case KOwn{s}:
54      V.JPair{s, val, pairs}
55    case KCut{src, n}:
56      V.JBind{src, n, val, pairs}
57
58def put.text(key: Key) -> V.Json:
59  match key:
60    case KOwn{s}:
61      V.JStr{s}
62    case KCut{src, n}:
63      V.JSpan{src, n}
64
65# a finished value lands in the innermost open frame, or becomes the root
66def put(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, vv: V.Json, bad: Bool) -> P:
67  match stack root vv:
68    case Nil{} None{} val:
69      P{bad, Some{val}, Nil{}}
70    case Nil{} Some{r} val:
71      P{True{}, Some{r}, Nil{}}
72    case Con{FArr{items, SValue{}}, rest} r val:
73      P{bad, r, FArr{V.JCons{val, items}, SComma{}} <> rest}
74    case Con{FObj{pairs, k, SValue{}}, rest} r val:
75      P{bad, r, FObj{put.pair(k, val, pairs), KOwn{""}, SComma{}} <> rest}
76    case s r val:
77      P{True{}, r, Nil{}}
78
79def put.str(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, key: Key, bad: Bool) -> P:
80  match stack:
81    case Con{FObj{pairs, k, SKey{}}, rest}:
82      P{bad, root, FObj{pairs, key, SColon{}} <> rest}
83    case other:
84      put(stack, root, put.text(key), bad)
85
86def step.open_arr.go(bad: Bool, root: Maybe<&2, V.Json>, stack: List<&2, Frame>, ok: Bool) -> P:
87  match ok:
88    case True{}:
89      P{bad, root, FArr{V.JNil{}, SValue{}} <> stack}
90    case False{}:
91      P{True{}, root, Nil{}}
92
93def step.open_arr(st: P) -> P:
94  P{bad, +root, +stack} = st
95  step.open_arr.go(bad, root, stack, step.ready(stack, root))
96
97def step.open_obj.go(bad: Bool, root: Maybe<&2, V.Json>, stack: List<&2, Frame>, ok: Bool) -> P:
98  match ok:
99    case True{}:
100      P{bad, root, FObj{V.JNil{}, KOwn{""}, SKey{}} <> stack}
101    case False{}:
102      P{True{}, root, Nil{}}
103
104def step.open_obj(st: P) -> P:
105  P{bad, +root, +stack} = st
106  step.open_obj.go(bad, root, stack, step.ready(stack, root))
107
108def step.comma(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P:
109  match stack:
110    case Con{FArr{items, SComma{}}, rest}:
111      P{bad, root, FArr{items, SValue{}} <> rest}
112    case Con{FObj{pairs, k, SComma{}}, rest}:
113      P{bad, root, FObj{pairs, KOwn{""}, SKey{}} <> rest}
114    case other:
115      P{True{}, root, Nil{}}
116
117def step.colon(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P:
118  match stack:
119    case Con{FObj{pairs, k, SColon{}}, rest}:
120      P{bad, root, FObj{pairs, k, SValue{}} <> rest}
121    case other:
122      P{True{}, root, Nil{}}
123
124def step.word.go(raw: String, st: P, ok: Bool) -> P:
125  P{bad, root, stack} = st
126  match ok:
127    case True{}:
128      put(stack, root, word(raw), bad)
129    case False{}:
130      P{True{}, root, stack}
131
132def step.word(+raw: String, st: P) -> P:
133  step.word.go(raw, st, word.ok(raw))
134
135# a `]`: the innermost frame must be an array, empty or just after a value
136def close_arr(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P:
137  match stack:
138    case Con{FArr{V.JNil{}, SValue{}}, rest}:
139      put(rest, root, V.JArr{V.JNil{}}, bad)
140    case Con{FArr{items, SComma{}}, rest}:
141      put(rest, root, V.JArr{V.reverse(items, V.JNil{})}, bad)
142    case other:
143      P{True{}, root, Nil{}}
144
145# a `}`: the innermost frame must be an object, empty or just after a value
146def close_obj(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P:
147  match stack:
148    case Con{FObj{V.JNil{}, k, SKey{}}, rest}:
149      put(rest, root, V.JObj{V.JNil{}}, bad)
150    case Con{FObj{pairs, k, SComma{}}, rest}:
151      put(rest, root, V.JObj{V.reverse(pairs, V.JNil{})}, bad)
152    case other:
153      P{True{}, root, Nil{}}
154
155def step.span.ok(+raw: String, ok: Bool) -> (V.Json & Bool):
156  match ok:
157    case True{}:
158      (V.JNum{raw}, True{})
159    case False{}:
160      (V.JNull{}, False{})
161
162def step.span.use(+raw: String) -> (V.Json & Bool):
163  step.span.ok(raw, V.num.ok(raw))
164
165def step.span.chk(src: String, +nn: U32) -> (V.Json & Bool):
166  step.span.use(V.span.str(src, nn, U32.is_zero(nn)))
167
168def step.span.go(src: String, nn: U32, is_null: Bool, is_true: Bool, is_false: Bool) -> (V.Json & Bool):
169  match is_null is_true is_false:
170    case True{} x y:
171      (V.JNull{}, True{})
172    case False{} True{} y:
173      (V.JBool{True{}}, True{})
174    case False{} False{} True{}:
175      (V.JBool{False{}}, True{})
176    case False{} False{} False{}:
177      step.span.chk(src, nn)
178
179def step.span.use2(st: P, val: V.Json, ok: Bool) -> P:
180  P{bad, root, stack} = st
181  match ok:
182    case True{}:
183      put(stack, root, val, bad)
184    case False{}:
185      P{True{}, root, stack}
186
187def step.span.open(st: P, got: (V.Json & Bool)) -> P:
188  (v, ok) = got
189  step.span.use2(st, v, ok)
190
191def step.span(+src: String, +nn: U32, st: P) -> P:
192  step.span.open(st, step.span.go(src, nn, V.span.eq(src, nn, "null"),
193    V.span.eq(src, nn, "true"), V.span.eq(src, nn, "false")))
194
195# one token
196def step(tok: Lex.Tok, st: P) -> P:
197  match tok:
198    case Lex.TOpenArr{}:
199      step.open_arr(st)
200    case Lex.TOpenObj{}:
201      step.open_obj(st)
202    case Lex.TCloseArr{}:
203      P{bad, root, stack} = st
204      close_arr(stack, root, bad)
205    case Lex.TCloseObj{}:
206      P{bad, root, stack} = st
207      close_obj(stack, root, bad)
208    case Lex.TColon{}:
209      P{bad, root, stack} = st
210      step.colon(stack, root, bad)
211    case Lex.TComma{}:
212      P{bad, root, stack} = st
213      step.comma(stack, root, bad)
214    case Lex.TStr{s}:
215      P{bad, root, stack} = st
216      put.str(stack, root, KOwn{s}, bad)
217    case Lex.TWord{raw}:
218      step.word(raw, st)
219    case Lex.TStrS{src, n}:
220      P{bad, root, stack} = st
221      put.str(stack, root, KCut{src, n}, bad)
222    case Lex.TWordS{src, n}:
223      step.span(src, n, st)
224    case Lex.TBad{}:
225      P{bad, root, stack} = st
226      P{True{}, root, stack}
227
228# every token, in order
229def run(toks: List<&2, Lex.Tok>, st: P) -> P:
230  match toks:
231    case Nil{}:
232      st
233    case Con{t, rest}:
234      run(rest, step(t, st))
235
236# the root, when nothing went wrong and nothing stayed open
237def result(bad: Bool, stack: List<&2, Frame>, root: Maybe<&2, V.Json>) -> Maybe<&2, V.Json>:
238  match bad stack root:
239    case False{} Nil{} Some{j}:
240      Some{j}
241    case b s r:
242      None{}
243
244# the value once the tokens are over
245def finish(st: P) -> Maybe<&2, V.Json>:
246  P{bad, root, stack} = st
247  result(bad, stack, root)
248
249# a text as a value, or None when it is not JSON
250def parse(txt: String) -> Maybe<&2, V.Json>:
251  finish(run(Lex.tokens(txt), P{False{}, None{}, Nil{}}))