1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
// sj.h - v0.4 - rxi 2025
// public domain - no warranty implied, use at your own risk
#ifndef SJ_H
#define SJ_H
#include <stddef.h>
#include <stdbool.h>
typedef struct {
char *data, *cur, *end;
int depth;
char *error;
} sj_Reader;
typedef struct {
int type;
char *start, *end;
int depth;
} sj_Value;
enum { SJ_ERROR, SJ_END, SJ_ARRAY, SJ_OBJECT, SJ_NUMBER, SJ_STRING, SJ_BOOL, SJ_NULL };
sj_Reader sj_reader(char *data, size_t len);
sj_Value sj_read(sj_Reader *r);
bool sj_iter_array(sj_Reader *r, sj_Value arr, sj_Value *val);
bool sj_iter_object(sj_Reader *r, sj_Value obj, sj_Value *key, sj_Value *val);
void sj_location(sj_Reader *r, int *line, int *col);
#endif // #ifndef SJ_H
#ifdef SJ_IMPL
sj_Reader sj_reader(char *data, size_t len) {
return (sj_Reader){ .data = data, .cur = data, .end = data + len };
}
static bool sj__is_number_cont(char c) {
return (c >= '0' && c <= '9')
|| c == 'e' || c == 'E' || c == '.' || c == '-' || c == '+';
}
static bool sj__is_string(char *cur, char *end, char *expect) {
while (*expect) {
if (cur == end || *cur != *expect) {
return false;
}
expect++, cur++;
}
return true;
}
sj_Value sj_read(sj_Reader *r) {
sj_Value res;
top:
if (r->error) { return (sj_Value){ .type = SJ_ERROR, .start = r->cur, .end = r->cur }; }
if (r->cur == r->end) { r->error = "unexpected eof"; goto top; }
res.start = r->cur;
switch (*r->cur) {
case ' ': case '\n': case '\r': case '\t':
case ':': case ',':
r->cur++;
goto top;
case '-': case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
res.type = SJ_NUMBER;
while (r->cur != r->end && sj__is_number_cont(*r->cur)) { r->cur++; }
break;
case '"':
res.type = SJ_STRING;
res.start = ++r->cur;
for (;;) {
if ( r->cur == r->end) { r->error = "unclosed string"; goto top; }
if (*r->cur == '"') { break; }
if (*r->cur == '\\') { r->cur++; }
if ( r->cur != r->end) { r->cur++; }
}
res.end = r->cur++;
return res;
case '{': case '[':
res.type = (*r->cur == '{') ? SJ_OBJECT : SJ_ARRAY;
res.depth = ++r->depth;
r->cur++;
break;
case '}': case ']':
res.type = SJ_END;
if (--r->depth < 0) {
r->error = (*r->cur == '}') ? "stray '}'" : "stray ']'";
goto top;
}
r->cur++;
break;
case 'n': case 't': case 'f':
res.type = (*r->cur == 'n') ? SJ_NULL : SJ_BOOL;
if (sj__is_string(r->cur, r->end, "null")) { r->cur += 4; break; }
if (sj__is_string(r->cur, r->end, "true")) { r->cur += 4; break; }
if (sj__is_string(r->cur, r->end, "false")) { r->cur += 5; break; }
// fallthrough
default:
r->error = "unknown token";
goto top;
}
res.end = r->cur;
return res;
}
static void sj__discard_until(sj_Reader *r, int depth) {
sj_Value val;
val.type = SJ_NULL;
while (r->depth != depth && val.type != SJ_ERROR) {
val = sj_read(r);
}
}
bool sj_iter_array(sj_Reader *r, sj_Value arr, sj_Value *val) {
sj__discard_until(r, arr.depth);
*val = sj_read(r);
if (val->type == SJ_ERROR || val->type == SJ_END) { return false; }
return true;
}
bool sj_iter_object(sj_Reader *r, sj_Value obj, sj_Value *key, sj_Value *val) {
sj__discard_until(r, obj.depth);
*key = sj_read(r);
if (key->type == SJ_ERROR || key->type == SJ_END) { return false; }
*val = sj_read(r);
if (val->type == SJ_END) { r->error = "unexpected object end"; return false; }
if (val->type == SJ_ERROR) { return false; }
return true;
}
void sj_location(sj_Reader *r, int *line, int *col) {
int ln = 1, cl = 1;
for (char *p = r->data; p != r->cur; p++) {
if (*p == '\n') { ln++; cl = 0; }
cl++;
}
*line = ln;
*col = cl;
}
#endif // #ifdef SJ_IMPL
|