aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ast.c8
-rw-r--r--src/compile/assertions.c14
-rw-r--r--src/compile/blocks.c4
-rw-r--r--src/compile/cli.c4
-rw-r--r--src/compile/comparisons.c1
-rw-r--r--src/compile/files.c4
-rw-r--r--src/compile/functions.c3
-rw-r--r--src/compile/headers.c4
-rw-r--r--src/compile/indexing.c2
-rw-r--r--src/compile/optionals.c8
-rw-r--r--src/compile/promotions.c16
-rw-r--r--src/compile/statements.c2
-rw-r--r--src/compile/text.c15
-rw-r--r--src/compile/text.h8
-rw-r--r--src/config.h9
-rw-r--r--src/environment.c24
-rw-r--r--src/modules.c9
-rw-r--r--src/parse/expressions.c4
-rw-r--r--src/parse/utils.c8
-rw-r--r--src/stdlib/bigint.c12
-rw-r--r--src/stdlib/bigint.h24
-rw-r--r--src/stdlib/bools.h24
-rw-r--r--src/stdlib/bytes.c10
-rw-r--r--src/stdlib/bytes.h8
-rw-r--r--src/stdlib/c_strings.c4
-rw-r--r--src/stdlib/enums.c2
-rw-r--r--src/stdlib/floatX.c.h20
-rw-r--r--src/stdlib/intX.c.h22
-rw-r--r--src/stdlib/intX.h44
-rw-r--r--src/stdlib/lists.c12
-rw-r--r--src/stdlib/lists.h15
-rw-r--r--src/stdlib/mapmacro.h4
-rw-r--r--src/stdlib/memory.c2
-rw-r--r--src/stdlib/numX.h115
-rw-r--r--src/stdlib/paths.c105
-rw-r--r--src/stdlib/paths.h2
-rw-r--r--src/stdlib/pointers.c8
-rw-r--r--src/stdlib/print.h16
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c98
-rw-r--r--src/stdlib/stdlib.h5
-rw-r--r--src/stdlib/structs.c4
-rw-r--r--src/stdlib/tables.c87
-rw-r--r--src/stdlib/tables.h6
-rw-r--r--src/stdlib/text.c28
-rw-r--r--src/stdlib/text.h12
-rw-r--r--src/tomo.c144
-rw-r--r--src/typecheck.c9
-rw-r--r--src/types.c17
-rw-r--r--src/types.h1
50 files changed, 705 insertions, 304 deletions
diff --git a/src/ast.c b/src/ast.c
index e87ca005..69c55327 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -85,7 +85,9 @@ static Text_t tags_to_sexp(tag_ast_t *tags);
static Text_t optional_sexp(const char *tag, ast_t *ast);
static Text_t optional_type_sexp(const char *tag, type_ast_t *ast);
-static Text_t quoted_text(const char *text) { return Text$quoted(Text$from_str(text), false, Text("\"")); }
+static Text_t quoted_text(const char *text) {
+ return Text$quoted(Text$from_str(text), false, Text("\""));
+}
Text_t ast_list_to_sexp(ast_list_t *asts) {
Text_t c = EMPTY_TEXT;
@@ -281,7 +283,9 @@ Text_t ast_to_sexp(ast_t *ast) {
}
}
-const char *ast_to_sexp_str(ast_t *ast) { return Text$as_c_string(ast_to_sexp(ast)); }
+const char *ast_to_sexp_str(ast_t *ast) {
+ return Text$as_c_string(ast_to_sexp(ast));
+}
OptionalText_t ast_source(ast_t *ast) {
if (ast == NULL || ast->start == NULL || ast->end == NULL) return NONE_TEXT;
diff --git a/src/compile/assertions.c b/src/compile/assertions.c
index 34055998..18531fd9 100644
--- a/src/compile/assertions.c
+++ b/src/compile/assertions.c
@@ -60,13 +60,10 @@ Text_t compile_assertion(env_t *env, ast_t *ast) {
compile_declaration(operand_t, Text("_rhs")), " = ", compile_to_type(env, cmp.rhs, operand_t), ";\n",
"\n#line ", line, "\n", "if (!(", compile_condition(env, var_comparison), "))\n", "#line ", line, "\n",
Texts("fail_source(", quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text),
- ", ", (int64_t)(expr->end - expr->file->text), ", ",
- message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")")
- : Text("\"This assertion failed!\""),
- ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")),
- ", "
- "\" ",
- failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"),
+ ", ", (int64_t)(expr->end - expr->file->text), ", Text$concat(",
+ message ? compile_to_type(env, message, Type(TextType)) : Text("Text(\"This assertion failed!\")"),
+ ", Text(\" (\"), ", expr_as_text(Text("_lhs"), operand_t, Text("no")), ", Text(\" ", failure,
+ " \"), ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", Text(\")\")));\n"),
"}\n");
}
default: {
@@ -74,8 +71,7 @@ Text_t compile_assertion(env_t *env, ast_t *ast) {
return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", line, "\n", "fail_source(",
quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text), ", ",
(int64_t)(expr->end - expr->file->text), ", ",
- message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")")
- : Text("\"This assertion failed!\""),
+ message ? compile_to_type(env, message, Type(TextType)) : Text("Text(\"This assertion failed!\")"),
");\n");
}
}
diff --git a/src/compile/blocks.c b/src/compile/blocks.c
index 1059fd34..66869ecc 100644
--- a/src/compile/blocks.c
+++ b/src/compile/blocks.c
@@ -9,7 +9,9 @@
#include "compilation.h"
public
-Text_t compile_block(env_t *env, ast_t *ast) { return Texts("{\n", compile_inline_block(env, ast), "}\n"); }
+Text_t compile_block(env_t *env, ast_t *ast) {
+ return Texts("{\n", compile_inline_block(env, ast), "}\n");
+}
Text_t compile_block_expression(env_t *env, ast_t *ast) {
ast_list_t *stmts = Match(ast, Block)->statements;
diff --git a/src/compile/cli.c b/src/compile/cli.c
index 63a467ca..ade6caa7 100644
--- a/src/compile/cli.c
+++ b/src/compile/cli.c
@@ -83,10 +83,8 @@ static Text_t generate_usage(env_t *env, type_t *fn_type) {
else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
else if (t->tag == EnumType) usage = Texts(usage, "[", flags, " val]");
else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
- } else if (t->tag == EnumType) {
- usage = Texts(usage, "\x1b[1m", flag, "\x1b[m");
} else {
- usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m");
+ usage = Texts(usage, "\x1b[1m", flag, "\x1b[m");
}
}
return usage;
diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c
index 5e95459c..afb5dc15 100644
--- a/src/compile/comparisons.c
+++ b/src/compile/comparisons.c
@@ -41,6 +41,7 @@ Text_t compile_comparison(env_t *env, ast_t *ast) {
} else {
code_err(ast, "I can't do comparisons between ", type_to_text(lhs_t), " and ", type_to_text(rhs_t));
}
+ assert(operand_t);
Text_t lhs, rhs;
lhs = compile_to_type(env, binop.lhs, operand_t);
diff --git a/src/compile/files.c b/src/compile/files.c
index 27c2e041..555f848c 100644
--- a/src/compile/files.c
+++ b/src/compile/files.c
@@ -194,8 +194,8 @@ Text_t compile_file(env_t *env, ast_t *ast) {
const char *name = file_base_name(ast->file->filename);
return Texts(env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT,
- "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n",
- "#include <tomo@" TOMO_VERSION "/tomo.h>\n"
+ "#define __SOURCE_FILE__ ", quoted_str(ast->file->filename), "\n", "#include <tomo@", TOMO_VERSION,
+ "/tomo.h>\n"
"#include \"",
name, ".tm.h\"\n\n", includes, env->code->local_typedefs, "\n", env->code->lambdas, "\n",
env->code->staticdefs, "\n", top_level_code, "public void ",
diff --git a/src/compile/functions.c b/src/compile/functions.c
index 77393ab2..cadd0453 100644
--- a/src/compile/functions.c
+++ b/src/compile/functions.c
@@ -630,7 +630,10 @@ static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) {
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);
+ assert(var);
code_err(var, "This variable was assigned to, but never read from.");
}
}
diff --git a/src/compile/headers.c b/src/compile/headers.c
index e90556a1..df7142d1 100644
--- a/src/compile/headers.c
+++ b/src/compile/headers.c
@@ -135,7 +135,7 @@ Text_t compile_file_header(env_t *env, Path_t header_path, ast_t *ast) {
Text_t header =
Texts("#pragma once\n",
env->do_source_mapping ? Texts("#line 1 ", quoted_str(ast->file->filename), "\n") : EMPTY_TEXT,
- "#include <tomo@" TOMO_VERSION "/tomo.h>\n");
+ "#include <tomo@", TOMO_VERSION, "/tomo.h>\n");
compile_typedef_info_t info = {.env = env, .header = &header, .header_path = header_path};
visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn = (void *)_make_typedefs, &info});
@@ -161,7 +161,7 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast)
module_info_t mod = get_used_module_info(ast);
glob_t tm_files;
const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name;
- if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
+ if (glob(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
&tm_files)
!= 0) {
if (!try_install_module(mod, true)) code_err(ast, "Could not find library");
diff --git a/src/compile/indexing.c b/src/compile/indexing.c
index 13062641..031ef9a0 100644
--- a/src/compile/indexing.c
+++ b/src/compile/indexing.c
@@ -50,7 +50,7 @@ Text_t compile_indexing(env_t *env, ast_t *ast, bool checked) {
return Texts("({ ", compile_declaration(item_type, Text("opt")), " = ", code, "; ", "if unlikely (",
check_none(item_type, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
quoted_str(ast->file->filename), ", ", start, ", ", end, ", ",
- "\"This was expected to be a value, but it's `none`\\n\");\n",
+ "Text(\"This was expected to be a value, but it's `none`\\n\"));\n",
optional_into_nonnone(item_type, Text("opt")), "; })");
}
return code;
diff --git a/src/compile/optionals.c b/src/compile/optionals.c
index 9aca84d0..f54d8931 100644
--- a/src/compile/optionals.c
+++ b/src/compile/optionals.c
@@ -134,9 +134,9 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) {
compile_to_pointer_depth(env, f->fielded, 0, true), ";",
"if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(",
quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text),
- ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ",
- tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")),
- ", \"\\n\");\n}\n",
+ ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ",
+ "Text$concat(Text(\"This was expected to be ", tag->name, ", but it was: \"), ",
+ expr_as_text(Text("_test_enum"), enum_t, Text("false")), ", Text(\"\\n\")));\n}\n",
compile_maybe_incref(
env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type),
"; })");
@@ -149,7 +149,7 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) {
check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ",
(int64_t)(value->end - value->file->text), ", ",
- "\"This was expected to be a value, but it's `none`\\n\");\n",
+ "Text(\"This was expected to be a value, but it's `none`\\n\"));\n",
optional_into_nonnone(value_t, Text("opt")), "; })");
}
}
diff --git a/src/compile/promotions.c b/src/compile/promotions.c
index 4b5458c9..151850f0 100644
--- a/src/compile/promotions.c
+++ b/src/compile/promotions.c
@@ -8,7 +8,9 @@
#include "../types.h"
#include "compilation.h"
-static Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); }
+static Text_t quoted_str(const char *str) {
+ return Text$quoted(Text$from_str(str), false, Text("\""));
+}
public
bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed) {
@@ -62,12 +64,12 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede
if (needed->tag == FloatType && actual->tag == OptionalType
&& Match(actual, OptionalType)->type->tag == FloatType) {
int64_t line = get_line_number(ast->file, ast->start);
- *code =
- Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (",
- check_none(actual, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
- quoted_str(ast->file->filename), ", ", (int64_t)(ast->start - ast->file->text), ", ",
- (int64_t)(ast->end - ast->file->text), ", ", "\"This was expected to be a value, but it's none\");\n",
- optional_into_nonnone(actual, Text("opt")), "; })");
+ *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (",
+ check_none(actual, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
+ quoted_str(ast->file->filename), ", ", (int64_t)(ast->start - ast->file->text), ", ",
+ (int64_t)(ast->end - ast->file->text), ", ",
+ "Text(\"This was expected to be a value, but it's none\"));\n",
+ optional_into_nonnone(actual, Text("opt")), "; })");
return true;
}
diff --git a/src/compile/statements.c b/src/compile/statements.c
index 81a10ddd..c9a0fd0e 100644
--- a/src/compile/statements.c
+++ b/src/compile/statements.c
@@ -197,7 +197,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
module_info_t mod = get_used_module_info(ast);
glob_t tm_files;
const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name;
- if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
+ if (glob(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
&tm_files)
!= 0) {
if (!try_install_module(mod, true)) code_err(ast, "Could not find library");
diff --git a/src/compile/text.c b/src/compile/text.c
index 25a6e9a7..7f11169b 100644
--- a/src/compile/text.c
+++ b/src/compile/text.c
@@ -4,7 +4,6 @@
#include "../ast.h"
#include "../environment.h"
-#include "../naming.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/tables.h"
#include "../stdlib/text.h"
@@ -102,19 +101,12 @@ Text_t compile_text_ast(env_t *env, ast_t *ast) {
type_t *text_t = lang ? Table$str_get(*env->types, lang) : TEXT_TYPE;
if (!text_t || text_t->tag != TextType) code_err(ast, quoted(lang), " is not a valid text language name");
- Text_t lang_constructor;
- if (!lang || streq(lang, "Text")) lang_constructor = Text("Text");
- else
- lang_constructor = namespace_name(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent,
- Text$from_str(lang));
-
ast_list_t *chunks = Match(ast, TextJoin)->children;
if (!chunks) {
- return Texts(lang_constructor, "(\"\")");
+ return Text("EMPTY_TEXT");
} else if (!chunks->next && chunks->ast->tag == TextLiteral) {
Text_t literal = Match(chunks->ast, TextLiteral)->text;
- if (string_literal_is_all_ascii(literal))
- return Texts(lang_constructor, "(", compile_text_literal(literal), ")");
+ if (string_literal_is_all_ascii(literal)) return Texts("Text(", compile_text_literal(literal), ")");
return Texts("((", compile_type(text_t), ")", compile(env, chunks->ast), ")");
} else {
Text_t code = EMPTY_TEXT;
@@ -142,7 +134,6 @@ Text_t compile_text_ast(env_t *env, ast_t *ast) {
code = Texts(code, chunk_code);
if (chunk->next) code = Texts(code, ", ");
}
- if (chunks->next) return Texts(lang_constructor, "s(", code, ")");
- else return code;
+ return Texts("Text$concat(", code, ")");
}
}
diff --git a/src/compile/text.h b/src/compile/text.h
index c160c7a9..ae3cc5c3 100644
--- a/src/compile/text.h
+++ b/src/compile/text.h
@@ -14,5 +14,9 @@ Text_t compile_text(env_t *env, ast_t *ast, Text_t color);
Text_t compile_text_literal(Text_t literal);
Text_t expr_as_text(Text_t expr, type_t *t, Text_t color);
-MACROLIKE Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); }
-MACROLIKE Text_t quoted_text(Text_t text) { return Text$quoted(text, false, Text("\"")); }
+MACROLIKE Text_t quoted_str(const char *str) {
+ return Text$quoted(Text$from_str(str), false, Text("\""));
+}
+MACROLIKE Text_t quoted_text(Text_t text) {
+ return Text$quoted(text, false, Text("\""));
+}
diff --git a/src/config.h b/src/config.h
index 8ee44200..e703f95d 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,18 +1,11 @@
// Configuration of values that will be baked into the executable:
-#ifndef TOMO_VERSION
-#define TOMO_VERSION "v???"
-#endif
-
#ifndef GIT_VERSION
#define GIT_VERSION "???"
#endif
-#ifndef TOMO_INSTALL
-#define TOMO_INSTALL "/usr/local"
-#endif
-
extern const char *TOMO_PATH;
+extern const char *TOMO_VERSION;
#ifndef DEFAULT_C_COMPILER
#define DEFAULT_C_COMPILER "cc"
diff --git a/src/environment.c b/src/environment.c
index f59194d4..27a57d7a 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -101,7 +101,7 @@ env_t *global_env(bool source_mapping) {
"Byte", Type(ByteType), Text("Byte_t"), Text("Byte$info"),
{"get_bit", "Byte$get_bit", "func(x:Byte, bit_index:Int -> Bool)"}, //
{"hex", "Byte$hex", "func(byte:Byte, uppercase=yes, prefix=no -> Text)"}, //
- {"is_between", "Byte$is_between", "func(x:Byte, low:Byte, high:Byte -> Bool)"}, //
+ {"is_between", "Byte$is_between", "func(x:Byte, a:Byte, b:Byte -> Bool)"}, //
{"max", "Byte$max", "Byte"}, //
{"min", "Byte$min", "Byte"}, //
{"parse", "Byte$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Byte?)"}, //
@@ -118,7 +118,7 @@ env_t *global_env(bool source_mapping) {
{"gcd", "Int$gcd", "func(x,y:Int -> Int)"}, //
{"get_bit", "Int$get_bit", "func(x,bit_index:Int -> Bool)"}, //
{"hex", "Int$hex", "func(i:Int, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
- {"is_between", "Int$is_between", "func(x:Int,low:Int,high:Int -> Bool)"}, //
+ {"is_between", "Int$is_between", "func(x:Int, a:Int, b:Int -> Bool)"}, //
{"is_prime", "Int$is_prime", "func(x:Int,reps=50 -> Bool)"}, //
{"left_shifted", "Int$left_shifted", "func(x,y:Int -> Int)"}, //
{"minus", "Int$minus", "func(x,y:Int -> Int)"}, //
@@ -151,7 +151,7 @@ env_t *global_env(bool source_mapping) {
{"parse", "Int64$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int64?)"}, //
{"get_bit", "Int64$get_bit", "func(x:Int64, bit_index:Int -> Bool)"}, //
{"hex", "Int64$hex", "func(i:Int64, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
- {"is_between", "Int64$is_between", "func(x:Int64,low:Int64,high:Int64 -> Bool)"}, //
+ {"is_between", "Int64$is_between", "func(x:Int64, a:Int64, b:Int64 -> Bool)"}, //
{"max", "Int64$max", "Int64"}, //
{"min", "Int64$min", "Int64"}, //
{"modulo", "Int64$modulo", "func(x,y:Int64 -> Int64)"}, //
@@ -173,7 +173,7 @@ env_t *global_env(bool source_mapping) {
{"parse", "Int32$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int32?)"}, //
{"get_bit", "Int32$get_bit", "func(x:Int32, bit_index:Int -> Bool)"}, //
{"hex", "Int32$hex", "func(i:Int32, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
- {"is_between", "Int32$is_between", "func(x:Int32,low:Int32,high:Int32 -> Bool)"}, //
+ {"is_between", "Int32$is_between", "func(x:Int32, a:Int32, b:Int32 -> Bool)"}, //
{"max", "Int32$max", "Int32"}, //
{"min", "Int32$min", "Int32"}, //
{"modulo", "Int32$modulo", "func(x,y:Int32 -> Int32)"}, //
@@ -195,7 +195,7 @@ env_t *global_env(bool source_mapping) {
{"parse", "Int16$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int16?)"}, //
{"get_bit", "Int16$get_bit", "func(x:Int16, bit_index:Int -> Bool)"}, //
{"hex", "Int16$hex", "func(i:Int16, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
- {"is_between", "Int16$is_between", "func(x:Int16,low:Int16,high:Int16 -> Bool)"}, //
+ {"is_between", "Int16$is_between", "func(x:Int16, a:Int16, b:Int16 -> Bool)"}, //
{"max", "Int16$max", "Int16"}, //
{"min", "Int16$min", "Int16"}, //
{"modulo", "Int16$modulo", "func(x,y:Int16 -> Int16)"}, //
@@ -217,7 +217,7 @@ env_t *global_env(bool source_mapping) {
{"parse", "Int8$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int8?)"}, //
{"get_bit", "Int8$get_bit", "func(x:Int8, bit_index:Int -> Bool)"}, //
{"hex", "Int8$hex", "func(i:Int8, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
- {"is_between", "Int8$is_between", "func(x:Int8,low:Int8,high:Int8 -> Bool)"}, //
+ {"is_between", "Int8$is_between", "func(x:Int8, a:Int8, b:Int8 -> Bool)"}, //
{"max", "Int8$max", "Int8"}, //
{"min", "Int8$min", "Int8"}, //
{"modulo", "Int8$modulo", "func(x,y:Int8 -> Int8)"}, //
@@ -239,7 +239,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Float64$clamped", "func(x,low,high:Float64 -> Float64)"}, //
{"percent", "Float64$percent", "func(n:Float64,precision=0.01 -> Text)"}, //
{"with_precision", "Float64$with_precision", "func(n:Float64,precision:Float64 -> Float64)"}, //
- {"is_between", "Float64$is_between", "func(x:Float64,low:Float64,high:Float64 -> Bool)"}, //
+ {"is_between", "Float64$is_between", "func(x:Float64, a:Float64, b:Float64 -> Bool)"}, //
{"isinf", "Float64$isinf", "func(n:Float64 -> Bool)"}, //
{"isfinite", "Float64$isfinite", "func(n:Float64 -> Bool)"}, //
{"modulo", "Float64$mod", "func(x,y:Float64 -> Float64)"}, //
@@ -269,7 +269,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Float32$clamped", "func(x,low,high:Float32 -> Float32)"}, //
{"percent", "Float32$percent", "func(n:Float32,precision=Float32(.01) -> Text)"}, //
{"with_precision", "Float32$with_precision", "func(n:Float32,precision:Float32 -> Float32)"}, //
- {"is_between", "Float32$is_between", "func(x:Float32,low:Float32,high:Float32 -> Bool)"}, //
+ {"is_between", "Float32$is_between", "func(x:Float32, a:Float32, b:Float32 -> Bool)"}, //
{"isinf", "Float32$isinf", "func(n:Float32 -> Bool)"}, //
{"isfinite", "Float32$isfinite", "func(n:Float32 -> Bool)"}, //
C(2_SQRTPI), C(E), C(PI_2), C(2_PI), C(1_PI), C(LN10), C(LN2), C(LOG2E), C(PI), C(PI_4), C(SQRT2),
@@ -352,6 +352,10 @@ env_t *global_env(bool source_mapping) {
{"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, //
{"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"}, //
{"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, //
+ {"writer", "Path$writer",
+ "func(path:Path, append=no, permissions=Int32(0o644) -> func(text:Text, close=no -> Result))"}, //
+ {"byte_writer", "Path$byte_writer",
+ "func(path:Path, append=no, permissions=Int32(0o644) -> func(bytes:[Byte], close=no -> Result))"}, //
{"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, //
{"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path?)"}, //
{"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path?)"}),
@@ -723,7 +727,9 @@ env_t *namespace_env(env_t *env, const char *namespace_name) {
return ns_env;
}
-PUREFUNC binding_t *get_binding(env_t *env, const char *name) { return Table$str_get(*env->locals, name); }
+PUREFUNC binding_t *get_binding(env_t *env, const char *name) {
+ return Table$str_get(*env->locals, name);
+}
binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name) {
type_t *self_type = get_type(env, self);
diff --git a/src/modules.c b/src/modules.c
index 36952ec8..c7c29d24 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -102,7 +102,7 @@ module_info_t get_used_module_info(ast_t *use) {
const char *name = Match(use, Use)->path;
module_info_t *info = new (module_info_t, .name = name);
Path_t tomo_default_modules =
- Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/modules.ini"));
+ Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/modules.ini"));
read_modules_ini(tomo_default_modules, info);
read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info);
read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info);
@@ -111,8 +111,8 @@ module_info_t get_used_module_info(ast_t *use) {
}
bool try_install_module(module_info_t mod, bool ask_confirmation) {
- Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@" TOMO_VERSION "/", Text$from_str(mod.name),
- "@", Text$from_str(mod.version)));
+ Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/",
+ Text$from_str(mod.name), "@", Text$from_str(mod.version)));
if (Path$exists(dest)) return true;
print("No such path: ", dest);
@@ -131,6 +131,9 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) {
if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest);
else if (mod.version) xsystem("git clone --depth=1 --branch ", mod.version, " ", mod.git, " ", dest);
else xsystem("git clone --depth=1 ", mod.git, " ", dest);
+ // Clean up .git/ folder after cloning:
+ xsystem("rm -rf ", dest, "/.git");
+ // Build library:
xsystem("tomo -L ", dest);
return true;
} else if (mod.url) {
diff --git a/src/parse/expressions.c b/src/parse/expressions.c
index d031c49f..27e44129 100644
--- a/src/parse/expressions.c
+++ b/src/parse/expressions.c
@@ -195,7 +195,9 @@ ast_t *parse_term(parse_ctx_t *ctx, const char *pos) {
return term;
}
-ast_t *parse_expr(parse_ctx_t *ctx, const char *pos) { return parse_infix_expr(ctx, pos, 0); }
+ast_t *parse_expr(parse_ctx_t *ctx, const char *pos) {
+ return parse_infix_expr(ctx, pos, 0);
+}
ast_t *parse_extended_expr(parse_ctx_t *ctx, const char *pos) {
ast_t *expr = NULL;
diff --git a/src/parse/utils.c b/src/parse/utils.c
index f1b518ca..03e0ebcd 100644
--- a/src/parse/utils.c
+++ b/src/parse/utils.c
@@ -41,7 +41,9 @@ size_t some_not(const char **pos, const char *forbid) {
return len;
}
-size_t spaces(const char **pos) { return some_of(pos, " \t"); }
+size_t spaces(const char **pos) {
+ return some_of(pos, " \t");
+}
void whitespace(parse_ctx_t *ctx, const char **pos) {
while (some_of(pos, " \t\r\n") || comment(ctx, pos))
@@ -93,7 +95,9 @@ const char *get_id(const char **inout) {
return word;
}
-PUREFUNC const char *eol(const char *str) { return str + strcspn(str, "\r\n"); }
+PUREFUNC const char *eol(const char *str) {
+ return str + strcspn(str, "\r\n");
+}
bool comment(parse_ctx_t *ctx, const char **pos) {
if ((*pos)[0] == '#') {
diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c
index 7b81e319..a1ffc12e 100644
--- a/src/stdlib/bigint.c
+++ b/src/stdlib/bigint.c
@@ -72,8 +72,8 @@ 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);
- else if (y.small & 1) return mpz_cmp_si(x.big, 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);
}
@@ -102,7 +102,9 @@ CONSTFUNC Int_t Int$clamped(Int_t x, Int_t low, Int_t high) {
public
CONSTFUNC bool Int$is_between(const Int_t x, const Int_t low, const Int_t high) {
- return Int$compare_value(low, x) <= 0 && Int$compare_value(x, high) <= 0;
+ int32_t low_cmp = Int$compare_value(x, low);
+ int32_t high_cmp = Int$compare_value(x, high);
+ return (low_cmp >= 0 && high_cmp <= 0) || (low_cmp <= 0 && high_cmp >= 0);
}
public
@@ -395,7 +397,9 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) {
}
public
-Int_t Int$from_str(const char *str) { return Int$parse(Text$from_str(str), NONE_INT, NULL); }
+Int_t Int$from_str(const char *str) {
+ return Int$parse(Text$from_str(str), NONE_INT, NULL);
+}
public
OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h
index 2936f2cd..a948e1e7 100644
--- a/src/stdlib/bigint.h
+++ b/src/stdlib/bigint.h
@@ -198,18 +198,30 @@ MACROLIKE PUREFUNC Int_t Int$from_float64(double n, bool truncate) {
if (!truncate && unlikely(mpz_get_d(result) != n)) fail("Could not convert to an integer without truncation: ", n);
return Int$from_mpz(result);
}
-MACROLIKE PUREFUNC Int_t Int$from_float32(float n, bool truncate) { return Int$from_float64((double)n, truncate); }
+MACROLIKE PUREFUNC Int_t Int$from_num32(float n, bool truncate) {
+ return Int$from_float64((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);
}
-MACROLIKE CONSTFUNC Int_t Int$from_int32(Int32_t i) { return Int$from_int64((Int32_t)i); }
-MACROLIKE CONSTFUNC Int_t Int$from_int16(Int16_t i) { return I_small(i); }
-MACROLIKE CONSTFUNC Int_t Int$from_int8(Int8_t i) { return I_small(i); }
-MACROLIKE CONSTFUNC Int_t Int$from_byte(Byte_t b) { return I_small(b); }
-MACROLIKE CONSTFUNC Int_t Int$from_bool(Bool_t b) { return I_small(b); }
+MACROLIKE CONSTFUNC Int_t Int$from_int32(Int32_t i) {
+ return Int$from_int64((Int32_t)i);
+}
+MACROLIKE CONSTFUNC Int_t Int$from_int16(Int16_t i) {
+ return I_small(i);
+}
+MACROLIKE CONSTFUNC Int_t Int$from_int8(Int8_t i) {
+ return I_small(i);
+}
+MACROLIKE CONSTFUNC Int_t Int$from_byte(Byte_t b) {
+ return I_small(b);
+}
+MACROLIKE CONSTFUNC Int_t Int$from_bool(Bool_t b) {
+ return I_small(b);
+}
#ifdef __GNUC__
#pragma GCC diagnostic pop
diff --git a/src/stdlib/bools.h b/src/stdlib/bools.h
index 52bd45a8..17ff3681 100644
--- a/src/stdlib/bools.h
+++ b/src/stdlib/bools.h
@@ -13,11 +13,23 @@
PUREFUNC Text_t Bool$as_text(const void *b, bool colorize, const TypeInfo_t *type);
OptionalBool_t Bool$parse(Text_t text, Text_t *remainder);
-MACROLIKE Bool_t Bool$from_int(Int_t i) { return (i.small != 0); }
-MACROLIKE Bool_t Bool$from_int64(Int64_t i) { return (i != 0); }
-MACROLIKE Bool_t Bool$from_int32(Int32_t i) { return (i != 0); }
-MACROLIKE Bool_t Bool$from_int16(Int16_t i) { return (i != 0); }
-MACROLIKE Bool_t Bool$from_int8(Int8_t i) { return (i != 0); }
-MACROLIKE Bool_t Bool$from_byte(uint8_t b) { return (b != 0); }
+MACROLIKE Bool_t Bool$from_int(Int_t i) {
+ return (i.small != 0);
+}
+MACROLIKE Bool_t Bool$from_int64(Int64_t i) {
+ return (i != 0);
+}
+MACROLIKE Bool_t Bool$from_int32(Int32_t i) {
+ return (i != 0);
+}
+MACROLIKE Bool_t Bool$from_int16(Int16_t i) {
+ return (i != 0);
+}
+MACROLIKE Bool_t Bool$from_int8(Int8_t i) {
+ return (i != 0);
+}
+MACROLIKE Bool_t Bool$from_byte(uint8_t b) {
+ return (b != 0);
+}
extern const TypeInfo_t Bool$info;
diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c
index 4416d804..9874f222 100644
--- a/src/stdlib/bytes.c
+++ b/src/stdlib/bytes.c
@@ -25,12 +25,14 @@ PUREFUNC public Text_t Byte$as_text(const void *b, bool colorize, const TypeInfo
'\0',
};
Text_t text = Text$from_str(digits);
- if (colorize) text = Texts(Text("\x1b[35m"), text, Text("\x1b[m"));
+ if (colorize) text = Text$concat(Text("\x1b[35m"), text, Text("\x1b[m"));
return text;
}
public
-CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { return low <= x && x <= high; }
+CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) {
+ return (low <= x && x <= high) || (high <= x && x <= low);
+}
public
OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
@@ -67,7 +69,9 @@ public
bool Byte$get_bit(Byte_t x, Int_t bit_index) {
if (Int$compare_value(bit_index, I(1)) < 0) fail("Invalid bit index (expected 1 or higher): ", bit_index);
if (Int$compare_value(bit_index, I(8)) > 0)
- fail("Bit index is too large! There are only 8 bits in a byte, but index is: ", bit_index);
+ fail("Bit index is too large! There are only 8 bits in a byte, but index "
+ "is: ",
+ bit_index);
return ((x & (Byte_t)(1L << (Int64$from_int(bit_index, true) - 1L))) != 0);
}
diff --git a/src/stdlib/bytes.h b/src/stdlib/bytes.h
index 6581f300..b0a1c213 100644
--- a/src/stdlib/bytes.h
+++ b/src/stdlib/bytes.h
@@ -21,8 +21,12 @@ Byte_t Byte$from_int16(int16_t i, bool truncate);
OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder);
Closure_t Byte$to(Byte_t first, Byte_t last, OptionalInt8_t step);
-MACROLIKE Byte_t Byte$from_int8(int8_t i) { return (Byte_t)i; }
-MACROLIKE Byte_t Byte$from_bool(bool b) { return (Byte_t)b; }
+MACROLIKE Byte_t Byte$from_int8(int8_t i) {
+ return (Byte_t)i;
+}
+MACROLIKE Byte_t Byte$from_bool(bool b) {
+ return (Byte_t)b;
+}
CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high);
extern const Byte_t Byte$min;
diff --git a/src/stdlib/c_strings.c b/src/stdlib/c_strings.c
index 57960577..cbe46b68 100644
--- a/src/stdlib/c_strings.c
+++ b/src/stdlib/c_strings.c
@@ -70,8 +70,8 @@ const char *CString$join(const char *glue, List_t strings) {
Text_t ret = EMPTY_TEXT;
Text_t glue_text = Text$from_str(glue);
for (int64_t i = 0; i < (int64_t)strings.length; i++) {
- if (i > 0) ret = Texts(ret, glue_text);
- ret = Texts(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride)));
+ if (i > 0) ret = Text$concat(ret, glue_text);
+ ret = Text$concat(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride)));
}
return Text$as_c_string(ret);
}
diff --git a/src/stdlib/enums.c b/src/stdlib/enums.c
index b9b970fa..9cc16c5d 100644
--- a/src/stdlib/enums.c
+++ b/src/stdlib/enums.c
@@ -65,7 +65,7 @@ Text_t Enum$as_text(const void *obj, bool colorize, const TypeInfo_t *type) {
NamedType_t value = type->EnumInfo.tags[tag - 1];
if (!value.type || value.type->size == 0) {
Text_t text = Text$from_str(value.name);
- return colorize ? Texts(Text("\x1b[1m"), text, Text("\x1b[m")) : text;
+ return colorize ? Text$concat(Text("\x1b[1m"), text, Text("\x1b[m")) : text;
}
return generic_as_text(obj + value_offset(type), colorize, value.type);
diff --git a/src/stdlib/floatX.c.h b/src/stdlib/floatX.c.h
index 54477ee3..cfef29fd 100644
--- a/src/stdlib/floatX.c.h
+++ b/src/stdlib/floatX.c.h
@@ -50,7 +50,7 @@ PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo
if (!x) return Text(TYPE_STR);
static const Text_t color_prefix = Text("\x1b[35m"), color_suffix = Text("\x1b[m");
Text_t text = NAMESPACED(value_as_text)(*(FLOAT_T *)x);
- return colorize ? Texts(color_prefix, text, color_suffix) : text;
+ return colorize ? Text$concat(color_prefix, text, color_suffix) : text;
}
public
PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) {
@@ -66,7 +66,9 @@ PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInf
}
#elif FLOATX_C_H__BITS == 32
public
-PUREFUNC Text_t NAMESPACED(value_as_text)(FLOAT_T x) { return Float64$value_as_text((double)x); }
+PUREFUNC Text_t NAMESPACED(value_as_text)(FLOAT_T x) {
+ return Float64$value_as_text((double)x);
+}
public
PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *info) {
(void)info;
@@ -115,7 +117,7 @@ public
Text_t NAMESPACED(percent)(FLOAT_T x, FLOAT_T precision) {
FLOAT_T d = SUFFIXED(100.) * x;
d = NAMESPACED(with_precision)(d, precision);
- return Texts(NAMESPACED(value_as_text)(d), Text("%"));
+ return Text$concat(NAMESPACED(value_as_text)(d), Text("%"));
}
public
@@ -186,11 +188,17 @@ CONSTFUNC bool NAMESPACED(is_none)(const void *n, const TypeInfo_t *info) {
}
public
-CONSTFUNC bool NAMESPACED(isinf)(FLOAT_T n) { return (fpclassify(n) == FP_INFINITE); }
+CONSTFUNC bool NAMESPACED(isinf)(FLOAT_T n) {
+ return (fpclassify(n) == FP_INFINITE);
+}
public
-CONSTFUNC bool NAMESPACED(finite)(FLOAT_T n) { return (fpclassify(n) != FP_INFINITE); }
+CONSTFUNC bool NAMESPACED(finite)(FLOAT_T n) {
+ return (fpclassify(n) != FP_INFINITE);
+}
public
-CONSTFUNC bool NAMESPACED(isnan)(FLOAT_T n) { return (fpclassify(n) == FP_NAN); }
+CONSTFUNC bool NAMESPACED(isnan)(FLOAT_T n) {
+ return (fpclassify(n) == FP_NAN);
+}
public
const TypeInfo_t NAMESPACED(info) = {
diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h
index 0910c7f1..04c8ef3b 100644
--- a/src/stdlib/intX.c.h
+++ b/src/stdlib/intX.c.h
@@ -55,12 +55,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
- fwrite(obj, sizeof(INT_T), 1, out);
+ if (fwrite(obj, sizeof(INT_T), 1, out) != sizeof(INT_T)) fail("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) {
- fputc((uint8_t)(z | 0x80L), out);
+ if (fputc((uint8_t)(z | 0x80L), out) == EOF) fail("Failed to write full integer");
z >>= 7L;
}
fputc((uint8_t)z, out);
@@ -71,11 +71,13 @@ 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
- fread(outval, sizeof(INT_T), 1, in);
+ if (fread(outval, sizeof(INT_T), 1, in) != sizeof(INT_T)) fail("Failed to read full integer");
#else
UINT_T z = 0;
for (size_t shift = 0;; shift += 7) {
- uint8_t byte = (uint8_t)fgetc(in);
+ int i = fgetc(in);
+ if (i == EOF) fail("Failed to read whole integer");
+ uint8_t byte = (uint8_t)i;
z |= ((UINT_T)(byte & 0x7F)) << shift;
if ((byte & 0x80) == 0) break;
}
@@ -96,10 +98,12 @@ Text_t NAMESPACED(as_text)(const void *i, bool colorize, const TypeInfo_t *info)
(void)info;
if (!i) return Text(NAME_STR);
Text_t text = _int64_to_text((int64_t)(*(INT_T *)i));
- return colorize ? Texts(Text("\033[35m"), text, Text("\033[m")) : text;
+ return colorize ? Text$concat(Text("\033[35m"), text, Text("\033[m")) : text;
}
public
-Text_t NAMESPACED(value_as_text)(INT_T i) { return _int64_to_text((int64_t)i); }
+Text_t NAMESPACED(value_as_text)(INT_T i) {
+ return _int64_to_text((int64_t)i);
+}
public
PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) {
(void)info;
@@ -112,10 +116,12 @@ PUREFUNC bool NAMESPACED(equal)(const void *x, const void *y, const TypeInfo_t *
}
public
CONSTFUNC bool NAMESPACED(is_between)(const INT_T x, const INT_T low, const INT_T high) {
- return low <= x && x <= high;
+ return (low <= x && x <= high) || (high <= x && x <= low);
}
public
-CONSTFUNC INT_T NAMESPACED(clamped)(INT_T x, INT_T min, INT_T max) { return x < min ? min : (x > max ? max : x); }
+CONSTFUNC INT_T NAMESPACED(clamped)(INT_T x, INT_T min, INT_T max) {
+ return x < min ? min : (x > max ? max : x);
+}
public
Text_t NAMESPACED(hex)(INT_T i, Int_t digits_int, bool uppercase, bool prefix) {
Int_t as_int = Int$from_int64((int64_t)i);
diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h
index 4d8f8e3d..c90babcb 100644
--- a/src/stdlib/intX.h
+++ b/src/stdlib/intX.h
@@ -49,8 +49,12 @@ Closure_t NAMESPACED(onward)(INTX_T first, INTX_T step);
PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder);
CONSTFUNC bool NAMESPACED(is_between)(const INTX_T x, const INTX_T low, const INTX_T high);
CONSTFUNC INTX_T NAMESPACED(clamped)(INTX_T x, INTX_T min, INTX_T max);
-MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) { return (INTX_T)b; }
-MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_bool)(Bool_t b) { return (INTX_T)b; }
+MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) {
+ return (INTX_T)b;
+}
+MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_bool)(Bool_t b) {
+ return (INTX_T)b;
+}
CONSTFUNC INTX_T NAMESPACED(gcd)(INTX_T x, INTX_T y);
extern const INTX_T NAMESPACED(min), NAMESPACED(max);
extern const TypeInfo_t NAMESPACED(info);
@@ -75,15 +79,25 @@ MACROLIKE INTX_T NAMESPACED(modulo)(INTX_T D, INTX_T d) {
return r;
}
-MACROLIKE INTX_T NAMESPACED(modulo1)(INTX_T D, INTX_T d) { return NAMESPACED(modulo)(D - 1, d) + 1; }
+MACROLIKE INTX_T NAMESPACED(modulo1)(INTX_T D, INTX_T d) {
+ return NAMESPACED(modulo)(D - 1, d) + 1;
+}
-MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_plus)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x + (UINTX_T)y); }
+MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_plus)(INTX_T x, INTX_T y) {
+ return (INTX_T)((UINTX_T)x + (UINTX_T)y);
+}
-MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_minus)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x + (UINTX_T)y); }
+MACROLIKE PUREFUNC INTX_T NAMESPACED(wrapping_minus)(INTX_T x, INTX_T y) {
+ return (INTX_T)((UINTX_T)x + (UINTX_T)y);
+}
-MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_left_shifted)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x << y); }
+MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_left_shifted)(INTX_T x, INTX_T y) {
+ return (INTX_T)((UINTX_T)x << y);
+}
-MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) { return (INTX_T)((UINTX_T)x >> y); }
+MACROLIKE PUREFUNC INTX_T NAMESPACED(unsigned_right_shifted)(INTX_T x, INTX_T y) {
+ return (INTX_T)((UINTX_T)x >> 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 *);
@@ -122,7 +136,9 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int64)(Int64_t i64, bool truncate) {
return i;
}
#elif INTX_H__INT_BITS > 64
-MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int64)(Int64_t i) { return (INTX_T)i; }
+MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int64)(Int64_t i) {
+ return (INTX_T)i;
+}
#endif
#if INTX_H__INT_BITS < 32
@@ -132,7 +148,9 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int32)(Int32_t i32, bool truncate) {
return i;
}
#elif INTX_H__INT_BITS > 32
-MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int32)(Int32_t i) { return (INTX_T)i; }
+MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int32)(Int32_t i) {
+ return (INTX_T)i;
+}
#endif
#if INTX_H__INT_BITS < 16
@@ -142,11 +160,15 @@ MACROLIKE PUREFUNC INTX_T NAMESPACED(from_int16)(Int16_t i16, bool truncate) {
return i;
}
#elif INTX_H__INT_BITS > 16
-MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int16)(Int16_t i) { return (INTX_T)i; }
+MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int16)(Int16_t i) {
+ return (INTX_T)i;
+}
#endif
#if INTX_H__INT_BITS > 8
-MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int8)(Int8_t i) { return (INTX_T)i; }
+MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_int8)(Int8_t i) {
+ return (INTX_T)i;
+}
#endif
#undef PASTE3_
diff --git a/src/stdlib/lists.c b/src/stdlib/lists.c
index 1a47f2e3..93dc63e9 100644
--- a/src/stdlib/lists.c
+++ b/src/stdlib/lists.c
@@ -440,10 +440,14 @@ List_t List$sample(List_t list, Int_t int_n, List_t weights, OptionalClosure_t r
}
public
-List_t List$from(List_t list, Int_t first) { return List$slice(list, first, I_small(-1)); }
+List_t List$from(List_t list, Int_t first) {
+ return List$slice(list, first, I_small(-1));
+}
public
-List_t List$to(List_t list, Int_t last) { return List$slice(list, I_small(1), last); }
+List_t List$to(List_t list, Int_t last) {
+ return List$slice(list, I_small(1), last);
+}
public
List_t List$by(List_t list, Int_t int_stride, int64_t padded_item_size) {
@@ -554,7 +558,9 @@ bool List$has(List_t list, void *item, const TypeInfo_t *type) {
}
public
-void List$clear(List_t *list) { *list = list->atomic ? EMPTY_ATOMIC_LIST : EMPTY_LIST; }
+void List$clear(List_t *list) {
+ *list = list->atomic ? EMPTY_ATOMIC_LIST : EMPTY_LIST;
+}
public
int32_t List$compare(const void *vx, const void *vy, const TypeInfo_t *type) {
diff --git a/src/stdlib/lists.h b/src/stdlib/lists.h
index 457fed52..9ac8bf1b 100644
--- a/src/stdlib/lists.h
+++ b/src/stdlib/lists.h
@@ -20,8 +20,9 @@ extern char _EMPTY_LIST_SENTINEL;
int64_t index = index_expr; \
int64_t off = index + (index < 0) * (list.length + 1) - 1; \
if (unlikely(off < 0 || off >= list.length)) \
- fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \
- (int64_t)list.length, ")\n"); \
+ fail_source(__SOURCE_FILE__, start, end, \
+ Text$concat(Text("Invalid list index: "), convert_to_text(index), Text(" (list has length "), \
+ convert_to_text((int64_t)list.length), Text(")\n"))); \
*(item_type *)(list.data + list.stride * off); \
})
#define List_get(list_expr, index_expr, item_type, var, optional_expr, none_expr) \
@@ -40,8 +41,9 @@ extern char _EMPTY_LIST_SENTINEL;
int64_t index = index_expr; \
int64_t off = index + (index < 0) * (list->length + 1) - 1; \
if (unlikely(off < 0 || off >= list->length)) \
- fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \
- (int64_t)list->length, ")\n"); \
+ fail_source(__SOURCE_FILE__, start, end, \
+ Text$concat(Text("Invalid list index: "), convert_to_text(index), Text(" (list has length "), \
+ convert_to_text((int64_t)list->length), Text(")\n"))); \
if (list->data_refcount > 0) List$compact(list, sizeof(item_type)); \
(item_type *)(list->data + list->stride * off); \
})
@@ -61,7 +63,8 @@ extern char _EMPTY_LIST_SENTINEL;
t items[] = {__VA_ARGS__}; \
(List_t){.length = sizeof(items) / sizeof(items[0]), \
.stride = (int64_t)&items[1] - (int64_t)&items[0], \
- .data = memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \
+ .data = sizeof(items) == 0 ? &_EMPTY_LIST_SENTINEL \
+ : memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \
.atomic = 0, \
.data_refcount = 0}; \
})
@@ -70,7 +73,7 @@ extern char _EMPTY_LIST_SENTINEL;
t items[N] = {__VA_ARGS__}; \
(List_t){.length = N, \
.stride = (int64_t)&items[1] - (int64_t)&items[0], \
- .data = memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \
+ .data = N == 0 ? &_EMPTY_LIST_SENTINEL : memcpy(GC_MALLOC(sizeof(items)), items, sizeof(items)), \
.atomic = 0, \
.data_refcount = 0}; \
})
diff --git a/src/stdlib/mapmacro.h b/src/stdlib/mapmacro.h
index 7b0e3c4e..5e9eaa36 100644
--- a/src/stdlib/mapmacro.h
+++ b/src/stdlib/mapmacro.h
@@ -7,9 +7,7 @@
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
-#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
-#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
-#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
+#define EVAL(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define MAP_END(...)
#define MAP_OUT
diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c
index fd396463..53a180fb 100644
--- a/src/stdlib/memory.c
+++ b/src/stdlib/memory.c
@@ -18,7 +18,7 @@ Text_t Memory$as_text(const void *p, bool colorize, const TypeInfo_t *info) {
(void)info;
if (!p) return Text("Memory");
Text_t text = Text$from_str(String("Memory<", (void *)p, ">"));
- return colorize ? Texts(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text;
+ return colorize ? Text$concat(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text;
}
public
diff --git a/src/stdlib/numX.h b/src/stdlib/numX.h
new file mode 100644
index 00000000..87794762
--- /dev/null
+++ b/src/stdlib/numX.h
@@ -0,0 +1,115 @@
+// Template header for 64 and 32 bit Nums
+// This file expects `NUMX_H__BITS` to be defined before including:
+//
+// #define NUMX_H__BITS 64
+// #include "numX.h"
+//
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "datatypes.h"
+#include "stdlib.h"
+#include "types.h"
+#include "util.h"
+
+#ifndef NUMX_H__BITS
+#define NUMX_H__BITS 64
+#endif
+
+#if NUMX_H__BITS == 64
+#define NUM_T double
+#define OPT_T double
+#define NAMESPACED(x) Num$##x
+#define TYPE_STR "Num"
+#define SUFFIXED(x) x
+#elif NUMX_H__BITS == 32
+#define NUM_T float
+#define OPT_T float
+#define NAMESPACED(x) Num32$##x
+#define TYPE_STR "Num32"
+#define SUFFIXED(x) x##f
+#else
+#error "Unsupported bit width for Num"
+#endif
+
+Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *type);
+Text_t NAMESPACED(value_as_text)(NUM_T x);
+PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *type);
+PUREFUNC bool NAMESPACED(equal)(const void *x, const void *y, const TypeInfo_t *type);
+CONSTFUNC bool NAMESPACED(near)(NUM_T a, NUM_T b, NUM_T ratio, NUM_T absolute);
+Text_t NAMESPACED(percent)(NUM_T x, NUM_T precision);
+NUM_T CONSTFUNC NAMESPACED(with_precision)(NUM_T num, NUM_T precision);
+NUM_T NAMESPACED(mod)(NUM_T num, NUM_T modulus);
+NUM_T NAMESPACED(mod1)(NUM_T num, NUM_T modulus);
+CONSTFUNC bool NAMESPACED(isinf)(NUM_T n);
+CONSTFUNC bool NAMESPACED(finite)(NUM_T n);
+CONSTFUNC bool NAMESPACED(isnan)(NUM_T n);
+bool NAMESPACED(is_none)(const void *n, const TypeInfo_t *info);
+NUM_T NAMESPACED(nan)(Text_t tag);
+CONSTFUNC NUM_T NAMESPACED(mix)(NUM_T amount, NUM_T x, NUM_T y);
+OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder);
+CONSTFUNC bool NAMESPACED(is_between)(const NUM_T x, const NUM_T low, const NUM_T high);
+CONSTFUNC NUM_T NAMESPACED(clamped)(NUM_T x, NUM_T low, NUM_T high);
+
+#if NUMX_H__BITS == 64
+MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_num32)(float n) {
+ return (NUM_T)n;
+}
+#elif NUMX_H__BITS == 32
+MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_num64)(double n) {
+ return (NUM_T)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;
+}
+MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int32)(Int32_t i) {
+ return (NUM_T)i;
+}
+MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int16)(Int16_t i) {
+ return (NUM_T)i;
+}
+MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_int8)(Int8_t i) {
+ return (NUM_T)i;
+}
+MACROLIKE CONSTFUNC NUM_T NAMESPACED(from_byte)(Byte_t i) {
+ return (NUM_T)i;
+}
+
+extern const TypeInfo_t NAMESPACED(info);
+
+#undef NUM_T
+#undef OPT_T
+#undef NAMESPACED
+#undef TYPE_STR
+#undef SUFFIXED
+#undef NUMX_H__BITS
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index ed8383fd..dcedcd03 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -98,7 +98,9 @@ Path_t Path$from_str(const char *str) {
}
public
-Path_t Path$from_text(Text_t text) { return Path$from_str(Text$as_c_string(text)); }
+Path_t Path$from_text(Text_t text) {
+ return Path$from_str(Text$as_c_string(text));
+}
public
Path_t Path$expand_home(Path_t path) {
@@ -324,6 +326,79 @@ Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) {
return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
+typedef struct {
+ const char *path_str;
+ int fd;
+ int mode;
+ int permissions;
+} writer_data_t;
+
+static Result_t _write_bytes_to_fd(List_t bytes, bool close_file, void *userdata) {
+ writer_data_t *data = userdata;
+ if (bytes.length > 0) {
+ if (data->fd == -1) {
+ data->fd = open(data->path_str, data->mode, data->permissions);
+ if (data->fd == -1) {
+ if (errno == EMFILE || errno == ENFILE) {
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles
+ // that will be closed by GC finalizers.
+ GC_gcollect();
+ data->fd = open(data->path_str, data->mode, data->permissions);
+ }
+ if (data->fd == -1)
+ return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")");
+ }
+ }
+
+ if (bytes.stride != 1) List$compact(&bytes, 1);
+ ssize_t written = write(data->fd, bytes.data, (size_t)bytes.length);
+ if (written != (ssize_t)bytes.length)
+ return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")");
+ }
+ // After first successful write, all writes are appends
+ data->mode = (O_WRONLY | O_CREAT | O_APPEND);
+
+ if (close_file && data->fd != -1) {
+ if (close(data->fd) == -1)
+ return FailureResult("Failed to close file: ", data->path_str, " (", strerror(errno), ")");
+ data->fd = -1;
+ }
+ return SuccessResult;
+}
+
+static Result_t _write_text_to_fd(Text_t text, bool close_file, void *userdata) {
+ return _write_bytes_to_fd(Text$utf8(text), close_file, userdata);
+}
+
+static void _writer_cleanup(writer_data_t *data) {
+ if (data && data->fd != -1) {
+ close(data->fd);
+ data->fd = -1;
+ }
+}
+
+public
+Closure_t Path$byte_writer(Path_t path, bool append, int permissions) {
+ path = Path$expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC);
+ writer_data_t *userdata =
+ new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions);
+ GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL);
+ return (Closure_t){.fn = _write_bytes_to_fd, .userdata = userdata};
+}
+
+public
+Closure_t Path$writer(Path_t path, bool append, int permissions) {
+ path = Path$expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC);
+ writer_data_t *userdata =
+ new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions);
+ GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL);
+ return (Closure_t){.fn = _write_text_to_fd, .userdata = userdata};
+}
+
public
OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
path = Path$expand_home(path);
@@ -331,8 +406,8 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
int fd = open(path_str, O_RDONLY);
if (fd == -1) {
if (errno == EMFILE || errno == ENFILE) {
- // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
- // be closed by GC finalizers.
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that
+ // will be closed by GC finalizers.
GC_gcollect();
fd = open(path_str, O_RDONLY);
}
@@ -574,7 +649,9 @@ OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) {
}
public
-OptionalPath_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); }
+OptionalPath_t Path$write_unique(Path_t path, Text_t text) {
+ return Path$write_unique_bytes(path, Text$utf8(text));
+}
public
OptionalPath_t Path$parent(Path_t path) {
@@ -619,7 +696,7 @@ bool Path$has_extension(Path_t path, Text_t extension) {
if (extension.length == 0)
return !Text$has(Text$from(last, I(2)), Text(".")) || Text$equal_values(last, Text(".."));
- if (!Text$starts_with(extension, Text("."), NULL)) extension = Texts(Text("."), extension);
+ if (!Text$starts_with(extension, Text("."), NULL)) extension = Text$concat(Text("."), extension);
return Text$ends_with(Text$from(last, I(2)), extension, NULL);
}
@@ -638,7 +715,9 @@ Path_t Path$child(Path_t path, Text_t name) {
}
public
-Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); }
+Path_t Path$sibling(Path_t path, Text_t name) {
+ return Path$child(Path$parent(path), name);
+}
public
OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) {
@@ -705,8 +784,8 @@ OptionalClosure_t Path$by_line(Path_t path) {
FILE *f = fopen(path_str, "r");
if (f == NULL) {
if (errno == EMFILE || errno == ENFILE) {
- // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
- // be closed by GC finalizers.
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that
+ // will be closed by GC finalizers.
GC_gcollect();
f = fopen(path_str, "r");
}
@@ -726,8 +805,8 @@ OptionalList_t Path$lines(Path_t path) {
FILE *f = fopen(path_str, "r");
if (f == NULL) {
if (errno == EMFILE || errno == ENFILE) {
- // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
- // be closed by GC finalizers.
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that
+ // will be closed by GC finalizers.
GC_gcollect();
f = fopen(path_str, "r");
}
@@ -794,7 +873,9 @@ int Path$print(FILE *f, Path_t path) {
}
public
-const char *Path$as_c_string(Path_t path) { return String(path); }
+const char *Path$as_c_string(Path_t path) {
+ return String(path);
+}
public
Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
@@ -808,7 +889,7 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
&& (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text(".."))))
text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text);
- if (color) text = Texts(Text("\033[32;1m"), text, Text("\033[m"));
+ if (color) text = Text$concat(Text("\033[32;1m"), text, Text("\033[m"));
return text;
}
diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h
index 881a3c78..c272314c 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -39,6 +39,8 @@ Result_t Path$write(Path_t path, Text_t text, int permissions);
Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions);
Result_t Path$append(Path_t path, Text_t text, int permissions);
Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions);
+Closure_t Path$byte_writer(Path_t path, bool append, int permissions);
+Closure_t Path$writer(Path_t path, bool append, int permissions);
OptionalText_t Path$read(Path_t path);
OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t limit);
Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks);
diff --git a/src/stdlib/pointers.c b/src/stdlib/pointers.c
index 74037613..0bf9a274 100644
--- a/src/stdlib/pointers.c
+++ b/src/stdlib/pointers.c
@@ -38,14 +38,14 @@ Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *type) {
if (top_level) {
root = ptr;
} else if (ptr == root) {
- Text_t text = Texts(Text$from_str(ptr_info.sigil), Text("~1"));
- return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text;
+ Text_t text = Text$concat(Text$from_str(ptr_info.sigil), Text("~1"));
+ return colorize ? Text$concat(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text;
} else {
TypeInfo_t rec_table = *Table$info(type, &Int64$info);
int64_t *id = Table$get(pending, x, &rec_table);
if (id) {
- Text_t text = Texts(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id));
- return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text;
+ Text_t text = Text$concat(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id));
+ return colorize ? Text$concat(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text;
}
int64_t next_id = (int64_t)pending.entries.length + 2;
Table$set(&pending, x, &next_id, &rec_table);
diff --git a/src/stdlib/print.h b/src/stdlib/print.h
index 65de72a0..0837390f 100644
--- a/src/stdlib/print.h
+++ b/src/stdlib/print.h
@@ -81,10 +81,18 @@ int _print_real(FILE *f, Real_t x);
int _print_hex(FILE *f, hex_format_t hex);
int _print_hex_double(FILE *f, hex_double_t hex);
int _print_oct(FILE *f, oct_format_t oct);
-PRINT_FN _print_float(FILE *f, float x) { return _print_double(f, (double)x); }
-PRINT_FN _print_pointer(FILE *f, void *p) { return _print_hex(f, hex((uint64_t)p)); }
-PRINT_FN _print_bool(FILE *f, bool b) { return fputs(b ? "yes" : "no", f); }
-PRINT_FN _print_str(FILE *f, const char *s) { return fputs(s ? s : "(null)", f); }
+PRINT_FN _print_float(FILE *f, float x) {
+ return _print_double(f, (double)x);
+}
+PRINT_FN _print_pointer(FILE *f, void *p) {
+ return _print_hex(f, hex((uint64_t)p));
+}
+PRINT_FN _print_bool(FILE *f, bool b) {
+ return fputs(b ? "yes" : "no", f);
+}
+PRINT_FN _print_str(FILE *f, const char *s) {
+ return fputs(s ? s : "(null)", f);
+}
int _print_char(FILE *f, char c);
int _print_quoted(FILE *f, quoted_t quoted);
PRINT_FN _print_string_slice(FILE *f, string_slice_t slice) {
diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c
index ea939f62..1eba8188 100644
--- a/src/stdlib/stacktrace.c
+++ b/src/stdlib/stacktrace.c
@@ -98,7 +98,7 @@ void print_stacktrace(FILE *out, int offset) {
cwd[cwd_len++] = '/';
cwd[cwd_len] = '\0';
- const char *install_dir = String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/");
+ const char *install_dir = String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/");
static void *stack[1024];
int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0]));
diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c
index f4e6d678..a05b8753 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -17,6 +17,7 @@
#include "files.h"
#include "metamethods.h"
#include "optionals.h"
+#include "paths.h"
#include "print.h"
#include "siphash.h"
#include "stacktrace.h"
@@ -39,11 +40,91 @@ static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
public
bool USE_COLOR;
+
+public
+const char *TOMO_PATH = "/usr/local";
+
public
-Text_t TOMO_VERSION_TEXT = Text(TOMO_VERSION);
+const char *TOMO_VERSION = "v0";
+
+public
+Text_t TOMO_VERSION_TEXT = Text("v0");
+
+#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#include <dlfcn.h>
+
+static inline const char *get_library_path(void *func) {
+ static Dl_info info;
+ if (dladdr(func, &info)) {
+ return info.dli_fname; // full path of the library
+ }
+ return NULL;
+}
+
+#elif defined(_WIN32)
+#include <windows.h>
+
+static inline const char *get_library_path(void *func) {
+ static char path[MAX_PATH];
+ HMODULE hm = NULL;
+ if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ (LPCSTR)func, &hm)) {
+ if (GetModuleFileName(hm, path, MAX_PATH)) {
+ return path;
+ }
+ }
+ return NULL;
+}
+
+#else
+#error "Unsupported platform"
+#endif
+
+char *find_in_path(const char *name) {
+ if (strchr(name, '/')) {
+ // name contains a slash → treat as path
+ char *abs = realpath(name, NULL);
+ if (abs == NULL) fail("Couldn't find real path of: ", name);
+ char *ret = String(abs);
+ free(abs);
+ return ret;
+ }
+
+ char *path_env = getenv("PATH");
+ if (!path_env) return NULL;
+
+ char *paths = strdup(path_env);
+ if (!paths) return NULL;
+
+ char *token = strtok(paths, ":");
+ while (token) {
+ char candidate[PATH_MAX];
+ snprintf(candidate, sizeof(candidate), "%s/%s", token, name);
+ if (access(candidate, X_OK) == 0) {
+ char *abs = realpath(candidate, NULL);
+ free(paths);
+ char *ret = String(abs);
+ free(abs);
+ return ret;
+ }
+ token = strtok(NULL, ":");
+ }
+
+ free(paths);
+ return NULL; // not found
+}
public
-const char *TOMO_PATH = TOMO_INSTALL;
+void tomo_configure(void) {
+ const char *p = get_library_path(get_library_path);
+ p = find_in_path(p);
+ Path_t path = Path$from_str(p);
+ TOMO_PATH = Path$as_c_string(Path$parent(Path$parent(path)));
+ Text_t base_name = Path$base_name(path);
+ TOMO_VERSION_TEXT = Text$without_suffix(
+ Text$without_prefix(Text$without_prefix(base_name, Text("lib")), Text("tomo@")), Text(".so"));
+ TOMO_VERSION = Text$as_c_string(TOMO_VERSION_TEXT);
+}
static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) {
(void)info, (void)userdata;
@@ -60,6 +141,7 @@ static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) {
public
void tomo_init(void) {
GC_INIT();
+ tomo_configure();
const char *color_env = getenv("COLOR");
USE_COLOR = color_env ? strcmp(color_env, "1") == 0 : isatty(STDOUT_FILENO);
const char *no_color_env = getenv("NO_COLOR");
@@ -77,10 +159,14 @@ void tomo_init(void) {
}
public
-_Noreturn void fail_text(Text_t message) { fail(message); }
+_Noreturn void fail_text(Text_t message) {
+ fail(message);
+}
public
-Text_t builtin_last_err() { return Text$from_str(strerror(errno)); }
+Text_t builtin_last_err() {
+ return Text$from_str(strerror(errno));
+}
static int _inspect_depth = 0;
static file_t *file = NULL;
@@ -246,7 +332,9 @@ typedef struct cleanup_s {
static cleanup_t *cleanups = NULL;
public
-void tomo_at_cleanup(Closure_t fn) { cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); }
+void tomo_at_cleanup(Closure_t fn) {
+ cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups);
+}
public
void tomo_cleanup(void) {
diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h
index 3afe3529..087ed4bf 100644
--- a/src/stdlib/stdlib.h
+++ b/src/stdlib/stdlib.h
@@ -15,6 +15,7 @@
extern bool USE_COLOR;
extern Text_t TOMO_VERSION_TEXT;
+void tomo_configure(void);
void tomo_init(void);
void tomo_at_cleanup(Closure_t fn);
void tomo_cleanup(void);
@@ -36,7 +37,7 @@ void tomo_cleanup(void);
exit(1); \
})
-#define fail_source(filename, start, end, ...) \
+#define fail_source(filename, start, end, message) \
({ \
tomo_cleanup(); \
fflush(stdout); \
@@ -45,7 +46,7 @@ void tomo_cleanup(void);
print_stacktrace(stderr, 0); \
fputs("\n", stderr); \
if (USE_COLOR) fputs("\x1b[31;1m", stderr); \
- fprint_inline(stderr, __VA_ARGS__); \
+ Text$print(stderr, message); \
file_t *_file = (filename) ? load_file(filename) : NULL; \
if ((filename) && _file) { \
fputs("\n", stderr); \
diff --git a/src/stdlib/structs.c b/src/stdlib/structs.c
index da8f1461..d1b9f824 100644
--- a/src/stdlib/structs.c
+++ b/src/stdlib/structs.c
@@ -126,10 +126,10 @@ PUREFUNC public Text_t Struct$as_text(const void *obj, bool colorize, const Type
Text_t name = Text$from_str(type->StructInfo.name);
if (type->StructInfo.is_secret || type->StructInfo.is_opaque) {
- return colorize ? Texts(Text("\x1b[0;1m"), name, Text("\x1b[m(...)")) : Texts(name, Text("(...)"));
+ return colorize ? Text$concat(Text("\x1b[0;1m"), name, Text("\x1b[m(...)")) : Text$concat(name, Text("(...)"));
}
- Text_t text = colorize ? Texts(Text("\x1b[0;1m"), name, Text("\x1b[m(")) : Texts(name, Text("("));
+ Text_t text = colorize ? Text$concat(Text("\x1b[0;1m"), name, Text("\x1b[m(")) : Text$concat(name, Text("("));
ptrdiff_t byte_offset = 0;
ptrdiff_t bit_offset = 0;
for (int i = 0; i < type->StructInfo.num_fields; i++) {
diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c
index a801957f..753059c8 100644
--- a/src/stdlib/tables.c
+++ b/src/stdlib/tables.c
@@ -24,14 +24,6 @@
#include "types.h"
#include "util.h"
-// #define DEBUG_TABLES
-
-#ifdef DEBUG_TABLES
-#define hdebug(...) print_inline("\x1b[2m", __VA_ARGS__, "\x1b[m")
-#else
-#define hdebug(...) (void)0
-#endif
-
// Helper accessors for type functions/values:
#define HASH_KEY(t, k) (generic_hash((k), type->TableInfo.key) % ((t).bucket_info->count))
#define EQUAL_KEYS(x, y) (generic_equal((x), (y), type->TableInfo.key))
@@ -76,18 +68,6 @@ PUREFUNC static INLINE size_t value_offset(const TypeInfo_t *info) {
return offset;
}
-static INLINE void hshow(const Table_t *t) {
- hdebug("{");
- for (uint32_t i = 0; t->bucket_info && i < t->bucket_info->count; i++) {
- if (i > 0) hdebug(" ");
- if (t->bucket_info->buckets[i].occupied)
- hdebug("[", i, "]=", (uint32_t)t->bucket_info->buckets[i].index, "(",
- t->bucket_info->buckets[i].next_bucket, ")");
- else hdebug("[", i, "]=_");
- }
- hdebug("}\n");
-}
-
static void maybe_copy_on_write(Table_t *t, const TypeInfo_t *type) {
if (t->entries.data_refcount != 0) List$compact(&t->entries, (int64_t)entry_size(type));
@@ -104,14 +84,10 @@ PUREFUNC public void *Table$get_raw(Table_t t, const void *key, const TypeInfo_t
if (!key || !t.bucket_info) return NULL;
uint64_t hash = HASH_KEY(t, key);
- hshow(&t);
- hdebug("Getting value with initial probe at ", hash, "\n");
bucket_t *buckets = t.bucket_info->buckets;
for (uint64_t i = hash; buckets[i].occupied; i = buckets[i].next_bucket) {
- hdebug("Checking against key in bucket ", i, "\n");
void *entry = GET_ENTRY(t, buckets[i].index);
if (EQUAL_KEYS(entry, key)) {
- hdebug("Found key!\n");
return entry + value_offset(type);
}
if (buckets[i].next_bucket == END_OF_CHAIN) break;
@@ -130,24 +106,18 @@ PUREFUNC public void *Table$get(Table_t t, const void *key, const TypeInfo_t *ty
static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const TypeInfo_t *type) {
assert(t->bucket_info);
- hshow(t);
const void *key = entry;
bucket_t *buckets = t->bucket_info->buckets;
uint64_t hash = HASH_KEY(*t, key);
- hdebug("Hash value (mod ", (int32_t)t->bucket_info->count, ") = ", hash, "\n");
bucket_t *bucket = &buckets[hash];
if (!bucket->occupied) {
- hdebug("Got an empty space\n");
// Empty space:
bucket->occupied = 1;
bucket->index = index;
bucket->next_bucket = END_OF_CHAIN;
- hshow(t);
return;
}
- hdebug("Collision detected in bucket ", hash, " (entry ", (uint32_t)bucket->index, ")\n");
-
while (buckets[t->bucket_info->last_free].occupied) {
assert(t->bucket_info->last_free > 0);
--t->bucket_info->last_free;
@@ -155,7 +125,6 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const
uint64_t collided_hash = HASH_KEY(*t, GET_ENTRY(*t, bucket->index));
if (collided_hash != hash) { // Collided with a mid-chain entry
- hdebug("Hit a mid-chain entry at bucket ", hash, " (chain starting at ", collided_hash, ")\n");
// Find chain predecessor
uint64_t predecessor = collided_hash;
while (buckets[predecessor].next_bucket != hash)
@@ -168,20 +137,18 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const
bucket->occupied = 1;
bucket->index = index;
bucket->next_bucket = END_OF_CHAIN;
- } else { // Collided with the start of a chain, put the new entry in chain position #2
- hdebug("Hit start of a chain\n");
+ } else { // Collided with the start of a chain, put the new entry in chain
+ // position #2
buckets[t->bucket_info->last_free] =
(bucket_t){.occupied = 1, .index = index, .next_bucket = bucket->next_bucket};
bucket->next_bucket = t->bucket_info->last_free;
}
- hshow(t);
}
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!");
- hdebug("About to resize from ", t->bucket_info ? (int32_t)t->bucket_info->count : 0, " to ", new_capacity, "\n");
- hshow(t);
+ fail("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]));
@@ -189,12 +156,8 @@ static void hashmap_resize_buckets(Table_t *t, uint32_t new_capacity, const Type
t->bucket_info->last_free = new_capacity - 1;
// Rehash:
for (int64_t i = 0; i < (int64_t)Table$length(*t); i++) {
- hdebug("Rehashing ", i, "\n");
Table$set_bucket(t, GET_ENTRY(*t, i), i, type);
}
-
- hshow(t);
- hdebug("Finished resizing\n");
}
// Return address of value
@@ -206,7 +169,6 @@ public
void *Table$reserve(Table_t *t, const void *key, const void *value, const TypeInfo_t *type) {
assert(type->tag == TableInfo);
if (!t || !key) return NULL;
- hshow(t);
t->hash = 0;
@@ -295,12 +257,10 @@ void Table$remove(Table_t *t, const void *key, const TypeInfo_t *type) {
// maybe update lastfree_index1 to removed bucket's index
uint64_t hash = HASH_KEY(*t, key);
- hdebug("Removing key with hash ", hash, "\n");
bucket_t *bucket, *prev = NULL;
for (uint64_t i = hash; t->bucket_info->buckets[i].occupied; i = t->bucket_info->buckets[i].next_bucket) {
if (EQUAL_KEYS(GET_ENTRY(*t, t->bucket_info->buckets[i].index), key)) {
bucket = &t->bucket_info->buckets[i];
- hdebug("Found key to delete in bucket ", i, "\n");
goto found_it;
}
if (t->bucket_info->buckets[i].next_bucket == END_OF_CHAIN) return;
@@ -319,8 +279,6 @@ found_it:;
// instead of O(N)
int64_t last_entry = (int64_t)t->entries.length - 1;
if (bucket->index != last_entry) {
- hdebug("Removing key/value from the middle of the entries list\n");
-
// Find the bucket that points to the last entry's index:
uint64_t i = HASH_KEY(*t, GET_ENTRY(*t, last_entry));
while (t->bucket_info->buckets[i].index != last_entry)
@@ -341,22 +299,17 @@ found_it:;
int64_t bucket_to_clear;
if (prev) { // Middle (or end) of a chain
- hdebug("Removing from middle of a chain\n");
bucket_to_clear = (bucket - t->bucket_info->buckets);
prev->next_bucket = bucket->next_bucket;
} else if (bucket->next_bucket != END_OF_CHAIN) { // Start of a chain
- hdebug("Removing from start of a chain\n");
bucket_to_clear = bucket->next_bucket;
*bucket = t->bucket_info->buckets[bucket_to_clear];
} else { // Empty chain
- hdebug("Removing from empty chain\n");
bucket_to_clear = (bucket - t->bucket_info->buckets);
}
t->bucket_info->buckets[bucket_to_clear] = (bucket_t){0};
if (bucket_to_clear > t->bucket_info->last_free) t->bucket_info->last_free = bucket_to_clear;
-
- hshow(t);
}
CONSTFUNC public void *Table$entry(Table_t t, int64_t n) {
@@ -365,7 +318,9 @@ CONSTFUNC public void *Table$entry(Table_t t, int64_t n) {
}
public
-void Table$clear(Table_t *t) { memset(t, 0, sizeof(Table_t)); }
+void Table$clear(Table_t *t) {
+ *t = EMPTY_TABLE;
+}
public
Table_t Table$sorted(Table_t t, const TypeInfo_t *type) {
@@ -537,9 +492,9 @@ Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *type) {
__typeof(type->TableInfo) table = type->TableInfo;
if (!t) {
- return table.value->size > 0 ? Texts("{", generic_as_text(NULL, false, table.key), ":",
- generic_as_text(NULL, false, table.value), "}")
- : Texts("{", generic_as_text(NULL, false, table.key), "}");
+ return table.value->size > 0 ? Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text(":"),
+ generic_as_text(NULL, false, table.value), Text("}"))
+ : Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text("}"));
}
int64_t val_off = (int64_t)value_offset(type);
@@ -584,7 +539,8 @@ Table_t Table$from_entries(List_t entries, const TypeInfo_t *type) {
// "set intersection" in formal terms
public
Table_t Table$intersection(Table_t a, Table_t b, const TypeInfo_t *type) {
- // Return a table such that t[k]==a[k] for all k such that a.has(k), b.has(k), and a[k]==b[k]
+ // Return a table such that t[k]==a[k] for all k such that a.has(k), b.has(k),
+ // and a[k]==b[k]
Table_t result = EMPTY_TABLE;
const size_t offset = value_offset(type);
for (Table_t *t = &a; t; t = t->fallback) {
@@ -602,8 +558,8 @@ Table_t Table$intersection(Table_t a, Table_t b, const TypeInfo_t *type) {
// "set union" in formal terms
public
Table_t Table$with(Table_t a, Table_t b, const TypeInfo_t *type) {
- // return a table such that t[k]==b[k] for all k such that b.has(k), and t[k]==a[k] for all k such that a.has(k) and
- // not b.has(k)
+ // return a table such that t[k]==b[k] for all k such that b.has(k), and
+ // t[k]==a[k] for all k such that a.has(k) and not b.has(k)
Table_t result = EMPTY_TABLE;
const size_t offset = value_offset(type);
for (Table_t *t = &a; t; t = t->fallback) {
@@ -645,7 +601,8 @@ Table_t Table$difference(Table_t a, Table_t b, const TypeInfo_t *type) {
// "without" is "set difference" in formal terms
public
Table_t Table$without(Table_t a, Table_t b, const TypeInfo_t *type) {
- // Return a table such that t[k]==a[k] for all k such that not b.has(k) or b[k] != a[k]
+ // Return a table such that t[k]==a[k] for all k such that not b.has(k) or
+ // b[k] != a[k]
Table_t result = EMPTY_TABLE;
const size_t offset = value_offset(type);
for (Table_t *t = &a; t; t = t->fallback) {
@@ -704,12 +661,18 @@ void *Table$str_reserve(Table_t *t, const char *key, const void *value) {
}
public
-void Table$str_set(Table_t *t, const char *key, const void *value) { Table$set(t, &key, &value, &CStrToVoidStarTable); }
+void Table$str_set(Table_t *t, const char *key, const void *value) {
+ Table$set(t, &key, &value, &CStrToVoidStarTable);
+}
public
-void Table$str_remove(Table_t *t, const char *key) { return Table$remove(t, &key, &CStrToVoidStarTable); }
+void Table$str_remove(Table_t *t, const char *key) {
+ return Table$remove(t, &key, &CStrToVoidStarTable);
+}
-CONSTFUNC public void *Table$str_entry(Table_t t, int64_t n) { return Table$entry(t, n); }
+CONSTFUNC public void *Table$str_entry(Table_t t, int64_t n) {
+ return Table$entry(t, n);
+}
PUREFUNC public bool Table$is_none(const void *obj, const TypeInfo_t *info) {
(void)info;
diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h
index cf1c3625..f3f9d2c7 100644
--- a/src/stdlib/tables.h
+++ b/src/stdlib/tables.h
@@ -4,7 +4,7 @@
#include <stdbool.h>
#include <stdint.h>
-#include <string.h>
+#include <string.h> // IWYU pragma: export
#include "datatypes.h"
#include "lists.h"
@@ -47,8 +47,8 @@ void *Table$get(Table_t t, const void *key, const TypeInfo_t *type);
val_t *value = Table$get(t, &key, info); \
if (unlikely(value == NULL)) \
fail_source(__SOURCE_FILE__, start, end, \
- "This key was not found in the table: ", generic_as_text(&key, false, info->TableInfo.key), \
- "\n"); \
+ Text$concat(Text("This key was not found in the table: "), \
+ generic_as_text(&key, false, info->TableInfo.key), Text("\n"))); \
*value; \
})
#define Table$get_or_setdefault(table_expr, key_t, val_t, key_expr, default_expr, info_expr) \
diff --git a/src/stdlib/text.c b/src/stdlib/text.c
index b4b27fed..4bf6d999 100644
--- a/src/stdlib/text.c
+++ b/src/stdlib/text.c
@@ -606,8 +606,8 @@ Text_t Text$middle_pad(Text_t text, Int_t width, Text_t padding, Text_t language
if (padding.length == 0) fail("Cannot pad with an empty text!");
int64_t needed = Int64$from_int(width, false) - Int64$from_int(Text$width(text, language), false);
- return Texts(Text$repeat_to_width(padding, needed / 2, language), text,
- Text$repeat_to_width(padding, (needed + 1) / 2, language));
+ return Text$concat(Text$repeat_to_width(padding, needed / 2, language), text,
+ Text$repeat_to_width(padding, (needed + 1) / 2, language));
}
public
@@ -669,10 +669,14 @@ Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int) {
}
public
-Text_t Text$from(Text_t text, Int_t first) { return Text$slice(text, first, I_small(-1)); }
+Text_t Text$from(Text_t text, Int_t first) {
+ return Text$slice(text, first, I_small(-1));
+}
public
-Text_t Text$to(Text_t text, Int_t last) { return Text$slice(text, I_small(1), last); }
+Text_t Text$to(Text_t text, Int_t last) {
+ return Text$slice(text, I_small(1), last);
+}
public
Text_t Text$reversed(Text_t text) {
@@ -814,7 +818,9 @@ OptionalText_t Text$from_strn(const char *str, size_t len) {
}
public
-OptionalText_t Text$from_str(const char *str) { return str ? Text$from_strn(str, strlen(str)) : Text(""); }
+OptionalText_t Text$from_str(const char *str) {
+ return str ? Text$from_strn(str, strlen(str)) : Text("");
+}
static void u8_buf_append(Text_t text, Byte_t **buf, int64_t *capacity, int64_t *i) {
switch (text.tag) {
@@ -1506,8 +1512,8 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) {
Text_t ret = Text$escaped(text, colorize, quotation_mark);
if (!(Text$equal_values(quotation_mark, Text("\"")) || Text$equal_values(quotation_mark, Text("'"))
|| Text$equal_values(quotation_mark, Text("`"))))
- ret = Texts("$", quotation_mark, ret, quotation_mark);
- else ret = Texts(quotation_mark, ret, quotation_mark);
+ ret = Text$concat(Text("$"), quotation_mark, ret, quotation_mark);
+ else ret = Text$concat(quotation_mark, ret, quotation_mark);
return ret;
}
@@ -1803,11 +1809,11 @@ Int_t Text$memory_size(Text_t text) {
public
Text_t Text$layout(Text_t text) {
switch (text.tag) {
- case TEXT_ASCII: return Texts(Text("ASCII("), Int64$value_as_text(text.length), Text(")"));
- case TEXT_GRAPHEMES: return Texts(Text("Graphemes("), Int64$value_as_text(text.length), Text(")"));
- case TEXT_BLOB: return Texts(Text("Blob("), Int64$value_as_text(text.length), Text(")"));
+ case TEXT_ASCII: return Text$concat(Text("ASCII("), Int64$value_as_text(text.length), Text(")"));
+ case TEXT_GRAPHEMES: return Text$concat(Text("Graphemes("), Int64$value_as_text(text.length), Text(")"));
+ case TEXT_BLOB: return Text$concat(Text("Blob("), Int64$value_as_text(text.length), Text(")"));
case TEXT_CONCAT:
- return Texts(Text("Concat("), Text$layout(*text.left), Text(", "), Text$layout(*text.right), Text(")"));
+ return Text$concat(Text("Concat("), Text$layout(*text.left), Text(", "), Text$layout(*text.right), Text(")"));
default: errx(1, "Invalid text tag: %d", text.tag);
}
}
diff --git a/src/stdlib/text.h b/src/stdlib/text.h
index 8c0c43b3..989fcb8b 100644
--- a/src/stdlib/text.h
+++ b/src/stdlib/text.h
@@ -33,7 +33,9 @@ static inline Text_t Text_from_str_literal(const char *str) {
return (Text_t){.length = strlen(str), .tag = TEXT_ASCII, .ascii = str};
}
-static inline Text_t Text_from_text(Text_t t) { return t; }
+static inline Text_t Text_from_text(Text_t t) {
+ return t;
+}
#define convert_to_text(x) \
_Generic(x, \
@@ -45,7 +47,8 @@ static inline Text_t Text_from_text(Text_t t) { return t; }
int32_t: Int32$value_as_text, \
int64_t: Int64$value_as_text, \
double: Float64$value_as_text, \
- float: Float32$value_as_text)(x)
+ float: Float32$value_as_text, \
+ Int_t: Int$value_as_text)(x)
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__})
@@ -63,8 +66,9 @@ OptionalText_t Text$cluster(Text_t text, Int_t index_int);
Int_t index = index_expr; \
OptionalText_t cluster = Text$cluster(text, index); \
if (unlikely(cluster.tag == TEXT_NONE)) \
- fail_source(__SOURCE_FILE__, start, end, "Invalid text index: ", index, " (text has length ", \
- (int64_t)text.length, ")\n"); \
+ fail_source(__SOURCE_FILE__, start, end, \
+ Text$concat(Text("Invalid text index: "), convert_to_text(index), Text(" (text has length "), \
+ convert_to_text((int64_t)text.length), Text(")\n"))); \
cluster; \
})
OptionalText_t Text$from_str(const char *str);
diff --git a/src/tomo.c b/src/tomo.c
index 04f0289a..ceee2518 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -14,7 +14,6 @@
#endif
#include "ast.h"
-#include "changes.md.h"
#include "compile/cli.h"
#include "compile/files.h"
#include "compile/headers.h"
@@ -70,14 +69,9 @@ static const char *paths_str(List_t paths) {
return Text$as_c_string(result);
}
-#ifdef __APPLE__
-#define SHARED_SUFFIX ".dylib"
-#else
-#define SHARED_SUFFIX ".so"
-#endif
-
static OptionalBool_t verbose = false, quiet = false, show_version = false, show_prefix = false, clean_build = false,
- source_mapping = true, show_changelog = false, should_install = false;
+ source_mapping = true, should_install = false;
+static bool is_gcc = false, is_clang = false;
static List_t format_files = EMPTY_LIST, format_files_inplace = EMPTY_LIST, parse_files = EMPTY_LIST,
transpile_files = EMPTY_LIST, compile_objects = EMPTY_LIST, compile_executables = EMPTY_LIST,
@@ -92,8 +86,8 @@ static OptionalText_t show_codegen = NONE_TEXT,
" -D_BSD_SOURCE"
#endif
" -DGC_THREADS"),
- ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo@" TOMO_VERSION), ldflags = Text(""),
- optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER);
+ ldlibs = Text("-lgc -lm -lgmp -lunistring"), 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>"
@@ -139,6 +133,9 @@ static List_t normalize_tm_paths(List_t paths) {
}
int main(int argc, char *argv[]) {
+ GC_INIT();
+ tomo_configure();
+
#ifdef __linux__
// Get the file modification time of the compiler, so we
// can recompile files after changing the compiler:
@@ -168,7 +165,7 @@ int main(int argc, char *argv[]) {
if (getenv("TOMO_PATH")) TOMO_PATH = getenv("TOMO_PATH");
- cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "' ", cflags);
+ cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "' ", cflags);
// Set up environment variables:
const char *PATH = getenv("PATH");
@@ -187,7 +184,7 @@ int main(int argc, char *argv[]) {
// Run a tool:
if ((streq(argv[1], "-r") || streq(argv[1], "--run")) && argc >= 3) {
if (strcspn(argv[2], "/;$") == strlen(argv[2])) {
- const char *program = String("'", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", argv[2], "/", argv[2]);
+ const char *program = String("'", TOMO_PATH, "'/lib/tomo@", TOMO_VERSION, "/", argv[2], "/", argv[2]);
execv(program, &argv[2]);
}
print_err("This is not an installed tomo program: ", argv[2]);
@@ -219,7 +216,7 @@ int main(int argc, char *argv[]) {
" --source-mapping|-m <yes|no>: toggle source mapping in generated code\n"
" --changelog: show the Tomo changelog\n"
" --run|-r: run a program from ",
- TOMO_PATH, "/share/tomo@" TOMO_VERSION "/installed\n");
+ TOMO_PATH, "/share/tomo@", TOMO_VERSION, "/installed\n");
Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage);
cli_arg_t tomo_args[] = {
{"run", &run_files, List$info(&Path$info), .short_flag = 'r'}, //
@@ -240,7 +237,6 @@ int main(int argc, char *argv[]) {
{"optimization", &optimization, &Text$info, .short_flag = 'O'}, //
{"force-rebuild", &clean_build, &Bool$info, .short_flag = 'f'}, //
{"source-mapping", &source_mapping, &Bool$info, .short_flag = 'm'},
- {"changelog", &show_changelog, &Bool$info}, //
};
tomo_parse_args(argc, argv, usage, help, TOMO_VERSION, sizeof(tomo_args) / sizeof(tomo_args[0]), tomo_args);
@@ -249,29 +245,26 @@ int main(int argc, char *argv[]) {
return 0;
}
- if (show_changelog) {
- print_inline(string_slice((const char *)CHANGES_md, (size_t)CHANGES_md_len));
- return 0;
- }
-
if (show_version) {
if (verbose) print(TOMO_VERSION, " ", GIT_VERSION);
else print(TOMO_VERSION);
return 0;
}
- bool is_gcc = (system(String(cc, " -v 2>&1 | grep -q 'gcc version'")) == 0);
+ is_gcc = (system(String(cc, " -v 2>&1 | grep -q 'gcc version'")) == 0);
if (is_gcc) {
cflags = Texts(cflags, Text(" -fsanitize=signed-integer-overflow -fno-sanitize-recover"
" -fno-signaling-nans -fno-trapping-math -fno-finite-math-only"));
}
- bool is_clang = (system(String(cc, " -v 2>&1 | grep -q 'clang version'")) == 0);
+ is_clang = (system(String(cc, " -v 2>&1 | grep -q 'clang version'")) == 0);
if (is_clang) {
cflags = Texts(cflags, Text(" -Wno-parentheses-equality"));
}
- ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib' ", ldflags);
+ ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib' ", ldflags, " -ffunction-sections -fdata-sections");
+ if (is_gcc) ldflags = Texts(ldflags, " -Wl,--gc-sections");
+ else if (is_clang) ldflags = Texts(ldflags, " -Wl,-dead_strip");
#ifdef __APPLE__
cflags = Texts(cflags, Text(" -I/opt/homebrew/include"));
@@ -293,7 +286,7 @@ int main(int argc, char *argv[]) {
// Uninstall libraries:
for (int64_t i = 0; i < (int64_t)uninstall_libraries.length; i++) {
Text_t *u = (Text_t *)(uninstall_libraries.data + i * uninstall_libraries.stride);
- xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo@" TOMO_VERSION "/", *u, " '", TOMO_PATH, "'/bin/", *u,
+ xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo@", TOMO_VERSION, "/", *u, " '", TOMO_PATH, "'/bin/", *u,
" '", TOMO_PATH, "'/man/man1/", *u, ".1");
print("Uninstalled ", *u);
}
@@ -400,6 +393,32 @@ int main(int argc, char *argv[]) {
run_files = normalize_tm_paths(run_files);
+ if (run_files.length == 0 && format_files.length == 0 && format_files_inplace.length == 0 && parse_files.length == 0
+ && transpile_files.length == 0 && compile_objects.length == 0 && compile_executables.length == 0
+ && run_files.length == 0 && uninstall_libraries.length == 0 && libraries.length == 0) {
+ Path_t path = Path$from_str(String("~/.local/tomo/state/tomo@", TOMO_VERSION, "/run.tm"));
+ path = Path$expand_home(path);
+ Path$create_directory(Path$parent(path), 0755, true);
+ if (!Path$exists(path)) {
+ Path$write(path,
+ Text("# This is a handy Tomo REPL-like runner\n" //
+ "# Normally you would run `tomo ./file.tm` to run a script\n" //
+ "# See `tomo --help` for full usage\n" //
+ "\n" //
+ "func main()\n" //
+ " # Put your code here:\n" //
+ " pass\n" //
+ "\n" //
+ "# Save and exit to run\n"),
+ 0644);
+ }
+ List$insert(&run_files, &path, I(0), sizeof(path));
+ const char *editor = getenv("EDITOR");
+ if (!editor || editor[0] == '\0') editor = "vim";
+ int status = system(String(editor, " ", path));
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return 1;
+ }
+
// Compile runnable files in parallel, then execute in serial:
for (int64_t i = 0; i < (int64_t)run_files.length; i++) {
Path_t path = *(Path_t *)(run_files.data + i * run_files.stride);
@@ -484,33 +503,21 @@ void build_library(Path_t lib_dir) {
compile_files(env, tm_files, &object_files, &extra_ldlibs, COMPILE_OBJ);
Text_t lib_name = get_library_name(lib_dir);
- Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_name, Text(SHARED_SUFFIX)));
- if (!is_stale_for_any(shared_lib, object_files, false)) {
- if (verbose) whisper("Unchanged: ", shared_lib);
- return;
+ Path_t archive = Path$child(lib_dir, Texts(Text("lib"), lib_name, ".a"));
+ if (is_stale_for_any(archive, object_files, false)) {
+ FILE *prog = run_cmd("ar -rcs '", archive, "' ", paths_str(object_files));
+ if (!prog) print_err("Failed to run `ar`");
+ int status = pclose(prog);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE);
+ if (!quiet) print("Compiled static library:\t", Path$relative_to(archive, Path$current_dir()));
+ } else {
+ if (verbose) whisper("Unchanged: ", archive);
}
-
- FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs),
-#ifdef __APPLE__
- " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX,
- "'"
-#else
- " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX,
- "'"
-#endif
- " -shared ",
- paths_str(object_files), " -o '", shared_lib, "'");
-
- if (!prog) print_err("Failed to run C compiler: ", cc);
- int status = pclose(prog);
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE);
-
- if (!quiet) print("Compiled library:\t", Path$relative_to(shared_lib, Path$current_dir()));
}
void install_library(Path_t lib_dir) {
Text_t lib_name = get_library_name(lib_dir);
- Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION)), lib_name);
+ Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION)), lib_name);
print("Installing ", lib_dir, " into ", dest);
if (!Enum$equal(&lib_dir, &dest, &Path$info)) {
if (verbose) whisper("Clearing out any pre-existing version of ", lib_name);
@@ -522,15 +529,15 @@ void install_library(Path_t lib_dir) {
}
// 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, SHARED_SUFFIX);
+ 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, SHARED_SUFFIX,
+ 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);
+ print("Installed \033[1m", lib_dir, "\033[m to ", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", lib_name);
}
void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs, compile_mode_t mode) {
@@ -692,13 +699,12 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l
case USE_MODULE: {
module_info_t mod = get_used_module_info(stmt_ast);
const char *full_name = mod.version ? String(mod.name, "@", mod.version) : mod.name;
- Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name),
- "' '", TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", Text$from_str(full_name), "/lib",
- Text$from_str(full_name), SHARED_SUFFIX "'");
+ Text_t lib = Texts(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", Text$from_str(full_name), "/lib",
+ Text$from_str(full_name), ".a");
Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info));
- List_t children =
- Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm")));
+ List_t children = Path$glob(
+ Path$from_str(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", full_name, "/[!._0-9]*.tm")));
for (int64_t i = 0; i < (int64_t)children.length; i++) {
Path_t *child = (Path_t *)(children.data + i * children.stride);
Table_t discarded = {.entries = EMPTY_LIST, .fallback = to_compile};
@@ -920,8 +926,36 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t
Path_t runner_file = build_file(path, ".runner.c");
Path$write(runner_file, program, 0644);
- FILE *runner = run_cmd(cc, " ", cflags, " -O", optimization, " ", ldflags, " ", ldlibs, " ",
- list_text(extra_ldlibs), " ", paths_str(object_files), " ", runner_file, " -o ", exe_path);
+ // .a archive files need to go later in the positional order:
+ List_t archives = EMPTY_LIST;
+ for (int64_t i = 0; i < (int64_t)extra_ldlibs.length;) {
+ Text_t *lib = (Text_t *)(extra_ldlibs.data + i * extra_ldlibs.stride);
+ if (Text$ends_with(*lib, Text(".a"), NULL)) {
+ List$insert(&archives, lib, I(0), sizeof(Text_t));
+ List$remove_at(&extra_ldlibs, I(i + 1), I(1), sizeof(Text_t));
+ } else {
+ i += 1;
+ }
+ }
+
+ FILE *runner = run_cmd(
+ cc,
+ // C flags:
+ " ", cflags, " -O", optimization,
+ // Linker flags and dynamically linked shared libraries:
+ " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), " ",
+ // Object files:
+ paths_str(object_files),
+ // Input file:
+ " ", runner_file,
+ // Statically linked archive files (must come after runner):
+ // Libraries are grouped to allow for circular dependencies among
+ // 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",
+ // Output file:
+ " -o ", exe_path);
if (show_codegen.length > 0) {
FILE *out = run_cmd(show_codegen);
diff --git a/src/typecheck.c b/src/typecheck.c
index 9ba7284e..dc234bac 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -225,7 +225,8 @@ static env_t *load_module(env_t *env, ast_t *use_ast) {
module_info_t mod = get_used_module_info(use_ast);
glob_t tm_files;
const char *folder = mod.version ? String(mod.name, "@", mod.version) : mod.name;
- if (glob(String(TOMO_PATH, "/lib/tomo@" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files)
+ if (glob(String(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL,
+ &tm_files)
!= 0) {
if (!try_install_module(mod, true)) code_err(use_ast, "Couldn't find or install library: ", folder);
}
@@ -1530,11 +1531,7 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) {
case Metadata: return true;
default: break;
}
- type_t *t = get_type(env, ast);
- if (t->tag == StructType) {
- return (Match(t, StructType)->fields == NULL);
- }
- return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType);
+ return is_discardable_type(get_type(env, ast));
}
type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) {
diff --git a/src/types.c b/src/types.c
index aeadbceb..bffeb653 100644
--- a/src/types.c
+++ b/src/types.c
@@ -138,7 +138,9 @@ bool type_is_a(type_t *t, type_t *req) {
return false;
}
-type_t *non_optional(type_t *t) { return t->tag == OptionalType ? Match(t, OptionalType)->type : t; }
+type_t *non_optional(type_t *t) {
+ return t->tag == OptionalType ? Match(t, OptionalType)->type : t;
+}
PUREFUNC type_t *value_type(type_t *t) {
while (t->tag == PointerType)
@@ -146,6 +148,13 @@ PUREFUNC type_t *value_type(type_t *t) {
return t;
}
+PUREFUNC bool is_discardable_type(type_t *t) {
+ if (t->tag == StructType) {
+ return (Match(t, StructType)->fields == NULL);
+ }
+ return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType);
+}
+
type_t *type_or_type(type_t *a, type_t *b) {
if (!a) return b;
if (!b) return a;
@@ -155,6 +164,8 @@ type_t *type_or_type(type_t *a, type_t *b) {
return a->tag == OptionalType ? a : Type(OptionalType, a);
if (a->tag == ReturnType && b->tag == ReturnType)
return Type(ReturnType, .ret = type_or_type(Match(a, ReturnType)->ret, Match(b, ReturnType)->ret));
+ if ((a->tag == VoidType && is_discardable_type(b)) || (is_discardable_type(a) && b->tag == VoidType))
+ return Type(VoidType);
if (is_incomplete_type(a) && type_eq(b, most_complete_type(a, b))) return b;
if (is_incomplete_type(b) && type_eq(a, most_complete_type(a, b))) return a;
@@ -458,7 +469,9 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed) {
return false;
}
-PUREFUNC bool is_int_type(type_t *t) { return t->tag == IntType || t->tag == BigIntType || t->tag == ByteType; }
+PUREFUNC bool is_int_type(type_t *t) {
+ return t->tag == IntType || t->tag == BigIntType || t->tag == ByteType;
+}
PUREFUNC bool is_numeric_type(type_t *t) {
return t->tag == IntType || t->tag == BigIntType || t->tag == FloatType || t->tag == ByteType || t->tag == RealType;
diff --git a/src/types.h b/src/types.h
index 9f468c1e..f6b2f43f 100644
--- a/src/types.h
+++ b/src/types.h
@@ -146,6 +146,7 @@ PUREFUNC bool type_eq(type_t *a, type_t *b);
PUREFUNC bool type_is_a(type_t *t, type_t *req);
type_t *type_or_type(type_t *a, type_t *b);
type_t *value_type(type_t *a);
+PUREFUNC bool is_discardable_type(type_t *t);
typedef enum {
NUM_PRECISION_EQUAL,
NUM_PRECISION_LESS,