summaryrefslogtreecommitdiff
path: root/encoding/json/value.ha
blob: fe686885c484adf3c5ceb999945cd341fb3dd63c (plain)
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// License: MPL-2.0
// (c) 2022 Drew DeVault <sir@cmpwn.com>
use hash::fnv;
use htab;
use strings;

export type object = struct {
	table: htab::table,
};

// A JSON value.
export type value = (f64 | str | bool | _null | []value | object);

type entry = (str, value);

fn htab_eq(ctx: *opaque, ent: *opaque) bool =
	*(ctx: *str) == (ent: *entry).0;

fn _get(obj: *object, hash: u64, key: str) nullable *entry =
	htab::get(&obj.table, hash, &htab_eq, &key, size(entry)):
		nullable *entry;

// Initializes a new (empty) JSON object. Call [[finish]] to free associated
// resources when you're done using it.
export fn newobject() object = {
	return object {
		table = htab::new(0, size(entry)),
	};
};

// Gets a value from a JSON object. The return value is borrowed from the
// object.
export fn get(obj: *object, key: str) (*value | void) = {
	const hash = fnv::string(key);
	match (_get(obj, hash, key)) {
	case let ent: *entry =>
		return &ent.1;
	case null =>
		return void;
	};
};

// Sets a value in a JSON object. The key and value will be duplicated.
export fn set(obj: *object, key: const str, val: const value) void = {
	put(obj, key, dup(val));
};

// Sets a value in a JSON object. The key will be duplicated. The object will
// assume ownership over the value, without duplicating it.
export fn put(obj: *object, key: const str, val: const value) void = {
	const hash = fnv::string(key);
	match (_get(obj, hash, key)) {
	case let ent: *entry =>
		finish(ent.1);
		ent.1 = val;
	case null =>
		const ent = htab::add(&obj.table, hash, size(entry)): *entry;
		*ent = (strings::dup(key), val);
	};
};

// Deletes values from a JSON object, if they are present.
export fn del(obj: *object, keys: const str...) void = {
	for (let i = 0z; i < len(keys); i += 1) {
		match (take(obj, keys[i])) {
		case let val: value =>
			finish(val);
		case void => void;
		};
	};
};

// Deletes a key from a JSON object, returning its previous value, if any.
// The caller is responsible for freeing the value.
export fn take(obj: *object, key: const str) (value | void) = {
	const hash = fnv::string(key);
	match (_get(obj, hash, key)) {
	case let ent: *entry =>
		free(ent.0);
		const val = ent.1;
		htab::del(&obj.table, ent, size(entry));
		return val;
	case null => void;
	};
};

// Clears all values from a JSON object, leaving it empty.
export fn reset(obj: *object) void = {
	let it = iter(obj);
	for (true) match (next(&it)) {
	case void =>
		break;
	case let v: (const str, const *value) =>
		del(obj, v.0);
	};
};

// Returns the number of key/value pairs in a JSON object.
export fn count(obj: *object) size = {
	return htab::count(&obj.table);
};

export type iterator = struct {
	iter: htab::iterator,
};

// Creates an iterator that enumerates over the key/value pairs in an
// [[object]].
export fn iter(obj: *object) iterator = {
	return iterator { iter = htab::iter(&obj.table) };
};

// Returns the next key/value pair from this iterator, or void if none remain.
export fn next(iter: *iterator) ((const str, const *value) | void) = {
	match (htab::next(&iter.iter, size(entry))) {
	case let ent: *opaque =>
		const ent = ent: *entry;
		return (ent.0, &ent.1);
	case null => void;
	};
};

// Duplicates a JSON value. The caller must pass the return value to [[finish]]
// to free associated resources when they're done using it.
export fn dup(val: value) value = {
	match (val) {
	case let s: str =>
		return strings::dup(s);
	case let v: []value =>
		let new: []value = alloc([], len(v));
		for (let i = 0z; i < len(v); i += 1) {
			append(new, dup(v[i]));
		};
		return new;
	case let o: object =>
		let new = newobject();
		const i = iter(&o);
		for (true) {
			const pair = match (next(&i)) {
			case void =>
				break;
			case let pair: (const str, const *value) =>
				yield pair;
			};
			set(&new, pair.0, *pair.1);
		};
		return new;
	case =>
		return val;
	};
};

// Checks two JSON values for equality.
export fn equal(a: value, b: value) bool = {
	match (a) {
	case _null =>
		return b is _null;
	case let a: bool =>
		return b is bool && a == b as bool;
	case let a: f64 =>
		return b is f64 && a == b as f64;
	case let a: str =>
		return b is str && a == b as str;
	case let a: []value =>
		if (!(b is []value)) return false;
		const b = b as []value;
		if (len(a) != len(b)) return false;
		for (let i = 0z; i < len(a); i += 1) {
			if (!equal(a[i], b[i])) {
				return false;
			};
		};
		return true;
	case let a: object =>
		if (!(b is object)) return false;
		let b = b as object;
		if (count(&a) != count(&b)) {
			return false;
		};
		let a = iter(&a);
		for (true) match (next(&a)) {
		case let a: (const str, const *value) =>
			match (get(&b, a.0)) {
			case let b: *value =>
				if (!equal(*a.1, *b)) {
					return false;
				};
			case void => return false;
			};
		case void => break;
		};
		return true;
	};
};

// Frees state associated with a JSON value.
export fn finish(val: value) void = {
	match (val) {
	case let s: str =>
		free(s);
	case let v: []value =>
		for (let i = 0z; i < len(v); i += 1) {
			finish(v[i]);
		};
		free(v);
	case let o: object =>
		let i = iter(&o);
		for (true) match (next(&i)) {
		case let ent: (const str, const *value) =>
			free(ent.0);
			finish(*ent.1);
		case void => break;
		};
		htab::finish(&o.table);
	case => void;
	};
};