MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <stdio.h>
4#include <uthash.h>
5#include <yyjson.h>
6
7#include "ant.h"
8#include "errors.h"
9#include "runtime.h"
10#include "internal.h"
11#include "descriptors.h"
12
13#include "modules/symbol.h"
14#include "modules/localstorage.h"
15
16typedef struct storage_entry {
17 char *key;
18 char *value;
19 UT_hash_handle hh;
20} storage_entry_t;
21
22static storage_entry_t *local_storage = NULL;
23static char *storage_file_path = NULL;
24
25static void storage_save(void) {
26 if (!storage_file_path) return;
27
28 yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
29 if (!doc) return;
30
31 yyjson_mut_val *root = yyjson_mut_obj(doc);
32 yyjson_mut_doc_set_root(doc, root);
33
34 storage_entry_t *entry, *tmp;
35 HASH_ITER(hh, local_storage, entry, tmp) {
36 yyjson_mut_obj_add_str(doc, root, entry->key, entry->value);
37 }
38
39 yyjson_write_err err;
40 yyjson_mut_write_file(storage_file_path, doc, YYJSON_WRITE_PRETTY, NULL, &err);
41 yyjson_mut_doc_free(doc);
42}
43
44static void storage_load(void) {
45 if (!storage_file_path) return;
46
47 yyjson_read_err err;
48 yyjson_doc *doc = yyjson_read_file(storage_file_path, 0, NULL, &err);
49 if (!doc) return;
50
51 yyjson_val *root = yyjson_doc_get_root(doc);
52 if (!yyjson_is_obj(root)) {
53 yyjson_doc_free(doc);
54 return;
55 }
56
57 yyjson_obj_iter iter;
58 yyjson_obj_iter_init(root, &iter);
59 yyjson_val *key, *val;
60
61 while ((key = yyjson_obj_iter_next(&iter))) {
62 val = yyjson_obj_iter_get_val(key);
63 if (yyjson_is_str(key) && yyjson_is_str(val)) {
64 const char *k = yyjson_get_str(key);
65 const char *v = yyjson_get_str(val);
66 size_t klen = yyjson_get_len(key);
67 size_t vlen = yyjson_get_len(val);
68
69 storage_entry_t *entry = ant_calloc(sizeof(storage_entry_t) + klen + 1 + vlen + 1);
70 if (!entry) continue;
71
72 entry->key = (char *)(entry + 1);
73 entry->value = entry->key + klen + 1;
74
75 memcpy(entry->key, k, klen);
76 entry->key[klen] = '\0';
77 memcpy(entry->value, v, vlen);
78 entry->value[vlen] = '\0';
79
80 HASH_ADD_KEYPTR(hh, local_storage, entry->key, klen, entry);
81 }
82 }
83
84 yyjson_doc_free(doc);
85}
86
87static void storage_clear(void) {
88 storage_entry_t *entry, *tmp;
89 HASH_ITER(hh, local_storage, entry, tmp) {
90 HASH_DEL(local_storage, entry);
91 free(entry);
92 }
93}
94
95static void storage_set_item(const char *key, size_t key_len, const char *value, size_t value_len) {
96 storage_entry_t *entry = NULL;
97
98 HASH_FIND(hh, local_storage, key, key_len, entry);
99
100 if (entry) {
101 HASH_DEL(local_storage, entry);
102 free(entry);
103 }
104
105 entry = ant_calloc(sizeof(storage_entry_t) + key_len + 1 + value_len + 1);
106 if (!entry) return;
107
108 entry->key = (char *)(entry + 1);
109 entry->value = entry->key + key_len + 1;
110
111 memcpy(entry->key, key, key_len);
112 entry->key[key_len] = '\0';
113 memcpy(entry->value, value, value_len);
114 entry->value[value_len] = '\0';
115
116 HASH_ADD_KEYPTR(hh, local_storage, entry->key, key_len, entry);
117
118 storage_save();
119}
120
121static char *storage_get_item(const char *key, size_t key_len) {
122 storage_entry_t *entry = NULL;
123 HASH_FIND(hh, local_storage, key, key_len, entry);
124 return entry ? entry->value : NULL;
125}
126
127static void storage_remove_item(const char *key, size_t key_len) {
128 storage_entry_t *entry = NULL;
129 HASH_FIND(hh, local_storage, key, key_len, entry);
130
131 if (entry) {
132 HASH_DEL(local_storage, entry);
133 free(entry);
134 storage_save();
135 }
136}
137
138static size_t storage_length(void) {
139 return HASH_COUNT(local_storage);
140}
141
142static char *storage_key(size_t index) {
143 storage_entry_t *entry;
144 size_t i = 0;
145
146 for (entry = local_storage; entry != NULL; entry = entry->hh.next) {
147 if (i == index) return entry->key;
148 i++;
149 }
150
151 return NULL;
152}
153
154#define CHECK_FILE_SET(js) \
155 if (!storage_file_path) { \
156 return js_mkerr(js, "Warning: `--localstorage-file` or `localStorage.setFile` were not provided with valid paths."); \
157 }
158
159// localStorage.setItem(key, value)
160static ant_value_t js_localstorage_setItem(ant_t *js, ant_value_t *args, int nargs) {
161 CHECK_FILE_SET(js);
162
163 if (nargs < 2) {
164 return js_mkerr(js, "Failed to execute 'setItem' on 'Storage': 2 arguments required");
165 }
166
167 size_t key_len, value_len;
168 char *key = js_getstr(js, args[0], &key_len);
169 char *value = js_getstr(js, js_tostring_val(js, args[1]), &value_len);
170
171 storage_set_item(key, key_len, value, value_len);
172
173 return js_mkundef();
174}
175
176// localStorage.getItem(key)
177static ant_value_t js_localstorage_getItem(ant_t *js, ant_value_t *args, int nargs) {
178 CHECK_FILE_SET(js);
179
180 if (nargs < 1) {
181 return js_mkerr(js, "Failed to execute 'getItem' on 'Storage': 1 argument required");
182 }
183
184 size_t key_len;
185 char *key = js_getstr(js, args[0], &key_len);
186 char *value = storage_get_item(key, key_len);
187
188 if (!value) return js_mknull();
189
190 return js_mkstr(js, value, strlen(value));
191}
192
193// localStorage.removeItem(key)
194static ant_value_t js_localstorage_removeItem(ant_t *js, ant_value_t *args, int nargs) {
195 CHECK_FILE_SET(js);
196
197 if (nargs < 1) {
198 return js_mkerr(js, "Failed to execute 'removeItem' on 'Storage': 1 argument required");
199 }
200
201 size_t key_len;
202 char *key = js_getstr(js, args[0], &key_len);
203 storage_remove_item(key, key_len);
204
205 return js_mkundef();
206}
207
208// localStorage.clear()
209static ant_value_t js_localstorage_clear(ant_t *js, ant_value_t *args, int nargs) {
210 CHECK_FILE_SET(js);
211 (void)args; (void)nargs;
212 storage_clear();
213 storage_save();
214 return js_mkundef();
215}
216
217// localStorage.key(index)
218static ant_value_t js_localstorage_key(ant_t *js, ant_value_t *args, int nargs) {
219 CHECK_FILE_SET(js);
220
221 if (nargs < 1) {
222 return js_mkerr(js, "Failed to execute 'key' on 'Storage': 1 argument required");
223 }
224
225 if (vtype(args[0]) != T_NUM) {
226 return js_mknull();
227 }
228
229 double idx = js_getnum(args[0]);
230 if (idx < 0) return js_mknull();
231
232 size_t index = (size_t)idx;
233 char *key = storage_key(index);
234
235 if (!key) return js_mknull();
236
237 return js_mkstr(js, key, strlen(key));
238}
239
240// localStorage.length
241static ant_value_t js_localstorage_length(ant_t *js, ant_value_t *args, int nargs) {
242 (void)args; (void)nargs;
243 CHECK_FILE_SET(js)
244 return js_mknum((double)storage_length());
245}
246
247// localStorage.setFile(path)
248static ant_value_t js_localstorage_setFile(ant_t *js, ant_value_t *args, int nargs) {
249 if (nargs < 1) {
250 return js_mkerr(js, "Failed to execute 'setFile' on 'Storage': 1 argument required");
251 }
252
253 size_t path_len;
254 char *path = js_getstr(js, args[0], &path_len);
255
256 if (storage_file_path) {
257 free(storage_file_path);
258 }
259
260 storage_file_path = malloc(path_len + 1);
261 if (!storage_file_path) {
262 return js_mkerr(js, "Failed to allocate memory for file path");
263 }
264
265 memcpy(storage_file_path, path, path_len);
266 storage_file_path[path_len] = '\0';
267
268 // Load existing data from the new file
269 storage_load();
270
271 return js_mkundef();
272}
273
274void init_localstorage_module() {
275 ant_t *js = rt->js;
276
277 ant_value_t glob = js_glob(js);
278 const char *file_path = rt->ls_fp;
279
280 if (file_path) {
281 storage_file_path = strdup(file_path);
282 storage_load();
283 }
284
285 ant_value_t storage_obj = js_mkobj(js);
286
287 js_set(js, storage_obj, "setItem", js_mkfun(js_localstorage_setItem));
288 js_set(js, storage_obj, "getItem", js_mkfun(js_localstorage_getItem));
289 js_set(js, storage_obj, "removeItem", js_mkfun(js_localstorage_removeItem));
290 js_set(js, storage_obj, "clear", js_mkfun(js_localstorage_clear));
291 js_set(js, storage_obj, "key", js_mkfun(js_localstorage_key));
292 js_set(js, storage_obj, "setFile", js_mkfun(js_localstorage_setFile));
293
294 ant_value_t length_getter = js_mkfun(js_localstorage_length);
295 js_set_getter_desc(js, storage_obj, "length", 6, length_getter, JS_DESC_E);
296
297 js_set_sym(js, storage_obj, get_toStringTag_sym(), js_mkstr(js, "Storage", 7));
298 js_set(js, glob, "localStorage", storage_obj);
299}