Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}