/Users/alexjokela/projects/lattice/src/yaml_ops.c
Line | Count | Source |
1 | | #include "yaml_ops.h" |
2 | | #include <stdlib.h> |
3 | | #include <string.h> |
4 | | #include <stdio.h> |
5 | | #include <stdarg.h> |
6 | | #include <ctype.h> |
7 | | |
8 | | /* ======================================================================== |
9 | | * Internal: YAML Parser (indentation-based) |
10 | | * ======================================================================== */ |
11 | | |
12 | | typedef struct { |
13 | | const char *src; |
14 | | size_t pos; |
15 | | char *err; |
16 | | } YamlParser; |
17 | | |
18 | | /* ── Scalar auto-detection ── */ |
19 | 42 | static LatValue yaml_detect_scalar(const char *s) { |
20 | 42 | if (!s || *s == '\0') return value_string(""); |
21 | | |
22 | 42 | if (strcmp(s, "true") == 0 || strcmp(s, "True") == 0 || strcmp(s, "TRUE") == 0 || |
23 | 42 | strcmp(s, "yes") == 0 || strcmp(s, "Yes") == 0 || strcmp(s, "YES") == 0) |
24 | 3 | return value_bool(true); |
25 | 39 | if (strcmp(s, "false") == 0 || strcmp(s, "False") == 0 || strcmp(s, "FALSE") == 0 || |
26 | 39 | strcmp(s, "no") == 0 || strcmp(s, "No") == 0 || strcmp(s, "NO") == 0) |
27 | 3 | return value_bool(false); |
28 | 36 | if (strcmp(s, "null") == 0 || strcmp(s, "Null") == 0 || strcmp(s, "NULL") == 0 || |
29 | 36 | strcmp(s, "~") == 0) |
30 | 0 | return value_nil(); |
31 | | |
32 | | /* Integer */ |
33 | 36 | const char *p = s; |
34 | 36 | if (*p == '-' || *p == '+') p++; |
35 | 36 | if (*p != '\0' && isdigit((unsigned char)*p)) { |
36 | 18 | bool all_digits = true; |
37 | 51 | while (*p) { |
38 | 33 | if (!isdigit((unsigned char)*p)) { all_digits = false; break; } |
39 | 33 | p++; |
40 | 33 | } |
41 | 18 | if (all_digits) |
42 | 18 | return value_int(strtoll(s, NULL, 10)); |
43 | 18 | } |
44 | | |
45 | | /* Float */ |
46 | 18 | char *endptr; |
47 | 18 | double fval = strtod(s, &endptr); |
48 | 18 | if (*endptr == '\0' && endptr != s && strchr(s, '.')) |
49 | 0 | return value_float(fval); |
50 | | |
51 | 18 | return value_string(s); |
52 | 18 | } |
53 | | |
54 | | /* ── Strip quotes from a string ── */ |
55 | 60 | static char *yaml_strip_quotes(const char *s) { |
56 | 60 | size_t len = strlen(s); |
57 | 60 | if (len >= 2 && |
58 | 60 | ((s[0] == '"' && s[len-1] == '"') || (s[0] == '\'' && s[len-1] == '\''))) |
59 | 0 | return strndup(s + 1, len - 2); |
60 | 60 | return strdup(s); |
61 | 60 | } |
62 | | |
63 | | /* ── Trim trailing whitespace ── */ |
64 | 69 | static char *yaml_trim_end(const char *s, size_t len) { |
65 | 69 | while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\t' || s[len-1] == '\r')) |
66 | 0 | len--; |
67 | 69 | return strndup(s, len); |
68 | 69 | } |
69 | | |
70 | | /* ── Count indentation at position ── */ |
71 | 57 | static int yaml_indent_at(const char *src, size_t pos) { |
72 | 57 | int indent = 0; |
73 | 75 | while (src[pos + (size_t)indent] == ' ') indent++; |
74 | 57 | return indent; |
75 | 57 | } |
76 | | |
77 | | /* ── Read rest of line as trimmed value ── */ |
78 | 33 | static char *yaml_read_line_value(YamlParser *p) { |
79 | 33 | size_t start = p->pos; |
80 | 186 | while (p->src[p->pos] && p->src[p->pos] != '\n' && p->src[p->pos] != '\r') { |
81 | 153 | if (p->src[p->pos] == '#' && p->pos > start && p->src[p->pos - 1] == ' ') |
82 | 0 | break; |
83 | 153 | p->pos++; |
84 | 153 | } |
85 | 33 | char *val = yaml_trim_end(p->src + start, p->pos - start); |
86 | 33 | if (p->src[p->pos] == '\r') p->pos++; |
87 | 33 | if (p->src[p->pos] == '\n') p->pos++; |
88 | 33 | return val; |
89 | 33 | } |
90 | | |
91 | | /* ── Skip blank and comment-only lines ── */ |
92 | 75 | static void yaml_skip_blanks(YamlParser *p) { |
93 | 78 | while (p->src[p->pos]) { |
94 | 78 | size_t saved = p->pos; |
95 | 96 | while (p->src[p->pos] == ' ' || p->src[p->pos] == '\t') p->pos++; |
96 | 78 | if (p->src[p->pos] == '\n' || p->src[p->pos] == '\r' || p->src[p->pos] == '#') { |
97 | 3 | while (p->src[p->pos] && p->src[p->pos] != '\n') p->pos++; |
98 | 3 | if (p->src[p->pos] == '\n') p->pos++; |
99 | 3 | continue; |
100 | 3 | } |
101 | 75 | p->pos = saved; |
102 | 75 | break; |
103 | 78 | } |
104 | 75 | } |
105 | | |
106 | | /* Forward declarations */ |
107 | | static LatValue yaml_parse_node(YamlParser *p, int min_indent); |
108 | | static LatValue yaml_parse_flow_value(YamlParser *p); |
109 | | |
110 | | /* ── Parse flow sequence [a, b, c] ── */ |
111 | 3 | static LatValue yaml_parse_flow_seq(YamlParser *p) { |
112 | 3 | p->pos++; /* skip [ */ |
113 | 3 | size_t cap = 4, len = 0; |
114 | 3 | LatValue *elems = malloc(cap * sizeof(LatValue)); |
115 | | |
116 | 12 | while (p->src[p->pos] && p->src[p->pos] != ']') { |
117 | 15 | while (p->src[p->pos] == ' ' || p->src[p->pos] == '\t' || |
118 | 15 | p->src[p->pos] == '\n' || p->src[p->pos] == '\r') |
119 | 6 | p->pos++; |
120 | 9 | if (p->src[p->pos] == ']') break; |
121 | | |
122 | 9 | LatValue elem = yaml_parse_flow_value(p); |
123 | 9 | if (p->err) { |
124 | 0 | value_free(&elem); |
125 | 0 | for (size_t i = 0; i < len; i++) value_free(&elems[i]); |
126 | 0 | free(elems); |
127 | 0 | return value_unit(); |
128 | 0 | } |
129 | 9 | if (len >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); } |
130 | 9 | elems[len++] = elem; |
131 | | |
132 | 9 | while (p->src[p->pos] == ' ') p->pos++; |
133 | 9 | if (p->src[p->pos] == ',') p->pos++; |
134 | 9 | } |
135 | 3 | if (p->src[p->pos] == ']') p->pos++; |
136 | 3 | LatValue arr = value_array(elems, len); |
137 | 3 | free(elems); |
138 | 3 | return arr; |
139 | 3 | } |
140 | | |
141 | | /* ── Parse flow mapping {a: b, c: d} ── */ |
142 | 0 | static LatValue yaml_parse_flow_map(YamlParser *p) { |
143 | 0 | p->pos++; /* skip { */ |
144 | 0 | LatValue map = value_map_new(); |
145 | |
|
146 | 0 | while (p->src[p->pos] && p->src[p->pos] != '}') { |
147 | 0 | while (p->src[p->pos] == ' ' || p->src[p->pos] == '\t') p->pos++; |
148 | 0 | if (p->src[p->pos] == '}') break; |
149 | | |
150 | 0 | size_t kstart = p->pos; |
151 | 0 | while (p->src[p->pos] && p->src[p->pos] != ':' && p->src[p->pos] != '}') |
152 | 0 | p->pos++; |
153 | 0 | char *key = yaml_trim_end(p->src + kstart, p->pos - kstart); |
154 | 0 | char *stripped_key = yaml_strip_quotes(key); |
155 | 0 | free(key); |
156 | |
|
157 | 0 | if (p->src[p->pos] == ':') { |
158 | 0 | p->pos++; |
159 | 0 | while (p->src[p->pos] == ' ') p->pos++; |
160 | 0 | } |
161 | |
|
162 | 0 | LatValue val = yaml_parse_flow_value(p); |
163 | 0 | if (p->err) { free(stripped_key); value_free(&val); value_free(&map); return value_unit(); } |
164 | 0 | lat_map_set(map.as.map.map, stripped_key, &val); |
165 | 0 | free(stripped_key); |
166 | |
|
167 | 0 | while (p->src[p->pos] == ' ') p->pos++; |
168 | 0 | if (p->src[p->pos] == ',') p->pos++; |
169 | 0 | } |
170 | 0 | if (p->src[p->pos] == '}') p->pos++; |
171 | 0 | return map; |
172 | 0 | } |
173 | | |
174 | | /* ── Parse a flow value ── */ |
175 | 9 | static LatValue yaml_parse_flow_value(YamlParser *p) { |
176 | 9 | while (p->src[p->pos] == ' ') p->pos++; |
177 | 9 | if (p->src[p->pos] == '[') return yaml_parse_flow_seq(p); |
178 | 9 | if (p->src[p->pos] == '{') return yaml_parse_flow_map(p); |
179 | | |
180 | | /* Quoted string */ |
181 | 9 | if (p->src[p->pos] == '"' || p->src[p->pos] == '\'') { |
182 | 0 | char quote = p->src[p->pos++]; |
183 | 0 | size_t start = p->pos; |
184 | 0 | while (p->src[p->pos] && p->src[p->pos] != quote) { |
185 | 0 | if (p->src[p->pos] == '\\') p->pos++; |
186 | 0 | p->pos++; |
187 | 0 | } |
188 | 0 | char *s = strndup(p->src + start, p->pos - start); |
189 | 0 | if (p->src[p->pos] == quote) p->pos++; |
190 | 0 | LatValue v = value_string(s); |
191 | 0 | free(s); |
192 | 0 | return v; |
193 | 0 | } |
194 | | |
195 | | /* Unquoted scalar */ |
196 | 9 | size_t start = p->pos; |
197 | 18 | while (p->src[p->pos] && p->src[p->pos] != ',' && |
198 | 18 | p->src[p->pos] != ']' && p->src[p->pos] != '}' && |
199 | 18 | p->src[p->pos] != '\n' && p->src[p->pos] != '\r') |
200 | 9 | p->pos++; |
201 | 9 | char *raw = yaml_trim_end(p->src + start, p->pos - start); |
202 | 9 | LatValue v = yaml_detect_scalar(raw); |
203 | 9 | free(raw); |
204 | 9 | return v; |
205 | 9 | } |
206 | | |
207 | | /* ── Parse a YAML node at given indentation level ── */ |
208 | 21 | static LatValue yaml_parse_node(YamlParser *p, int min_indent) { |
209 | 21 | yaml_skip_blanks(p); |
210 | 21 | if (p->src[p->pos] == '\0') return value_unit(); |
211 | | |
212 | 21 | int cur_indent = yaml_indent_at(p->src, p->pos); |
213 | 21 | if (cur_indent < min_indent) return value_unit(); |
214 | | |
215 | 21 | size_t line_start = p->pos; |
216 | 21 | p->pos += (size_t)cur_indent; |
217 | | |
218 | | /* Flow values */ |
219 | 21 | if (p->src[p->pos] == '[') return yaml_parse_flow_seq(p); |
220 | 18 | if (p->src[p->pos] == '{') return yaml_parse_flow_map(p); |
221 | | |
222 | | /* Detect sequence (starts with "- ") */ |
223 | 18 | if (p->src[p->pos] == '-' && |
224 | 18 | (p->src[p->pos + 1] == ' ' || p->src[p->pos + 1] == '\n' || |
225 | 3 | p->src[p->pos + 1] == '\r' || p->src[p->pos + 1] == '\0')) { |
226 | 3 | p->pos = line_start; |
227 | 3 | size_t arr_cap = 4, arr_len = 0; |
228 | 3 | LatValue *arr_elems = malloc(arr_cap * sizeof(LatValue)); |
229 | | |
230 | 12 | while (p->src[p->pos]) { |
231 | 9 | yaml_skip_blanks(p); |
232 | 9 | if (p->src[p->pos] == '\0') break; |
233 | | |
234 | 9 | int indent = yaml_indent_at(p->src, p->pos); |
235 | 9 | if (indent != cur_indent) break; |
236 | 9 | if (p->src[p->pos + (size_t)indent] != '-') break; |
237 | | |
238 | 9 | p->pos += (size_t)indent + 1; /* skip indent + '-' */ |
239 | 9 | if (p->src[p->pos] == ' ') p->pos++; |
240 | | |
241 | 9 | LatValue elem; |
242 | 9 | if (p->src[p->pos] == '\n' || p->src[p->pos] == '\r' || p->src[p->pos] == '\0') { |
243 | 0 | if (p->src[p->pos] == '\r') p->pos++; |
244 | 0 | if (p->src[p->pos] == '\n') p->pos++; |
245 | 0 | elem = yaml_parse_node(p, cur_indent + 2); |
246 | 9 | } else if (p->src[p->pos] == '[' || p->src[p->pos] == '{') { |
247 | 0 | elem = yaml_parse_flow_value(p); |
248 | 0 | while (p->src[p->pos] && p->src[p->pos] != '\n') p->pos++; |
249 | 0 | if (p->src[p->pos] == '\n') p->pos++; |
250 | 9 | } else { |
251 | | /* Check for inline key: value (mapping element) */ |
252 | 9 | size_t scan = p->pos; |
253 | 9 | bool is_mapping = false; |
254 | 60 | while (p->src[scan] && p->src[scan] != '\n' && p->src[scan] != '\r') { |
255 | 51 | if (p->src[scan] == ':' && |
256 | 51 | (p->src[scan+1] == ' ' || p->src[scan+1] == '\n' || |
257 | 0 | p->src[scan+1] == '\r' || p->src[scan+1] == '\0')) { |
258 | 0 | is_mapping = true; |
259 | 0 | break; |
260 | 0 | } |
261 | 51 | if (p->src[scan] == '"' || p->src[scan] == '\'') break; |
262 | 51 | scan++; |
263 | 51 | } |
264 | | |
265 | 9 | if (is_mapping) { |
266 | | /* Parse mapping starting at this position */ |
267 | 0 | int item_indent = (int)(p->pos - line_start); |
268 | | /* Parse first key */ |
269 | 0 | size_t kstart = p->pos; |
270 | 0 | while (p->src[p->pos] && p->src[p->pos] != ':') p->pos++; |
271 | 0 | char *key = yaml_trim_end(p->src + kstart, p->pos - kstart); |
272 | 0 | char *stripped_key = yaml_strip_quotes(key); |
273 | 0 | free(key); |
274 | |
|
275 | 0 | p->pos++; /* skip : */ |
276 | 0 | if (p->src[p->pos] == ' ') p->pos++; |
277 | |
|
278 | 0 | LatValue map_elem = value_map_new(); |
279 | |
|
280 | 0 | if (p->src[p->pos] == '\n' || p->src[p->pos] == '\r' || p->src[p->pos] == '\0') { |
281 | 0 | if (p->src[p->pos] == '\r') p->pos++; |
282 | 0 | if (p->src[p->pos] == '\n') p->pos++; |
283 | 0 | LatValue val = yaml_parse_node(p, cur_indent + 2); |
284 | 0 | lat_map_set(map_elem.as.map.map, stripped_key, &val); |
285 | 0 | } else { |
286 | 0 | char *raw = yaml_read_line_value(p); |
287 | 0 | char *stripped = yaml_strip_quotes(raw); |
288 | 0 | LatValue val = yaml_detect_scalar(stripped); |
289 | 0 | free(stripped); free(raw); |
290 | 0 | lat_map_set(map_elem.as.map.map, stripped_key, &val); |
291 | 0 | } |
292 | 0 | free(stripped_key); |
293 | | |
294 | | /* Continue parsing additional keys for this map element */ |
295 | 0 | yaml_skip_blanks(p); |
296 | 0 | while (p->src[p->pos]) { |
297 | 0 | int next_indent = yaml_indent_at(p->src, p->pos); |
298 | 0 | if (next_indent <= cur_indent) break; |
299 | 0 | if (next_indent < item_indent) break; |
300 | 0 | if (p->src[p->pos + (size_t)next_indent] == '-') break; |
301 | | |
302 | 0 | p->pos += (size_t)next_indent; |
303 | 0 | size_t kstart2 = p->pos; |
304 | 0 | while (p->src[p->pos] && p->src[p->pos] != ':' && p->src[p->pos] != '\n') |
305 | 0 | p->pos++; |
306 | 0 | if (p->src[p->pos] != ':') break; |
307 | 0 | char *key2 = yaml_trim_end(p->src + kstart2, p->pos - kstart2); |
308 | 0 | char *stripped_key2 = yaml_strip_quotes(key2); |
309 | 0 | free(key2); |
310 | 0 | p->pos++; |
311 | 0 | if (p->src[p->pos] == ' ') p->pos++; |
312 | |
|
313 | 0 | if (p->src[p->pos] == '\n' || p->src[p->pos] == '\r' || p->src[p->pos] == '\0') { |
314 | 0 | if (p->src[p->pos] == '\r') p->pos++; |
315 | 0 | if (p->src[p->pos] == '\n') p->pos++; |
316 | 0 | LatValue val = yaml_parse_node(p, next_indent + 1); |
317 | 0 | lat_map_set(map_elem.as.map.map, stripped_key2, &val); |
318 | 0 | } else { |
319 | 0 | char *raw2 = yaml_read_line_value(p); |
320 | 0 | char *stripped2 = yaml_strip_quotes(raw2); |
321 | 0 | LatValue val = yaml_detect_scalar(stripped2); |
322 | 0 | free(stripped2); free(raw2); |
323 | 0 | lat_map_set(map_elem.as.map.map, stripped_key2, &val); |
324 | 0 | } |
325 | 0 | free(stripped_key2); |
326 | 0 | yaml_skip_blanks(p); |
327 | 0 | } |
328 | |
|
329 | 0 | elem = map_elem; |
330 | 9 | } else { |
331 | 9 | char *raw = yaml_read_line_value(p); |
332 | 9 | char *stripped = yaml_strip_quotes(raw); |
333 | 9 | elem = yaml_detect_scalar(stripped); |
334 | 9 | free(stripped); free(raw); |
335 | 9 | } |
336 | 9 | } |
337 | | |
338 | 9 | if (arr_len >= arr_cap) { arr_cap *= 2; arr_elems = realloc(arr_elems, arr_cap * sizeof(LatValue)); } |
339 | 9 | arr_elems[arr_len++] = elem; |
340 | | |
341 | | /* Update line_start for next iteration */ |
342 | 9 | line_start = p->pos; |
343 | 9 | } |
344 | 3 | LatValue arr = value_array(arr_elems, arr_len); |
345 | 3 | free(arr_elems); |
346 | 3 | return arr; |
347 | 3 | } |
348 | | |
349 | | /* Detect mapping (key: value) */ |
350 | 15 | { |
351 | 15 | size_t scan = p->pos; |
352 | 90 | while (p->src[scan] && p->src[scan] != '\n' && p->src[scan] != '\r') { |
353 | 90 | if (p->src[scan] == ':' && |
354 | 90 | (p->src[scan+1] == ' ' || p->src[scan+1] == '\n' || |
355 | 15 | p->src[scan+1] == '\r' || p->src[scan+1] == '\0')) { |
356 | | /* This is a mapping */ |
357 | 15 | p->pos = line_start; |
358 | 15 | LatValue map = value_map_new(); |
359 | | |
360 | 42 | while (p->src[p->pos]) { |
361 | 27 | yaml_skip_blanks(p); |
362 | 27 | if (p->src[p->pos] == '\0') break; |
363 | | |
364 | 27 | int indent = yaml_indent_at(p->src, p->pos); |
365 | 27 | if (indent != cur_indent) break; |
366 | | |
367 | 27 | if (p->src[p->pos + (size_t)indent] == '-' && |
368 | 27 | (p->src[p->pos + (size_t)indent + 1] == ' ' || |
369 | 0 | p->src[p->pos + (size_t)indent + 1] == '\n')) |
370 | 0 | break; |
371 | | |
372 | 27 | p->pos += (size_t)indent; |
373 | | |
374 | 27 | size_t kstart = p->pos; |
375 | 147 | while (p->src[p->pos] && p->src[p->pos] != ':' && p->src[p->pos] != '\n') |
376 | 120 | p->pos++; |
377 | 27 | if (p->src[p->pos] != ':') { |
378 | 0 | p->pos = kstart; |
379 | 0 | break; |
380 | 0 | } |
381 | 27 | char *key = yaml_trim_end(p->src + kstart, p->pos - kstart); |
382 | 27 | char *stripped_key = yaml_strip_quotes(key); |
383 | 27 | free(key); |
384 | | |
385 | 27 | p->pos++; /* skip : */ |
386 | 27 | if (p->src[p->pos] == ' ') p->pos++; |
387 | | |
388 | 27 | if (p->src[p->pos] == '\n' || p->src[p->pos] == '\r' || p->src[p->pos] == '\0') { |
389 | 3 | if (p->src[p->pos] == '\r') p->pos++; |
390 | 3 | if (p->src[p->pos] == '\n') p->pos++; |
391 | 3 | LatValue val = yaml_parse_node(p, cur_indent + 1); |
392 | 3 | lat_map_set(map.as.map.map, stripped_key, &val); |
393 | 24 | } else if (p->src[p->pos] == '[' || p->src[p->pos] == '{') { |
394 | 0 | LatValue val = yaml_parse_flow_value(p); |
395 | 0 | lat_map_set(map.as.map.map, stripped_key, &val); |
396 | 0 | while (p->src[p->pos] && p->src[p->pos] != '\n') p->pos++; |
397 | 0 | if (p->src[p->pos] == '\n') p->pos++; |
398 | 24 | } else { |
399 | 24 | char *raw = yaml_read_line_value(p); |
400 | 24 | char *stripped = yaml_strip_quotes(raw); |
401 | 24 | LatValue val = yaml_detect_scalar(stripped); |
402 | 24 | free(stripped); free(raw); |
403 | 24 | lat_map_set(map.as.map.map, stripped_key, &val); |
404 | 24 | } |
405 | 27 | free(stripped_key); |
406 | 27 | } |
407 | 15 | return map; |
408 | 15 | } |
409 | 75 | if (p->src[scan] == '"' || p->src[scan] == '\'') { |
410 | 0 | char q = p->src[scan++]; |
411 | 0 | while (p->src[scan] && p->src[scan] != q) { |
412 | 0 | if (p->src[scan] == '\\') scan++; |
413 | 0 | scan++; |
414 | 0 | } |
415 | 0 | if (p->src[scan]) scan++; |
416 | 0 | continue; |
417 | 0 | } |
418 | 75 | scan++; |
419 | 75 | } |
420 | 15 | } |
421 | | |
422 | | /* Plain scalar */ |
423 | 0 | char *raw = yaml_read_line_value(p); |
424 | 0 | char *stripped = yaml_strip_quotes(raw); |
425 | 0 | LatValue v = yaml_detect_scalar(stripped); |
426 | 0 | free(stripped); free(raw); |
427 | 0 | return v; |
428 | 15 | } |
429 | | |
430 | | /* ── Main YAML parse function ── */ |
431 | 18 | LatValue yaml_ops_parse(const char *yaml_str, char **err) { |
432 | 18 | YamlParser p = { .src = yaml_str, .pos = 0, .err = NULL }; |
433 | | |
434 | 18 | yaml_skip_blanks(&p); |
435 | 18 | if (strncmp(p.src + p.pos, "---", 3) == 0) { |
436 | 0 | p.pos += 3; |
437 | 0 | while (p.src[p.pos] && p.src[p.pos] != '\n') p.pos++; |
438 | 0 | if (p.src[p.pos] == '\n') p.pos++; |
439 | 0 | } |
440 | | |
441 | 18 | LatValue result = yaml_parse_node(&p, 0); |
442 | | |
443 | 18 | if (p.err) { |
444 | 0 | value_free(&result); |
445 | 0 | *err = p.err; |
446 | 0 | return value_unit(); |
447 | 0 | } |
448 | 18 | return result; |
449 | 18 | } |
450 | | |
451 | | /* ======================================================================== |
452 | | * YAML Stringify |
453 | | * ======================================================================== */ |
454 | | |
455 | | typedef struct { |
456 | | char *buf; |
457 | | size_t len; |
458 | | size_t cap; |
459 | | } YamlBuf; |
460 | | |
461 | 3 | static void yb_init(YamlBuf *b) { |
462 | 3 | b->cap = 256; |
463 | 3 | b->buf = malloc(b->cap); |
464 | 3 | b->len = 0; |
465 | 3 | b->buf[0] = '\0'; |
466 | 3 | } |
467 | | |
468 | 27 | static void yb_append(YamlBuf *b, const char *s) { |
469 | 27 | size_t slen = strlen(s); |
470 | 27 | while (b->len + slen + 1 > b->cap) { b->cap *= 2; b->buf = realloc(b->buf, b->cap); } |
471 | 27 | memcpy(b->buf + b->len, s, slen); |
472 | 27 | b->len += slen; |
473 | 27 | b->buf[b->len] = '\0'; |
474 | 27 | } |
475 | | |
476 | | static void yb_appendf(YamlBuf *b, const char *fmt, ...) __attribute__((format(printf, 2, 3))); |
477 | 3 | static void yb_appendf(YamlBuf *b, const char *fmt, ...) { |
478 | 3 | va_list ap; |
479 | 3 | va_start(ap, fmt); |
480 | 3 | char tmp[256]; |
481 | 3 | vsnprintf(tmp, sizeof(tmp), fmt, ap); |
482 | 3 | va_end(ap); |
483 | 3 | yb_append(b, tmp); |
484 | 3 | } |
485 | | |
486 | 6 | static void yb_indent(YamlBuf *b, int level) { |
487 | 6 | for (int i = 0; i < level * 2; i++) |
488 | 0 | yb_append(b, " "); |
489 | 6 | } |
490 | | |
491 | 3 | static bool yaml_needs_quoting(const char *s) { |
492 | 3 | if (s[0] == '\0') return true; |
493 | 3 | if (strcmp(s, "true") == 0 || strcmp(s, "false") == 0 || |
494 | 3 | strcmp(s, "null") == 0 || strcmp(s, "~") == 0 || |
495 | 3 | strcmp(s, "yes") == 0 || strcmp(s, "no") == 0 || |
496 | 3 | strcmp(s, "True") == 0 || strcmp(s, "False") == 0 || |
497 | 3 | strcmp(s, "Yes") == 0 || strcmp(s, "No") == 0) |
498 | 0 | return true; |
499 | 15 | for (const char *c = s; *c; c++) { |
500 | 12 | if (*c == ':' || *c == '#' || *c == '[' || *c == ']' || |
501 | 12 | *c == '{' || *c == '}' || *c == ',' || *c == '\n' || |
502 | 12 | *c == '"' || *c == '\'' || *c == '|' || *c == '>') |
503 | 0 | return true; |
504 | 12 | } |
505 | 3 | if (s[0] == '-' || s[0] == '?' || s[0] == '*' || s[0] == '&') |
506 | 0 | return true; |
507 | | /* Looks like a number */ |
508 | 3 | char *endptr; |
509 | 3 | strtod(s, &endptr); |
510 | 3 | if (*endptr == '\0' && endptr != s) return true; |
511 | 3 | return false; |
512 | 3 | } |
513 | | |
514 | | static void yaml_stringify_value(YamlBuf *b, const LatValue *val, int indent); |
515 | | |
516 | 9 | static void yaml_stringify_value(YamlBuf *b, const LatValue *val, int indent) { |
517 | 9 | switch (val->type) { |
518 | 3 | case VAL_STR: |
519 | 3 | if (yaml_needs_quoting(val->as.str_val)) { |
520 | 0 | yb_append(b, "\""); |
521 | 0 | for (const char *s = val->as.str_val; *s; s++) { |
522 | 0 | switch (*s) { |
523 | 0 | case '"': yb_append(b, "\\\""); break; |
524 | 0 | case '\\': yb_append(b, "\\\\"); break; |
525 | 0 | case '\n': yb_append(b, "\\n"); break; |
526 | 0 | case '\t': yb_append(b, "\\t"); break; |
527 | 0 | default: { char c[2] = { *s, '\0' }; yb_append(b, c); } |
528 | 0 | } |
529 | 0 | } |
530 | 0 | yb_append(b, "\""); |
531 | 3 | } else { |
532 | 3 | yb_append(b, val->as.str_val); |
533 | 3 | } |
534 | 3 | break; |
535 | 3 | case VAL_INT: |
536 | 3 | yb_appendf(b, "%lld", (long long)val->as.int_val); |
537 | 3 | break; |
538 | 0 | case VAL_FLOAT: |
539 | 0 | yb_appendf(b, "%g", val->as.float_val); |
540 | 0 | break; |
541 | 0 | case VAL_BOOL: |
542 | 0 | yb_append(b, val->as.bool_val ? "true" : "false"); |
543 | 0 | break; |
544 | 0 | case VAL_UNIT: |
545 | 0 | case VAL_NIL: |
546 | 0 | yb_append(b, "null"); |
547 | 0 | break; |
548 | 0 | case VAL_ARRAY: { |
549 | 0 | if (val->as.array.len == 0) { |
550 | 0 | yb_append(b, "[]"); |
551 | 0 | } else { |
552 | 0 | yb_append(b, "\n"); |
553 | 0 | for (size_t i = 0; i < val->as.array.len; i++) { |
554 | 0 | yb_indent(b, indent); |
555 | 0 | yb_append(b, "- "); |
556 | 0 | yaml_stringify_value(b, &val->as.array.elems[i], indent + 1); |
557 | 0 | if (val->as.array.elems[i].type != VAL_MAP && |
558 | 0 | val->as.array.elems[i].type != VAL_ARRAY) |
559 | 0 | yb_append(b, "\n"); |
560 | 0 | } |
561 | 0 | } |
562 | 0 | break; |
563 | 0 | } |
564 | 3 | case VAL_MAP: { |
565 | 3 | size_t count = lat_map_len(val->as.map.map); |
566 | 3 | if (count == 0) { |
567 | 0 | yb_append(b, "{}"); |
568 | 3 | } else { |
569 | 3 | yb_append(b, "\n"); |
570 | 51 | for (size_t i = 0; i < val->as.map.map->cap; i++) { |
571 | 48 | if (val->as.map.map->entries[i].state != MAP_OCCUPIED) continue; |
572 | 6 | yb_indent(b, indent); |
573 | 6 | yb_append(b, val->as.map.map->entries[i].key); |
574 | 6 | yb_append(b, ": "); |
575 | 6 | LatValue *v = (LatValue *)val->as.map.map->entries[i].value; |
576 | 6 | yaml_stringify_value(b, v, indent + 1); |
577 | 6 | if (v->type != VAL_MAP && v->type != VAL_ARRAY) |
578 | 6 | yb_append(b, "\n"); |
579 | 6 | } |
580 | 3 | } |
581 | 3 | break; |
582 | 0 | } |
583 | 0 | case VAL_TUPLE: { |
584 | 0 | if (val->as.tuple.len == 0) { |
585 | 0 | yb_append(b, "[]"); |
586 | 0 | } else { |
587 | 0 | yb_append(b, "\n"); |
588 | 0 | for (size_t i = 0; i < val->as.tuple.len; i++) { |
589 | 0 | yb_indent(b, indent); |
590 | 0 | yb_append(b, "- "); |
591 | 0 | yaml_stringify_value(b, &val->as.tuple.elems[i], indent + 1); |
592 | 0 | if (val->as.tuple.elems[i].type != VAL_MAP && |
593 | 0 | val->as.tuple.elems[i].type != VAL_ARRAY) |
594 | 0 | yb_append(b, "\n"); |
595 | 0 | } |
596 | 0 | } |
597 | 0 | break; |
598 | 0 | } |
599 | 0 | default: |
600 | 0 | yb_append(b, "null"); |
601 | 0 | break; |
602 | 9 | } |
603 | 9 | } |
604 | | |
605 | 3 | char *yaml_ops_stringify(const LatValue *val, char **err) { |
606 | 3 | (void)err; |
607 | 3 | YamlBuf b; |
608 | 3 | yb_init(&b); |
609 | 3 | yaml_stringify_value(&b, val, 0); |
610 | 3 | if (b.len > 0 && b.buf[b.len - 1] != '\n') |
611 | 0 | yb_append(&b, "\n"); |
612 | 3 | return b.buf; |
613 | 3 | } |