From 5ee185a4896e43c67b6d299becfa616da78fb9f4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 21 Mar 2025 21:48:53 -0400 Subject: Move stdlib into src/ --- src/stdlib/stdlib.c | 732 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 732 insertions(+) create mode 100644 src/stdlib/stdlib.c (limited to 'src/stdlib/stdlib.c') diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c new file mode 100644 index 00000000..819414fa --- /dev/null +++ b/src/stdlib/stdlib.c @@ -0,0 +1,732 @@ +// Built-in functions + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bools.h" +#include "files.h" +#include "functiontype.h" +#include "integers.h" +#include "optionals.h" +#include "metamethods.h" +#include "nums.h" +#include "patterns.h" +#include "paths.h" +#include "rng.h" +#include "siphash.h" +#include "stdlib.h" +#include "tables.h" +#include "text.h" +#include "util.h" + +public bool USE_COLOR; + +static void signal_handler(int sig, siginfo_t *, void *) +{ + assert(sig == SIGILL); + fflush(stdout); + if (USE_COLOR) fputs("\x1b[31;7m ===== ILLEGAL INSTRUCTION ===== \n\n\x1b[m", stderr); + else fputs("===== ILLEGAL INSTRUCTION =====\n\n", stderr); + print_stack_trace(stderr, 3, 4); + fflush(stderr); + raise(SIGABRT); + _exit(1); +} + +public void tomo_init(void) +{ + GC_INIT(); + USE_COLOR = getenv("COLOR") ? strcmp(getenv("COLOR"), "1") == 0 : isatty(STDOUT_FILENO); + if (getenv("NO_COLOR") && getenv("NO_COLOR")[0] != '\0') + USE_COLOR = false; + + setlocale(LC_ALL, ""); + getrandom(TOMO_HASH_KEY, sizeof(TOMO_HASH_KEY), 0); + + uint8_t *random_bytes[40] = {}; + getrandom(random_bytes, sizeof(random_bytes), 0); + Array_t rng_seed = {.length=sizeof(random_bytes), .data=random_bytes, .stride=1, .atomic=1}; + RNG$set_seed(default_rng, rng_seed); + + if (register_printf_specifier('k', printf_text, printf_text_size)) + errx(1, "Couldn't set printf specifier"); + + struct sigaction sigact; + sigact.sa_sigaction = signal_handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(SIGILL, &sigact, (struct sigaction *)NULL); +} + +static bool parse_single_arg(const TypeInfo_t *info, char *arg, void *dest) +{ + if (!arg) return false; + + if (info->tag == OptionalInfo && streq(arg, "none")) + return true; + + while (info->tag == OptionalInfo) + info = info->OptionalInfo.type; + + if (info == &Int$info) { + OptionalInt_t parsed = Int$from_str(arg); + if (parsed.small != 0) + *(OptionalInt_t*)dest = parsed; + return parsed.small != 0; + } else if (info == &Int64$info) { + OptionalInt64_t parsed = Int64$parse(Text$from_str(arg)); + if (!parsed.is_none) + *(OptionalInt64_t*)dest = parsed; + return !parsed.is_none; + } else if (info == &Int32$info) { + OptionalInt32_t parsed = Int32$parse(Text$from_str(arg)); + if (!parsed.is_none) + *(OptionalInt32_t*)dest = parsed; + return !parsed.is_none; + } else if (info == &Int16$info) { + OptionalInt16_t parsed = Int16$parse(Text$from_str(arg)); + if (!parsed.is_none) + *(OptionalInt16_t*)dest = parsed; + return !parsed.is_none; + } else if (info == &Int8$info) { + OptionalInt8_t parsed = Int8$parse(Text$from_str(arg)); + if (!parsed.is_none) + *(OptionalInt8_t*)dest = parsed; + return !parsed.is_none; + } else if (info == &Bool$info) { + OptionalBool_t parsed = Bool$parse(Text$from_str(arg)); + if (parsed != NONE_BOOL) + *(OptionalBool_t*)dest = parsed; + return parsed != NONE_BOOL; + } else if (info == &Num$info) { + OptionalNum_t parsed = Num$parse(Text$from_str(arg)); + if (!isnan(parsed)) + *(OptionalNum_t*)dest = parsed; + return !isnan(parsed); + } else if (info == &Num32$info) { + OptionalNum32_t parsed = Num32$parse(Text$from_str(arg)); + if (!isnan(parsed)) + *(OptionalNum32_t*)dest = parsed; + return !isnan(parsed); + } else if (info == &Path$info) { + *(OptionalPath_t*)dest = Path$from_str(arg); + return true; + } else if (info->tag == TextInfo) { + *(OptionalText_t*)dest = Text$from_str(arg); + return true; + } else if (info->tag == EnumInfo) { + for (int t = 0; t < info->EnumInfo.num_tags; t++) { + NamedType_t named = info->EnumInfo.tags[t]; + size_t len = strlen(named.name); + if (strncmp(arg, named.name, len) == 0 && (arg[len] == '\0' || arg[len] == ':')) { + *(int32_t*)dest = (t + 1); + + // Simple tag (no associated data): + if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0)) + return true; + + // Single-argument tag: + if (arg[len] != ':') + errx(1, "Invalid value for %k.%s: %s", &t, named.name, arg); + size_t offset = sizeof(int32_t); + if (named.type->align > 0 && offset % (size_t)named.type->align > 0) + offset += (size_t)named.type->align - (offset % (size_t)named.type->align); + if (!parse_single_arg(named.type, arg + len + 1, dest + offset)) + return false; + return true; + } + } + errx(1, "Invalid value for %s: %s", info->EnumInfo.name, arg); + } else if (info->tag == StructInfo) { + if (info->StructInfo.num_fields == 0) + return true; + else if (info->StructInfo.num_fields == 1) + return parse_single_arg(info->StructInfo.fields[0].type, arg, dest); + + Text_t t = generic_as_text(NULL, false, info); + errx(1, "Unsupported multi-argument struct type for argument parsing: %k", &t); + } else if (info->tag == ArrayInfo) { + errx(1, "Array arguments must be specified as `--flag ...` not `--flag=...`"); + } else if (info->tag == TableInfo) { + errx(1, "Table arguments must be specified as `--flag ...` not `--flag=...`"); + } else { + Text_t t = generic_as_text(NULL, false, info); + errx(1, "Unsupported type for argument parsing: %k", &t); + } +} + +static Array_t parse_array(const TypeInfo_t *item_info, int n, char *args[]) +{ + int64_t padded_size = item_info->size; + if ((padded_size % item_info->align) > 0) + padded_size = padded_size + item_info->align - (padded_size % item_info->align); + + Array_t items = { + .stride=padded_size, + .length=n, + .data=GC_MALLOC((size_t)(padded_size*n)), + }; + for (int i = 0; i < n; i++) { + bool success = parse_single_arg(item_info, args[i], items.data + items.stride*i); + if (!success) + errx(1, "Couldn't parse argument: %s", args[i]); + } + return items; +} + +// Arguments take the form key=value, with a guarantee that there is an '=' +static Table_t parse_table(const TypeInfo_t *table, int n, char *args[]) +{ + const TypeInfo_t *key = table->TableInfo.key, *value = table->TableInfo.value; + int64_t padded_size = key->size; + if ((padded_size % value->align) > 0) + padded_size = padded_size + value->align - (padded_size % value->align); + int64_t value_offset = padded_size; + padded_size += value->size; + if ((padded_size % key->align) > 0) + padded_size = padded_size + key->align - (padded_size % key->align); + + Array_t entries = { + .stride=padded_size, + .length=n, + .data=GC_MALLOC((size_t)(padded_size*n)), + }; + for (int i = 0; i < n; i++) { + char *key_arg = args[i]; + char *equals = strchr(key_arg, '='); + assert(equals); + char *value_arg = equals + 1; + *equals = '\0'; + + bool success = parse_single_arg(key, key_arg, entries.data + entries.stride*i); + if (!success) + errx(1, "Couldn't parse table key: %s", key_arg); + + success = parse_single_arg(value, value_arg, entries.data + entries.stride*i + value_offset); + if (!success) + errx(1, "Couldn't parse table value: %s", value_arg); + + *equals = '='; + } + return Table$from_entries(entries, table); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstack-protector" +public void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, int spec_len, cli_arg_t spec[spec_len]) +{ + bool populated_args[spec_len]; + bool used_args[argc]; + memset(populated_args, 0, sizeof(populated_args)); + memset(used_args, 0, sizeof(used_args)); + for (int i = 1; i < argc; ) { + if (argv[i][0] == '-' && argv[i][1] == '-') { + if (argv[i][2] == '\0') { // "--" signals the rest of the arguments are literal + used_args[i] = true; + i += 1; + break; + } + + for (int s = 0; s < spec_len; s++) { + const TypeInfo_t *non_opt_type = spec[s].type; + while (non_opt_type->tag == OptionalInfo) + non_opt_type = non_opt_type->OptionalInfo.type; + + if (non_opt_type == &Bool$info + && strncmp(argv[i], "--no-", strlen("--no-")) == 0 + && strcmp(argv[i] + strlen("--no-"), spec[s].name) == 0) { + *(OptionalBool_t*)spec[s].dest = false; + populated_args[s] = true; + used_args[i] = true; + goto next_arg; + } + + if (strncmp(spec[s].name, argv[i] + 2, strlen(spec[s].name)) != 0) + continue; + + char after_name = argv[i][2+strlen(spec[s].name)]; + if (after_name == '\0') { // --foo val + used_args[i] = true; + if (non_opt_type->tag == ArrayInfo) { + int num_args = 0; + while (i + 1 + num_args < argc) { + if (argv[i+1+num_args][0] == '-') + break; + used_args[i+1+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + *(OptionalArray_t*)spec[s].dest = parse_array(non_opt_type->ArrayInfo.item, num_args, &argv[i+1]); + } else if (non_opt_type->tag == TableInfo) { + int num_args = 0; + while (i + 1 + num_args < argc) { + if (argv[i+1+num_args][0] == '-' || !strchr(argv[i+1+num_args], '=')) + break; + used_args[i+1+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + *(OptionalTable_t*)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i+1]); + } else if (non_opt_type == &Bool$info) { // --flag + populated_args[s] = true; + *(OptionalBool_t*)spec[s].dest = true; + } else { + if (i + 1 >= argc) + errx(1, "Missing argument: %s\n%k", argv[i], &usage); + used_args[i+1] = true; + populated_args[s] = parse_single_arg(spec[s].type, argv[i+1], spec[s].dest); + if (!populated_args[s]) + errx(1, "Couldn't parse argument: %s %s\n%k", argv[i], argv[i+1], &usage); + } + goto next_arg; + } else if (after_name == '=') { // --foo=val + used_args[i] = true; + populated_args[s] = parse_single_arg(spec[s].type, 2 + argv[i] + strlen(spec[s].name) + 1, spec[s].dest); + if (!populated_args[s]) + errx(1, "Couldn't parse argument: %s\n%k", argv[i], &usage); + goto next_arg; + } else { + continue; + } + } + + if (streq(argv[i], "--help")) { + say(help, true); + exit(0); + } + errx(1, "Unrecognized argument: %s\n%k", argv[i], &usage); + } else if (argv[i][0] == '-' && argv[i][1] && argv[i][1] != '-') { // Single flag args + used_args[i] = true; + for (char *f = argv[i] + 1; *f; f++) { + for (int s = 0; s < spec_len; s++) { + if (spec[s].name[0] != *f || strlen(spec[s].name) > 1) + continue; + + const TypeInfo_t *non_opt_type = spec[s].type; + while (non_opt_type->tag == OptionalInfo) + non_opt_type = non_opt_type->OptionalInfo.type; + + if (non_opt_type->tag == ArrayInfo) { + if (f[1]) errx(1, "No value provided for -%c\n%k", *f, &usage); + int num_args = 0; + while (i + 1 + num_args < argc) { + if (argv[i+1+num_args][0] == '-') + break; + used_args[i+1+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + *(OptionalArray_t*)spec[s].dest = parse_array(non_opt_type->ArrayInfo.item, num_args, &argv[i+1]); + } else if (non_opt_type->tag == TableInfo) { + int num_args = 0; + while (i + 1 + num_args < argc) { + if (argv[i+1+num_args][0] == '-' || !strchr(argv[i+1+num_args], '=')) + break; + used_args[i+1+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + *(OptionalTable_t*)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i+1]); + } else if (non_opt_type == &Bool$info) { // -f + populated_args[s] = true; + *(OptionalBool_t*)spec[s].dest = true; + } else { + if (f[1] || i+1 >= argc) errx(1, "No value provided for -%c\n%k", *f, &usage); + used_args[i+1] = true; + populated_args[s] = parse_single_arg(spec[s].type, argv[i+1], spec[s].dest); + if (!populated_args[s]) + errx(1, "Couldn't parse argument: %s %s\n%k", argv[i], argv[i+1], &usage); + } + goto next_flag; + } + + if (*f == 'h') { + say(help, true); + exit(0); + } + errx(1, "Unrecognized flag: -%c\n%k", *f, &usage); + next_flag:; + } + } else { + // Handle positional args later + i += 1; + continue; + } + + next_arg: + while (used_args[i] && i < argc) + i += 1; + } + + // Get remaining positional arguments + bool ignore_dashes = false; + for (int i = 1, s = 0; i < argc; i++) { + if (!ignore_dashes && streq(argv[i], "--")) { + ignore_dashes = true; + continue; + } + if (used_args[i]) continue; + + while (populated_args[s]) { + next_non_bool_flag: + ++s; + if (s >= spec_len) + errx(1, "Extra argument: %s\n%k", argv[i], &usage); + } + + const TypeInfo_t *non_opt_type = spec[s].type; + while (non_opt_type->tag == OptionalInfo) + non_opt_type = non_opt_type->OptionalInfo.type; + + // You can't specify boolean flags positionally + if (non_opt_type == &Bool$info) + goto next_non_bool_flag; + + if (non_opt_type->tag == ArrayInfo) { + int num_args = 0; + while (i + num_args < argc) { + if (!ignore_dashes && argv[i+num_args][0] == '-') + break; + used_args[i+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + *(OptionalArray_t*)spec[s].dest = parse_array(non_opt_type->ArrayInfo.item, num_args, &argv[i]); + } else if (non_opt_type->tag == TableInfo) { + int num_args = 0; + while (i + num_args < argc) { + if (argv[i+num_args][0] == '-' || !strchr(argv[i+num_args], '=')) + break; + used_args[i+num_args] = true; + num_args += 1; + } + populated_args[s] = true; + *(OptionalTable_t*)spec[s].dest = parse_table(non_opt_type, num_args, &argv[i]); + } else { + populated_args[s] = parse_single_arg(spec[s].type, argv[i], spec[s].dest); + } + + if (!populated_args[s]) + errx(1, "Invalid value for %s: %s\n%k", spec[s].name, argv[i], &usage); + } + + for (int s = 0; s < spec_len; s++) { + if (!populated_args[s] && spec[s].required) { + if (spec[s].type->tag == ArrayInfo) + *(OptionalArray_t*)spec[s].dest = (Array_t){}; + else if (spec[s].type->tag == TableInfo) + *(OptionalTable_t*)spec[s].dest = (Table_t){}; + else + errx(1, "The required argument '%s' was not provided\n%k", spec[s].name, &usage); + } + } +} +#pragma GCC diagnostic pop + +static void print_stack_line(FILE *out, OptionalText_t fn_name, const char *filename, int64_t line_num) +{ + // NOTE: this function is a bit inefficient. Each time we print a line, we + // do a linear scan through the whole file. However, performance shouldn't + // really matter if we only print stack lines when there's a crash. + if (filename) { + fprintf(out, "\033[34mFile\033[m \033[35;1m%s\033[m", filename); + if (line_num >= 1) + fprintf(out, "\033[34m line\033[m \033[35;1m%ld\033[m", line_num); + } + if (fn_name.length > 0) { + fprintf(out, filename ? "\033[34m, in \033[m \033[36;1m%k\033[m" : "\033[36;1m%k\033[m", &fn_name); + } + fprintf(out, "\n"); + + FILE *f = fopen(filename, "r"); + if (!f) return; + char *line = NULL; + size_t size = 0; + ssize_t nread; + int64_t cur_line = 1; + while ((nread = getline(&line, &size, f)) != -1) { + if (line[strlen(line)-1] == '\n') + line[strlen(line)-1] = '\0'; + + if (cur_line >= line_num) + fprintf(out, "\033[33;1m%s\033[m\n", line); + + cur_line += 1; + if (cur_line > line_num) + break; + } + if (line) free(line); + fclose(f); +} + +void print_stack_trace(FILE *out, int start, int stop) +{ + // Print stack trace: + void *stack[1024]; + int64_t size = (int64_t)backtrace(stack, sizeof(stack)/sizeof(stack[0])); + char **strings = strings = backtrace_symbols(stack, size); + for (int64_t i = start; i < size - stop; i++) { + char *filename = strings[i]; + char *paren = strchrnul(strings[i], '('); + char *addr_end = paren + 1 + strcspn(paren + 1, ")"); + ptrdiff_t offset = strtol(paren + 1, &addr_end, 16) - 1; + const char *cmd = heap_strf("addr2line -e %.*s -is +0x%x", strcspn(filename, "("), filename, offset); + FILE *fp = popen(cmd, "r"); + OptionalText_t fn_name = get_function_name(stack[i]); + const char *src_filename = NULL; + int64_t line_number = 0; + if (fp) { + char buf[PATH_MAX + 10] = {}; + if (fgets(buf, sizeof(buf), fp)) { + char *saveptr, *line_num_str; + if ((src_filename=strtok_r(buf, ":", &saveptr)) + && (line_num_str=strtok_r(NULL, ":", &saveptr))) + line_number = atoi(line_num_str); + } + pclose(fp); + } + print_stack_line(out, fn_name, src_filename, line_number); + } +} + +__attribute__((format(printf, 1, 2))) +public _Noreturn void fail(const char *fmt, ...) +{ + fflush(stdout); + if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); + else fputs("==================== ERROR ====================\n\n", stderr); + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + if (USE_COLOR) fputs("\x1b[m", stderr); + fputs("\n\n", stderr); + va_end(args); + print_stack_trace(stderr, 2, 4); + fflush(stderr); + raise(SIGABRT); + _exit(1); +} + +public _Noreturn void fail_text(Text_t message) +{ + fail("%k", &message); +} + +__attribute__((format(printf, 4, 5))) +public _Noreturn void fail_source(const char *filename, int64_t start, int64_t end, const char *fmt, ...) +{ + if (USE_COLOR) fputs("\n\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); + else fputs("\n==================== ERROR ====================\n\n", stderr); + + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + file_t *file = filename ? load_file(filename) : NULL; + if (filename && file) { + fputs("\n", stderr); + highlight_error(file, file->text+start, file->text+end, "\x1b[31;1m", 2, USE_COLOR); + fputs("\n", stderr); + } + if (USE_COLOR) fputs("\x1b[m", stderr); + + print_stack_trace(stderr, 2, 4); + fflush(stderr); + raise(SIGABRT); + _exit(1); +} + +public Text_t builtin_last_err() +{ + return Text$from_str(strerror(errno)); +} + +static int _inspect_depth = 0; +static file_t *file = NULL; + +__attribute__((nonnull)) +public void start_inspect(const char *filename, int64_t start, int64_t end) +{ + if (file == NULL || strcmp(file->filename, filename) != 0) + file = load_file(filename); + + if (file) { + const char *spaces = " "; + int64_t first_line_len = (int64_t)strcspn(file->text + start, "\r\n"); + const char *slash = strrchr(filename, '/'); + const char *file_base = slash ? slash + 1 : filename; + + int64_t line_num = get_line_number(file, file->text + start); + fprintf(stderr, USE_COLOR ? "%.*s\x1b[33;1m>> \x1b[m%.*s %.*s\x1b[32;2m[%s:%ld]\x1b[m\n" : "%.*s>> %.*s %.*s[%s:%ld]\n", + 3*_inspect_depth, spaces, first_line_len, file->text + start, + MAX(0, 35-first_line_len-3*_inspect_depth), spaces, file_base, line_num); + + // For multi-line expressions, dedent each and print it on a new line with ".. " in front: + if (end > start + first_line_len) { + const char *line_start = get_line(file, line_num); + int64_t indent_len = (int64_t)strspn(line_start, " \t"); + for (const char *line = file->text + start + first_line_len; line < file->text + end; line += strcspn(line, "\r\n")) { + line += strspn(line, "\r\n"); + if ((int64_t)strspn(line, " \t") >= indent_len) + line += indent_len; + fprintf(stderr, USE_COLOR ? "%.*s\x1b[33m.. \x1b[m%.*s\n" : "%.*s.. %.*s\n", + 3*_inspect_depth, spaces, strcspn(line, "\r\n"), line); + } + } + } + _inspect_depth += 1; +} + +__attribute__((nonnull)) +public void end_inspect(const void *expr, const TypeInfo_t *type) +{ + _inspect_depth -= 1; + + if (type->metamethods.as_text) { + Text_t expr_text = generic_as_text(expr, USE_COLOR, type); + Text_t type_name = generic_as_text(NULL, false, type); + + for (int i = 0; i < 3*_inspect_depth; i++) fputc(' ', stderr); + fprintf(stderr, USE_COLOR ? "\x1b[33;1m=\x1b[0m %k \x1b[2m: \x1b[36m%k\x1b[m\n" : "= %k : %k\n", &expr_text, &type_name); + } +} + +__attribute__((nonnull)) +public void test_value(const void *expr, const TypeInfo_t *type, const char *expected) +{ + Text_t expr_text = generic_as_text(expr, USE_COLOR, type); + Text_t type_name = generic_as_text(NULL, false, type); + + Text_t expected_text = Text$from_str(expected); + Text_t expr_plain = USE_COLOR ? generic_as_text(expr, false, type) : expr_text; + bool success = Text$equal_values(expr_plain, expected_text); + if (!success) { + OptionalMatch_t colon = Text$find(expected_text, Text(":"), I_small(1)); + if (colon.index.small) { + Text_t with_type = Text$concat(expr_plain, Text(" : "), type_name); + success = Text$equal_values(with_type, expected_text); + } + } + + if (!success) { + print_stack_trace(stderr, 2, 4); + fprintf(stderr, + USE_COLOR + ? "\n\x1b[31;7m ==================== TEST FAILED ==================== \x1b[0;1m\n\nExpected: \x1b[1;32m%s\x1b[0m\n\x1b[1m But got:\x1b[m %k\n\n" + : "\n==================== TEST FAILED ====================\n\nExpected: %s\n But got: %k\n\n", + expected, &expr_text); + + fflush(stderr); + raise(SIGABRT); + } +} + +public void say(Text_t text, bool newline) +{ + Text$print(stdout, text); + if (newline) + fputc('\n', stdout); + fflush(stdout); +} + +public _Noreturn void tomo_exit(Text_t text, int32_t status) +{ + if (text.length > 0) + say(text, true); + _exit(status); +} + +public OptionalText_t ask(Text_t prompt, bool bold, bool force_tty) +{ + OptionalText_t ret = NONE_TEXT; + FILE *out = stdout; + FILE *in = stdin; + + char *line = NULL; + size_t bufsize = 0; + ssize_t length = 0; + char *gc_input = NULL; + + if (force_tty && !isatty(STDOUT_FILENO)) { + out = fopen("/dev/tty", "w"); + if (!out) goto cleanup; + } + + if (bold) fputs("\x1b[1m", out); + Text$print(out, prompt); + if (bold) fputs("\x1b[m", out); + fflush(out); + + if (force_tty && !isatty(STDIN_FILENO)) { + in = fopen("/dev/tty", "r"); + if (!in) { + fputs("\n", out); // finish the line, since the user can't + goto cleanup; + } + } + + length = getline(&line, &bufsize, in); + if (length == -1) { + fputs("\n", out); // finish the line, since we didn't get any input + goto cleanup; + } + + if (length > 0 && line[length-1] == '\n') { + line[length-1] = '\0'; + --length; + } + + gc_input = GC_MALLOC_ATOMIC((size_t)(length + 1)); + memcpy(gc_input, line, (size_t)(length + 1)); + + ret = Text$from_strn(gc_input, (size_t)(length)); + + cleanup: + if (out && out != stdout) fclose(out); + if (in && in != stdin) fclose(in); + return ret; +} + +public bool pop_flag(char **argv, int *i, const char *flag, Text_t *result) +{ + if (argv[*i][0] != '-' || argv[*i][1] != '-') { + return false; + } else if (streq(argv[*i] + 2, flag)) { + *result = EMPTY_TEXT; + argv[*i] = NULL; + *i += 1; + return true; + } else if (strncmp(argv[*i] + 2, "no-", 3) == 0 && streq(argv[*i] + 5, flag)) { + *result = Text("no"); + argv[*i] = NULL; + *i += 1; + return true; + } else if (strncmp(argv[*i] + 2, flag, strlen(flag)) == 0 && argv[*i][2 + strlen(flag)] == '=') { + *result = Text$from_str(argv[*i] + 2 + strlen(flag) + 1); + argv[*i] = NULL; + *i += 1; + return true; + } else { + return false; + } +} + +public void sleep_num(double seconds) +{ + struct timespec ts; + ts.tv_sec = (time_t)seconds; + ts.tv_nsec = (long)((seconds - (double)ts.tv_sec) * 1e9); + nanosleep(&ts, NULL); +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 -- cgit v1.2.3