code / tomo

Lines41.3K C23.7K Markdown9.7K YAML5.0K Tomo2.3K
7 others 763
Python231 Shell230 make212 INI47 Text21 SVG16 Lua6
(349 lines)
1 // Built-in functions
3 #include <errno.h>
4 #include <execinfo.h>
5 #include <fcntl.h>
6 #include <gc.h>
7 #include <locale.h>
8 #include <math.h>
9 #include <signal.h>
10 #include <stdbool.h>
11 #include <stdint.h>
12 #include <stdlib.h>
13 #include <sys/param.h>
14 #include <time.h>
16 #include "../config.h"
17 #include "files.h"
18 #include "metamethods.h"
19 #include "optionals.h"
20 #include "paths.h"
21 #include "print.h"
22 #include "siphash.h"
23 #include "stacktrace.h"
24 #include "stdlib.h"
25 #include "text.h"
26 #include "util.h"
28 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
29 static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
30 (void)flags;
31 arc4random_buf(buf, buflen);
32 return buflen;
34 #elif defined(__linux__)
35 // Use getrandom()
36 #include <sys/random.h>
37 #else
38 #error "Unsupported platform for secure random number generation"
39 #endif
41 public
42 bool USE_COLOR;
44 public
45 const char *TOMO_PATH = "/usr/local";
47 public
48 const char *TOMO_VERSION = "v0";
50 public
51 Text_t TOMO_VERSION_TEXT = Text("v0");
53 #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
54 #include <dlfcn.h>
56 static inline const char *get_library_path(void *func) {
57 static Dl_info info;
58 if (dladdr(func, &info)) {
59 return info.dli_fname; // full path of the library
61 return NULL;
64 #elif defined(_WIN32)
65 #include <windows.h>
67 static inline const char *get_library_path(void *func) {
68 static char path[MAX_PATH];
69 HMODULE hm = NULL;
70 if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
71 (LPCSTR)func, &hm)) {
72 if (GetModuleFileName(hm, path, MAX_PATH)) {
73 return path;
76 return NULL;
79 #else
80 #error "Unsupported platform"
81 #endif
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);
89 free(abs);
90 return ret;
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, ":");
100 while (token) {
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);
105 free(paths);
106 char *ret = String(abs);
107 free(abs);
108 return ret;
110 token = strtok(NULL, ":");
113 free(paths);
114 return NULL; // not found
117 public
118 void tomo_configure(void) {
119 const char *p = get_library_path(get_library_path);
120 p = find_in_path(p);
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);
132 fflush(stdout);
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);
136 fflush(stderr);
137 raise(SIGABRT);
138 _exit(1);
141 public
142 void tomo_init(void) {
143 GC_INIT();
144 tomo_configure();
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);
156 sigact.sa_flags = 0;
157 sigaction(SIGILL, &sigact, (struct sigaction *)NULL);
158 atexit(tomo_cleanup);
161 public
162 _Noreturn void fail_text(Text_t message) {
163 fail(message);
166 public
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);
178 if (file) {
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);
185 if (USE_COLOR) {
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");
190 } else {
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,
193 ":", line_num, "]");
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")));
209 _inspect_depth += 1;
212 public
213 void end_inspect(const void *expr, const TypeInfo_t *type) {
214 _inspect_depth -= 1;
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++)
220 fputc(' ', stdout);
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" : "");
226 public
227 void say(Text_t text, bool newline) {
228 Text$print(stdout, text);
229 if (newline) fputc('\n', stdout);
230 fflush(stdout);
233 public
234 _Noreturn void tomo_exit(Text_t text, int32_t status) {
235 if (text.length > 0) print(text);
236 exit(status);
239 public
240 OptionalText_t ask(Text_t prompt, bool bold, bool force_tty) {
241 OptionalText_t ret = NONE_TEXT;
242 FILE *out = stdout;
243 FILE *in = stdin;
244 bool opened_out = false, opened_in = false;
246 char *line = NULL;
247 size_t bufsize = 0;
248 ssize_t length = 0;
249 char *gc_input = NULL;
251 if (force_tty && !isatty(STDOUT_FILENO)) {
252 out = fopen("/dev/tty", "w");
253 if (!out) goto cleanup;
254 opened_out = true;
257 if (bold) fputs("\x1b[1m", out);
258 Text$print(out, prompt);
259 if (bold) fputs("\x1b[m", out);
260 fflush(out);
262 if (force_tty && !isatty(STDIN_FILENO)) {
263 in = fopen("/dev/tty", "r");
264 if (!in) {
265 fputs("\n", out); // finish the line, since the user can't
266 goto cleanup;
268 opened_in = true;
271 length = getline(&line, &bufsize, in);
272 if (length == -1) {
273 fputs("\n", out); // finish the line, since we didn't get any input
274 goto cleanup;
277 if (length > 0 && line[length - 1] == '\n') {
278 line[length - 1] = '\0';
279 --length;
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));
287 cleanup:
288 if (opened_out) fclose(out);
289 if (opened_in) fclose(in);
290 if (line != NULL) free(line);
291 return ret;
294 public
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");
298 struct timespec ts;
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), ")");
307 public
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;
313 public
314 void setenv_text(Text_t name, OptionalText_t value) {
315 int status;
316 if (value.tag == TEXT_NONE) {
317 status = unsetenv(Text$as_c_string(name));
318 } else {
319 status = setenv(Text$as_c_string(name), Text$as_c_string(value), 1);
321 if (status != 0) {
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;
330 } cleanup_t;
332 static cleanup_t *cleanups = NULL;
334 public
335 void tomo_at_cleanup(Closure_t fn) {
336 cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups);
339 public
340 void tomo_cleanup(void) {
341 while (cleanups) {
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);