13 #include <sys/param.h>
16 #include "../config.h"
18 #include "metamethods.h"
19 #include "optionals.h"
23 #include "stacktrace.h"
28 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
29 static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
31 arc4random_buf(buf, buflen);
34 #elif defined(__linux__)
36 #include <sys/random.h>
38 #error "Unsupported platform for secure random number generation"
45 const char *TOMO_PATH = "/usr/local";
48 const char *TOMO_VERSION = "v0";
51 Text_t TOMO_VERSION_TEXT = Text("v0");
53 #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
56 static inline const char *get_library_path(void *func) {
58 if (dladdr(func, &info)) {
59 return info.dli_fname; // full path of the library
67 static inline const char *get_library_path(void *func) {
68 static char path[MAX_PATH];
70 if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
72 if (GetModuleFileName(hm, path, MAX_PATH)) {
80 #error "Unsupported platform"
83 char *find_in_path(const char *name) {
84 if (strchr(name, '/')) {
85 // name contains a slash → treat as path
86 char *abs = realpath(name, NULL);
87 if (abs == NULL) fail("Couldn't find real path of: ", name);
88 char *ret = String(abs);
93 char *path_env = getenv("PATH");
94 if (!path_env) return NULL;
96 char *paths = strdup(path_env);
97 if (!paths) return NULL;
99 char *token = strtok(paths, ":");
101 char candidate[PATH_MAX];
102 snprintf(candidate, sizeof(candidate), "%s/%s", token, name);
103 if (access(candidate, X_OK) == 0) {
104 char *abs = realpath(candidate, NULL);
106 char *ret = String(abs);
110 token = strtok(NULL, ":");
114 return NULL; // not found
118 void tomo_configure(void) {
119 const char *p = get_library_path(get_library_path);
121 Path_t path = Path$from_str(p);
122 TOMO_PATH = Path$as_c_string(Path$parent(Path$parent(path)));
123 Text_t base_name = Path$base_name(path);
124 TOMO_VERSION_TEXT = Text$without_suffix(
125 Text$without_prefix(Text$without_prefix(base_name, Text("lib")), Text("tomo@")), Text(".so"));
126 TOMO_VERSION = Text$as_c_string(TOMO_VERSION_TEXT);
129 static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) {
130 (void)info, (void)userdata;
131 assert(sig == SIGILL);
133 if (USE_COLOR) fputs("\x1b[31;7m ===== ILLEGAL INSTRUCTION ===== \n\n\x1b[m", stderr);
134 else fputs("===== ILLEGAL INSTRUCTION =====\n\n", stderr);
135 print_stacktrace(stderr, 3);
142 void tomo_init(void) {
145 const char *color_env = getenv("COLOR");
146 USE_COLOR = color_env ? strcmp(color_env, "1") == 0 : isatty(STDOUT_FILENO);
147 const char *no_color_env = getenv("NO_COLOR");
148 if (no_color_env && no_color_env[0] != '\0') USE_COLOR = false;
150 setlocale(LC_ALL, "");
151 assert(getrandom(TOMO_HASH_KEY, sizeof(TOMO_HASH_KEY), 0) == sizeof(TOMO_HASH_KEY));
153 struct sigaction sigact;
154 sigact.sa_sigaction = signal_handler;
155 sigemptyset(&sigact.sa_mask);
157 sigaction(SIGILL, &sigact, (struct sigaction *)NULL);
158 atexit(tomo_cleanup);
162 _Noreturn void fail_text(Text_t message) {
167 Text_t builtin_last_err() {
168 return Text$from_str(strerror(errno));
171 static int _inspect_depth = 0;
172 static file_t *file = NULL;
174 __attribute__((nonnull)) public
175 void start_inspect(const char *filename, int64_t start, int64_t end) {
176 if (file == NULL || strcmp(file->filename, filename) != 0) file = load_file(filename);
179 size_t first_line_len = strcspn(file->text + start, "\r\n");
180 if (first_line_len > (size_t)(end - start)) first_line_len = (size_t)(end - start);
181 const char *slash = strrchr(filename, '/');
182 const char *file_base = slash ? slash + 1 : filename;
184 int64_t line_num = get_line_number(file, file->text + start);
186 print(repeated_char(' ', 3 * _inspect_depth), "\x1b[33;1m>> \x1b[m",
187 string_slice(file->text + start, first_line_len), " ",
188 repeated_char(' ', MAX(0, 35 - (int64_t)first_line_len - 3 * _inspect_depth)), "\x1b[32;2m[",
189 file_base, ":", line_num, "]\x1b[m");
191 print(repeated_char(' ', 3 * _inspect_depth), ">> ", string_slice(file->text + start, first_line_len),
192 " ", repeated_char(' ', MAX(0, 35 - (int64_t)first_line_len - 3 * _inspect_depth)), "[", file_base,
196 // For multi-line expressions, dedent each and print it on a new line with ".. " in front:
197 if (end > start + (int64_t)first_line_len) {
198 const char *line_start = get_line(file, line_num);
199 int64_t indent_len = (int64_t)strspn(line_start, " \t");
200 for (const char *line = file->text + start + first_line_len; line < file->text + end;
201 line += strcspn(line, "\r\n")) {
202 line += strspn(line, "\r\n");
203 if ((int64_t)strspn(line, " \t") >= indent_len) line += indent_len;
204 print(repeated_char(' ', 3 * _inspect_depth), USE_COLOR ? "\x1b[33m..\033[m " : ".. ",
205 string_slice(line, strcspn(line, "\r\n")));
213 void end_inspect(const void *expr, const TypeInfo_t *type) {
216 if (type && type->metamethods.as_text) {
217 Text_t expr_text = generic_as_text(expr, USE_COLOR, type);
218 Text_t type_name = generic_as_text(NULL, false, type);
219 for (int i = 0; i < 3 * _inspect_depth; i++)
221 fprint(stdout, USE_COLOR ? "\x1b[33;1m=\x1b[0m " : "= ", expr_text, USE_COLOR ? " \x1b[2m: \x1b[36m" : " : ",
222 type_name, USE_COLOR ? "\033[m" : "");
227 void say(Text_t text, bool newline) {
228 Text$print(stdout, text);
229 if (newline) fputc('\n', stdout);
234 _Noreturn void tomo_exit(Text_t text, int32_t status) {
235 if (text.length > 0) print(text);
240 OptionalText_t ask(Text_t prompt, bool bold, bool force_tty) {
241 OptionalText_t ret = NONE_TEXT;
244 bool opened_out = false, opened_in = false;
249 char *gc_input = NULL;
251 if (force_tty && !isatty(STDOUT_FILENO)) {
252 out = fopen("/dev/tty", "w");
253 if (!out) goto cleanup;
257 if (bold) fputs("\x1b[1m", out);
258 Text$print(out, prompt);
259 if (bold) fputs("\x1b[m", out);
262 if (force_tty && !isatty(STDIN_FILENO)) {
263 in = fopen("/dev/tty", "r");
265 fputs("\n", out); // finish the line, since the user can't
271 length = getline(&line, &bufsize, in);
273 fputs("\n", out); // finish the line, since we didn't get any input
277 if (length > 0 && line[length - 1] == '\n') {
278 line[length - 1] = '\0';
282 gc_input = GC_MALLOC_ATOMIC((size_t)(length + 1));
283 memcpy(gc_input, line, (size_t)(length + 1));
285 ret = Text$from_strn(gc_input, (size_t)(length));
288 if (opened_out) fclose(out);
289 if (opened_in) fclose(in);
290 if (line != NULL) free(line);
295 void sleep_seconds(double seconds) {
296 if (seconds < 0) fail("Cannot sleep for a negative amount of time: ", seconds);
297 else if (isnan(seconds)) fail("Cannot sleep for a time that is NaN");
299 ts.tv_sec = (time_t)seconds;
300 ts.tv_nsec = (long)((seconds - (double)ts.tv_sec) * 1e9);
301 while (nanosleep(&ts, NULL) != 0) {
302 if (errno == EINTR) continue;
303 fail("Failed to sleep for the requested time (", strerror(errno), ")");
308 OptionalText_t getenv_text(Text_t name) {
309 const char *val = getenv(Text$as_c_string(name));
310 return val ? Text$from_str(val) : NONE_TEXT;
314 void setenv_text(Text_t name, OptionalText_t value) {
316 if (value.tag == TEXT_NONE) {
317 status = unsetenv(Text$as_c_string(name));
319 status = setenv(Text$as_c_string(name), Text$as_c_string(value), 1);
322 if (errno == EINVAL) fail("Invalid environment variable name: ", Text$quoted(name, false, Text("\"")));
323 else fail("Failed to set environment variable (", strerror(errno));
327 typedef struct cleanup_s {
328 Closure_t cleanup_fn;
329 struct cleanup_s *next;
332 static cleanup_t *cleanups = NULL;
335 void tomo_at_cleanup(Closure_t fn) {
336 cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups);
340 void tomo_cleanup(void) {
342 // NOTE: we *must* remove the cleanup function from the stack before calling it,
343 // otherwise it will cause an infinite loop if the cleanup function fails or exits.
344 void (*run_cleanup)(void *) = cleanups->cleanup_fn.fn;
345 void *userdata = cleanups->cleanup_fn.userdata;
346 cleanups = cleanups->next;
347 run_cleanup(userdata);