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.

standard library support

+381 -26
+4 -1
include/ant.h
··· 75 75 void js_reject_promise(struct js *js, jsval_t promise, jsval_t value); 76 76 77 77 void js_run_event_loop(struct js *js); 78 - void js_setup_import_meta(struct js *js, const char *filename); 78 + void js_setup_import_meta(struct js *js, const char *filename); 79 + 80 + typedef jsval_t (*ant_library_init_fn)(struct js *js); 81 + void ant_register_library(const char *name, ant_library_init_fn init_fn);
+8
include/modules/shell.h
··· 1 + #ifndef ANT_SHELL_MODULE_H 2 + #define ANT_SHELL_MODULE_H 3 + 4 + #include "ant.h" 5 + 6 + jsval_t shell_library(struct js *js); 7 + 8 + #endif
+1 -1
meson.build
··· 44 44 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 45 45 46 46 version_conf = configuration_data() 47 - version_conf.set('ANT_VERSION', '0.0.6.15') 47 + version_conf.set('ANT_VERSION', '0.0.6.16') 48 48 version_conf.set('ANT_GIT_HASH', git_hash) 49 49 version_conf.set('ANT_BUILD_DATE', build_date) 50 50
+63 -21
src/ant.c
··· 108 108 int count; 109 109 } esm_module_cache_t; 110 110 111 + typedef struct ant_library { 112 + char *name; 113 + ant_library_init_fn init_fn; 114 + struct ant_library *next; 115 + } ant_library_t; 116 + 117 + static ant_library_t *library_registry = NULL; 111 118 static esm_module_cache_t global_module_cache = {NULL, 0}; 119 + 120 + void ant_register_library(const char *name, ant_library_init_fn init_fn) { 121 + ant_library_t *lib = (ant_library_t *)malloc(sizeof(ant_library_t)); 122 + if (!lib) return; 123 + 124 + lib->name = strdup(name); 125 + lib->init_fn = init_fn; 126 + lib->next = library_registry; 127 + library_registry = lib; 128 + } 112 129 113 130 struct js { 114 131 jsoff_t css; // max observed C stack size ··· 6967 6984 jsoff_t spec_off = vstr(js, args[0], &spec_len); 6968 6985 const char *specifier = (char *)&js->mem[spec_off]; 6969 6986 6987 + ant_library_t *lib = library_registry; 6988 + while (lib) { 6989 + if (strlen(lib->name) == spec_len && memcmp(lib->name, specifier, spec_len) == 0) { 6990 + jsval_t lib_obj = lib->init_fn(js); 6991 + if (is_err(lib_obj)) return builtin_Promise_reject(js, &lib_obj, 1); 6992 + jsval_t promise_args[] = { lib_obj }; 6993 + return builtin_Promise_resolve(js, promise_args, 1); 6994 + } 6995 + lib = lib->next; 6996 + } 6997 + 6970 6998 const char *base_path = js->filename ? js->filename : "."; 6971 6999 char *resolved_path = esm_resolve_path(specifier, base_path); 6972 7000 if (!resolved_path) { ··· 7231 7259 EXPECT(TOK_STRING, ); 7232 7260 7233 7261 jsval_t spec = js_str_literal(js); 7234 - 7235 7262 jsoff_t spec_len; 7263 + 7236 7264 jsoff_t spec_off = vstr(js, spec, &spec_len); 7237 7265 const char *specifier = (char *)&js->mem[spec_off]; 7238 7266 7239 - const char *base_path = js->filename ? js->filename : "."; 7240 - char *resolved_path = esm_resolve_path(specifier, base_path); 7241 - if (!resolved_path) { 7242 - return js_mkerr(js, "Cannot resolve module: %.*s", (int)spec_len, specifier); 7267 + jsval_t ns = js_mkundef(); 7268 + bool is_library = false; 7269 + 7270 + ant_library_t *lib = library_registry; 7271 + while (lib) { 7272 + if (strlen(lib->name) == spec_len && memcmp(lib->name, specifier, spec_len) == 0) { 7273 + ns = lib->init_fn(js); 7274 + is_library = true; 7275 + break; 7276 + } 7277 + lib = lib->next; 7243 7278 } 7244 7279 7245 - esm_module_t *mod = esm_find_module(resolved_path); 7246 - if (!mod) { 7247 - mod = esm_create_module(specifier, resolved_path); 7280 + if (!is_library) { 7281 + const char *base_path = js->filename ? js->filename : "."; 7282 + char *resolved_path = esm_resolve_path(specifier, base_path); 7283 + if (!resolved_path) return js_mkerr(js, "Cannot resolve module: %.*s", (int)spec_len, specifier); 7284 + 7285 + esm_module_t *mod = esm_find_module(resolved_path); 7248 7286 if (!mod) { 7249 - free(resolved_path); 7250 - return js_mkerr(js, "Cannot create module"); 7287 + mod = esm_create_module(specifier, resolved_path); 7288 + if (!mod) { 7289 + free(resolved_path); 7290 + return js_mkerr(js, "Cannot create module"); 7291 + } 7251 7292 } 7293 + 7294 + const char *saved_code = js->code; 7295 + jsoff_t saved_clen = js->clen; 7296 + jsoff_t saved_pos = js->pos; 7297 + 7298 + ns = esm_load_module(js, mod); 7299 + 7300 + js->code = saved_code; 7301 + js->clen = saved_clen; 7302 + js->pos = saved_pos; 7303 + 7304 + free(resolved_path); 7252 7305 } 7253 7306 7254 - const char *saved_code = js->code; 7255 - jsoff_t saved_clen = js->clen; 7256 - jsoff_t saved_pos = js->pos; 7257 - 7258 - jsval_t ns = esm_load_module(js, mod); 7259 - 7260 - js->code = saved_code; 7261 - js->clen = saved_clen; 7262 - js->pos = saved_pos; 7263 7307 js->consumed = 1; 7264 7308 next(js); 7265 7309 js->consumed = 0; 7266 - 7267 - free(resolved_path); 7268 7310 7269 7311 if (is_err(ns)) return ns; 7270 7312
+5 -1
src/main.c
··· 18 18 #include "modules/timer.h" 19 19 #include "modules/json.h" 20 20 #include "modules/fetch.h" 21 + #include "modules/shell.h" 21 22 22 23 int js_result = EXIT_SUCCESS; 23 24 ··· 120 121 } 121 122 122 123 if (gct->count > 0) js_setgct(js, gct->ival[0]); 124 + 123 125 ant_runtime_init(js); 124 126 125 127 init_builtin_module(); ··· 129 131 init_json_module(); 130 132 init_server_module(); 131 133 init_timer_module(); 132 - 134 + 135 + ant_register_library("ant/shell", shell_library); 136 + 133 137 if (repl_mode) ant_repl_run(); else { 134 138 js_result = execute_module(js, module_file); 135 139 js_run_event_loop(js);
+2 -2
src/modules/fetch.c
··· 194 194 return promise; 195 195 } 196 196 197 - void init_fetch_module(void) { 197 + void init_fetch_module() { 198 198 static int curl_initialized = 0; 199 199 if (!curl_initialized) { 200 200 curl_global_init(CURL_GLOBAL_DEFAULT); ··· 212 212 js_set(js, js_glob(js), "fetch", js_mkfun(js_fetch)); 213 213 } 214 214 215 - static void cleanup_fetch_module(void) { 215 + static void cleanup_fetch_module() { 216 216 if (global_curl) { 217 217 curl_easy_cleanup(global_curl); 218 218 global_curl = NULL;
+221
src/modules/shell.c
··· 1 + #include <stdio.h> 2 + #include <stdlib.h> 3 + #include <string.h> 4 + #include <unistd.h> 5 + #include <sys/wait.h> 6 + 7 + #include "ant.h" 8 + 9 + static jsval_t builtin_shell_text(struct js *js, jsval_t *args, int nargs); 10 + static jsval_t builtin_shell_lines(struct js *js, jsval_t *args, int nargs); 11 + 12 + static jsval_t shell_exec(struct js *js, const char *cmd, size_t cmd_len) { 13 + jsval_t result = js_mkobj(js); 14 + 15 + FILE *fp = popen(cmd, "r"); 16 + if (!fp) { 17 + js_set(js, result, "stdout", js_mkstr(js, "", 0)); 18 + js_set(js, result, "stderr", js_mkstr(js, "Failed to execute command", 25)); 19 + js_set(js, result, "exitCode", js_mknum(1)); 20 + return result; 21 + } 22 + 23 + char *output = NULL; 24 + size_t output_size = 0; 25 + size_t output_capacity = 4096; 26 + output = malloc(output_capacity); 27 + 28 + if (!output) { 29 + pclose(fp); 30 + return js_mkerr(js, "Out of memory"); 31 + } 32 + 33 + char buffer[4096]; 34 + while (fgets(buffer, sizeof(buffer), fp) != NULL) { 35 + size_t len = strlen(buffer); 36 + if (output_size + len >= output_capacity) { 37 + output_capacity *= 2; 38 + char *new_output = realloc(output, output_capacity); 39 + if (!new_output) { 40 + free(output); 41 + pclose(fp); 42 + return js_mkerr(js, "Out of memory"); 43 + } 44 + output = new_output; 45 + } 46 + memcpy(output + output_size, buffer, len); 47 + output_size += len; 48 + } 49 + 50 + int status = pclose(fp); 51 + int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1; 52 + if (output_size > 0 && output[output_size - 1] == '\n') output_size--; 53 + 54 + jsval_t stdout_val = js_mkstr(js, output, output_size); 55 + free(output); 56 + 57 + js_set(js, result, "stdout", stdout_val); 58 + js_set(js, result, "stderr", js_mkstr(js, "", 0)); 59 + js_set(js, result, "exitCode", js_mknum(exit_code)); 60 + 61 + jsval_t text_fn = js_mkfun(builtin_shell_text); 62 + js_set(js, result, "text", text_fn); 63 + 64 + jsval_t lines_fn = js_mkfun(builtin_shell_lines); 65 + js_set(js, result, "lines", lines_fn); 66 + 67 + return result; 68 + } 69 + 70 + static jsval_t builtin_shell_text(struct js *js, jsval_t *args, int nargs) { 71 + (void)args; 72 + (void)nargs; 73 + 74 + jsval_t this_val = js_getthis(js); 75 + if (js_type(this_val) != JS_PRIV) { 76 + return js_mkerr(js, "text() must be called on a shell result"); 77 + } 78 + 79 + jsval_t stdout_val = js_get(js, this_val, "stdout"); 80 + return stdout_val; 81 + } 82 + 83 + static jsval_t builtin_shell_lines(struct js *js, jsval_t *args, int nargs) { 84 + (void)args; 85 + (void)nargs; 86 + 87 + jsval_t this_val = js_getthis(js); 88 + if (js_type(this_val) != JS_PRIV) return js_mkerr(js, "lines() must be called on a shell result"); 89 + 90 + jsval_t stdout_val = js_get(js, this_val, "stdout"); 91 + if (js_type(stdout_val) != JS_STR) return js_mkerr(js, "No stdout available"); 92 + 93 + size_t text_len; 94 + char *text = js_getstr(js, stdout_val, &text_len); 95 + if (!text) return js_mkerr(js, "Failed to get stdout"); 96 + 97 + jsval_t lines_array = js_mkobj(js); 98 + 99 + size_t line_count = 0; 100 + size_t line_start = 0; 101 + 102 + for (size_t i = 0; i <= text_len; i++) { 103 + if (i == text_len || text[i] == '\n') { 104 + size_t line_len = i - line_start; 105 + if (i < text_len && text[i] == '\n') {} 106 + 107 + char idx_str[32]; 108 + snprintf(idx_str, sizeof(idx_str), "%zu", line_count); 109 + 110 + jsval_t line_val = js_mkstr(js, text + line_start, line_len); 111 + js_set(js, lines_array, idx_str, line_val); 112 + 113 + line_count++; 114 + line_start = i + 1; 115 + } 116 + } 117 + 118 + js_set(js, lines_array, "length", js_mknum((double)line_count)); 119 + 120 + return lines_array; 121 + } 122 + 123 + static jsval_t builtin_shell_dollar(struct js *js, jsval_t *args, int nargs) { 124 + if (nargs < 1) return js_mkerr(js, "$() requires at least one argument"); 125 + 126 + if (js_type(args[0]) != JS_PRIV) { 127 + if (js_type(args[0]) == JS_STR) { 128 + size_t cmd_len; 129 + char *cmd = js_getstr(js, args[0], &cmd_len); 130 + if (!cmd) return js_mkerr(js, "Failed to get command string"); 131 + 132 + jsval_t result = shell_exec(js, cmd, cmd_len); 133 + jsval_t promise = js_mkpromise(js); 134 + js_resolve_promise(js, promise, result); 135 + 136 + return promise; 137 + } 138 + 139 + return js_mkerr(js, "$() requires a template string"); 140 + } 141 + 142 + jsval_t strings_array = args[0]; 143 + 144 + char *command = malloc(4096); 145 + if (!command) return js_mkerr(js, "Out of memory"); 146 + 147 + size_t cmd_pos = 0; 148 + size_t cmd_capacity = 4096; 149 + 150 + jsval_t length_val = js_get(js, strings_array, "length"); 151 + int length = (int)js_getnum(length_val); 152 + 153 + for (int i = 0; i < length; i++) { 154 + char idx_str[32]; 155 + snprintf(idx_str, sizeof(idx_str), "%d", i); 156 + 157 + jsval_t str_val = js_get(js, strings_array, idx_str); 158 + if (js_type(str_val) == JS_STR) { 159 + size_t str_len; 160 + char *str = js_getstr(js, str_val, &str_len); 161 + 162 + if (cmd_pos + str_len >= cmd_capacity) { 163 + cmd_capacity *= 2; 164 + char *new_cmd = realloc(command, cmd_capacity); 165 + if (!new_cmd) { 166 + free(command); 167 + return js_mkerr(js, "Out of memory"); 168 + } 169 + command = new_cmd; 170 + } 171 + 172 + memcpy(command + cmd_pos, str, str_len); 173 + cmd_pos += str_len; 174 + } 175 + 176 + if (i + 1 < nargs) { 177 + jsval_t val = args[i + 1]; 178 + char val_str[256]; 179 + size_t val_len = 0; 180 + 181 + if (js_type(val) == JS_STR) { 182 + size_t len; 183 + char *s = js_getstr(js, val, &len); 184 + val_len = len < sizeof(val_str) - 1 ? len : sizeof(val_str) - 1; 185 + memcpy(val_str, s, val_len); 186 + } else if (js_type(val) == JS_NUM) { 187 + val_len = snprintf(val_str, sizeof(val_str), "%g", js_getnum(val)); 188 + } 189 + 190 + if (cmd_pos + val_len >= cmd_capacity) { 191 + cmd_capacity *= 2; 192 + char *new_cmd = realloc(command, cmd_capacity); 193 + if (!new_cmd) { 194 + free(command); 195 + return js_mkerr(js, "Out of memory"); 196 + } 197 + command = new_cmd; 198 + } 199 + 200 + memcpy(command + cmd_pos, val_str, val_len); 201 + cmd_pos += val_len; 202 + } 203 + } 204 + 205 + command[cmd_pos] = '\0'; 206 + 207 + jsval_t result = shell_exec(js, command, cmd_pos); 208 + free(command); 209 + 210 + jsval_t promise = js_mkpromise(js); 211 + js_resolve_promise(js, promise, result); 212 + 213 + return promise; 214 + } 215 + 216 + jsval_t shell_library(struct js *js) { 217 + jsval_t lib = js_mkobj(js); 218 + js_set(js, lib, "$", js_mkfun(builtin_shell_dollar)); 219 + 220 + return lib; 221 + }
+77
tests/test_shell.js
··· 1 + import { $ } from 'ant/shell'; 2 + 3 + console.log('Testing $ shell command execution...'); 4 + 5 + console.log('\n=== Test 1: Simple echo ==='); 6 + const result1 = await $`echo "Hello, world!"`; 7 + const text1 = result1.text(); 8 + console.log('Output:', text1); 9 + if (text1 !== 'Hello, world!') { 10 + throw new Error('Expected "Hello, world!" but got: ' + text1); 11 + } 12 + if (result1.exitCode !== 0) { 13 + throw new Error('Expected exit code 0 but got: ' + result1.exitCode); 14 + } 15 + 16 + console.log('\n=== Test 2: Multi-line output ==='); 17 + const result2 = await $`echo "line1\nline2\nline3"`; 18 + const lines2 = result2.lines(); 19 + console.log('Line count:', lines2.length); 20 + console.log('Line 0:', lines2[0]); 21 + console.log('Line 1:', lines2[1]); 22 + console.log('Line 2:', lines2[2]); 23 + 24 + if (lines2.length !== 3) { 25 + throw new Error('Expected 3 lines but got: ' + lines2.length); 26 + } 27 + if (lines2[0] !== 'line1') { 28 + throw new Error('Expected "line1" but got: ' + lines2[0]); 29 + } 30 + if (lines2[1] !== 'line2') { 31 + throw new Error('Expected "line2" but got: ' + lines2[1]); 32 + } 33 + if (lines2[2] !== 'line3') { 34 + throw new Error('Expected "line3" but got: ' + lines2[2]); 35 + } 36 + 37 + console.log('\n=== Test 3: ls command ==='); 38 + const result3 = await $`ls tests/test_shell_dollar.cjs`; 39 + const text3 = result3.text(); 40 + console.log('Output:', text3); 41 + if (!text3.includes('test_shell_dollar.cjs')) { 42 + throw new Error('Expected output to contain test_shell_dollar.cjs'); 43 + } 44 + 45 + console.log('\n=== Test 4: pwd command ==='); 46 + const result4 = await $`pwd`; 47 + const text4 = result4.text(); 48 + console.log('Current directory:', text4); 49 + if (text4.length === 0) { 50 + throw new Error('Expected non-empty pwd output'); 51 + } 52 + 53 + console.log('\n=== Test 5: .text() method ==='); 54 + const result5 = await $`echo "test text method"`; 55 + const text5 = result5.text(); 56 + console.log('Text:', text5); 57 + if (text5 !== 'test text method') { 58 + throw new Error('Expected "test text method" but got: ' + text5); 59 + } 60 + 61 + console.log('\n=== Test 6: Listing files with .lines() ==='); 62 + const result6 = await $`ls tests/*.cjs | head -5`; 63 + const lines6 = result6.lines(); 64 + console.log('Found', lines6.length, 'files'); 65 + for (let i = 0; i < lines6.length && i < 3; i++) { 66 + console.log(' -', lines6[i]); 67 + } 68 + 69 + console.log('\n=== Test 7: date command ==='); 70 + const result7 = await $`date +%Y`; 71 + const year = result7.text(); 72 + console.log('Current year:', year); 73 + if (year.length !== 4) { 74 + throw new Error('Expected 4-digit year'); 75 + } 76 + 77 + console.log('\nโœ“ All $ shell tests passed!');