aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Makefile44
-rwxr-xr-xconfigure.sh11
-rw-r--r--src/ast.h2
-rw-r--r--src/compile/assertions.c2
-rw-r--r--src/compile/assignments.c2
-rw-r--r--src/compile/binops.c2
-rw-r--r--src/compile/blocks.c2
-rw-r--r--src/compile/cli.c2
-rw-r--r--src/compile/conditionals.c2
-rw-r--r--src/compile/debuglog.c2
-rw-r--r--src/compile/declarations.c2
-rw-r--r--src/compile/expressions.c2
-rw-r--r--src/compile/fieldaccess.c2
-rw-r--r--src/compile/functions.c16
-rw-r--r--src/compile/functions.c.orig955
-rw-r--r--src/compile/indexing.c2
-rw-r--r--src/compile/integers.c15
-rw-r--r--src/compile/lists.c2
-rw-r--r--src/compile/loops.c8
-rw-r--r--src/compile/optionals.c2
-rw-r--r--src/compile/reductions.c2
-rw-r--r--src/compile/statements.c2
-rw-r--r--src/compile/text.h2
-rw-r--r--src/compile/types.c2
-rw-r--r--src/compile/whens.c2
-rw-r--r--src/environment.c7
-rw-r--r--src/modules.c2
-rw-r--r--src/parse/binops.c2
-rw-r--r--src/parse/containers.c2
-rw-r--r--src/parse/controlflow.c2
-rw-r--r--src/parse/expressions.c2
-rw-r--r--src/parse/files.c2
-rw-r--r--src/parse/functions.c2
-rw-r--r--src/parse/statements.c2
-rw-r--r--src/parse/suffixes.c2
-rw-r--r--src/parse/text.c2
-rw-r--r--src/parse/typedefs.c2
-rw-r--r--src/parse/utils.c2
-rw-r--r--src/parse/utils.h2
-rw-r--r--src/stdlib/bigint.c84
-rw-r--r--src/stdlib/bigint.h42
-rw-r--r--src/stdlib/bools.c1
-rw-r--r--src/stdlib/bytes.c3
-rw-r--r--src/stdlib/c_strings.c4
-rw-r--r--src/stdlib/datatypes.h3
-rw-r--r--src/stdlib/enums.c2
-rw-r--r--src/stdlib/fail.c38
-rw-r--r--src/stdlib/fail.h9
-rw-r--r--src/stdlib/files.c3
-rw-r--r--src/stdlib/int64.c16
-rw-r--r--src/stdlib/int64.h2
-rw-r--r--src/stdlib/intX.c.h84
-rw-r--r--src/stdlib/intX.h64
-rw-r--r--src/stdlib/lists.c3
-rw-r--r--src/stdlib/lists.h4
-rw-r--r--src/stdlib/memory.c1
-rw-r--r--src/stdlib/metamethods.c15
-rw-r--r--src/stdlib/numX.c.h38
-rw-r--r--src/stdlib/numX.h33
-rw-r--r--src/stdlib/paths.c1
-rw-r--r--src/stdlib/paths.h3
-rw-r--r--src/stdlib/print.c17
-rw-r--r--src/stdlib/print.h19
-rw-r--r--src/stdlib/result.c1
-rw-r--r--src/stdlib/stdlib.c16
-rw-r--r--src/stdlib/stdlib.h44
-rw-r--r--src/stdlib/tables.c4
-rw-r--r--src/stdlib/text.c9
-rw-r--r--src/stdlib/text.h7
-rw-r--r--src/stdlib/tomo.h3
-rw-r--r--src/stdlib/types.c1
-rw-r--r--src/stdlib/util.h13
-rw-r--r--src/tomo.c21
-rw-r--r--src/typecheck.c2
-rw-r--r--src/types.c2
-rw-r--r--src/util.h29
-rw-r--r--vendor/Makefile70
-rw-r--r--vendor/gmp-configure-fix.patch11
79 files changed, 1469 insertions, 377 deletions
diff --git a/.gitignore b/.gitignore
index 5f521074..8cf369a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Makefile b/Makefile
index bd4bc1dc..4d618928 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/src/ast.h b/src/ast.h
index b6930ab7..65ab0c63 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -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
diff --git a/src/tomo.c b/src/tomo.c
index ceee2518..700e6f6a 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -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;}