MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add local storage module with file persistence

+442 -18
+94
examples/spec/localstorage.js
··· 1 + import { test, testThrows, summary } from './helpers.js'; 2 + 3 + console.log('localStorage Tests\n'); 4 + 5 + test('localStorage exists', typeof localStorage, 'object'); 6 + 7 + test('setFile method exists', typeof localStorage.setFile, 'function'); 8 + 9 + testThrows('setItem throws without file', () => localStorage.setItem('x', 'y')); 10 + testThrows('getItem throws without file', () => localStorage.getItem('x')); 11 + testThrows('removeItem throws without file', () => localStorage.removeItem('x')); 12 + testThrows('clear throws without file', () => localStorage.clear()); 13 + testThrows('key throws without file', () => localStorage.key(0)); 14 + 15 + localStorage.setFile('storage.json'); 16 + 17 + localStorage.setItem('key1', 'value1'); 18 + test('setItem/getItem basic', localStorage.getItem('key1'), 'value1'); 19 + 20 + localStorage.setItem('key1', 'newValue'); 21 + test('setItem overwrites', localStorage.getItem('key1'), 'newValue'); 22 + 23 + test('getItem non-existent', localStorage.getItem('nonExistent'), null); 24 + 25 + localStorage.clear(); 26 + test('length after clear', localStorage.length, 0); 27 + 28 + localStorage.setItem('a', '1'); 29 + test('length after 1 item', localStorage.length, 1); 30 + 31 + localStorage.setItem('b', '2'); 32 + test('length after 2 items', localStorage.length, 2); 33 + 34 + localStorage.setItem('c', '3'); 35 + test('length after 3 items', localStorage.length, 3); 36 + 37 + let keys = []; 38 + for (let i = 0; i < localStorage.length; i++) { 39 + keys.push(localStorage.key(i)); 40 + } 41 + test('key() returns keys', keys.length, 3); 42 + test('key() includes a', keys.includes('a'), true); 43 + test('key() includes b', keys.includes('b'), true); 44 + test('key() includes c', keys.includes('c'), true); 45 + 46 + test('key() out of bounds', localStorage.key(100), null); 47 + test('key() negative index', localStorage.key(-1), null); 48 + 49 + localStorage.removeItem('b'); 50 + test('removeItem decreases length', localStorage.length, 2); 51 + test('removeItem removes key', localStorage.getItem('b'), null); 52 + test('removeItem keeps others', localStorage.getItem('a'), '1'); 53 + 54 + localStorage.removeItem('nonExistent'); 55 + test('removeItem non-existent key', localStorage.length, 2); 56 + 57 + localStorage.clear(); 58 + test('clear removes all', localStorage.length, 0); 59 + test('clear removes a', localStorage.getItem('a'), null); 60 + test('clear removes c', localStorage.getItem('c'), null); 61 + 62 + localStorage.setItem('number', '42'); 63 + test('store number string', localStorage.getItem('number'), '42'); 64 + 65 + localStorage.setItem('bool', 'true'); 66 + test('store bool string', localStorage.getItem('bool'), 'true'); 67 + 68 + localStorage.setItem('empty', ''); 69 + test('store empty string', localStorage.getItem('empty'), ''); 70 + 71 + localStorage.setItem('key-with-dash', 'dash'); 72 + test('key with dash', localStorage.getItem('key-with-dash'), 'dash'); 73 + 74 + localStorage.setItem('key_with_underscore', 'underscore'); 75 + test('key with underscore', localStorage.getItem('key_with_underscore'), 'underscore'); 76 + 77 + localStorage.setItem('key.with.dot', 'dot'); 78 + test('key with dot', localStorage.getItem('key.with.dot'), 'dot'); 79 + 80 + localStorage.setItem('special', 'hello\nworld'); 81 + test('value with newline', localStorage.getItem('special'), 'hello\nworld'); 82 + 83 + localStorage.setItem('unicode', 'Hello'); 84 + test('value with unicode', localStorage.getItem('unicode'), 'Hello'); 85 + 86 + const obj = { name: 'John', age: 30 }; 87 + localStorage.setItem('user', JSON.stringify(obj)); 88 + const retrieved = JSON.parse(localStorage.getItem('user')); 89 + test('JSON storage name', retrieved.name, 'John'); 90 + test('JSON storage age', retrieved.age, 30); 91 + 92 + localStorage.clear(); 93 + 94 + summary();
+1
include/ant.h
··· 81 81 82 82 jsval_t js_get_proto(struct js *, jsval_t obj); 83 83 jsval_t js_get_ctor_proto(struct js *, const char *name, size_t len); 84 + jsval_t js_tostring_val(struct js *js, jsval_t value); 84 85 85 86 int js_type(jsval_t val); 86 87 int js_type_ex(struct js *js, jsval_t val);
-1
include/modules/atomics.h
··· 2 2 #define ATOMICS_H 3 3 4 4 #include <stdint.h> 5 - #include <stddef.h> 6 5 #include <pthread.h> 7 6 8 7 void init_atomics_module(void);
+6
include/modules/localstorage.h
··· 1 + #ifndef LOCALSTORAGE_H 2 + #define LOCALSTORAGE_H 3 + 4 + void init_localstorage_module(void); 5 + 6 + #endif
+8 -6
include/runtime.h
··· 2 2 #define RUNTIME_H 3 3 4 4 #include "ant.h" 5 + #include <argtable3.h> 5 6 6 7 #define ANT_RUNTIME_CRYPTO_INIT (1u << 0) 7 8 #define ANT_RUNTIME_EXT_EVENT_LOOP (1u << 1) 8 9 9 10 struct ant_runtime { 10 - struct js *js; 11 - char **argv; 12 - jsval_t ant_obj; 13 - int argc; 14 - unsigned int flags; 11 + struct js *js; // offset 0 12 + char **argv; // offset 8 13 + jsval_t ant_obj; // offset 16 14 + int argc; // offset 24 15 + unsigned int flags; // offset 28 16 + const char *ls_fp; // offset 32 15 17 }; 16 18 17 19 extern struct ant_runtime *const rt; 18 - struct ant_runtime *ant_runtime_init(struct js *js, int argc, char **argv); 20 + struct ant_runtime *ant_runtime_init(struct js *js, int argc, char **argv, struct arg_file *ls_p); 19 21 20 22 #endif
+1 -1
meson.build
··· 75 75 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 76 76 77 77 version_conf = configuration_data() 78 - version_conf.set('ANT_VERSION', '0.2.0.10') 78 + version_conf.set('ANT_VERSION', '0.2.0.11') 79 79 version_conf.set('ANT_GIT_HASH', git_hash) 80 80 version_conf.set('ANT_BUILD_DATE', build_date) 81 81
+3 -6
src/ant.c
··· 511 511 512 512 static bool parse_func_params(struct js *js, uint8_t *flags, int *out_count); 513 513 static double js_to_number(struct js *js, jsval_t arg); 514 + static jsval_t js_call_toString(struct js *js, jsval_t value); 514 515 515 516 static jsval_t js_expr(struct js *js); 516 517 static jsval_t js_eval_slice(struct js *js, jsoff_t off, jsoff_t len); ··· 559 560 static jsval_t call_js_internal(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope, jsval_t *bound_args, int bound_argc); 560 561 static jsval_t call_js_internal_nfe(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope, jsval_t *bound_args, int bound_argc, jsval_t func_name, jsval_t func_val); 561 562 562 - static jsval_t call_js_with_args(struct js *js, jsval_t func, jsval_t *args, int nargs); 563 + static jsval_t call_js_with_args(struct js *js, jsval_t fn, jsval_t *args, int nargs); 563 564 static jsval_t call_js_code_with_args(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope, jsval_t *args, int nargs); 564 565 static jsval_t call_js_code_with_args_nfe(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope, jsval_t *args, int nargs, jsval_t func_name, jsval_t func_val); 565 566 ··· 588 589 if (prim_off == 0) return val; 589 590 return resolveprop(js, mkval(T_PROP, prim_off)); 590 591 } 591 - 592 - static jsval_t call_js_with_args(struct js *js, jsval_t fn, jsval_t *args, int nargs); 593 592 594 593 static jsval_t to_string_val(struct js *js, jsval_t val) { 595 594 uint8_t t = vtype(val); ··· 2017 2016 } 2018 2017 } 2019 2018 2020 - static jsval_t js_call_toString(struct js *js, jsval_t value); 2021 - 2022 - static jsval_t js_tostring_val(struct js *js, jsval_t value) { 2019 + jsval_t js_tostring_val(struct js *js, jsval_t value) { 2023 2020 uint8_t t = vtype(value); 2024 2021 char buf[256]; 2025 2022 size_t len;
+5 -2
src/main.c
··· 37 37 #include "modules/symbol.h" 38 38 #include "modules/textcodec.h" 39 39 #include "modules/sessionstorage.h" 40 + #include "modules/localstorage.h" 40 41 41 42 int js_result = EXIT_SUCCESS; 42 43 ··· 140 141 struct arg_int *gct = arg_int0(NULL, "gct", "<threshold>", "set garbage collection threshold"); 141 142 struct arg_int *initial_mem = arg_int0(NULL, "initial-mem", "<size>", "initial memory size in MB (default: 4)"); 142 143 struct arg_int *max_mem = arg_int0(NULL, "max-mem", "<size>", "maximum memory size in MB (default: 512)"); 144 + struct arg_file *localstorage_file = arg_file0(NULL, "localstorage-file", "<path>", "file path for localStorage persistence"); 143 145 struct arg_file *file = arg_file0(NULL, NULL, "<module.js>", "JavaScript module file to execute"); 144 146 struct arg_end *end = arg_end(20); 145 147 146 - void *argtable[] = {help, version, debug, eval, print, gct, initial_mem, max_mem, file, end}; 148 + void *argtable[] = {help, version, debug, eval, print, gct, initial_mem, max_mem, localstorage_file, file, end}; 147 149 int nerrors = arg_parse(argc, argv, argtable); 148 150 149 151 if (help->count > 0) { ··· 188 190 } 189 191 190 192 if (gct->count > 0) js_setgct(js, gct->ival[0]); 191 - ant_runtime_init(js, argc, argv); 193 + ant_runtime_init(js, argc, argv, localstorage_file); 192 194 193 195 init_symbol_module(); 194 196 init_builtin_module(); ··· 208 210 init_reflect_module(); 209 211 init_textcodec_module(); 210 212 init_sessionstorage_module(); 213 + init_localstorage_module(); 211 214 212 215 ant_register_library(shell_library, "ant:shell", NULL); 213 216 ant_register_library(ffi_library, "ant:ffi", NULL);
+321
src/modules/localstorage.c
··· 1 + #include <stdlib.h> 2 + #include <string.h> 3 + #include <stdio.h> 4 + #include <uthash.h> 5 + #include <yyjson.h> 6 + 7 + #include "runtime.h" 8 + #include "modules/localstorage.h" 9 + #include "modules/symbol.h" 10 + 11 + typedef struct storage_entry { 12 + char *key; 13 + char *value; 14 + UT_hash_handle hh; 15 + } storage_entry_t; 16 + 17 + static storage_entry_t *local_storage = NULL; 18 + static char *storage_file_path = NULL; 19 + 20 + static void storage_save(void) { 21 + if (!storage_file_path) return; 22 + 23 + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); 24 + if (!doc) return; 25 + 26 + yyjson_mut_val *root = yyjson_mut_obj(doc); 27 + yyjson_mut_doc_set_root(doc, root); 28 + 29 + storage_entry_t *entry, *tmp; 30 + HASH_ITER(hh, local_storage, entry, tmp) { 31 + yyjson_mut_obj_add_str(doc, root, entry->key, entry->value); 32 + } 33 + 34 + yyjson_write_err err; 35 + yyjson_mut_write_file(storage_file_path, doc, YYJSON_WRITE_PRETTY, NULL, &err); 36 + yyjson_mut_doc_free(doc); 37 + } 38 + 39 + static void storage_load(void) { 40 + if (!storage_file_path) return; 41 + 42 + yyjson_read_err err; 43 + yyjson_doc *doc = yyjson_read_file(storage_file_path, 0, NULL, &err); 44 + if (!doc) return; 45 + 46 + yyjson_val *root = yyjson_doc_get_root(doc); 47 + if (!yyjson_is_obj(root)) { 48 + yyjson_doc_free(doc); 49 + return; 50 + } 51 + 52 + yyjson_obj_iter iter; 53 + yyjson_obj_iter_init(root, &iter); 54 + yyjson_val *key, *val; 55 + 56 + while ((key = yyjson_obj_iter_next(&iter))) { 57 + val = yyjson_obj_iter_get_val(key); 58 + if (yyjson_is_str(key) && yyjson_is_str(val)) { 59 + const char *k = yyjson_get_str(key); 60 + const char *v = yyjson_get_str(val); 61 + size_t klen = yyjson_get_len(key); 62 + size_t vlen = yyjson_get_len(val); 63 + 64 + storage_entry_t *entry = malloc(sizeof(storage_entry_t)); 65 + if (!entry) continue; 66 + 67 + entry->key = malloc(klen + 1); 68 + entry->value = malloc(vlen + 1); 69 + 70 + if (!entry->key || !entry->value) { 71 + free(entry->key); 72 + free(entry->value); 73 + free(entry); 74 + continue; 75 + } 76 + 77 + memcpy(entry->key, k, klen); 78 + entry->key[klen] = '\0'; 79 + memcpy(entry->value, v, vlen); 80 + entry->value[vlen] = '\0'; 81 + 82 + HASH_ADD_KEYPTR(hh, local_storage, entry->key, klen, entry); 83 + } 84 + } 85 + 86 + yyjson_doc_free(doc); 87 + } 88 + 89 + static void storage_clear(void) { 90 + storage_entry_t *entry, *tmp; 91 + HASH_ITER(hh, local_storage, entry, tmp) { 92 + HASH_DEL(local_storage, entry); 93 + free(entry->key); 94 + free(entry->value); 95 + free(entry); 96 + } 97 + } 98 + 99 + static void storage_set_item(const char *key, size_t key_len, const char *value, size_t value_len) { 100 + storage_entry_t *entry = NULL; 101 + 102 + HASH_FIND(hh, local_storage, key, key_len, entry); 103 + 104 + if (entry) { 105 + free(entry->value); 106 + entry->value = malloc(value_len + 1); 107 + if (entry->value) { 108 + memcpy(entry->value, value, value_len); 109 + entry->value[value_len] = '\0'; 110 + } 111 + } else { 112 + entry = malloc(sizeof(storage_entry_t)); 113 + if (!entry) return; 114 + 115 + entry->key = malloc(key_len + 1); 116 + entry->value = malloc(value_len + 1); 117 + 118 + if (!entry->key || !entry->value) { 119 + free(entry->key); 120 + free(entry->value); 121 + free(entry); 122 + return; 123 + } 124 + 125 + memcpy(entry->key, key, key_len); 126 + entry->key[key_len] = '\0'; 127 + memcpy(entry->value, value, value_len); 128 + entry->value[value_len] = '\0'; 129 + 130 + HASH_ADD_KEYPTR(hh, local_storage, entry->key, key_len, entry); 131 + } 132 + 133 + storage_save(); 134 + } 135 + 136 + static char *storage_get_item(const char *key, size_t key_len) { 137 + storage_entry_t *entry = NULL; 138 + HASH_FIND(hh, local_storage, key, key_len, entry); 139 + return entry ? entry->value : NULL; 140 + } 141 + 142 + static void storage_remove_item(const char *key, size_t key_len) { 143 + storage_entry_t *entry = NULL; 144 + HASH_FIND(hh, local_storage, key, key_len, entry); 145 + 146 + if (entry) { 147 + HASH_DEL(local_storage, entry); 148 + free(entry->key); 149 + free(entry->value); 150 + free(entry); 151 + storage_save(); 152 + } 153 + } 154 + 155 + static size_t storage_length(void) { 156 + return HASH_COUNT(local_storage); 157 + } 158 + 159 + static char *storage_key(size_t index) { 160 + storage_entry_t *entry; 161 + size_t i = 0; 162 + 163 + for (entry = local_storage; entry != NULL; entry = entry->hh.next) { 164 + if (i == index) return entry->key; 165 + i++; 166 + } 167 + 168 + return NULL; 169 + } 170 + 171 + #define CHECK_FILE_SET(js) \ 172 + if (!storage_file_path) { \ 173 + return js_mkerr(js, "Warning: `--localstorage-file` or `localStorage.setFile` were not provided with valid paths."); \ 174 + } 175 + 176 + // localStorage.setItem(key, value) 177 + static jsval_t js_localstorage_setItem(struct js *js, jsval_t *args, int nargs) { 178 + CHECK_FILE_SET(js); 179 + 180 + if (nargs < 2) { 181 + return js_mkerr(js, "Failed to execute 'setItem' on 'Storage': 2 arguments required"); 182 + } 183 + 184 + size_t key_len, value_len; 185 + char *key = js_getstr(js, args[0], &key_len); 186 + char *value = js_getstr(js, js_tostring_val(js, args[1]), &value_len); 187 + 188 + storage_set_item(key, key_len, value, value_len); 189 + 190 + return js_mkundef(); 191 + } 192 + 193 + // localStorage.getItem(key) 194 + static jsval_t js_localstorage_getItem(struct js *js, jsval_t *args, int nargs) { 195 + CHECK_FILE_SET(js); 196 + 197 + if (nargs < 1) { 198 + return js_mkerr(js, "Failed to execute 'getItem' on 'Storage': 1 argument required"); 199 + } 200 + 201 + size_t key_len; 202 + char *key = js_getstr(js, args[0], &key_len); 203 + char *value = storage_get_item(key, key_len); 204 + 205 + if (!value) return js_mknull(); 206 + 207 + return js_mkstr(js, value, strlen(value)); 208 + } 209 + 210 + // localStorage.removeItem(key) 211 + static jsval_t js_localstorage_removeItem(struct js *js, jsval_t *args, int nargs) { 212 + CHECK_FILE_SET(js); 213 + 214 + if (nargs < 1) { 215 + return js_mkerr(js, "Failed to execute 'removeItem' on 'Storage': 1 argument required"); 216 + } 217 + 218 + size_t key_len; 219 + char *key = js_getstr(js, args[0], &key_len); 220 + storage_remove_item(key, key_len); 221 + 222 + return js_mkundef(); 223 + } 224 + 225 + // localStorage.clear() 226 + static jsval_t js_localstorage_clear(struct js *js, jsval_t *args, int nargs) { 227 + CHECK_FILE_SET(js); 228 + (void)args; (void)nargs; 229 + storage_clear(); 230 + storage_save(); 231 + return js_mkundef(); 232 + } 233 + 234 + // localStorage.key(index) 235 + static jsval_t js_localstorage_key(struct js *js, jsval_t *args, int nargs) { 236 + CHECK_FILE_SET(js); 237 + 238 + if (nargs < 1) { 239 + return js_mkerr(js, "Failed to execute 'key' on 'Storage': 1 argument required"); 240 + } 241 + 242 + if (js_type(args[0]) != JS_NUM) { 243 + return js_mknull(); 244 + } 245 + 246 + double idx = js_getnum(args[0]); 247 + if (idx < 0) return js_mknull(); 248 + 249 + size_t index = (size_t)idx; 250 + char *key = storage_key(index); 251 + 252 + if (!key) return js_mknull(); 253 + 254 + return js_mkstr(js, key, strlen(key)); 255 + } 256 + 257 + // localStorage.length 258 + static jsval_t js_localstorage_length(struct js *js, jsval_t *args, int nargs) { 259 + (void)args; (void)nargs; 260 + CHECK_FILE_SET(js) 261 + return js_mknum((double)storage_length()); 262 + } 263 + 264 + // localStorage.setFile(path) 265 + static jsval_t js_localstorage_setFile(struct js *js, jsval_t *args, int nargs) { 266 + if (nargs < 1) { 267 + return js_mkerr(js, "Failed to execute 'setFile' on 'Storage': 1 argument required"); 268 + } 269 + 270 + size_t path_len; 271 + char *path = js_getstr(js, args[0], &path_len); 272 + 273 + if (storage_file_path) { 274 + free(storage_file_path); 275 + } 276 + 277 + storage_file_path = malloc(path_len + 1); 278 + if (!storage_file_path) { 279 + return js_mkerr(js, "Failed to allocate memory for file path"); 280 + } 281 + 282 + memcpy(storage_file_path, path, path_len); 283 + storage_file_path[path_len] = '\0'; 284 + 285 + // Load existing data from the new file 286 + storage_load(); 287 + 288 + return js_mkundef(); 289 + } 290 + 291 + void init_localstorage_module() { 292 + struct js *js = rt->js; 293 + 294 + jsval_t glob = js_glob(js); 295 + const char *file_path = rt->ls_fp; 296 + 297 + if (file_path) { 298 + storage_file_path = strdup(file_path); 299 + storage_load(); 300 + } 301 + 302 + jsval_t storage_obj = js_mkobj(js); 303 + 304 + js_set(js, storage_obj, "setItem", js_mkfun(js_localstorage_setItem)); 305 + js_set(js, storage_obj, "getItem", js_mkfun(js_localstorage_getItem)); 306 + js_set(js, storage_obj, "removeItem", js_mkfun(js_localstorage_removeItem)); 307 + js_set(js, storage_obj, "clear", js_mkfun(js_localstorage_clear)); 308 + js_set(js, storage_obj, "key", js_mkfun(js_localstorage_key)); 309 + js_set(js, storage_obj, "setFile", js_mkfun(js_localstorage_setFile)); 310 + 311 + jsval_t length_getter = js_mkfun(js_localstorage_length); 312 + jsval_t desc = js_mkobj(js); 313 + js_set(js, desc, "get", length_getter); 314 + js_set(js, desc, "enumerable", js_mktrue()); 315 + js_set(js, desc, "configurable", js_mkfalse()); 316 + js_set(js, storage_obj, "__desc_length", desc); 317 + js_set(js, storage_obj, "__getter", js_mktrue()); 318 + 319 + js_set(js, storage_obj, get_toStringTag_sym_key(), js_mkstr(js, "Storage", 7)); 320 + js_set(js, glob, "localStorage", storage_obj); 321 + }
+1 -1
src/modules/sessionstorage.c
··· 101 101 102 102 size_t key_len, value_len; 103 103 char *key = js_getstr(js, args[0], &key_len); 104 - char *value = js_getstr(js, args[1], &value_len); 104 + char *value = js_getstr(js, js_tostring_val(js, args[1]), &value_len); 105 105 106 106 storage_set_item(key, key_len, value, value_len); 107 107
+2 -1
src/runtime.c
··· 6 6 7 7 struct ant_runtime *const rt = &runtime; 8 8 9 - struct ant_runtime *ant_runtime_init(struct js *js, int argc, char **argv) { 9 + struct ant_runtime *ant_runtime_init(struct js *js, int argc, char **argv, struct arg_file *ls_p) { 10 10 runtime.js = js; 11 11 runtime.ant_obj = js_mkobj(js); 12 12 runtime.flags = 0; 13 13 14 14 runtime.argc = argc; 15 15 runtime.argv = argv; 16 + runtime.ls_fp = ls_p->count > 0 ? ls_p->filename[0] : NULL; 16 17 17 18 js_set(js, js_glob(js), "Ant", runtime.ant_obj); 18 19 js_set(js, js_glob(js), "global", js_glob(js));