diff options
79 files changed, 1469 insertions, 377 deletions
@@ -11,6 +11,8 @@ /lib/*/*.so /lib/*/*.a +/vendor/*/ + .build /build *.o @@ -20,4 +22,6 @@ *.tm.h *.tm.o *.testresult +*.tar.gz +*.tar.xz tags @@ -83,17 +83,11 @@ O=-O3 # Note: older versions of Make have buggy behavior with hash marks inside strings, so this ugly code is necessary: TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print $$2; exit}' CHANGES.md) GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h" 2>/dev/null || echo "unknown") -CFLAGS+=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ +CFLAGS+=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) \ -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ -DGIT_VERSION='"$(GIT_VERSION)"' -ffunction-sections -fdata-sections CFLAGS_PLACEHOLDER="$$(printf '\033[2m<flags...>\033[m\n')" -LDLIBS=-lgc -lm -lunistring -lgmp - -ifeq ($(OS),OpenBSD) - LDLIBS += -lexecinfo -else - LDLIBS += -ldl -endif +LDLIBS=-lm AR_FILE=libtomo@$(TOMO_VERSION).a ifeq ($(OS),Darwin) @@ -108,7 +102,7 @@ TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/[!_]*.tm API_YAML=$(wildcard api/*.yaml) API_MD=$(patsubst %.yaml,%.md,$(API_YAML)) -all: config.mk check-c-compiler check-libs build +all: config.mk check-c-compiler build @$(ECHO) "All done!" BUILD_DIR=build/tomo@$(TOMO_VERSION) @@ -141,12 +135,14 @@ $(BUILD_DIR)/man/%.gz: man/% | $(BUILD_DIR)/man/man1 $(BUILD_DIR)/man/man3 $(BUILD_DIR)/bin/tomo: $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) | $(BUILD_DIR)/bin ln -sf tomo@$(TOMO_VERSION) $@ -$(BUILD_DIR)/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS) | $(BUILD_DIR)/bin - @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@ - @$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ +$(BUILD_DIR)/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS) build/gc/lib/libgc.a build/gmp/lib/libgmp.a build/unistring/lib/libunistring.a | $(BUILD_DIR)/bin deps + @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $(LDLIBS) $^ -o $@ + @$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) $^ -o $@ -$(BUILD_DIR)/lib/$(AR_FILE): $(STDLIB_OBJS) | $(BUILD_DIR)/lib - ar -rcs $@ $^ +$(BUILD_DIR)/lib/$(AR_FILE): $(STDLIB_OBJS) build/gc/lib/libgc.a build/unistring/lib/libunistring.a build/gmp/lib/libgmp.a | $(BUILD_DIR)/lib + $(CC) -no-pie -r -nostdlib $^ -o libtomo.o + ar rcs $@ libtomo.o + rm -f libtomo.o $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION)/modules.ini: modules/core.ini modules/examples.ini | $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION) @cat $^ > $@ @@ -165,9 +161,9 @@ check-c-compiler: @$(DEFAULT_C_COMPILER) -v 2>/dev/null >/dev/null \ || { printf '\033[31;1m%s\033[m\n' "You have set your DEFAULT_C_COMPILER to $(DEFAULT_C_COMPILER) in your config.mk, but I can't run it!"; exit 1; } -check-libs: check-c-compiler - @echo 'int main() { return 0; }' | $(DEFAULT_C_COMPILER) $(LDFLAGS) $(LDLIBS) -x c - -o /dev/null 2>/dev/null >/dev/null \ - || { printf '\033[31;1m%s\033[m\n' "I expected to find the following libraries on your system, but I can't find them: $(LDLIBS)"; exit 1; } +# check-libs: check-c-compiler | deps +# @echo 'int main() { return 0; }' | $(DEFAULT_C_COMPILER) $(LDFLAGS) -x c - $(LDLIBS) -o /dev/null 2>/dev/null >/dev/null \ +# || { printf '\033[31;1m%s\033[m\n' "I expected to find the following libraries on your system, but I can't find them: $(LDLIBS)"; exit 1; } tags: ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} src/formatter/*.{c,h} @@ -200,7 +196,7 @@ test: $(TESTS) @printf '\033[32;7m ALL TESTS PASSED! \033[m\n' clean: - rm -rf build/* $(COMPILER_OBJS) $(STDLIB_OBJS) test/*.tm.testresult test/.build lib/*/.build examples/.build examples/*/.build + rm -rf build/tomo*/{bin,lib} $(COMPILER_OBJS) $(STDLIB_OBJS) test/*.tm.testresult test/.build lib/*/.build examples/.build examples/*/.build %: %.md pandoc --lua-filter=docs/.pandoc/bold-code.lua -s $< -t man -o $@ @@ -228,14 +224,12 @@ examples: core-libs: ./local-tomo -L modules/core.ini -deps: - bash ./install_dependencies.sh +deps: build/gc/lib/libgc.a build/unistring/lib/libgc.a build/gmp/lib/libgmp.a -check-utilities: check-c-compiler - @which debugedit 2>/dev/null >/dev/null \ - || printf '\033[33;1m%s\033[m\n' "I couldn't find 'debugedit' on your system! Try installing the package 'debugedit' with your package manager. (It's not required though)" +build/gc/lib/libgc.a build/unistring/lib/libgc.a build/gmp/lib/libgmp.a: + $(MAKE) -C vendor -install-files: build check-utilities +install-files: build check-c-compiler @if ! echo "$$PATH" | tr ':' '\n' | grep -qx "$(PREFIX)/bin"; then \ echo $$PATH; \ printf "\033[31;1mError: '$(PREFIX)/bin' is not in your \$$PATH variable!\033[m\n" >&2; \ @@ -263,4 +257,4 @@ uninstall: endif .SUFFIXES: -.PHONY: all build clean install install-files uninstall test tags core-libs examples deps check-utilities check-c-compiler check-libs version +.PHONY: all build clean install install-files uninstall test tags core-libs examples deps check-c-compiler version diff --git a/configure.sh b/configure.sh index a4a8daa5..1b5bd272 100755 --- a/configure.sh +++ b/configure.sh @@ -37,19 +37,8 @@ read DEFAULT_C_COMPILER if [ -z "$DEFAULT_C_COMPILER" ]; then DEFAULT_C_COMPILER="cc"; fi DEFAULT_C_COMPILER="${DEFAULT_C_COMPILER/#\~/$HOME}" -printf '\033[1mDo you want to build the compiler with Link Time Optimization (LTO)?\033[m\n\033[2m(This makes building the Tomo compiler slower, but makes running Tomo programs faster)\033[m\n\033[1m[y/N]\033[m ' -read USE_LTO -if [ "$USE_LTO" = "y" -o "$USE_LTO" = "Y" ]; then - if $DEFAULT_C_COMPILER -v 2>&1 | grep -q "gcc version"; then - LTO="-flto=auto -fno-fat-lto-objects -Wl,-flto"; - elif $DEFAULT_C_COMPILER -v 2>&1 | grep -q "clang version"; then - LTO="-flto=thin"; - fi -fi - cat <<END >config.mk PREFIX=$PREFIX DEFAULT_C_COMPILER=$DEFAULT_C_COMPILER SUDO=$SUDO -LTO=$LTO END @@ -9,7 +9,7 @@ #include "stdlib/datatypes.h" #include "stdlib/files.h" -#include "stdlib/util.h" +#include "util.h" #define NewAST(_file, _start, _end, ast_tag, ...) \ (new (ast_t, .file = _file, .start = _start, .end = _end, .tag = ast_tag, .__data.ast_tag = {__VA_ARGS__})) diff --git a/src/compile/assertions.c b/src/compile/assertions.c index 18531fd9..5cbb4359 100644 --- a/src/compile/assertions.c +++ b/src/compile/assertions.c @@ -5,7 +5,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/assignments.c b/src/compile/assignments.c index 74a00e0b..7d989b06 100644 --- a/src/compile/assignments.c +++ b/src/compile/assignments.c @@ -4,7 +4,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/binops.c b/src/compile/binops.c index acf1e031..a06cba22 100644 --- a/src/compile/binops.c +++ b/src/compile/binops.c @@ -4,7 +4,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "../types.h" #include "compilation.h" diff --git a/src/compile/blocks.c b/src/compile/blocks.c index 66869ecc..87be350a 100644 --- a/src/compile/blocks.c +++ b/src/compile/blocks.c @@ -4,7 +4,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/cli.c b/src/compile/cli.c index ade6caa7..447e437a 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -5,7 +5,7 @@ #include "../stdlib/datatypes.h" #include "../stdlib/optionals.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "../types.h" #include "compilation.h" diff --git a/src/compile/conditionals.c b/src/compile/conditionals.c index 64be29fa..c1b1e60e 100644 --- a/src/compile/conditionals.c +++ b/src/compile/conditionals.c @@ -5,7 +5,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/debuglog.c b/src/compile/debuglog.c index 4128bfa7..9da1bf57 100644 --- a/src/compile/debuglog.c +++ b/src/compile/debuglog.c @@ -5,7 +5,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/declarations.c b/src/compile/declarations.c index 3b80bade..ae5da0f7 100644 --- a/src/compile/declarations.c +++ b/src/compile/declarations.c @@ -4,7 +4,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/expressions.c b/src/compile/expressions.c index c3918de3..3a6683c4 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -5,7 +5,7 @@ #include "../config.h" #include "../environment.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/fieldaccess.c b/src/compile/fieldaccess.c index 033851a7..cca9d9e6 100644 --- a/src/compile/fieldaccess.c +++ b/src/compile/fieldaccess.c @@ -5,7 +5,7 @@ #include "../environment.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/functions.c b/src/compile/functions.c index f62e00f8..a3c25c37 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -10,9 +10,9 @@ #include "../stdlib/optionals.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" #include "../typecheck.h" #include "../types.h" +#include "../util.h" #include "compilation.h" public @@ -633,8 +633,7 @@ static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { // Global/file scoped vars are okay to mutate without reading if (get_binding(env, entry->name) != NULL) continue; ast_t *var = Table$str_get(assigned_vars, entry->name); - assert(var); - code_err(var, "This variable was assigned to, but never read from."); + if (var) code_err(var, "This variable was assigned to, but never read from."); } } @@ -684,8 +683,9 @@ Text_t compile_lambda(env_t *env, ast_t *ast) { code = Texts(code, "void *_)"); userdata = Text("NULL"); } else { - userdata = Texts("new(", name, "$userdata_t"); + userdata = Texts("heap(((", name, "$userdata_t){"); for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + if (i > 0) userdata = Text$concat(userdata, Text(", ")); struct { const char *name; binding_t *b; @@ -694,11 +694,11 @@ Text_t compile_lambda(env_t *env, ast_t *ast) { binding_t *b = get_binding(env, entry->name); assert(b); Text_t binding_code = b->code; - if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); - else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); - else userdata = Texts(userdata, ", ", binding_code); + if (entry->b->type->tag == ListType) userdata = Texts(userdata, "LIST_COPY(", binding_code, ")"); + else if (entry->b->type->tag == TableType) userdata = Texts(userdata, "TABLE_COPY(", binding_code, ")"); + else userdata = Texts(userdata, binding_code); } - userdata = Texts(userdata, ")"); + userdata = Texts(userdata, "}))"); code = Texts(code, name, "$userdata_t *userdata)"); } diff --git a/src/compile/functions.c.orig b/src/compile/functions.c.orig new file mode 100644 index 00000000..d62eb4f7 --- /dev/null +++ b/src/compile/functions.c.orig @@ -0,0 +1,955 @@ +// This file defines how to compile functions + +#include "../ast.h" +#include "../environment.h" +#include "../naming.h" +#include "../stdlib/c_strings.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/integers.h" +#include "../stdlib/nums.h" +#include "../stdlib/optionals.h" +#include "../stdlib/tables.h" +#include "../stdlib/text.h" +#include "../typecheck.h" +#include "../types.h" +#include "../util.h" +#include "compilation.h" + +public +Text_t compile_function_declaration(env_t *env, ast_t *ast) { + DeclareMatch(fndef, ast, FunctionDef); + const char *decl_name = Match(fndef->name, Var)->name; + bool is_private = decl_name[0] == '_'; + if (is_private) return EMPTY_TEXT; + Text_t arg_signature = Text("("); + for (arg_ast_t *arg = fndef->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + arg_signature = Texts(arg_signature, compile_declaration(arg_type, Texts("_$", arg->name))); + if (arg->next) arg_signature = Texts(arg_signature, ", "); + } + arg_signature = Texts(arg_signature, ")"); + + type_t *ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType); + Text_t ret_type_code = compile_type(ret_t); + if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code); + Text_t name = namespace_name(env, env->namespace, Text$from_str(decl_name)); + if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name)) + name = namespace_name(env, env->namespace, Texts(get_line_number(ast->file, ast->start))); + return Texts(ret_type_code, " ", name, arg_signature, ";\n"); +} + +public +Text_t compile_convert_declaration(env_t *env, ast_t *ast) { + DeclareMatch(def, ast, ConvertDef); + + Text_t arg_signature = Text("("); + for (arg_ast_t *arg = def->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + arg_signature = Texts(arg_signature, compile_declaration(arg_type, Texts("_$", arg->name))); + if (arg->next) arg_signature = Texts(arg_signature, ", "); + } + arg_signature = Texts(arg_signature, ")"); + + type_t *ret_t = def->ret_type ? parse_type_ast(env, def->ret_type) : Type(VoidType); + Text_t ret_type_code = compile_type(ret_t); + Text_t name = Text$from_str(get_type_name(ret_t)); + if (name.length == 0) + code_err(ast, + "Conversions are only supported for text, struct, and enum " + "types, not ", + type_to_text(ret_t)); + Text_t name_code = namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start))); + return Texts(ret_type_code, " ", name_code, arg_signature, ";\n"); +} + +public +Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args) { + Table_t used_args = EMPTY_TABLE; + Text_t code = EMPTY_TEXT; + env_t *default_scope = new (env_t); + *default_scope = *env; + default_scope->locals = new (Table_t, .fallback = env->namespace_bindings ? env->namespace_bindings : env->globals); + for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) { + int64_t i = 1; + // Find keyword: + assert(spec_arg->name); + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (!call_arg->name) continue; + if (!(streq(call_arg->name, spec_arg->name) || (spec_arg->alias && streq(call_arg->name, spec_arg->alias)))) + continue; + + Text_t value; + if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { + value = compile_int_to_type(env, call_arg->value, spec_arg->type); + } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { + OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); + if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); + if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) + value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); + else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); + } else { + env_t *arg_env = with_enum_scope(env, spec_arg->type); + value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); + } + Table$str_set(&used_args, call_arg->name, call_arg); + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, value); + goto found_it; + } + + // Find positional: + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name) continue; + const char *pseudoname = String(i++); + if (!Table$str_get(used_args, pseudoname)) { + Text_t value; + if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { + value = compile_int_to_type(env, call_arg->value, spec_arg->type); + } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { + OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); + if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); + if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) + value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); + else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); + } else { + env_t *arg_env = with_enum_scope(env, spec_arg->type); + value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); + } + + Table$str_set(&used_args, pseudoname, call_arg); + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, value); + goto found_it; + } + } + + if (spec_arg->default_val) { + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, compile_maybe_incref(default_scope, spec_arg->default_val, get_arg_type(env, spec_arg))); + goto found_it; + } + + assert(spec_arg->name); + code_err(call_ast, "The required argument '", spec_arg->name, "' was not provided"); + found_it: + continue; + } + + int64_t i = 1; + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name) { + if (!Table$str_get(used_args, call_arg->name)) + code_err(call_arg->value, "There is no argument with the name '", call_arg->name, "'"); + } else { + const char *pseudoname = String(i++); + if (!Table$str_get(used_args, pseudoname)) code_err(call_arg->value, "This is one argument too many!"); + } + } + return code; +} + +public +Text_t compile_function_call(env_t *env, ast_t *ast) { + DeclareMatch(call, ast, FunctionCall); + type_t *fn_t = get_type(env, call->fn); + if (fn_t->tag == FunctionType) { + Text_t fn = compile(env, call->fn); + if (!is_valid_call(env, Match(fn_t, FunctionType)->args, call->args, (call_opts_t){.promotion = true})) { + if (is_valid_call(env, Match(fn_t, FunctionType)->args, call->args, + (call_opts_t){.promotion = true, .underscores = true})) { + code_err(ast, "You can't pass underscore arguments to this function as positional arguments. You must " + "use keyword arguments."); + } else { + arg_t *args = NULL; + for (arg_ast_t *a = call->args; a; a = a->next) + args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args); + REVERSE_LIST(args); + code_err(ast, + "This function's signature doesn't match this call site. \n" + " The function takes these args: (", + arg_types_to_text(Match(fn_t, FunctionType)->args, ", "), + ") \n" + " But it's being called with: (", + arg_types_to_text(args, ", "), ")"); + } + } + return Texts(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")"); + } else if (fn_t->tag == TypeInfoType) { + type_t *t = Match(fn_t, TypeInfoType)->type; + + // Literal constructors for numeric types like `Byte(123)` should + // not go through any conversion, just a cast: + if (is_numeric_type(t) && call->args && !call->args->next && call->args->value->tag == Int) + return compile_to_type(env, call->args->value, t); + else if (t->tag == NumType && call->args && !call->args->next && call->args->value->tag == Num) + return compile_to_type(env, call->args->value, t); + + binding_t *constructor = + get_constructor(env, t, call->args, env->current_type != NULL && type_eq(env->current_type, t)); + if (constructor) { + arg_t *arg_spec = Match(constructor->type, FunctionType)->args; + return Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, call->args), ")"); + } + + if (t->tag == TextType) { + if (!call->args) code_err(ast, "This constructor needs a value"); + if (!type_eq(t, TEXT_TYPE)) + code_err(call->fn, "I don't have a constructor defined for " + "these arguments"); + // Text constructor: + if (!call->args || call->args->next) code_err(call->fn, "This constructor takes exactly 1 argument"); + type_t *actual = call->args ? get_type(env, call->args->value) : NULL; + if (type_eq(actual, t)) return compile(env, call->args->value); + return expr_as_text(compile(env, call->args->value), actual, Text("no")); + } else if (t->tag == CStringType) { + // C String constructor: + if (!call->args || call->args->next) code_err(call->fn, "This constructor takes exactly 1 argument"); + if (call->args->value->tag == TextLiteral) + return compile_text_literal(Match(call->args->value, TextLiteral)->text); + else if (call->args->value->tag == TextJoin && Match(call->args->value, TextJoin)->children == NULL) + return Text("\"\""); + else if (call->args->value->tag == TextJoin && Match(call->args->value, TextJoin)->children->next == NULL) + return compile_text_literal( + Match(Match(call->args->value, TextJoin)->children->ast, TextLiteral)->text); + type_t *actual = call->args ? get_type(env, call->args->value) : NULL; + return Texts("Text$as_c_string(", expr_as_text(compile(env, call->args->value), actual, Text("no")), ")"); + } else if (t->tag == StructType) { + return compile_struct_literal(env, ast, t, call->args); + } + code_err(ast, + "I could not find a constructor matching these arguments " + "for ", + type_to_text(t)); + } else if (fn_t->tag == ClosureType) { + fn_t = Match(fn_t, ClosureType)->fn; + arg_t *type_args = Match(fn_t, FunctionType)->args; + + arg_t *closure_fn_args = NULL; + for (arg_t *arg = Match(fn_t, FunctionType)->args; arg; arg = arg->next) + closure_fn_args = new (arg_t, .name = arg->name, .type = arg->type, .default_val = arg->default_val, + .next = closure_fn_args); + closure_fn_args = new (arg_t, .name = "userdata", .type = Type(PointerType, .pointed = Type(MemoryType)), + .next = closure_fn_args); + REVERSE_LIST(closure_fn_args); + Text_t fn_type_code = + compile_type(Type(FunctionType, .args = closure_fn_args, .ret = Match(fn_t, FunctionType)->ret)); + + Text_t closure = compile(env, call->fn); + Text_t arg_code = compile_arguments(env, ast, type_args, call->args); + if (arg_code.length > 0) arg_code = Texts(arg_code, ", "); + if (call->fn->tag == Var) { + return Texts("((", fn_type_code, ")", closure, ".fn)(", arg_code, closure, ".userdata)"); + } else { + return Texts("({ Closure_t closure = ", closure, "; ((", fn_type_code, ")closure.fn)(", arg_code, + "closure.userdata); })"); + } + } else { + code_err(call->fn, "This is not a function, it's a ", type_to_text(fn_t)); + } +} + +static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t *env, ast_t *ast) { + if (ast == NULL) return; + + switch (ast->tag) { + case Var: { + binding_t *b = get_binding(enclosing_scope, Match(ast, Var)->name); + if (b) { + binding_t *shadow = get_binding(env, Match(ast, Var)->name); + if (!shadow || shadow == b) Table$str_set(closed_vars, Match(ast, Var)->name, b); + } + break; + } + case TextJoin: { + for (ast_list_t *child = Match(ast, TextJoin)->children; child; child = child->next) + add_closed_vars(closed_vars, enclosing_scope, env, child->ast); + break; + } + case Declare: { + ast_t *value = Match(ast, Declare)->value; + add_closed_vars(closed_vars, enclosing_scope, env, value); + bind_statement(env, ast); + break; + } + case Assign: { + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) + add_closed_vars(closed_vars, enclosing_scope, env, target->ast); + for (ast_list_t *value = Match(ast, Assign)->values; value; value = value->next) + add_closed_vars(closed_vars, enclosing_scope, env, value->ast); + break; + } + case BINOP_CASES: { + binary_operands_t binop = BINARY_OPERANDS(ast); + add_closed_vars(closed_vars, enclosing_scope, env, binop.lhs); + add_closed_vars(closed_vars, enclosing_scope, env, binop.rhs); + break; + } + case Not: + case Negative: + case HeapAllocate: + case StackReference: { + // UNSAFE: + ast_t *value = ast->__data.Not.value; + // END UNSAFE + add_closed_vars(closed_vars, enclosing_scope, env, value); + break; + } + case Min: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->lhs); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->rhs); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Min)->key); + break; + } + case Max: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Max)->lhs); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Max)->rhs); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Max)->key); + break; + } + case List: { + for (ast_list_t *item = Match(ast, List)->items; item; item = item->next) + add_closed_vars(closed_vars, enclosing_scope, env, item->ast); + break; + } + case Table: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Table)->default_value); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Table)->fallback); + for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next) + add_closed_vars(closed_vars, enclosing_scope, env, entry->ast); + break; + } + case TableEntry: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, TableEntry)->key); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, TableEntry)->value); + break; + } + case Comprehension: { + DeclareMatch(comp, ast, Comprehension); + if (comp->expr->tag == Comprehension) { // Nested comprehension + ast_t *body = comp->filter ? WrapAST(ast, If, .condition = comp->filter, .body = comp->expr) : comp->expr; + ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); + return add_closed_vars(closed_vars, enclosing_scope, env, loop); + } + + // List/Table comprehension: + ast_t *body = comp->expr; + if (comp->filter) body = WrapAST(comp->expr, If, .condition = comp->filter, .body = body); + ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .iter = comp->iter, .body = body); + add_closed_vars(closed_vars, enclosing_scope, env, loop); + break; + } + case Lambda: { + DeclareMatch(lambda, ast, Lambda); + env_t *lambda_scope = fresh_scope(env); + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) + set_binding(lambda_scope, arg->name, get_arg_ast_type(env, arg), Texts("_$", arg->name)); + add_closed_vars(closed_vars, enclosing_scope, lambda_scope, lambda->body); + break; + } + case FunctionCall: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, FunctionCall)->fn); + for (arg_ast_t *arg = Match(ast, FunctionCall)->args; arg; arg = arg->next) + add_closed_vars(closed_vars, enclosing_scope, env, arg->value); + break; + } + case MethodCall: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, MethodCall)->self); + for (arg_ast_t *arg = Match(ast, MethodCall)->args; arg; arg = arg->next) + add_closed_vars(closed_vars, enclosing_scope, env, arg->value); + break; + } + case Block: { + env = fresh_scope(env); + for (ast_list_t *statement = Match(ast, Block)->statements; statement; statement = statement->next) + add_closed_vars(closed_vars, enclosing_scope, env, statement->ast); + break; + } + case For: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, For)->iter); + env_t *body_scope = for_scope(env, ast); + add_closed_vars(closed_vars, enclosing_scope, body_scope, Match(ast, For)->body); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, For)->empty); + break; + } + case While: { + DeclareMatch(while_, ast, While); + add_closed_vars(closed_vars, enclosing_scope, env, while_->condition); + env_t *scope = fresh_scope(env); + add_closed_vars(closed_vars, enclosing_scope, scope, while_->body); + break; + } + case If: { + DeclareMatch(if_, ast, If); + ast_t *condition = if_->condition; + if (condition->tag == Declare) { + env_t *truthy_scope = fresh_scope(env); + bind_statement(truthy_scope, condition); + if (!Match(condition, Declare)->value) + code_err(condition, "This declared variable must have an initial value"); + add_closed_vars(closed_vars, enclosing_scope, env, Match(condition, Declare)->value); + ast_t *var = Match(condition, Declare)->var; + type_t *cond_t = get_type(truthy_scope, var); + if (cond_t->tag == OptionalType) { + set_binding(truthy_scope, Match(var, Var)->name, Match(cond_t, OptionalType)->type, EMPTY_TEXT); + } + add_closed_vars(closed_vars, enclosing_scope, truthy_scope, if_->body); + add_closed_vars(closed_vars, enclosing_scope, env, if_->else_body); + } else { + add_closed_vars(closed_vars, enclosing_scope, env, condition); + env_t *truthy_scope = env; + type_t *cond_t = get_type(env, condition); + if (condition->tag == Var && cond_t->tag == OptionalType) { + truthy_scope = fresh_scope(env); + set_binding(truthy_scope, Match(condition, Var)->name, Match(cond_t, OptionalType)->type, EMPTY_TEXT); + } + add_closed_vars(closed_vars, enclosing_scope, truthy_scope, if_->body); + add_closed_vars(closed_vars, enclosing_scope, env, if_->else_body); + } + break; + } + case When: { + DeclareMatch(when, ast, When); + add_closed_vars(closed_vars, enclosing_scope, env, when->subject); + type_t *subject_t = get_type(env, when->subject); + + if (subject_t->tag != EnumType) { + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + add_closed_vars(closed_vars, enclosing_scope, env, clause->pattern); + add_closed_vars(closed_vars, enclosing_scope, env, clause->body); + } + + if (when->else_body) add_closed_vars(closed_vars, enclosing_scope, env, when->else_body); + return; + } + + DeclareMatch(enum_t, subject_t, EnumType); + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + const char *clause_tag_name; + if (clause->pattern->tag == Var) clause_tag_name = Match(clause->pattern, Var)->name; + else if (clause->pattern->tag == FunctionCall && Match(clause->pattern, FunctionCall)->fn->tag == Var) + clause_tag_name = Match(Match(clause->pattern, FunctionCall)->fn, Var)->name; + else code_err(clause->pattern, "This is not a valid pattern for a ", type_to_text(subject_t), " enum"); + + type_t *tag_type = NULL; + for (tag_t *tag = enum_t->tags; tag; tag = tag->next) { + if (streq(tag->name, clause_tag_name)) { + tag_type = tag->type; + break; + } + } + assert(tag_type); + env_t *scope = when_clause_scope(env, subject_t, clause); + add_closed_vars(closed_vars, enclosing_scope, scope, clause->body); + } + if (when->else_body) add_closed_vars(closed_vars, enclosing_scope, env, when->else_body); + break; + } + case Repeat: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Repeat)->body); + break; + } + case Reduction: { + DeclareMatch(reduction, ast, Reduction); + static int64_t next_id = 1; + ast_t *item = FakeAST(Var, String("$it", next_id++)); + ast_t *loop = + FakeAST(For, .vars = new (ast_list_t, .ast = item), .iter = reduction->iter, .body = FakeAST(Pass)); + env_t *scope = for_scope(env, loop); + add_closed_vars(closed_vars, enclosing_scope, scope, reduction->key ? reduction->key : item); + break; + } + case Defer: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Defer)->body); + break; + } + case Return: { + ast_t *ret = Match(ast, Return)->value; + if (ret) add_closed_vars(closed_vars, enclosing_scope, env, ret); + break; + } + case Index: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Index)->indexed); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Index)->index); + break; + } + case FieldAccess: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, FieldAccess)->fielded); + break; + } + case NonOptional: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, NonOptional)->value); + break; + } + case DebugLog: { + for (ast_list_t *value = Match(ast, DebugLog)->values; value; value = value->next) + add_closed_vars(closed_vars, enclosing_scope, env, value->ast); + break; + } + case Assert: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Assert)->expr); + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Assert)->message); + break; + } + case ExplicitlyTyped: { + add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, ExplicitlyTyped)->ast); + break; + } + case Use: + case FunctionDef: + case ConvertDef: + case StructDef: + case EnumDef: + case LangDef: errx(1, "Definitions should not be reachable in a closure."); + default: break; + } +} + +public +Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) { + env_t *body_scope = fresh_scope(env); + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + Table_t closed_vars = EMPTY_TABLE; + add_closed_vars(&closed_vars, env, body_scope, block); + return closed_vars; +} + +static visit_behavior_t find_used_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Var: { + const char *name = Match(ast, Var)->name; + Table$str_set(vars, name, ast); + return VISIT_STOP; + } + case Assign: { + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) { + ast_t *index = Match(var, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + var = Match(var, Index)->indexed; + } else if (var->tag == FieldAccess) { + var = Match(var, FieldAccess)->fielded; + } else { + break; + } + } + } + for (ast_list_t *val = Match(ast, Assign)->values; val; val = val->next) { + ast_visit(val->ast, find_used_variables, userdata); + } + return VISIT_STOP; + } + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *lhs = operands.lhs; + for (;;) { + if (lhs->tag == Index) { + ast_t *index = Match(lhs, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + lhs = Match(lhs, Index)->indexed; + } else if (lhs->tag == FieldAccess) { + lhs = Match(lhs, FieldAccess)->fielded; + } else { + break; + } + } + ast_visit(operands.rhs, find_used_variables, userdata); + return VISIT_STOP; + } + case Declare: { + ast_visit(Match(ast, Declare)->value, find_used_variables, userdata); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static visit_behavior_t find_assigned_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Assign: + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + } + return VISIT_STOP; + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *var = operands.lhs; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + return VISIT_STOP; + } + case Declare: { + ast_t *var = Match(ast, Declare)->var; + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { + Table_t used_vars = EMPTY_TABLE; + ast_visit(body, find_used_variables, &used_vars); + Table_t assigned_vars = EMPTY_TABLE; + ast_visit(body, find_assigned_variables, &assigned_vars); + + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + if (arg_type->tag == PointerType) { + Table$str_remove(&assigned_vars, arg->name); + } + } + + Table_t unused = Table$without(assigned_vars, used_vars, Table$info(&CString$info, &Present$$info)); + for (int64_t i = 0; i < (int64_t)unused.entries.length; i++) { + struct { + const char *name; + } *entry = unused.entries.data + i * unused.entries.stride; + if (streq(entry->name, "_")) continue; + // Global/file scoped vars are okay to mutate without reading + if (get_binding(env, entry->name) != NULL) continue; + ast_t *var = Table$str_get(assigned_vars, entry->name); +<<<<<<< HEAD + if (var) code_err(var, "This variable was assigned to, but never read from."); +||||||| 0f9af5f4 + code_err(var, "This variable was assigned to, but never read from."); +======= + assert(var); + code_err(var, "This variable was assigned to, but never read from."); +>>>>>>> dev + } +} + +public +Text_t compile_lambda(env_t *env, ast_t *ast) { + DeclareMatch(lambda, ast, Lambda); + Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); + + env_t *body_scope = fresh_scope(env); + body_scope->deferred = NULL; + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + body_scope->fn = ast; + + Table_t closed_vars = get_closed_vars(env, lambda->args, ast); + if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata + Text_t def = Text("typedef struct {"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (has_stack_memory(entry->b->type)) + code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), + " stack memory in the variable `", entry->name, + "`, but the function may outlive the stack memory"); + if (entry->b->type->tag == ModuleType) continue; + set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); + def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); + } + def = Texts(def, "} ", name, "$userdata_t;"); + env->code->local_typedefs = Texts(env->code->local_typedefs, def); + } + + type_t *ret_t = get_function_return_type(env, ast); + Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); + } + + Text_t userdata; + if (Table$length(closed_vars) == 0) { + code = Texts(code, "void *_)"); + userdata = Text("NULL"); + } else { + userdata = Texts("heap(((", name, "$userdata_t){"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + if (i > 0) userdata = Text$concat(userdata, Text(", ")); + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (entry->b->type->tag == ModuleType) continue; + binding_t *b = get_binding(env, entry->name); + assert(b); + Text_t binding_code = b->code; + if (entry->b->type->tag == ListType) userdata = Texts(userdata, "LIST_COPY(", binding_code, ")"); + else if (entry->b->type->tag == TableType) userdata = Texts(userdata, "TABLE_COPY(", binding_code, ")"); + else userdata = Texts(userdata, binding_code); + } + userdata = Texts(userdata, "}))"); + code = Texts(code, name, "$userdata_t *userdata)"); + } + + Text_t body = EMPTY_TEXT; + for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType + || get_type(body_scope, stmt->ast)->tag == ReturnType) + body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); + else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); + bind_statement(body_scope, stmt->ast); + } + if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) + body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); + + env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); + + check_unused_vars(env, lambda->args, lambda->body); + + return Texts("((Closure_t){", name, ", ", userdata, "})"); +} + +public +Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) { + bool is_private = false; + const char *function_name; + arg_ast_t *args; + type_t *ret_t = get_function_return_type(env, ast); + ast_t *body; + ast_t *cache; + bool is_inline; + if (ast->tag == FunctionDef) { + DeclareMatch(fndef, ast, FunctionDef); + function_name = Match(fndef->name, Var)->name; + is_private = function_name[0] == '_'; + args = fndef->args; + body = fndef->body; + cache = fndef->cache; + is_inline = fndef->is_inline; + } else { + DeclareMatch(convertdef, ast, ConvertDef); + args = convertdef->args; + function_name = get_type_name(ret_t); + if (!function_name) + code_err(ast, + "Conversions are only supported for text, struct, and enum " + "types, not ", + type_to_text(ret_t)); + body = convertdef->body; + cache = convertdef->cache; + is_inline = convertdef->is_inline; + } + + Text_t arg_signature = Text("("); + Table_t used_names = EMPTY_TABLE; + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + arg_signature = Texts(arg_signature, compile_declaration(arg_type, Texts("_$", arg->name))); + if (arg->next) arg_signature = Texts(arg_signature, ", "); + if (Table$str_get(used_names, arg->name)) + code_err(ast, "The argument name '", arg->name, "' is used more than once"); + Table$str_set(&used_names, arg->name, arg->name); + } + arg_signature = Texts(arg_signature, ")"); + + Text_t ret_type_code = compile_type(ret_t); + if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code); + + if (is_private) *staticdefs = Texts(*staticdefs, "static ", ret_type_code, " ", name_code, arg_signature, ";\n"); + + Text_t code; + if (cache) { + code = Texts("static ", ret_type_code, " ", name_code, "$uncached", arg_signature); + } else { + code = Texts(ret_type_code, " ", name_code, arg_signature); + if (is_inline) code = Texts("INLINE ", code); + if (!is_private) code = Texts("public ", code); + } + + env_t *body_scope = fresh_scope(env); + while (body_scope->namespace) { + body_scope->locals->fallback = body_scope->locals->fallback->fallback; + body_scope->namespace = body_scope->namespace->parent; + } + + body_scope->deferred = NULL; + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + body_scope->fn = ast; + + type_t *body_type = get_type(body_scope, body); + if (ret_t->tag == AbortType) { + if (body_type->tag != AbortType) code_err(ast, "This function can reach the end without aborting!"); + } else if (ret_t->tag == VoidType) { + if (body_type->tag == AbortType) + code_err(ast, "This function will always abort before it reaches the " + "end, but it's declared as having a Void return. It should " + "be declared as an Abort return instead."); + } else { + if (body_type->tag != ReturnType && body_type->tag != AbortType) + code_err(ast, + "This function looks like it can reach the end without " + "returning a ", + type_to_text(ret_t), + " value! \n " + "If this is not the case, please add a call to " + "`fail(\"Unreachable\")` at the end of the function to " + "help the " + "compiler out."); + } + + Text_t body_code = Texts("{\n", compile_inline_block(body_scope, body), "}\n"); + Text_t definition = with_source_info(env, ast, Texts(code, " ", body_code, "\n")); + + if (cache && args == NULL) { // no-args cache just uses a static var + Text_t wrapper = + Texts(is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, + "(void) {\n" + "static ", + compile_declaration(ret_t, Text("cached_result")), ";\n", "static bool initialized = false;\n", + "if (!initialized) {\n" + "\tcached_result = ", + name_code, "$uncached();\n", "\tinitialized = true;\n", "}\n", + "return cached_result;\n" + "}\n"); + definition = Texts(definition, wrapper); + } else if (cache && cache->tag == Int) { + assert(args); + OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NONE_INT, NULL); + Text_t pop_code = EMPTY_TEXT; + if (cache->tag == Int && cache_size.has_value && cache_size.value > 0) { + // FIXME: this currently just deletes the first entry, but this + // should be more like a least-recently-used cache eviction policy + // or least-frequently-used + pop_code = Texts("if (cache.entries.length > ", cache_size.value, + ") Table$remove(&cache, cache.entries.data + " + "cache.entries.stride*0, table_type);\n"); + } + + if (!args->next) { + // Single-argument functions have simplified caching logic + type_t *arg_type = get_arg_ast_type(env, args); + Text_t wrapper = + Texts(is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, arg_signature, + "{\n" + "static Table_t cache = EMPTY_TABLE;\n", + "const TypeInfo_t *table_type = Table$info(", compile_type_info(arg_type), ", ", + compile_type_info(ret_t), ");\n", + compile_declaration(Type(PointerType, .pointed = ret_t), Text("cached")), + " = Table$get_raw(cache, &_$", args->name, + ", table_type);\n" + "if (cached) return *cached;\n", + compile_declaration(ret_t, Text("ret")), " = ", name_code, "$uncached(_$", args->name, ");\n", + pop_code, "Table$set(&cache, &_$", args->name, + ", &ret, table_type);\n" + "return ret;\n" + "}\n"); + definition = Texts(definition, wrapper); + } else { + // Multi-argument functions use a custom struct type (only defined + // internally) as a cache key: + arg_t *fields = NULL; + for (arg_ast_t *arg = args; arg; arg = arg->next) + fields = new (arg_t, .name = arg->name, .type = get_arg_ast_type(env, arg), .next = fields); + REVERSE_LIST(fields); + type_t *t = Type(StructType, .name = String("func$", get_line_number(ast->file, ast->start), "$args"), + .fields = fields, .env = env); + + int64_t num_fields = (int64_t)used_names.entries.length; + const char *metamethods = is_packed_data(t) ? "PackedData$metamethods" : "Struct$metamethods"; + Text_t args_typeinfo = Texts("((TypeInfo_t[1]){{.size=sizeof(args), " + ".align=__alignof__(args), .metamethods=", + metamethods, + ", .tag=StructInfo, " + ".StructInfo.name=\"FunctionArguments\", " + ".StructInfo.num_fields=", + num_fields, ", .StructInfo.fields=(NamedType_t[", num_fields, "]){"); + Text_t args_type = Text("struct { "); + for (arg_t *f = fields; f; f = f->next) { + args_typeinfo = Texts(args_typeinfo, "{\"", f->name, "\", ", compile_type_info(f->type), "}"); + args_type = Texts(args_type, compile_declaration(f->type, Text$from_str(f->name)), "; "); + if (f->next) args_typeinfo = Texts(args_typeinfo, ", "); + } + args_type = Texts(args_type, "}"); + args_typeinfo = Texts(args_typeinfo, "}}})"); + + Text_t all_args = EMPTY_TEXT; + for (arg_ast_t *arg = args; arg; arg = arg->next) + all_args = Texts(all_args, "_$", arg->name, arg->next ? Text(", ") : EMPTY_TEXT); + + Text_t wrapper = Texts( + is_private ? EMPTY_TEXT : Text("public "), ret_type_code, " ", name_code, arg_signature, + "{\n" + "static Table_t cache = EMPTY_TABLE;\n", + args_type, " args = {", all_args, + "};\n" + "const TypeInfo_t *table_type = Table$info(", + args_typeinfo, ", ", compile_type_info(ret_t), ");\n", + compile_declaration(Type(PointerType, .pointed = ret_t), Text("cached")), + " = Table$get_raw(cache, &args, table_type);\n" + "if (cached) return *cached;\n", + compile_declaration(ret_t, Text("ret")), " = ", name_code, "$uncached(", all_args, ");\n", pop_code, + "Table$set(&cache, &args, &ret, table_type);\n" + "return ret;\n" + "}\n"); + definition = Texts(definition, wrapper); + } + } + + check_unused_vars(env, args, body); + + return definition; +} + +public +Text_t compile_method_call(env_t *env, ast_t *ast) { + DeclareMatch(call, ast, MethodCall); + type_t *self_t = get_type(env, call->self); + type_t *self_value_t = value_type(self_t); + if (self_value_t->tag == TypeInfoType || self_value_t->tag == ModuleType) { + return compile(env, WrapAST(ast, FunctionCall, + .fn = WrapAST(call->self, FieldAccess, .fielded = call->self, .field = call->name), + .args = call->args)); + } + + type_t *field_type = get_field_type(self_value_t, call->name); + if (field_type && field_type->tag == ClosureType) field_type = Match(field_type, ClosureType)->fn; + if (field_type && field_type->tag == FunctionType) + return compile(env, WrapAST(ast, FunctionCall, + .fn = WrapAST(call->self, FieldAccess, .fielded = call->self, .field = call->name), + .args = call->args)); + + switch (self_value_t->tag) { + case ListType: return compile_list_method_call(env, ast); + case TableType: return compile_table_method_call(env, ast); + default: { + DeclareMatch(methodcall, ast, MethodCall); + type_t *fn_t = get_method_type(env, methodcall->self, methodcall->name); + arg_ast_t *args = new (arg_ast_t, .value = methodcall->self, .next = methodcall->args); + binding_t *b = get_namespace_binding(env, methodcall->self, methodcall->name); + if (!b) code_err(ast, "No such method"); + return Texts(b->code, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, args), ")"); + } + } +} diff --git a/src/compile/indexing.c b/src/compile/indexing.c index 031ef9a0..447b1a4e 100644 --- a/src/compile/indexing.c +++ b/src/compile/indexing.c @@ -6,7 +6,7 @@ #include "../config.h" #include "../environment.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/integers.c b/src/compile/integers.c index 78d48b70..0e89f3dd 100644 --- a/src/compile/integers.c +++ b/src/compile/integers.c @@ -7,9 +7,9 @@ #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" #include "../typecheck.h" #include "../types.h" +#include "../util.h" #include "compilation.h" public @@ -35,7 +35,11 @@ Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target) { if (int_val.small == 0) code_err(ast, "Failed to parse this integer"); mpz_t i; - mpz_init_set_int(i, int_val); + if likely (int_val.small & 1L) { + mpz_init_set_si(i, int_val.small >> 2L); + } else { + mpz_init_set(i, int_val.big); + } char *c_literal; if (strncmp(literal, "0x", 2) == 0 || strncmp(literal, "0X", 2) == 0 || strncmp(literal, "0b", 2) == 0) { @@ -86,7 +90,12 @@ Text_t compile_int(ast_t *ast) { OptionalInt_t int_val = Int$from_str(str); if (int_val.small == 0) code_err(ast, "Failed to parse this integer"); mpz_t i; - mpz_init_set_int(i, int_val); + if likely (int_val.small & 1L) { + mpz_init_set_si(i, int_val.small >> 2L); + } else { + mpz_init_set(i, int_val.big); + } + if (mpz_cmpabs_ui(i, BIGGEST_SMALL_INT) <= 0) { return Texts("I_small(", str, ")"); } else if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0) { diff --git a/src/compile/lists.c b/src/compile/lists.c index 31255c1e..d0d00473 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -9,7 +9,7 @@ #include "../config.h" #include "../environment.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/loops.c b/src/compile/loops.c index 96299c5e..d818bbe4 100644 --- a/src/compile/loops.c +++ b/src/compile/loops.c @@ -8,8 +8,8 @@ #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" #include "../typecheck.h" +#include "../util.h" #include "compilation.h" public @@ -236,7 +236,11 @@ Text_t compile_for_loop(env_t *env, ast_t *ast) { Int_t int_val = Int$from_str(str); if (int_val.small == 0) code_err(for_->iter, "Failed to parse this integer"); mpz_t i; - mpz_init_set_int(i, int_val); + if likely (int_val.small & 1L) { + mpz_init_set_si(i, int_val.small >> 2L); + } else { + mpz_init_set(i, int_val.big); + } if (mpz_cmpabs_ui(i, BIGGEST_SMALL_INT) <= 0) n = Text$from_str(mpz_get_str(NULL, 10, i)); else goto big_n; diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 75dff935..e0013375 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -4,7 +4,7 @@ #include "../naming.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "../types.h" #include "compilation.h" diff --git a/src/compile/reductions.c b/src/compile/reductions.c index 159158e3..eddbddb7 100644 --- a/src/compile/reductions.c +++ b/src/compile/reductions.c @@ -4,7 +4,7 @@ #include "../config.h" #include "../environment.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/compile/statements.c b/src/compile/statements.c index 13dcc064..755c9a09 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -12,8 +12,8 @@ #include "../stdlib/print.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" #include "../typecheck.h" +#include "../util.h" #include "compilation.h" typedef ast_t *(*comprehension_body_t)(ast_t *, ast_t *); diff --git a/src/compile/text.h b/src/compile/text.h index ae3cc5c3..6aefeeb8 100644 --- a/src/compile/text.h +++ b/src/compile/text.h @@ -6,7 +6,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../types.h" Text_t compile_text_ast(env_t *env, ast_t *ast); diff --git a/src/compile/types.c b/src/compile/types.c index aac27f4c..2f22e41c 100644 --- a/src/compile/types.c +++ b/src/compile/types.c @@ -6,7 +6,7 @@ #include "../naming.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "compilation.h" public diff --git a/src/compile/whens.c b/src/compile/whens.c index 618a667c..8cc8ae4f 100644 --- a/src/compile/whens.c +++ b/src/compile/whens.c @@ -6,7 +6,7 @@ #include "../naming.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "../typecheck.h" #include "compilation.h" diff --git a/src/environment.c b/src/environment.c index 0cbeb0fc..fb8bd766 100644 --- a/src/environment.c +++ b/src/environment.c @@ -10,8 +10,8 @@ #include "stdlib/datatypes.h" #include "stdlib/tables.h" #include "stdlib/text.h" -#include "stdlib/util.h" #include "typecheck.h" +#include "util.h" type_t *TEXT_TYPE = NULL; public @@ -132,11 +132,6 @@ env_t *global_env(bool source_mapping) { {"parse", "Int$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int?)"}, // {"plus", "Int$plus", "func(x,y:Int -> Int)"}, // {"power", "Int$power", "func(base:Int,exponent:Int -> Int)"}, // -#if __GNU_MP_VERSION >= 6 -#if __GNU_MP_VERSION_MINOR >= 3 - {"prev_prime", "Int$prev_prime", "func(x:Int -> Int?)"}, // -#endif -#endif {"right_shifted", "Int$right_shifted", "func(x,y:Int -> Int)"}, // {"sqrt", "Int$sqrt", "func(x:Int -> Int?)"}, // {"times", "Int$times", "func(x,y:Int -> Int)"}, // diff --git a/src/modules.c b/src/modules.c index c7c29d24..54b461f9 100644 --- a/src/modules.c +++ b/src/modules.c @@ -15,7 +15,7 @@ #include "stdlib/tables.h" #include "stdlib/text.h" #include "stdlib/types.h" -#include "stdlib/util.h" +#include "util.h" #define xsystem(...) \ ({ \ diff --git a/src/parse/binops.c b/src/parse/binops.c index ad3fff54..e095a1bc 100644 --- a/src/parse/binops.c +++ b/src/parse/binops.c @@ -2,7 +2,7 @@ #include <stdbool.h> #include "../ast.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "expressions.h" diff --git a/src/parse/containers.c b/src/parse/containers.c index 8f9922f3..416f562c 100644 --- a/src/parse/containers.c +++ b/src/parse/containers.c @@ -4,7 +4,7 @@ #include <stdbool.h> #include "../ast.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "expressions.h" diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c index 1087e20e..24de3cfe 100644 --- a/src/parse/controlflow.c +++ b/src/parse/controlflow.c @@ -4,7 +4,7 @@ #include <string.h> #include "../ast.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "controlflow.h" #include "errors.h" diff --git a/src/parse/expressions.c b/src/parse/expressions.c index 27e44129..c6a4f940 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -4,7 +4,7 @@ #include <string.h> #include "../ast.h" -#include "../stdlib/util.h" +#include "../util.h" #include "binops.h" #include "containers.h" #include "context.h" diff --git a/src/parse/files.c b/src/parse/files.c index f5d9554a..73f8fde5 100644 --- a/src/parse/files.c +++ b/src/parse/files.c @@ -12,7 +12,7 @@ #include "../stdlib/stdlib.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "expressions.h" diff --git a/src/parse/functions.c b/src/parse/functions.c index 8fb9f78e..742f29a6 100644 --- a/src/parse/functions.c +++ b/src/parse/functions.c @@ -12,7 +12,7 @@ #include "../formatter/utils.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "controlflow.h" #include "errors.h" diff --git a/src/parse/statements.c b/src/parse/statements.c index 24917a76..9c6f9e31 100644 --- a/src/parse/statements.c +++ b/src/parse/statements.c @@ -4,7 +4,7 @@ #include <stdbool.h> #include "../ast.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "expressions.h" diff --git a/src/parse/suffixes.c b/src/parse/suffixes.c index 85e20721..71844642 100644 --- a/src/parse/suffixes.c +++ b/src/parse/suffixes.c @@ -4,7 +4,7 @@ #include "../ast.h" #include "../stdlib/print.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "expressions.h" diff --git a/src/parse/text.c b/src/parse/text.c index e23b8417..d4c1f825 100644 --- a/src/parse/text.c +++ b/src/parse/text.c @@ -10,7 +10,7 @@ #include "../ast.h" #include "../stdlib/text.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "expressions.h" diff --git a/src/parse/typedefs.c b/src/parse/typedefs.c index 56bb687f..b00ee50d 100644 --- a/src/parse/typedefs.c +++ b/src/parse/typedefs.c @@ -4,7 +4,7 @@ #include <string.h> #include "../ast.h" -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #include "errors.h" #include "files.h" diff --git a/src/parse/utils.c b/src/parse/utils.c index 03e0ebcd..bdf59aff 100644 --- a/src/parse/utils.c +++ b/src/parse/utils.c @@ -7,7 +7,7 @@ #include <uniname.h> #include "../stdlib/tables.h" -#include "../stdlib/util.h" +#include "../util.h" #include "errors.h" #include "utils.h" diff --git a/src/parse/utils.h b/src/parse/utils.h index b8fb0756..9a6f0aac 100644 --- a/src/parse/utils.h +++ b/src/parse/utils.h @@ -3,7 +3,7 @@ #include <stdbool.h> -#include "../stdlib/util.h" +#include "../util.h" #include "context.h" #define SPACES_PER_INDENT 4 diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c index 8bffbaf1..7af1fa32 100644 --- a/src/stdlib/bigint.c +++ b/src/stdlib/bigint.c @@ -10,6 +10,7 @@ #include <stdio.h> #include <stdlib.h> +#include "../util.h" #include "datatypes.h" #include "integers.h" #include "optionals.h" @@ -18,12 +19,49 @@ #include "text.h" #include "types.h" +#define mpz_init_set_int(mpz, i) \ + do { \ + if likely ((i).small & 1L) mpz_init_set_si(mpz, (i).small >> 2L); \ + else mpz_init_set(mpz, (i).big); \ + } while (0) + +#define Int$from_mpz(mpz) \ + (mpz_cmpabs_ui(mpz, BIGGEST_SMALL_INT) <= 0 \ + ? ((Int_t){.small = (mpz_get_si(mpz) << 2L) | 1L}) \ + : ((Int_t){.big = memcpy(GC_MALLOC(sizeof(__mpz_struct)), mpz, sizeof(__mpz_struct))})) + +#define Int_mpz(i) (__mpz_struct *)((i).big) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif +public +PUREFUNC Int_t Int$from_num64(double n, bool truncate) { + mpz_t result; + mpz_init_set_d(result, n); + if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n); + return Int$from_mpz(result); +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +public +PUREFUNC Int_t Int$from_int64(int64_t i) { + if likely (i >= SMALLEST_SMALL_INT && i <= BIGGEST_SMALL_INT) return (Int_t){.small = (i << 2L) | 1L}; + mpz_t result; + mpz_init_set_si(result, i); + return Int$from_mpz(result); +} + public int Int$print(FILE *f, Int_t i) { if (likely(i.small & 1L)) { - return _print_int(f, (int64_t)((i.small) >> 2L)); + return Int64$print(f, (int64_t)((i.small) >> 2L)); } else { - return gmp_fprintf(f, "%Zd", i.big); + return gmp_fprintf(f, "%Zd", Int_mpz(i)); } } @@ -50,7 +88,7 @@ Text_t Int$value_as_text(Int_t i) { if (likely(i.small & 1L)) { return _int64_to_text(i.small >> 2L); } else { - char *str = mpz_get_str(NULL, 10, i.big); + char *str = mpz_get_str(NULL, 10, Int_mpz(i)); return Text$from_str(str); } } @@ -72,9 +110,9 @@ static bool Int$is_none(const void *i, const TypeInfo_t *info) { public PUREFUNC int32_t Int$compare_value(const Int_t x, const Int_t y) { if (likely(x.small & y.small & 1L)) return (x.small > y.small) - (x.small < y.small); - else if (x.small & 1) return -mpz_cmp_si(y.big, (x.small >> 2)); - else if (y.small & 1) return mpz_cmp_si(x.big, (y.small >> 2)); - else return x.big == y.big ? 0 : mpz_cmp(x.big, y.big); + else if (x.small & 1) return -mpz_cmp_si(Int_mpz(y), (x.small >> 2)); + else if (y.small & 1) return mpz_cmp_si(Int_mpz(x), (y.small >> 2)); + else return x.big == y.big ? 0 : mpz_cmp(Int_mpz(x), Int_mpz(y)); } public @@ -86,7 +124,7 @@ PUREFUNC int32_t Int$compare(const void *x, const void *y, const TypeInfo_t *inf public PUREFUNC bool Int$equal_value(const Int_t x, const Int_t y) { if (likely((x.small | y.small) & 1L)) return x.small == y.small; - else return x.big == y.big ? 0 : (mpz_cmp(x.big, y.big) == 0); + else return x.big == y.big ? 0 : (mpz_cmp(Int_mpz(x), Int_mpz(y)) == 0); } public @@ -114,7 +152,7 @@ PUREFUNC uint64_t Int$hash(const void *vx, const TypeInfo_t *info) { if (likely(x->small & 1L)) { return siphash24((void *)x, sizeof(Int_t)); } else { - char *str = mpz_get_str(NULL, 16, x->big); + char *str = mpz_get_str(NULL, 16, Int_mpz(*x)); return siphash24((void *)str, strlen(str)); } } @@ -128,7 +166,7 @@ Text_t Int$hex(Int_t i, Int_t digits_int, bool uppercase, bool prefix) { return Text$from_str(String( hex(u64, .no_prefix = !prefix, .digits = Int32$from_int(digits_int, false), .uppercase = uppercase))); } else { - char *str = mpz_get_str(NULL, 16, i.big); + char *str = mpz_get_str(NULL, 16, Int_mpz(i)); if (uppercase) { for (char *c = str; *c; c++) *c = (char)toupper(*c); @@ -153,7 +191,7 @@ Text_t Int$octal(Int_t i, Int_t digits_int, bool prefix) { return Text$from_str(String(oct(u64, .no_prefix = !prefix, .digits = Int32$from_int(digits_int, false)))); } else { int64_t digits = Int64$from_int(digits_int, false); - char *str = mpz_get_str(NULL, 8, i.big); + char *str = mpz_get_str(NULL, 8, Int_mpz(i)); int64_t needed_zeroes = digits - (int64_t)strlen(str); if (needed_zeroes <= 0) return prefix ? Text$concat(Text("0o"), Text$from_str(str)) : Text$from_str(str); @@ -172,7 +210,7 @@ Int_t Int$slow_plus(Int_t x, Int_t y) { if (y.small < 0L) mpz_sub_ui(result, result, (uint64_t)(-(y.small >> 2L))); else mpz_add_ui(result, result, (uint64_t)(y.small >> 2L)); } else { - mpz_add(result, result, y.big); + mpz_add(result, result, Int_mpz(y)); } return Int$from_mpz(result); } @@ -185,7 +223,7 @@ Int_t Int$slow_minus(Int_t x, Int_t y) { if (y.small < 0L) mpz_add_ui(result, result, (uint64_t)(-(y.small >> 2L))); else mpz_sub_ui(result, result, (uint64_t)(y.small >> 2L)); } else { - mpz_sub(result, result, y.big); + mpz_sub(result, result, Int_mpz(y)); } return Int$from_mpz(result); } @@ -195,7 +233,7 @@ Int_t Int$slow_times(Int_t x, Int_t y) { mpz_t result; mpz_init_set_int(result, x); if (y.small & 1L) mpz_mul_si(result, result, y.small >> 2L); - else mpz_mul(result, result, y.big); + else mpz_mul(result, result, Int_mpz(y)); return Int$from_mpz(result); } @@ -208,7 +246,7 @@ Int_t Int$slow_divided_by(Int_t dividend, Int_t divisor) { mpz_init_set_int(remainder, divisor); mpz_tdiv_qr(quotient, remainder, quotient, remainder); if (mpz_sgn(remainder) < 0) { - bool d_positive = likely(divisor.small & 1L) ? divisor.small > 0x1L : mpz_sgn(divisor.big) > 0; + bool d_positive = likely(divisor.small & 1L) ? divisor.small > 0x1L : mpz_sgn(Int_mpz(divisor)) > 0; if (d_positive) mpz_sub_ui(quotient, quotient, 1); else mpz_add_ui(quotient, quotient, 1); } @@ -330,9 +368,9 @@ Int_t Int$gcd(Int_t x, Int_t y) { mpz_t result; mpz_init(result); - if (x.small & 0x1L) mpz_gcd_ui(result, y.big, (uint64_t)labs(x.small >> 2L)); - else if (y.small & 0x1L) mpz_gcd_ui(result, x.big, (uint64_t)labs(y.small >> 2L)); - else mpz_gcd(result, x.big, y.big); + if (x.small & 0x1L) mpz_gcd_ui(result, Int_mpz(y), (uint64_t)labs(x.small >> 2L)); + else if (y.small & 0x1L) mpz_gcd_ui(result, Int_mpz(x), (uint64_t)labs(y.small >> 2L)); + else mpz_gcd(result, Int_mpz(x), Int_mpz(y)); return Int$from_mpz(result); } @@ -510,18 +548,6 @@ Int_t Int$next_prime(Int_t x) { return Int$from_mpz(p); } -#if __GNU_MP_VERSION >= 6 -#if __GNU_MP_VERSION_MINOR >= 3 -public -OptionalInt_t Int$prev_prime(Int_t x) { - mpz_t p; - mpz_init_set_int(p, x); - if (unlikely(mpz_prevprime(p, p) == 0)) return NONE_INT; - return Int$from_mpz(p); -} -#endif -#endif - public Int_t Int$choose(Int_t n, Int_t k) { if unlikely (Int$compare_value(n, I_small(0)) < 0) fail("Negative inputs are not supported for choose()"); diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h index 8c3502bf..d7eae403 100644 --- a/src/stdlib/bigint.h +++ b/src/stdlib/bigint.h @@ -1,16 +1,16 @@ // Big integer type (`Int` in Tomo) +#pragma once -#include <gmp.h> #include <stdbool.h> #include <stdint.h> #include "datatypes.h" -#include "stdlib.h" #include "types.h" #include "util.h" Text_t Int$as_text(const void *i, bool colorize, const TypeInfo_t *type); Text_t Int$value_as_text(Int_t i); +int Int$print(FILE *f, Int_t i); PUREFUNC uint64_t Int$hash(const void *x, const TypeInfo_t *type); PUREFUNC int32_t Int$compare(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC int32_t Int$compare_value(const Int_t x, const Int_t y); @@ -33,17 +33,6 @@ bool Int$get_bit(Int_t x, Int_t bit_index); #define BIGGEST_SMALL_INT 0x3fffffff #define SMALLEST_SMALL_INT -0x40000000 -#define Int$from_mpz(mpz) \ - (mpz_cmpabs_ui(mpz, BIGGEST_SMALL_INT) <= 0 \ - ? ((Int_t){.small = (mpz_get_si(mpz) << 2L) | 1L}) \ - : ((Int_t){.big = memcpy(new (__mpz_struct), mpz, sizeof(__mpz_struct))})) - -#define mpz_init_set_int(mpz, i) \ - do { \ - if likely ((i).small & 1L) mpz_init_set_si(mpz, (i).small >> 2L); \ - else mpz_init_set(mpz, (i).big); \ - } while (0) - #define I_small(i) ((Int_t){.small = (int64_t)((uint64_t)(i) << 2L) | 1L}) #define I(i) _Generic(i, int8_t: I_small(i), int16_t: I_small(i), default: Int$from_int64(i)) #define I_is_zero(i) ((i).small == 1L) @@ -63,11 +52,6 @@ Int_t Int$slow_negative(Int_t x); Int_t Int$slow_negated(Int_t x); bool Int$is_prime(Int_t x, Int_t reps); Int_t Int$next_prime(Int_t x); -#if __GNU_MP_VERSION >= 6 -#if __GNU_MP_VERSION_MINOR >= 3 -OptionalInt_t Int$prev_prime(Int_t x); -#endif -#endif Int_t Int$choose(Int_t n, Int_t k); Int_t Int$factorial(Int_t n); @@ -185,25 +169,11 @@ MACROLIKE PUREFUNC bool Int$is_negative(Int_t x) { // Constructors/conversion functions: // Int constructors: -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wfloat-equal" -#endif -MACROLIKE PUREFUNC Int_t Int$from_num64(double n, bool truncate) { - mpz_t result; - mpz_init_set_d(result, n); - if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n); - return Int$from_mpz(result); -} +PUREFUNC Int_t Int$from_num64(double n, bool truncate); MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) { return Int$from_num64((double)n, truncate); } -MACROLIKE Int_t Int$from_int64(int64_t i) { - if likely (i >= SMALLEST_SMALL_INT && i <= BIGGEST_SMALL_INT) return (Int_t){.small = (i << 2L) | 1L}; - mpz_t result; - mpz_init_set_si(result, i); - return Int$from_mpz(result); -} +PUREFUNC Int_t Int$from_int64(int64_t i); MACROLIKE CONSTFUNC Int_t Int$from_int32(Int32_t i) { return Int$from_int64((Int32_t)i); } @@ -219,7 +189,3 @@ MACROLIKE CONSTFUNC Int_t Int$from_byte(Byte_t b) { MACROLIKE CONSTFUNC Int_t Int$from_bool(Bool_t b) { return I_small(b); } - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif diff --git a/src/stdlib/bools.c b/src/stdlib/bools.c index dc7b83b1..d761a860 100644 --- a/src/stdlib/bools.c +++ b/src/stdlib/bools.c @@ -1,6 +1,5 @@ // Boolean methods/type info #include <err.h> -#include <gc.h> #include <stdbool.h> #include <stdint.h> #include <sys/param.h> diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c index 9874f222..25f95de1 100644 --- a/src/stdlib/bytes.c +++ b/src/stdlib/bytes.c @@ -1,10 +1,11 @@ // The logic for unsigned bytes +#include <gc.h> #include <stdbool.h> #include <stdint.h> +#include "../util.h" #include "bytes.h" #include "integers.h" -#include "stdlib.h" #include "text.h" #include "util.h" diff --git a/src/stdlib/c_strings.c b/src/stdlib/c_strings.c index cbe46b68..d581c220 100644 --- a/src/stdlib/c_strings.c +++ b/src/stdlib/c_strings.c @@ -7,6 +7,7 @@ #include <stdio.h> #include <stdlib.h> +#include "fail.h" #include "integers.h" #include "siphash.h" #include "stdlib.h" @@ -60,7 +61,8 @@ static void CString$deserialize(FILE *in, void *out, List_t *pointers, const Typ int64_t len = -1; Int64$deserialize(in, &len, pointers, &Int64$info); char *str = GC_MALLOC_ATOMIC((size_t)len + 1); - if (fread(str, sizeof(char), (size_t)len, in) != (size_t)len) fail("Not enough data in stream to deserialize"); + if (fread(str, sizeof(char), (size_t)len, in) != (size_t)len) + fail_text(Text("Not enough data in stream to deserialize")); str[len + 1] = '\0'; *(const char **)out = str; } diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h index 3cd99f38..90a116e6 100644 --- a/src/stdlib/datatypes.h +++ b/src/stdlib/datatypes.h @@ -2,7 +2,6 @@ #pragma once -#include <gmp.h> #include <stdbool.h> #include <stdint.h> @@ -30,7 +29,7 @@ typedef union { int64_t small; - __mpz_struct *big; + void *big; } Int_t; #define OptionalInt_t Int_t diff --git a/src/stdlib/enums.c b/src/stdlib/enums.c index 9cc16c5d..badf31fc 100644 --- a/src/stdlib/enums.c +++ b/src/stdlib/enums.c @@ -1,6 +1,8 @@ // Metamethods for enums #include <stdint.h> +#include <stdio.h> +#include <sys/param.h> #include "integers.h" #include "metamethods.h" diff --git a/src/stdlib/fail.c b/src/stdlib/fail.c new file mode 100644 index 00000000..a69ea469 --- /dev/null +++ b/src/stdlib/fail.c @@ -0,0 +1,38 @@ +// Failure functions +#include <errno.h> +#include <signal.h> +#include <stdio.h> + +#include "../util.h" +#include "fail.h" +#include "files.h" +#include "stacktrace.h" +#include "stdlib.h" +#include "text.h" +#include "util.h" + +public +_Noreturn void fail_text(Text_t message) { fail(message); } + +public +Text_t builtin_last_err() { return Text$from_str(strerror(errno)); } + +_Noreturn void fail_source(const char *filename, int start, int end, Text_t message) { + tomo_cleanup(); + fflush(stdout); + if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); + else fputs("==================== ERROR ====================\n\n", stderr); + print_stacktrace(stderr, 1); + fputs("\n", stderr); + if (USE_COLOR) fputs("\x1b[31;1m", stderr); + Text$print(stderr, message); + 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", 1, USE_COLOR); + } + if (USE_COLOR) fputs("\x1b[m", stderr); + fflush(stderr); + raise(SIGABRT); + exit(1); +} diff --git a/src/stdlib/fail.h b/src/stdlib/fail.h new file mode 100644 index 00000000..165b8db8 --- /dev/null +++ b/src/stdlib/fail.h @@ -0,0 +1,9 @@ +// Failure functions + +#pragma once + +#include "datatypes.h" + +_Noreturn void fail_text(Text_t message); +_Noreturn void fail_source(const char *filename, int start, int end, Text_t message); +Text_t builtin_last_err(); diff --git a/src/stdlib/files.c b/src/stdlib/files.c index b0545420..7d56fcfc 100644 --- a/src/stdlib/files.c +++ b/src/stdlib/files.c @@ -81,7 +81,8 @@ char *file_base_name(const char *path) { static file_t *_load_file(const char *filename, FILE *file) { if (!file) return NULL; - file_t *ret = new (file_t, .filename = filename); + file_t *ret = GC_MALLOC(sizeof(file_t)); + ret->filename = filename; size_t file_size = 0, line_cap = 0; char *file_buf = NULL, *line_buf = NULL; diff --git a/src/stdlib/int64.c b/src/stdlib/int64.c index 754ac619..131d77ab 100644 --- a/src/stdlib/int64.c +++ b/src/stdlib/int64.c @@ -1,3 +1,19 @@ #define INTX_C_H__INT_BITS 64 #include "intX.c.h" #undef INTX_C_H__INT_BITS + +public +int Int64$print(FILE *f, int64_t n) { + char buf[21] = {[20] = 0}; // Big enough for INT64_MIN + '\0' + char *p = &buf[19]; + bool negative = n < 0; + + do { + *(p--) = '0' + (n % 10); + n /= 10; + } while (n > 0); + + if (negative) *(p--) = '-'; + + return fwrite(p + 1, sizeof(char), (size_t)(&buf[19] - p), f); +} diff --git a/src/stdlib/int64.h b/src/stdlib/int64.h index dc47fa95..99df60a8 100644 --- a/src/stdlib/int64.h +++ b/src/stdlib/int64.h @@ -2,3 +2,5 @@ #define I64(i) (int64_t)(i) #include "intX.h" // IWYU pragma: export #define NONE_INT64 ((OptionalInt64_t){.has_value = false}) + +int Int64$print(FILE *f, int64_t n); diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h index 04c8ef3b..e6153ac9 100644 --- a/src/stdlib/intX.c.h +++ b/src/stdlib/intX.c.h @@ -6,12 +6,15 @@ // #include <gc.h> +#include <gmp.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include "../util.h" #include "datatypes.h" +#include "fail.h" #include "integers.h" #include "text.h" #include "types.h" @@ -55,12 +58,12 @@ public void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *info) { (void)info, (void)pointers; #if INTX_C_H__INT_BITS < 32 - if (fwrite(obj, sizeof(INT_T), 1, out) != sizeof(INT_T)) fail("Failed to write whole integer"); + if (fwrite(obj, sizeof(INT_T), 1, out) != sizeof(INT_T)) fail_text(Text("Failed to write whole integer")); #else INT_T i = *(INT_T *)obj; UINT_T z = (UINT_T)((i << 1L) ^ (i >> (INTX_C_H__INT_BITS - 1L))); // Zigzag encode while (z >= 0x80L) { - if (fputc((uint8_t)(z | 0x80L), out) == EOF) fail("Failed to write full integer"); + if (fputc((uint8_t)(z | 0x80L), out) == EOF) fail_text(Text("Failed to write full integer")); z >>= 7L; } fputc((uint8_t)z, out); @@ -71,12 +74,12 @@ public void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *info) { (void)info, (void)pointers; #if INTX_C_H__INT_BITS < 32 - if (fread(outval, sizeof(INT_T), 1, in) != sizeof(INT_T)) fail("Failed to read full integer"); + if (fread(outval, sizeof(INT_T), 1, in) != sizeof(INT_T)) fail_text(Text("Failed to read full integer")); #else UINT_T z = 0; for (size_t shift = 0;; shift += 7) { int i = fgetc(in); - if (i == EOF) fail("Failed to read whole integer"); + if (i == EOF) fail_text(Text("Failed to read whole integer")); uint8_t byte = (uint8_t)i; z |= ((UINT_T)(byte & 0x7F)) << shift; if ((byte & 0x80) == 0) break; @@ -207,6 +210,79 @@ PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remain } public +PUREFUNC INT_T NAMESPACED(from_num64)(Num_t n, bool truncate) { + INT_T i = (INT_T)n; + if (!truncate && unlikely((Num_t)i != n)) + fail_text( + Text$concat(Text("Could not convert Num to an " NAME_STR " without truncation: "), Num$value_as_text(n))); + return i; +} + +public +PUREFUNC INT_T NAMESPACED(from_num32)(Num32_t n, bool truncate) { + INT_T i = (INT_T)n; + if (!truncate && unlikely((Num32_t)i != n)) + fail_text(Text$concat(Text("Could not convert Num32 to an " NAME_STR " without truncation: "), + Num32$value_as_text(n))); + return i; +} + +public +PUREFUNC INT_T NAMESPACED(from_int)(Int_t i, bool truncate) { + if likely (i.small & 1L) { + INT_T ret = i.small >> 2L; +#if INTX_C_H__INT_BITS < 32 + if (!truncate && unlikely((int64_t)ret != (i.small >> 2L))) + fail("Integer is too big to fit in an " NAME_STR ": ", i); +#endif + return ret; + } + if (!truncate && unlikely(!mpz_fits_slong_p(i.big))) fail("Integer is too big to fit in an " NAME_STR ": ", i); + return mpz_get_si(i.big); +} + +#if INTX_C_H__INT_BITS < 64 +public +PUREFUNC INT_T NAMESPACED(from_int64)(Int64_t i64, bool truncate) { + INT_T i = (INT_T)i64; + if (!truncate && unlikely((int64_t)i != i64)) fail("Integer is too big to fit in an " NAME_STR ": ", i64); + return i; +} +#elif INTX_C_H__INT_BITS > 64 +public +CONSTFUNC INT_T NAMESPACED(from_int64)(Int64_t i) { return (INT_T)i; } +#endif + +#if INTX_C_H__INT_BITS < 32 +public +PUREFUNC INT_T NAMESPACED(from_int32)(Int32_t i32, bool truncate) { + INT_T i = (INT_T)i32; + if (!truncate && unlikely((int32_t)i != i32)) fail("Integer is too big to fit in an " NAME_STR ": ", i32); + return i; +} +#elif INTX_C_H__INT_BITS > 32 +public +CONSTFUNC INT_T NAMESPACED(from_int32)(Int32_t i) { return (INT_T)i; } +#endif + +#if INTX_C_H__INT_BITS < 16 +public +PUREFUNC INT_T NAMESPACED(from_int16)(Int16_t i16, bool truncate) { + INT_T i = (INT_T)i16; + if (!truncate && unlikely((int16_t)i != i16)) fail("Integer is too big to fit in an " NAME_STR ": ", i16); + return i; +} +#elif INTX_C_H__INT_BITS > 16 +public +CONSTFUNC INT_T NAMESPACED(from_int16)(Int16_t i) { return (INT_T)i; } +#endif + +#if INTX_C_H__INT_BITS > 8 +public +CONSTFUNC INT_T NAMESPACED(from_int8)(Int8_t i) { return (INT_T)i; } +#endif + +public CONSTFUNC INT_T NAMESPACED(gcd)(INT_T x, INT_T y) { if (x == 0 || y == 0) return 0; x = NAMESPACED(abs)(x); diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h index 3c4fa976..3b8a5679 100644 --- a/src/stdlib/intX.h +++ b/src/stdlib/intX.h @@ -6,9 +6,9 @@ // #include <stdbool.h> #include <stdint.h> +#include <stdlib.h> #include "datatypes.h" -#include "stdlib.h" #include "types.h" #include "util.h" @@ -102,72 +102,30 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *, const TypeInfo_t *); void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *, const TypeInfo_t *); -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_num64)(Num_t n, bool truncate) { - INTX_T i = (INTX_T)n; - if (!truncate && unlikely((Num_t)i != n)) fail("Could not convert Num to an " NAME_STR " without truncation: ", n); - return i; -} - -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_num32)(Num32_t n, bool truncate) { - INTX_T i = (INTX_T)n; - if (!truncate && unlikely((Num32_t)i != n)) - fail("Could not convert Num32 to an " NAME_STR " without truncation: ", n); - return i; -} - -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int)(Int_t i, bool truncate) { - if likely (i.small & 1L) { - INTX_T ret = i.small >> 2L; -#if INTX_H__INT_BITS < 32 - if (!truncate && unlikely((int64_t)ret != (i.small >> 2L))) - fail("Integer is too big to fit in an " NAME_STR ": ", i); -#endif - return ret; - } - if (!truncate && unlikely(!mpz_fits_slong_p(i.big))) fail("Integer is too big to fit in an " NAME_STR ": ", i); - return mpz_get_si(i.big); -} +PUREFUNC INTX_T NAMESPACED(from_num64)(Num_t n, bool truncate); +PUREFUNC INTX_T NAMESPACED(from_num32)(Num32_t n, bool truncate); +PUREFUNC INTX_T NAMESPACED(from_int)(Int_t i, bool truncate); #if INTX_H__INT_BITS < 64 -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int64)(Int64_t i64, bool truncate) { - INTX_T i = (INTX_T)i64; - if (!truncate && unlikely((int64_t)i != i64)) fail("Integer is too big to fit in an " NAME_STR ": ", i64); - return i; -} +PUREFUNC INTX_T NAMESPACED(from_int64)(Int64_t i64, bool truncate); #elif INTX_H__INT_BITS > 64 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int64)(Int64_t i) { - return (INTX_T)i; -} +CONSTFUNC INTX_T NAMESPACED(from_int64)(Int64_t i); #endif #if INTX_H__INT_BITS < 32 -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int32)(Int32_t i32, bool truncate) { - INTX_T i = (INTX_T)i32; - if (!truncate && unlikely((int32_t)i != i32)) fail("Integer is too big to fit in an " NAME_STR ": ", i32); - return i; -} +PUREFUNC INTX_T NAMESPACED(from_int32)(Int32_t i32, bool truncate); #elif INTX_H__INT_BITS > 32 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int32)(Int32_t i) { - return (INTX_T)i; -} +CONSTFUNC INTX_T NAMESPACED(from_int32)(Int32_t i); #endif #if INTX_H__INT_BITS < 16 -MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int16)(Int16_t i16, bool truncate) { - INTX_T i = (INTX_T)i16; - if (!truncate && unlikely((int16_t)i != i16)) fail("Integer is too big to fit in an " NAME_STR ": ", i16); - return i; -} +PUREFUNC INTX_T NAMESPACED(from_int16)(Int16_t i16, bool truncate); #elif INTX_H__INT_BITS > 16 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int16)(Int16_t i) { - return (INTX_T)i; -} +CONSTFUNC INTX_T NAMESPACED(from_int16)(Int16_t i); #endif #if INTX_H__INT_BITS > 8 -MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int8)(Int8_t i) { - return (INTX_T)i; -} +CONSTFUNC INTX_T NAMESPACED(from_int8)(Int8_t i); #endif #undef PASTE3_ diff --git a/src/stdlib/lists.c b/src/stdlib/lists.c index 9753cd86..3cc99b17 100644 --- a/src/stdlib/lists.c +++ b/src/stdlib/lists.c @@ -6,6 +6,7 @@ #include <stdint.h> #include <sys/param.h> +#include "../util.h" #include "integers.h" #include "lists.h" #include "math.h" @@ -227,7 +228,7 @@ void List$remove_item(List_t *list, void *item, Int_t max_removals, const TypeIn } public -OptionalInt_t List$find(List_t list, void *item, const TypeInfo_t *type) { +PUREFUNC OptionalInt_t List$find(List_t list, void *item, const TypeInfo_t *type) { const TypeInfo_t *item_type = type->ListInfo.item; for (int64_t i = 0; i < (int64_t)list.length; i++) { if (generic_equal(item, list.data + i * list.stride, item_type)) return I(i + 1); diff --git a/src/stdlib/lists.h b/src/stdlib/lists.h index 9ac8bf1b..f6d971f2 100644 --- a/src/stdlib/lists.h +++ b/src/stdlib/lists.h @@ -119,7 +119,7 @@ void List$remove_item(List_t *list, void *item, Int_t max_removals, const TypeIn : none_expr; \ }) -OptionalInt_t List$find(List_t list, void *item, const TypeInfo_t *type); +PUREFUNC OptionalInt_t List$find(List_t list, void *item, const TypeInfo_t *type); #define List$find_value(list, item_expr, type) \ ({ \ __typeof(item_expr) item = item_expr; \ @@ -134,7 +134,7 @@ void *List$random(List_t list, OptionalClosure_t random_int64); #define List$random_value(list, random_int64, t) \ ({ \ List_t _list_expr = list; \ - if (_list_expr.length == 0) fail("Cannot get a random value from an empty list!"); \ + if (_list_expr.length == 0) fail_text(Text("Cannot get a random value from an empty list!")); \ *(t *)List$random(_list_expr, random_int64); \ }) List_t List$sample(List_t list, Int_t n, List_t weights, Closure_t random_num, int64_t padded_item_size); diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c index 53a180fb..fcb1d5a6 100644 --- a/src/stdlib/memory.c +++ b/src/stdlib/memory.c @@ -1,7 +1,6 @@ // Type info and methods for "Memory" opaque type #include <err.h> -#include <gc.h> #include <stdbool.h> #include <stdint.h> #include <sys/param.h> diff --git a/src/stdlib/metamethods.c b/src/stdlib/metamethods.c index 70b8e4e1..b3fd233a 100644 --- a/src/stdlib/metamethods.c +++ b/src/stdlib/metamethods.c @@ -1,12 +1,15 @@ // Metamethods are methods that all types share for hashing, equality, comparison, and textifying +#include <gc.h> #include <stdint.h> #include <string.h> +#include "fail.h" #include "lists.h" #include "metamethods.h" #include "siphash.h" #include "tables.h" +#include "text.h" #include "types.h" #include "util.h" @@ -34,7 +37,7 @@ PUREFUNC public bool generic_equal(const void *x, const void *y, const TypeInfo_ public Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo_t *type) { - if (!type->metamethods.as_text) fail("No text metamethod provided for type!"); + if (!type->metamethods.as_text) fail_text(Text("No text metamethod provided for type!")); return type->metamethods.as_text(obj, colorize, type); } @@ -72,7 +75,7 @@ void _deserialize(FILE *input, void *outval, List_t *pointers, const TypeInfo_t return; } - if (fread(outval, (size_t)type->size, 1, input) != 1) fail("Not enough data in stream to deserialize"); + if (fread(outval, (size_t)type->size, 1, input) != 1) fail_text(Text("Not enough data in stream to deserialize")); } public @@ -88,13 +91,13 @@ void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type) { __attribute__((noreturn)) public void cannot_serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) { (void)obj, (void)out, (void)pointers; - Text_t typestr = generic_as_text(NULL, false, type); - fail("Values of type ", typestr, " cannot be serialized or deserialized!"); + Text_t type_text = generic_as_text(NULL, false, type); + fail_text(Text$concat(Text("Values of type "), type_text, Text(" cannot be serialized or deserialized!"))); } __attribute__((noreturn)) public void cannot_deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type) { (void)obj, (void)in, (void)pointers; - Text_t typestr = generic_as_text(NULL, false, type); - fail("Values of type ", typestr, " cannot be serialized or deserialized!"); + Text_t type_text = generic_as_text(NULL, false, type); + fail_text(Text$concat(Text("Values of type "), type_text, Text(" cannot be serialized or deserialized!"))); } diff --git a/src/stdlib/numX.c.h b/src/stdlib/numX.c.h index 22db3ca3..bef78c5b 100644 --- a/src/stdlib/numX.c.h +++ b/src/stdlib/numX.c.h @@ -6,11 +6,13 @@ // #include <float.h> #include <gc.h> +#include <gmp.h> #include <math.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> +#include "fail.h" #include "text.h" #include "types.h" @@ -91,6 +93,42 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf } #endif +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif +public +CONSTFUNC NUM_T NAMESPACED(from_int)(Int_t i, bool truncate) { + if likely (i.small & 0x1) { + NUM_T ret = (NUM_T)(i.small >> 2); + if unlikely (!truncate && (int64_t)ret != (i.small >> 2)) + fail_text(Text$concat(Text("Could not convert integer to " TYPE_STR " without losing precision: "), + Int64$value_as_text(i.small >> 2))); + return ret; + } else { + NUM_T ret = mpz_get_d(i.big); + if (!truncate) { + mpz_t roundtrip; + mpz_init_set_d(roundtrip, (double)ret); + if unlikely (mpz_cmp(i.big, roundtrip) != 0) + fail_text(Text$concat(Text("Could not convert integer to " TYPE_STR " without losing precision: "), + Int$value_as_text(i))); + } + return ret; + } +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +public +CONSTFUNC NUM_T NAMESPACED(from_int64)(Int64_t i, bool truncate) { + NUM_T n = (NUM_T)i; + if unlikely (!truncate && (Int64_t)n != i) + fail_text(Text$concat(Text("Could not convert integer to " TYPE_STR " without losing precision: "), + Int64$value_as_text(i))); + return n; +} + public PUREFUNC bool NAMESPACED(equal)(const void *x, const void *y, const TypeInfo_t *info) { (void)info; diff --git a/src/stdlib/numX.h b/src/stdlib/numX.h index 87794762..2ff2cc36 100644 --- a/src/stdlib/numX.h +++ b/src/stdlib/numX.h @@ -9,7 +9,6 @@ #include <stdint.h> #include "datatypes.h" -#include "stdlib.h" #include "types.h" #include "util.h" @@ -62,36 +61,8 @@ MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_num64)(double n) { } #endif -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wfloat-equal" -#endif -MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int)(Int_t i, bool truncate) { - if likely (i.small & 0x1) { - NUM_T ret = (NUM_T)(i.small >> 2); - if unlikely (!truncate && (int64_t)ret != (i.small >> 2)) - fail("Could not convert integer to " TYPE_STR " without losing precision: ", i.small >> 2); - return ret; - } else { - NUM_T ret = mpz_get_d(i.big); - if (!truncate) { - mpz_t roundtrip; - mpz_init_set_d(roundtrip, (double)ret); - if unlikely (mpz_cmp(i.big, roundtrip) != 0) - fail("Could not convert integer to " TYPE_STR " without losing precision: ", i); - } - return ret; - } -} -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif -MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int64)(Int64_t i, bool truncate) { - NUM_T n = (NUM_T)i; - if unlikely (!truncate && (Int64_t)n != i) - fail("Could not convert integer to " TYPE_STR " without losing precision: ", i); - return n; -} +CONSTFUNC NUM_T NAMESPACED(from_int)(Int_t i, bool truncate); +CONSTFUNC NUM_T NAMESPACED(from_int64)(Int64_t i, bool truncate); MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int32)(Int32_t i) { return (NUM_T)i; } diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index dcedcd03..26e43dfa 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -18,6 +18,7 @@ #include <unistd.h> #include "../unistr-fixed.h" +#include "../util.h" #include "enums.h" #include "integers.h" #include "lists.h" diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index c272314c..a9d49a34 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -11,8 +11,7 @@ Path_t Path$from_str(const char *str); Path_t Path$from_text(Text_t text); -// This function is defined as an extern in `src/stdlib/print.h` -// int Path$print(FILE *f, Path_t path); +int Path$print(FILE *f, Path_t path); // UNSAFE: this works because each type of path has a .components in the same place #define Path$components(path) ((path).components) // END UNSAFE diff --git a/src/stdlib/print.c b/src/stdlib/print.c index 7da1343f..bc5b01c6 100644 --- a/src/stdlib/print.c +++ b/src/stdlib/print.c @@ -1,6 +1,7 @@ // This file defines some of the helper functions used for printing values #include <ctype.h> +#include <gc.h> #include <stdio.h> #include <string.h> #include <unistd.h> @@ -10,22 +11,6 @@ #include "util.h" public -int _print_int(FILE *f, int64_t n) { - char buf[21] = {[20] = 0}; // Big enough for INT64_MIN + '\0' - char *p = &buf[19]; - bool negative = n < 0; - - do { - *(p--) = '0' + (n % 10); - n /= 10; - } while (n > 0); - - if (negative) *(p--) = '-'; - - return fwrite(p + 1, sizeof(char), (size_t)(&buf[19] - p), f); -} - -public int _print_uint(FILE *f, uint64_t n) { char buf[21] = {[20] = 0}; // Big enough for UINT64_MAX + '\0' char *p = &buf[19]; diff --git a/src/stdlib/print.h b/src/stdlib/print.h index 943a084a..5d789ead 100644 --- a/src/stdlib/print.h +++ b/src/stdlib/print.h @@ -9,7 +9,6 @@ #pragma once #include <assert.h> -#include <gc.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> @@ -18,8 +17,12 @@ #include <sys/param.h> #include <unistd.h> -#include "datatypes.h" +#include "bigint.h" // IWYU pragma: export +#include "datatypes.h" // IWYU pragma: export +#include "integers.h" // IWYU pragma: export #include "mapmacro.h" +#include "paths.h" // IWYU pragma: export +#include "text.h" // IWYU pragma: export // GCC lets you define macro-like functions which are always inlined and never // compiled using this combination of flags. See: https://gcc.gnu.org/onlinedocs/gcc/Inline.html @@ -74,7 +77,6 @@ typedef struct { #define FMT64 "l" #endif -int _print_int(FILE *f, int64_t x); int _print_uint(FILE *f, uint64_t x); int _print_double(FILE *f, double x); int _print_hex(FILE *f, hex_format_t hex); @@ -104,9 +106,6 @@ PRINT_FN _print_repeated_char(FILE *f, repeated_char_t repeated) { return len; } -extern int Text$print(FILE *stream, Text_t text); -extern int Path$print(FILE *stream, Path_t path); -extern int Int$print(FILE *f, Int_t i); #ifndef _fprint1 #define _fprint1(f, x) \ _Generic((x), \ @@ -114,10 +113,10 @@ extern int Int$print(FILE *f, Int_t i); const char *: _print_str, \ char: _print_char, \ bool: _print_bool, \ - int64_t: _print_int, \ - int32_t: _print_int, \ - int16_t: _print_int, \ - int8_t: _print_int, \ + int64_t: Int64$print, \ + int32_t: Int64$print, \ + int16_t: Int64$print, \ + int8_t: Int64$print, \ uint64_t: _print_uint, \ uint32_t: _print_uint, \ uint16_t: _print_uint, \ diff --git a/src/stdlib/result.c b/src/stdlib/result.c index 8fd2ca1e..bed8658a 100644 --- a/src/stdlib/result.c +++ b/src/stdlib/result.c @@ -1,6 +1,5 @@ // Result (Success/Failure) type info #include <err.h> -#include <gc.h> #include <stdbool.h> #include <stdint.h> #include <sys/param.h> diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index a05b8753..aabeb6b0 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -14,6 +14,7 @@ #include <time.h> #include "../config.h" +#include "../util.h" #include "files.h" #include "metamethods.h" #include "optionals.h" @@ -158,16 +159,6 @@ void tomo_init(void) { atexit(tomo_cleanup); } -public -_Noreturn void fail_text(Text_t message) { - fail(message); -} - -public -Text_t builtin_last_err() { - return Text$from_str(strerror(errno)); -} - static int _inspect_depth = 0; static file_t *file = NULL; @@ -333,7 +324,10 @@ static cleanup_t *cleanups = NULL; public void tomo_at_cleanup(Closure_t fn) { - cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); + cleanup_t *new_cleanup = GC_MALLOC(sizeof(cleanup_t)); + new_cleanup->cleanup_fn = fn; + new_cleanup->next = cleanups; + cleanups = new_cleanup; } public diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 087ed4bf..134f8ffa 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -2,14 +2,10 @@ #pragma once -#include <signal.h> #include <stdbool.h> #include <stdint.h> -#include <stdio.h> // IWYU pragma: export #include "datatypes.h" -#include "print.h" -#include "stacktrace.h" // IWYU pragma: export #include "types.h" extern bool USE_COLOR; @@ -20,46 +16,6 @@ void tomo_init(void); void tomo_at_cleanup(Closure_t fn); void tomo_cleanup(void); -#define fail(...) \ - ({ \ - tomo_cleanup(); \ - fflush(stdout); \ - if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \ - else fputs("==================== ERROR ====================\n\n", stderr); \ - print_stacktrace(stderr, 1); \ - if (USE_COLOR) fputs("\n\x1b[31;1m", stderr); \ - else fputs("\n", stderr); \ - fprint_inline(stderr, "Error: ", __VA_ARGS__); \ - if (USE_COLOR) fputs("\x1b[m\n", stderr); \ - else fputs("\n", stderr); \ - fflush(stderr); \ - raise(SIGABRT); \ - exit(1); \ - }) - -#define fail_source(filename, start, end, message) \ - ({ \ - tomo_cleanup(); \ - fflush(stdout); \ - if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); \ - else fputs("==================== ERROR ====================\n\n", stderr); \ - print_stacktrace(stderr, 0); \ - fputs("\n", stderr); \ - if (USE_COLOR) fputs("\x1b[31;1m", stderr); \ - Text$print(stderr, message); \ - 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", 1, USE_COLOR); \ - } \ - if (USE_COLOR) fputs("\x1b[m", stderr); \ - fflush(stderr); \ - raise(SIGABRT); \ - exit(1); \ - }) - -_Noreturn void fail_text(Text_t message); -Text_t builtin_last_err(); __attribute__((nonnull)) void start_inspect(const char *filename, int64_t start, int64_t end); void end_inspect(const void *expr, const TypeInfo_t *type); #define inspect(type, expr, typeinfo, start, end) \ diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index 753059c8..a89ae85f 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -13,6 +13,7 @@ #include "c_strings.h" #include "datatypes.h" +#include "fail.h" #include "lists.h" #include "memory.h" #include "metamethods.h" @@ -147,8 +148,7 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const static void hashmap_resize_buckets(Table_t *t, uint32_t new_capacity, const TypeInfo_t *type) { if (unlikely(new_capacity > TABLE_MAX_BUCKETS)) - fail("Table has exceeded the maximum table size (2^31) and cannot grow " - "further!"); + fail_text(Text("Table has exceeded the maximum table size (2^31) and cannot grow further!")); size_t alloc_size = sizeof(bucket_info_t) + sizeof(bucket_t[new_capacity]); t->bucket_info = GC_MALLOC_ATOMIC(alloc_size); memset(t->bucket_info->buckets, 0, sizeof(bucket_t[new_capacity])); diff --git a/src/stdlib/text.c b/src/stdlib/text.c index 4bf6d999..ccb2510f 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -108,6 +108,7 @@ #include <unistring/version.h> #include <uniwidth.h> +#include "../util.h" #include "bytes.h" #include "datatypes.h" #include "integers.h" @@ -199,9 +200,9 @@ int32_t get_synthetic_grapheme(const ucs4_t *codepoints, int64_t utf32_len) { if (num_synthetic_graphemes >= synthetic_grapheme_capacity) { // If we don't have space, allocate more: synthetic_grapheme_capacity = MAX(128, synthetic_grapheme_capacity * 2); - synthetic_grapheme_t *new = GC_MALLOC_ATOMIC(sizeof(synthetic_grapheme_t[synthetic_grapheme_capacity])); - memcpy(new, synthetic_graphemes, sizeof(synthetic_grapheme_t[num_synthetic_graphemes])); - synthetic_graphemes = new; + synthetic_grapheme_t *synth = GC_MALLOC_ATOMIC(sizeof(synthetic_grapheme_t[synthetic_grapheme_capacity])); + memcpy(synth, synthetic_graphemes, sizeof(synthetic_grapheme_t[num_synthetic_graphemes])); + synthetic_graphemes = synth; } int32_t grapheme_id = -(num_synthetic_graphemes + 1); @@ -1118,7 +1119,7 @@ static bool _has_grapheme(TextIter_t *text, int32_t g) { } public -OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start) { +PUREFUNC OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start) { if (text.length < target.length) return NONE_INT; if (target.length <= 0) return I(1); TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target); diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 856b173a..abf8bd3c 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -53,8 +53,7 @@ static inline Text_t Text_from_text(Text_t t) { Text_t Text$_concat(int n, Text_t items[n]); #define Text$concat(...) Text$_concat(sizeof((Text_t[]){__VA_ARGS__}) / sizeof(Text_t), (Text_t[]){__VA_ARGS__}) #define Texts(...) Text$concat(MAP_LIST(convert_to_text, __VA_ARGS__)) -// This function is defined as an extern in `src/stdlib/print.h` -// int Text$print(FILE *stream, Text_t t); +int Text$print(FILE *stream, Text_t t); Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int); Text_t Text$from(Text_t text, Int_t first); Text_t Text$to(Text_t text, Int_t last); @@ -89,7 +88,7 @@ PUREFUNC bool Text$starts_with(Text_t text, Text_t prefix, Text_t *remainder); PUREFUNC bool Text$ends_with(Text_t text, Text_t suffix, Text_t *remainder); Text_t Text$without_prefix(Text_t text, Text_t prefix); Text_t Text$without_suffix(Text_t text, Text_t suffix); -OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start); +PUREFUNC OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start); Text_t Text$replace(Text_t text, Text_t target, Text_t replacement); Text_t Text$translate(Text_t text, Table_t translations); PUREFUNC bool Text$has(Text_t text, Text_t target); @@ -123,7 +122,7 @@ Text_t Text$layout(Text_t text); void Text$serialize(const void *obj, FILE *out, Table_t *, const TypeInfo_t *); void Text$deserialize(FILE *in, void *out, List_t *, const TypeInfo_t *); -MACROLIKE int32_t Text$get_grapheme(Text_t text, int64_t index) { +MACROLIKE PUREFUNC int32_t Text$get_grapheme(Text_t text, int64_t index) { TextIter_t state = NEW_TEXT_ITER_STATE(text); return Text$get_grapheme_fast(&state, index); } diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h index ff8f90a2..fce9cdb5 100644 --- a/src/stdlib/tomo.h +++ b/src/stdlib/tomo.h @@ -14,6 +14,7 @@ #include "cli.h" // IWYU pragma: export #include "datatypes.h" // IWYU pragma: export #include "enums.h" // IWYU pragma: export +#include "fail.h" // IWYU pragma: export #include "files.h" // IWYU pragma: export #include "functiontype.h" // IWYU pragma: export #include "integers.h" // IWYU pragma: export @@ -24,10 +25,10 @@ #include "optionals.h" // IWYU pragma: export #include "paths.h" // IWYU pragma: export #include "pointers.h" // IWYU pragma: export -#include "print.h" // IWYU pragma: export #include "result.h" // IWYU pragma: export #include "siphash.h" // IWYU pragma: export #include "stacktrace.h" // IWYU pragma: export +#include "stdlib.h" // IWYU pragma: export #include "structs.h" // IWYU pragma: export #include "tables.h" // IWYU pragma: export #include "text.h" // IWYU pragma: export diff --git a/src/stdlib/types.c b/src/stdlib/types.c index 14a8f87c..aedc787c 100644 --- a/src/stdlib/types.c +++ b/src/stdlib/types.c @@ -1,7 +1,6 @@ // Type information and methods for TypeInfos (i.e. runtime representations of types) #include <err.h> -#include <gc.h> #include <sys/param.h> #include "text.h" diff --git a/src/stdlib/util.h b/src/stdlib/util.h index db667ccf..8341fee5 100644 --- a/src/stdlib/util.h +++ b/src/stdlib/util.h @@ -4,7 +4,6 @@ #include <assert.h> #include <err.h> -#include <gc.h> #include <stdbool.h> #include <string.h> @@ -12,12 +11,9 @@ #define starts_with(line, prefix) (strncmp(line, prefix, strlen(prefix)) == 0) #define ends_with(line, suffix) \ (strlen(line) >= strlen(suffix) && strcmp(line + strlen(line) - strlen(suffix), suffix) == 0) -#define new(t, ...) ((t *)memcpy(GC_MALLOC(sizeof(t)), &(t){__VA_ARGS__}, sizeof(t))) -#define heap(x) (__typeof(x) *)memcpy(GC_MALLOC(sizeof(x)), (__typeof(x)[1]){x}, sizeof(x)) -#define stack(x) (__typeof(x) *)((__typeof(x)[1]){x}) #define check_initialized(var, init_var, name) \ *({ \ - if (!init_var) fail("The variable " name " is being accessed before it has been initialized!"); \ + if (!init_var) fail_text(Text("The variable " name " is being accessed before it has been initialized!")); \ &var; \ }) @@ -61,3 +57,10 @@ #define MACROLIKE extern inline __attribute__((gnu_inline, always_inline)) #endif #endif + +#ifndef GC_MALLOC +extern void *GC_malloc(size_t); +#define GC_MALLOC GC_malloc +#define heap(x) (__typeof(x) *)memcpy(GC_malloc(sizeof(x)), (__typeof(x)[1]){x}, sizeof(x)) +#define stack(x) (__typeof(x) *)((__typeof(x)[1]){x}) +#endif @@ -36,8 +36,8 @@ #include "stdlib/siphash.h" #include "stdlib/tables.h" #include "stdlib/text.h" -#include "stdlib/util.h" #include "types.h" +#include "util.h" #define run_cmd(...) \ ({ \ @@ -78,16 +78,15 @@ static List_t format_files = EMPTY_LIST, format_files_inplace = EMPTY_LIST, pars run_files = EMPTY_LIST, uninstall_libraries = EMPTY_LIST, libraries = EMPTY_LIST, args = EMPTY_LIST; static OptionalText_t show_codegen = NONE_TEXT, - cflags = Text("-Werror -fdollars-in-identifiers -std=c2x -Wno-trigraphs " + cflags = Text("-Werror -fdollars-in-identifiers -std=c2x -Wno-trigraphs" " -ffunction-sections -fdata-sections" - " -fno-signed-zeros " + " -fno-signed-zeros" " -D_XOPEN_SOURCE -D_DEFAULT_SOURCE -fPIC -ggdb" #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) " -D_BSD_SOURCE" #endif " -DGC_THREADS"), - ldlibs = Text("-lgc -lm -lgmp -lunistring"), ldflags = Text(""), optimization = Text("2"), - cc = Text(DEFAULT_C_COMPILER); + ldlibs = Text("-lm"), ldflags = Text(""), optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER); static Text_t config_summary, // This will be either "" or "sudo -u <user>" or "doas -u <user>" @@ -527,16 +526,6 @@ void install_library(Path_t lib_dir) { xsystem(as_owner, "cp -r '", lib_dir, "'/* '", dest, "/'"); xsystem(as_owner, "cp -r '", lib_dir, "'/.build '", dest, "/'"); } - // If we have `debugedit` on this system, use it to remap the debugging source information - // to point to the installed version of the source file. Otherwise, fail silently. - if (verbose) whisper("Updating debug symbols for ", dest, "/lib", lib_name, ".a"); - int result = system(String(as_owner, "debugedit -b ", lib_dir, " -d '", dest, - "'" - " '", - dest, "/lib", lib_name, ".a", - "' " - ">/dev/null 2>/dev/null")); - (void)result; print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", lib_name); } @@ -953,7 +942,7 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t // the libraries that are used. " ", is_gcc ? Texts("-Wl,--start-group ", list_text(archives), " -Wl,--end-group") : list_text(archives), // Tomo static library: - " ", TOMO_PATH, "/lib/libtomo@", TOMO_VERSION, ".a", + " -Wl,--no-whole-archive", " ", TOMO_PATH, "/lib/libtomo@", TOMO_VERSION, ".a", // Output file: " -o ", exe_path); diff --git a/src/typecheck.c b/src/typecheck.c index a073cb2e..1e3e1c7c 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -18,9 +18,9 @@ #include "stdlib/paths.h" #include "stdlib/tables.h" #include "stdlib/text.h" -#include "stdlib/util.h" #include "typecheck.h" #include "types.h" +#include "util.h" type_t *parse_type_ast(env_t *env, type_ast_t *ast) { #ifdef __GNUC__ diff --git a/src/types.c b/src/types.c index 24453150..3c2347a5 100644 --- a/src/types.c +++ b/src/types.c @@ -10,8 +10,8 @@ #include "stdlib/integers.h" #include "stdlib/tables.h" #include "stdlib/text.h" -#include "stdlib/util.h" #include "types.h" +#include "util.h" Text_t arg_types_to_text(arg_t *args, const char *separator) { Text_t text = EMPTY_TEXT; diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..19e1113c --- /dev/null +++ b/src/util.h @@ -0,0 +1,29 @@ +#pragma once + +#include <gc.h> // IWYU pragma: export +#include <signal.h> // IWYU pragma: export +#include <stdio.h> // IWYU pragma: export + +#include "./stdlib/print.h" // IWYU pragma: export +#include "./stdlib/stacktrace.h" // IWYU pragma: export +#include "./stdlib/util.h" // IWYU pragma: export +#include "stdlib/stdlib.h" // IWYU pragma: export + +#define new(t, ...) ((t *)memcpy(GC_MALLOC(sizeof(t)), &(t){__VA_ARGS__}, sizeof(t))) + +#define fail(...) \ + ({ \ + tomo_cleanup(); \ + fflush(stdout); \ + if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \ + else fputs("==================== ERROR ====================\n\n", stderr); \ + print_stacktrace(stderr, 1); \ + if (USE_COLOR) fputs("\n\x1b[31;1m", stderr); \ + else fputs("\n", stderr); \ + fprint_inline(stderr, "Error: ", __VA_ARGS__); \ + if (USE_COLOR) fputs("\x1b[m\n", stderr); \ + else fputs("\n", stderr); \ + fflush(stderr); \ + raise(SIGABRT); \ + exit(1); \ + }) diff --git a/vendor/Makefile b/vendor/Makefile new file mode 100644 index 00000000..b6b4545e --- /dev/null +++ b/vendor/Makefile @@ -0,0 +1,70 @@ +GMP_VERSION=6.3.0 +UNISTRING_VERSION=1.4.1 +GC_VERSION=8.2.8 + +all: ../build/gc/lib/libgc.a ../build/gmp/lib/libgmp.a ../build/unistring/lib/libunistring.a + +# Boehm-Demers-Weiser Garbage collector +gc-$(GC_VERSION).tar.gz: + curl -LOJ 'https://hboehm.info/gc/gc_source/gc-$(GC_VERSION).tar.gz' + +gc-$(GC_VERSION)/configure: gc-$(GC_VERSION).tar.gz + tar xzfm $< + +gc-$(GC_VERSION)/Makefile: gc-$(GC_VERSION)/configure + prefix=$$(realpath ..)/build/gc; \ + cd gc-$(GC_VERSION); \ + ./configure \ + --enable-static \ + --disable-shared \ + --prefix="$$prefix"; \ + +../build/gc/lib/libgc.a: gc-$(GC_VERSION)/Makefile + cd gc-$(GC_VERSION); \ + $(MAKE) -j install + +# GNU Multiple Precision Arithmetic Library +gmp-$(GMP_VERSION).tar.xz: + curl -LOJ 'https://gmplib.org/download/gmp/gmp-$(GMP_VERSION).tar.xz' + +gmp-$(GMP_VERSION)/configure: gmp-$(GMP_VERSION).tar.xz + tar xJfm $< + cd gmp-$(GMP_VERSION) && patch -p0 -N < ../gmp-configure-fix.patch + +gmp-$(GMP_VERSION)/Makefile: gmp-$(GMP_VERSION)/configure + prefix=$$(realpath ..)/build/gmp; \ + cd gmp-$(GMP_VERSION); \ + ./configure \ + --enable-static \ + --disable-shared \ + --prefix="$$prefix" + +../build/gmp/lib/libgmp.a: gmp-$(GMP_VERSION)/Makefile + $(MAKE) -C gmp-$(GMP_VERSION) -j + $(MAKE) -C gmp-$(GMP_VERSION) check + $(MAKE) -C gmp-$(GMP_VERSION) install + +# Lib Unistring +libunistring-$(UNISTRING_VERSION).tar.gz: + curl -LOJ 'https://ftp.gnu.org/gnu/libunistring/libunistring-$(UNISTRING_VERSION).tar.gz' + +libunistring-$(UNISTRING_VERSION)/configure: libunistring-$(UNISTRING_VERSION).tar.gz + tar xzfm $< + +libunistring-$(UNISTRING_VERSION)/Makefile: libunistring-$(UNISTRING_VERSION)/configure + prefix=$$(realpath ..)/build/unistring; \ + cd libunistring-$(UNISTRING_VERSION); \ + ./configure \ + --enable-static \ + --disable-shared \ + --prefix="$$prefix" + +../build/unistring/lib/libunistring.a: libunistring-$(UNISTRING_VERSION)/Makefile + $(MAKE) -C libunistring-$(UNISTRING_VERSION) -j + $(MAKE) -C libunistring-$(UNISTRING_VERSION) check + $(MAKE) -C libunistring-$(UNISTRING_VERSION) install + +clean: + rm -rf ../build/gc ../build/gmp ../build/unistring + +.PHONY: all clean diff --git a/vendor/gmp-configure-fix.patch b/vendor/gmp-configure-fix.patch new file mode 100644 index 00000000..4810d138 --- /dev/null +++ b/vendor/gmp-configure-fix.patch @@ -0,0 +1,11 @@ +--- configure 2025-12-23 23:16:03.232370246 -0500 ++++ configure.fixed 2025-12-23 23:16:36.361925323 -0500 +@@ -6568,7 +6568,7 @@ + + #if defined (__GNUC__) && ! defined (__cplusplus) + typedef unsigned long long t1;typedef t1*t2; +-void g(){} ++void g(...){} + void h(){} + static __inline__ t1 e(t2 rp,t2 up,int n,t1 v0) + {t1 c,x,r;int i;if(v0){c=1;for(i=1;i<n;i++){x=up[i];r=x+1;rp[i]=r;}}return c;} |
