fmt/yamlw.bend

cswap-bend-cli@0.1.0.0 · 96 lines · 11 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/json/quote.bend as Quote
4import cswap-bend-cli@0.1.0.0/fmt/ylex.bend as L
5
6# The YAML emitter: block style, two spaces per level, `- ` items under their
7# key. A string is written plain only when no reader could take it for
8# another type (L.plain_ok); otherwise it is double-quoted with JSON escapes
9# (valid YAML). No trailing newline. Pure.
10
11# Where a value sits: the document, after `key:`, after `- `, or inside a
12# chain of fields or items.
13type Ctx is Data:
14  CTop{}
15  CKey{}
16  CItem{}
17  CChain{}
18
19def pad(+d: Nat) -> String:
20  String.repeat(" ", d)
21
22def str(+s: String) -> String:
23  Bool.pick(String, L.plain_ok(s), s, Quote.quote(s, False{}))
24
25# Before a scalar: a space after `key:`.
26def lead(+k: Ctx) -> String:
27  match k:
28    case CKey{}:
29      " "
30    case _:
31      ""
32
33# The indent of a container's lines.
34def ind(+k: Ctx, +d: Nat) -> Nat:
35  match k:
36    case CTop{}:
37      d
38    case _:
39      Nat.add(d, 2n)
40
41# Before a container's first line: under a key it starts a new line.
42def pre(+k: Ctx, +d: Nat) -> String:
43  match k:
44    case CKey{}:
45      "\n" ++ pad(Nat.add(d, 2n))
46    case _:
47      ""
48
49def is_nil(j: Json.Json) -> Bool:
50  match j:
51    case Json.JNil{}:
52      True{}
53    case _:
54      False{}
55
56def cont(empty: Bool, +k: Ctx, +d: Nat, lit: String, body: String) -> String:
57  match empty:
58    case True{}:
59      lead(k) ++ lit
60    case False{}:
61      pre(k, d) ++ body
62
63# A new line for the next field or item, none at the end of the chain.
64def sep(t: Json.Json, +d: Nat, rest: String) -> String:
65  match t:
66    case Json.JNil{}:
67      ""
68    case _:
69      "\n" ++ pad(d) ++ rest
70
71# `j` at the base indent `d` in context `k`. Chains (fields, items) carry their
72# lines at `d`; the first line of a chain starts wherever the caller left off.
73def y(j: Json.Json, +k: Ctx, +d: Nat) -> String:
74  match j:
75    case Json.JNull{}:
76      lead(k) ++ "null"
77    case Json.JBool{b}:
78      lead(k) ++ Json.bool.show(b)
79    case Json.JNum{t}:
80      lead(k) ++ t
81    case Json.JStr{s}:
82      lead(k) ++ str(s)
83    case Json.JArr{+items}:
84      cont(is_nil(items), k, d, "[]", y(items, CChain{}, ind(k, d)))
85    case Json.JObj{+fields}:
86      cont(is_nil(fields), k, d, "{}", y(fields, CChain{}, ind(k, d)))
87    case Json.JNil{}:
88      ""
89    case Json.JCons{h, +t}:
90      "- " ++ y(h, CItem{}, d) ++ sep(t, d, y(t, CChain{}, d))
91    case Json.JField{key, v, +t}:
92      str(key) ++ ":" ++ y(v, CKey{}, d) ++ sep(t, d, y(t, CChain{}, d))
93
94# One YAML document for the value (no `---`, no trailing newline).
95def emit(j: Json.Json) -> String:
96  y(j, CTop{}, 0n)