v / vlib / yaml / emit.v
204 lines · 195 sloc · 4.83 KB · d96fe54c0a96fd6d651e9cb3c61b137ccc5c7dbe
Raw
1module yaml
2
3import strings
4
5// write_spaces appends `n` spaces to `sb`.
6fn write_spaces(mut sb strings.Builder, n int) {
7 for _ in 0 .. n {
8 sb.write_u8(` `)
9 }
10}
11
12// emit_yaml_any streams `value` into `sb` as block-style YAML.
13fn emit_yaml_any(mut sb strings.Builder, value Any, indent int) {
14 match value {
15 map[string]Any { emit_yaml_map(mut sb, value, indent) }
16 []Any { emit_yaml_array(mut sb, value, indent) }
17 else { emit_yaml_scalar(mut sb, value) }
18 }
19}
20
21// emit_yaml_map writes `value` as a block-style YAML mapping. An empty map
22// is emitted as the inline `{}` form; otherwise each key is JSON-quoted (see
23// `write_json_escaped_string`) and nested containers indent one level deeper.
24fn emit_yaml_map(mut sb strings.Builder, value map[string]Any, indent int) {
25 if value.len == 0 {
26 sb.write_string('{}')
27 return
28 }
29 mut first := true
30 for key, item in value {
31 if !first {
32 sb.write_u8(`\n`)
33 }
34 first = false
35 write_spaces(mut sb, indent)
36 write_json_escaped_string(mut sb, key)
37 match item {
38 map[string]Any, []Any {
39 sb.write_u8(`:`)
40 sb.write_u8(`\n`)
41 emit_yaml_any(mut sb, item, indent + 2)
42 }
43 else {
44 sb.write_string(': ')
45 emit_yaml_scalar(mut sb, item)
46 }
47 }
48 }
49}
50
51// emit_yaml_array writes `value` as a block-style YAML sequence. An empty
52// array is emitted as the inline `[]` form; otherwise each item is prefixed
53// with `- ` and nested containers indent one level deeper.
54fn emit_yaml_array(mut sb strings.Builder, value []Any, indent int) {
55 if value.len == 0 {
56 sb.write_string('[]')
57 return
58 }
59 mut first := true
60 for item in value {
61 if !first {
62 sb.write_u8(`\n`)
63 }
64 first = false
65 write_spaces(mut sb, indent)
66 match item {
67 map[string]Any, []Any {
68 sb.write_u8(`-`)
69 sb.write_u8(`\n`)
70 emit_yaml_any(mut sb, item, indent + 2)
71 }
72 else {
73 sb.write_string('- ')
74 emit_yaml_scalar(mut sb, item)
75 }
76 }
77 }
78}
79
80// emit_yaml_scalar writes `value` as a single YAML scalar token: strings go
81// through `write_json_escaped_string`, booleans / numbers / null print their
82// literal form. The container branch is type-required by V's exhaustive
83// `match` over `Any` but is unreachable: `emit_yaml_any` routes maps and
84// arrays to their dedicated emitters before falling back here.
85fn emit_yaml_scalar(mut sb strings.Builder, value Any) {
86 match value {
87 string { write_json_escaped_string(mut sb, value) }
88 bool { sb.write_string(if value { 'true' } else { 'false' }) }
89 f64 { sb.write_string(value.str()) }
90 i64 { sb.write_string(value.str()) }
91 int { sb.write_string(value.str()) }
92 u64 { sb.write_string(value.str()) }
93 Null { sb.write_string('null') }
94 []Any, map[string]Any { emit_yaml_any(mut sb, value, 0) }
95 }
96}
97
98// write_json_escaped_string writes `value` as a JSON string literal directly
99// into `sb`. Matches `json2.encode`'s rules: standard short escapes for control
100// chars, `\u00XX` for the rest below 0x20, and UTF-8 bytes passed through
101// verbatim (no per-byte `\uXXXX` re-escape). Passes safe runs through in bulk
102// via `write_string` on the original slice — the overwhelmingly common case for
103// human YAML — and only switches to per-byte handling at escape boundaries.
104fn write_json_escaped_string(mut sb strings.Builder, value string) {
105 sb.write_u8(`"`)
106 mut start := 0
107 for i := 0; i < value.len; i++ {
108 c := value[i]
109 if c >= 0x20 && c != `"` && c != `\\` {
110 continue
111 }
112 if start < i {
113 sb.write_string(value[start..i])
114 }
115 match c {
116 `"` {
117 sb.write_string('\\"')
118 }
119 `\\` {
120 sb.write_string('\\\\')
121 }
122 `\n` {
123 sb.write_string('\\n')
124 }
125 `\r` {
126 sb.write_string('\\r')
127 }
128 `\t` {
129 sb.write_string('\\t')
130 }
131 `\b` {
132 sb.write_string('\\b')
133 }
134 `\f` {
135 sb.write_string('\\f')
136 }
137 else {
138 sb.write_string('\\u00')
139 hex := '0123456789abcdef'
140 sb.write_u8(hex[(c >> 4) & 0xf])
141 sb.write_u8(hex[c & 0xf])
142 }
143 }
144
145 start = i + 1
146 }
147 if start < value.len {
148 sb.write_string(value[start..])
149 }
150 sb.write_u8(`"`)
151}
152
153// emit_any_as_json writes `a` as a compact JSON document.
154fn emit_any_as_json(mut sb strings.Builder, a Any) {
155 match a {
156 map[string]Any {
157 sb.write_u8(`{`)
158 mut first := true
159 for key, value in a {
160 if !first {
161 sb.write_u8(`,`)
162 }
163 first = false
164 write_json_escaped_string(mut sb, key)
165 sb.write_u8(`:`)
166 emit_any_as_json(mut sb, value)
167 }
168 sb.write_u8(`}`)
169 }
170 []Any {
171 sb.write_u8(`[`)
172 mut first := true
173 for value in a {
174 if !first {
175 sb.write_u8(`,`)
176 }
177 first = false
178 emit_any_as_json(mut sb, value)
179 }
180 sb.write_u8(`]`)
181 }
182 string {
183 write_json_escaped_string(mut sb, a)
184 }
185 bool {
186 sb.write_string(if a { 'true' } else { 'false' })
187 }
188 f64 {
189 sb.write_string(a.str())
190 }
191 i64 {
192 sb.write_string(a.str())
193 }
194 int {
195 sb.write_string(a.str())
196 }
197 u64 {
198 sb.write_string(a.str())
199 }
200 Null {
201 sb.write_string('null')
202 }
203 }
204}
205