1 import Base 2 import ../json.bend as Json 3 import ../json/quote.bend as Quote 4 import ./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. 13 type Ctx is Data: 14 CTop{} 15 CKey{} 16 CItem{} 17 CChain{} 18 19 def pad(+d: Nat) -> String: 20 String.repeat(" ", d) 21 22 def 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:`. 26 def lead(+k: Ctx) -> String: 27 match k: 28 case CKey{}: 29 " " 30 case _: 31 "" 32 33 # The indent of a container's lines. 34 def 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. 42 def pre(+k: Ctx, +d: Nat) -> String: 43 match k: 44 case CKey{}: 45 "\n" ++ pad(Nat.add(d, 2n)) 46 case _: 47 "" 48 49 def is_nil(j: Json.Json) -> Bool: 50 match j: 51 case Json.JNil{}: 52 True{} 53 case _: 54 False{} 55 56 def 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. 64 def 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. 73 def 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). 95 def emit(j: Json.Json) -> String: 96 y(j, CTop{}, 0n)