MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#ifdef _WIN32
8#define popen _popen
9#define pclose _pclose
10#else
11#include <sys/wait.h>
12#endif
13
14#include "ant.h"
15#include "errors.h"
16#include "internal.h"
17#include "modules/symbol.h"
18
19static ant_value_t builtin_shell_text(ant_t *js, ant_value_t *args, int nargs);
20static ant_value_t builtin_shell_lines(ant_t *js, ant_value_t *args, int nargs);
21
22static ant_value_t shell_exec(ant_t *js, const char *cmd, size_t cmd_len) {
23 ant_value_t result = js_mkobj(js);
24
25 FILE *fp = popen(cmd, "r");
26 if (!fp) {
27 js_set(js, result, "stdout", js_mkstr(js, "", 0));
28 js_set(js, result, "stderr", js_mkstr(js, "Failed to execute command", 25));
29 js_set(js, result, "exitCode", js_mknum(1));
30 return result;
31 }
32
33 char *output = NULL;
34 size_t output_size = 0;
35 size_t output_capacity = 4096;
36 output = malloc(output_capacity);
37
38 if (!output) {
39 pclose(fp);
40 return js_mkerr(js, "Out of memory");
41 }
42
43 char buffer[4096];
44 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
45 size_t len = strlen(buffer);
46 if (output_size + len >= output_capacity) {
47 output_capacity *= 2;
48 char *new_output = realloc(output, output_capacity);
49 if (!new_output) {
50 free(output);
51 pclose(fp);
52 return js_mkerr(js, "Out of memory");
53 }
54 output = new_output;
55 }
56 memcpy(output + output_size, buffer, len);
57 output_size += len;
58 }
59
60 int status = pclose(fp);
61#ifdef _WIN32
62 int exit_code = status;
63#else
64 int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
65#endif
66 if (output_size > 0 && output[output_size - 1] == '\n') output_size--;
67
68 ant_value_t stdout_val = js_mkstr(js, output, output_size);
69 free(output);
70
71 js_set(js, result, "exitCode", js_mknum(exit_code));
72 js_set(js, result, "text", js_heavy_mkfun(js, builtin_shell_text, stdout_val));
73 js_set(js, result, "lines", js_heavy_mkfun(js, builtin_shell_lines, stdout_val));
74
75 return result;
76}
77
78static ant_value_t builtin_shell_text(ant_t *js, ant_value_t *args, int nargs) {
79 (void)args;
80 (void)nargs;
81
82 ant_value_t fn = js_getcurrentfunc(js);
83 return js_get_slot(fn, SLOT_DATA);
84}
85
86static ant_value_t builtin_shell_lines(ant_t *js, ant_value_t *args, int nargs) {
87 (void)args;
88 (void)nargs;
89
90 ant_value_t fn = js_getcurrentfunc(js);
91 ant_value_t stdout_val = js_get_slot(fn, SLOT_DATA);
92
93 size_t text_len;
94 char *text = js_getstr(js, stdout_val, &text_len);
95 if (!text) return js_mkarr(js);
96
97 ant_value_t lines_array = js_mkarr(js);
98 size_t line_start = 0;
99
100 for (size_t i = 0; i <= text_len; i++) {
101 if (i == text_len || text[i] == '\n') {
102 size_t line_len = i - line_start;
103 ant_value_t line_val = js_mkstr(js, text + line_start, line_len);
104 js_arr_push(js, lines_array, line_val);
105 line_start = i + 1;
106 }
107 }
108
109 return lines_array;
110}
111
112static ant_value_t builtin_shell_dollar(ant_t *js, ant_value_t *args, int nargs) {
113 if (nargs < 1) return js_mkerr(js, "$() requires at least one argument");
114
115 if (!is_special_object(args[0])) {
116 if (vtype(args[0]) == T_STR) {
117 size_t cmd_len;
118 char *cmd = js_getstr(js, args[0], &cmd_len);
119 if (!cmd) return js_mkerr(js, "Failed to get command string");
120 return shell_exec(js, cmd, cmd_len);
121 }
122
123 return js_mkerr(js, "$() requires a template string");
124 }
125
126 ant_value_t strings_array = args[0];
127
128 char *command = malloc(4096);
129 if (!command) return js_mkerr(js, "Out of memory");
130
131 size_t cmd_pos = 0;
132 size_t cmd_capacity = 4096;
133
134 ant_value_t length_val = js_get(js, strings_array, "length");
135 int length = (int)js_getnum(length_val);
136
137 for (int i = 0; i < length; i++) {
138 char idx_str[32];
139 snprintf(idx_str, sizeof(idx_str), "%d", i);
140
141 ant_value_t str_val = js_get(js, strings_array, idx_str);
142 if (vtype(str_val) == T_STR) {
143 size_t str_len;
144 char *str = js_getstr(js, str_val, &str_len);
145
146 if (cmd_pos + str_len >= cmd_capacity) {
147 cmd_capacity *= 2;
148 char *new_cmd = realloc(command, cmd_capacity);
149 if (!new_cmd) {
150 free(command);
151 return js_mkerr(js, "Out of memory");
152 }
153 command = new_cmd;
154 }
155
156 memcpy(command + cmd_pos, str, str_len);
157 cmd_pos += str_len;
158 }
159
160 if (i + 1 < nargs) {
161 ant_value_t val = args[i + 1];
162 char val_str[256];
163 size_t val_len = 0;
164
165 if (vtype(val) == T_STR) {
166 size_t len;
167 char *s = js_getstr(js, val, &len);
168 val_len = len < sizeof(val_str) - 1 ? len : sizeof(val_str) - 1;
169 memcpy(val_str, s, val_len);
170 } else if (vtype(val) == T_NUM) {
171 val_len = snprintf(val_str, sizeof(val_str), "%g", js_getnum(val));
172 }
173
174 if (cmd_pos + val_len >= cmd_capacity) {
175 cmd_capacity *= 2;
176 char *new_cmd = realloc(command, cmd_capacity);
177 if (!new_cmd) {
178 free(command);
179 return js_mkerr(js, "Out of memory");
180 }
181 command = new_cmd;
182 }
183
184 memcpy(command + cmd_pos, val_str, val_len);
185 cmd_pos += val_len;
186 }
187 }
188
189 command[cmd_pos] = '\0';
190
191 ant_value_t result = shell_exec(js, command, cmd_pos);
192 free(command);
193
194 return result;
195}
196
197ant_value_t shell_library(ant_t *js) {
198 ant_value_t lib = js_mkobj(js);
199
200 js_set(js, lib, "$", js_mkfun(builtin_shell_dollar));
201 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "shell", 5));
202
203 return lib;
204}