aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-12-11 13:50:01 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-12-11 13:52:46 -0500
commit7f8f2117799cdfa6b62909a9182b5adade1d0bd2 (patch)
tree1db466db870768e952f50572453660e090e434e0 /src
parent630f910563b6f27dd34a4a0496a43d32539eadcb (diff)
parent02886fab651d3f64d2c8ded5597e6c075dc69b5f (diff)
Merge branch 'dev' into constructive-reals
Diffstat (limited to 'src')
-rw-r--r--src/ast.c42
-rw-r--r--src/ast.h14
-rw-r--r--src/compile/assertions.c4
-rw-r--r--src/compile/assignments.c10
-rw-r--r--src/compile/cli.c159
-rw-r--r--src/compile/cli.h5
-rw-r--r--src/compile/comparisons.c8
-rw-r--r--src/compile/conditionals.c15
-rw-r--r--src/compile/enums.c42
-rw-r--r--src/compile/expressions.c45
-rw-r--r--src/compile/files.c6
-rw-r--r--src/compile/functions.c298
-rw-r--r--src/compile/headers.c73
-rw-r--r--src/compile/lists.c4
-rw-r--r--src/compile/optionals.c69
-rw-r--r--src/compile/pointers.c4
-rw-r--r--src/compile/promotions.c31
-rw-r--r--src/compile/statements.c11
-rw-r--r--src/compile/tables.c10
-rw-r--r--src/compile/types.c3
-rw-r--r--src/compile/whens.c20
-rw-r--r--src/environment.c76
-rw-r--r--src/environment.h4
-rw-r--r--src/modules.c15
-rw-r--r--src/naming.c4
-rw-r--r--src/parse/expressions.c2
-rw-r--r--src/parse/files.c41
-rw-r--r--src/parse/text.c12
-rw-r--r--src/parse/text.h4
-rw-r--r--src/stdlib/bigint.c116
-rw-r--r--src/stdlib/bigint.h2
-rw-r--r--src/stdlib/bytes.c4
-rw-r--r--src/stdlib/bytes.h2
-rw-r--r--src/stdlib/cli.c25
-rw-r--r--src/stdlib/datatypes.h86
-rw-r--r--src/stdlib/floatX.c.h1
-rw-r--r--src/stdlib/intX.c.h4
-rw-r--r--src/stdlib/intX.h2
-rw-r--r--src/stdlib/memory.c2
-rw-r--r--src/stdlib/metamethods.c6
-rw-r--r--src/stdlib/metamethods.h1
-rw-r--r--src/stdlib/optionals.h2
-rw-r--r--src/stdlib/paths.c361
-rw-r--r--src/stdlib/paths.h31
-rw-r--r--src/stdlib/print.c2
-rw-r--r--src/stdlib/result.c65
-rw-r--r--src/stdlib/result.h9
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c48
-rw-r--r--src/stdlib/stdlib.h10
-rw-r--r--src/stdlib/structs.c11
-rw-r--r--src/stdlib/structs.h2
-rw-r--r--src/stdlib/tables.c12
-rw-r--r--src/stdlib/tables.h2
-rw-r--r--src/stdlib/text.c27
-rw-r--r--src/stdlib/text.h2
-rw-r--r--src/stdlib/tomo.h1
-rw-r--r--src/tomo.c62
-rw-r--r--src/typecheck.c93
-rw-r--r--src/types.c132
-rw-r--r--src/types.h7
61 files changed, 1399 insertions, 764 deletions
diff --git a/src/ast.c b/src/ast.c
index 432ce2d4..e87ca005 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -274,6 +274,8 @@ Text_t ast_to_sexp(ast_t *ast) {
T(Assert, "(Assert ", ast_to_sexp(data.expr), " ", optional_sexp("message", data.message), ")");
T(Use, "(Use ", optional_sexp("var", data.var), " ", quoted_text(data.path), ")");
T(InlineCCode, "(InlineCCode ", ast_list_to_sexp(data.chunks), optional_type_sexp("type", data.type_ast), ")");
+ T(Metadata, "((Metadata ", Text$quoted(data.key, false, Text("\"")), " ",
+ Text$quoted(data.value, false, Text("\"")), ")");
default: errx(1, "S-expressions are not implemented for this AST");
#undef T
}
@@ -441,19 +443,19 @@ CONSTFUNC ast_e binop_tag(ast_e tag) {
}
}
-static void ast_visit_list(ast_list_t *ast_list, void (*visitor)(ast_t *, void *), void *userdata) {
+static void ast_visit_list(ast_list_t *ast_list, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) {
for (ast_list_t *ast = ast_list; ast; ast = ast->next)
ast_visit(ast->ast, visitor, userdata);
}
-static void ast_visit_args(arg_ast_t *args, void (*visitor)(ast_t *, void *), void *userdata) {
+static void ast_visit_args(arg_ast_t *args, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) {
for (arg_ast_t *arg = args; arg; arg = arg->next)
ast_visit(arg->value, visitor, userdata);
}
-void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) {
+void ast_visit(ast_t *ast, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata) {
if (!ast) return;
- visitor(ast, userdata);
+ if (visitor(ast, userdata) == VISIT_STOP) return;
switch (ast->tag) {
case Unknown:
@@ -463,7 +465,8 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) {
case Int:
case Num:
case Path:
- case TextLiteral: return;
+ case TextLiteral:
+ case Metadata: return;
case TextJoin: ast_visit_list(Match(ast, TextJoin)->children, visitor, userdata); return;
case Declare: {
DeclareMatch(decl, ast, Declare);
@@ -683,26 +686,25 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) {
static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) {
if (ast == NULL) return;
- void (*visit)(type_ast_t *, void *) = ((Closure_t *)userdata)->fn;
+ visit_behavior_t (*visit)(type_ast_t *, void *) = ((Closure_t *)userdata)->fn;
void *visitor_userdata = ((Closure_t *)userdata)->userdata;
+ if (visit(ast, visitor_userdata) == VISIT_STOP) return;
+
switch (ast->tag) {
case UnknownTypeAST:
- case VarTypeAST: visit(ast, visitor_userdata); break;
+ case VarTypeAST: break;
case PointerTypeAST: {
_recursive_type_ast_visit(Match(ast, PointerTypeAST)->pointed, userdata);
- visit(ast, visitor_userdata);
break;
}
case ListTypeAST: {
_recursive_type_ast_visit(Match(ast, ListTypeAST)->item, userdata);
- visit(ast, visitor_userdata);
break;
}
case TableTypeAST: {
DeclareMatch(table, ast, TableTypeAST);
_recursive_type_ast_visit(table->key, userdata);
_recursive_type_ast_visit(table->value, userdata);
- visit(ast, visitor_userdata);
break;
}
case FunctionTypeAST: {
@@ -710,12 +712,10 @@ static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) {
for (arg_ast_t *arg = fn->args; arg; arg = arg->next)
_recursive_type_ast_visit(arg->type, userdata);
_recursive_type_ast_visit(fn->ret, userdata);
- visit(ast, visitor_userdata);
break;
}
case OptionalTypeAST: {
_recursive_type_ast_visit(Match(ast, OptionalTypeAST)->type, userdata);
- visit(ast, visitor_userdata);
break;
}
case EnumTypeAST: {
@@ -724,14 +724,13 @@ static void _recursive_type_ast_visit(type_ast_t *ast, void *userdata) {
_recursive_type_ast_visit(field->type, userdata);
}
}
- visit(ast, visitor_userdata);
break;
}
default: errx(1, "Invalid type AST");
}
}
-static void _type_ast_visit(ast_t *ast, void *userdata) {
+static visit_behavior_t _type_ast_visit(ast_t *ast, void *userdata) {
switch (ast->tag) {
case Declare: {
_recursive_type_ast_visit(Match(ast, Declare)->type, userdata);
@@ -774,9 +773,22 @@ static void _type_ast_visit(ast_t *ast, void *userdata) {
}
default: break;
}
+ return VISIT_PROCEED;
}
-void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata) {
+void type_ast_visit(ast_t *ast, visit_behavior_t (*visitor)(type_ast_t *, void *), void *userdata) {
Closure_t fn = {.fn = visitor, .userdata = userdata};
ast_visit(ast, _type_ast_visit, &fn);
}
+
+OptionalText_t ast_metadata(ast_t *ast, const char *key) {
+ if (ast->tag != Block) return NONE_TEXT;
+ Text_t key_text = Text$from_str(key);
+ for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
+ if (stmt->ast->tag == Metadata) {
+ DeclareMatch(m, stmt->ast, Metadata);
+ if (Text$equal_values(m->key, key_text)) return m->value;
+ }
+ }
+ return NONE_TEXT;
+}
diff --git a/src/ast.h b/src/ast.h
index aaa2a993..b6930ab7 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -23,6 +23,9 @@
#define LiteralCode(code, ...) \
new (ast_t, .tag = InlineCCode, \
.__data.InlineCCode = {.chunks = new (ast_list_t, .ast = FakeAST(TextLiteral, code)), __VA_ARGS__})
+#define WrapLiteralCode(_ast, code, ...) \
+ new (ast_t, .tag = InlineCCode, .file = (_ast)->file, .start = (_ast)->start, .end = (_ast)->end, \
+ .__data.InlineCCode = {.chunks = new (ast_list_t, .ast = WrapAST(_ast, TextLiteral, code)), __VA_ARGS__})
#define Match(x, _tag) \
((x)->tag == _tag ? &(x)->__data._tag \
: (errx(1, __FILE__ ":%d This was supposed to be a " #_tag "\n", __LINE__), &(x)->__data._tag))
@@ -276,6 +279,7 @@ typedef enum {
Use,
InlineCCode,
ExplicitlyTyped,
+ Metadata,
} ast_e;
#define NUM_AST_TAGS (ExplicitlyTyped + 1)
@@ -287,6 +291,7 @@ struct ast_s {
struct {
} Unknown;
struct {
+ struct type_s *type;
} None;
struct {
bool b;
@@ -457,6 +462,9 @@ struct ast_s {
ast_t *ast;
struct type_s *type;
} ExplicitlyTyped;
+ struct {
+ Text_t key, value;
+ } Metadata;
} __data;
};
@@ -480,5 +488,7 @@ void visit_topologically(ast_list_t *ast, Closure_t fn);
CONSTFUNC bool is_update_assignment(ast_t *ast);
CONSTFUNC ast_e binop_tag(ast_e tag);
CONSTFUNC bool is_binary_operation(ast_t *ast);
-void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata);
-void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata);
+typedef enum { VISIT_STOP, VISIT_PROCEED } visit_behavior_t;
+void ast_visit(ast_t *ast, visit_behavior_t (*visitor)(ast_t *, void *), void *userdata);
+void type_ast_visit(ast_t *ast, visit_behavior_t (*visitor)(type_ast_t *, void *), void *userdata);
+OptionalText_t ast_metadata(ast_t *ast, const char *key);
diff --git a/src/compile/assertions.c b/src/compile/assertions.c
index 5746b21e..34055998 100644
--- a/src/compile/assertions.c
+++ b/src/compile/assertions.c
@@ -33,7 +33,9 @@ Text_t compile_assertion(env_t *env, ast_t *ast) {
type_t *lhs_t = get_type(env, cmp.lhs);
type_t *rhs_t = get_type(with_enum_scope(env, lhs_t), cmp.rhs);
type_t *operand_t;
- if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) {
+ if (type_eq(lhs_t, rhs_t)) {
+ operand_t = lhs_t;
+ } else if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) {
operand_t = rhs_t;
} else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) {
operand_t = lhs_t;
diff --git a/src/compile/assignments.c b/src/compile/assignments.c
index 84c43153..8c6af3ee 100644
--- a/src/compile/assignments.c
+++ b/src/compile/assignments.c
@@ -101,7 +101,7 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) {
"variable's scope may outlive the scope of the "
"stack memory.");
env_t *val_env = with_enum_scope(env, lhs_t);
- Text_t val = compile_to_type(val_env, assign->values->ast, lhs_t);
+ Text_t val = compile_maybe_incref(val_env, assign->values->ast, lhs_t);
return Texts(compile_assignment(env, assign->targets->ast, val), ";\n");
}
@@ -120,7 +120,7 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) {
"variable's scope may outlive the scope of the "
"stack memory.");
env_t *val_env = with_enum_scope(env, lhs_t);
- Text_t val = compile_to_type(val_env, value->ast, lhs_t);
+ Text_t val = compile_maybe_incref(val_env, value->ast, lhs_t);
code = Texts(code, compile_type(lhs_t), " $", i, " = ", val, ";\n");
i += 1;
}
@@ -178,13 +178,13 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) {
type_t *value_type = get_type(env, table_type->default_value);
return Texts("*Table$get_or_setdefault(", compile_to_pointer_depth(env, index->indexed, 1, false), ", ",
compile_type(table_type->key_type), ", ", compile_type(value_type), ", ",
- compile_to_type(env, index->index, table_type->key_type), ", ",
- compile_to_type(env, table_type->default_value, table_type->value_type), ", ",
+ compile_maybe_incref(env, index->index, table_type->key_type), ", ",
+ compile_maybe_incref(env, table_type->default_value, table_type->value_type), ", ",
compile_type_info(container_t), ")");
}
return Texts("*(", compile_type(Type(PointerType, table_type->value_type)), ")Table$reserve(",
compile_to_pointer_depth(env, index->indexed, 1, false), ", ", "stack(",
- compile_to_type(env, index->index, table_type->key_type), ")", ", NULL,",
+ compile_maybe_incref(env, index->index, table_type->key_type), ")", ", NULL,",
compile_type_info(container_t), ")");
} else {
code_err(ast, "I don't know how to assign to this target");
diff --git a/src/compile/cli.c b/src/compile/cli.c
index e3d2329f..63a467ca 100644
--- a/src/compile/cli.c
+++ b/src/compile/cli.c
@@ -38,7 +38,7 @@ static Text_t get_flag_options(type_t *t, Text_t separator) {
} else if (t->tag == ListType) {
Text_t item_option = get_flag_options(Match(t, ListType)->item_type, separator);
return Texts(item_option, "1 ", item_option, "2...");
- } else if (t->tag == TableType && Match(t, TableType)->value_type == EMPTY_TYPE) {
+ } else if (t->tag == TableType && Match(t, TableType)->value_type == PRESENT_TYPE) {
Text_t item_option = get_flag_options(Match(t, TableType)->key_type, separator);
return Texts(item_option, "1 ", item_option, "2...");
} else if (t->tag == TableType) {
@@ -58,80 +58,87 @@ static OptionalText_t flagify(const char *name, bool prefix) {
return flag;
}
-public
-Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) {
+static Text_t generate_usage(env_t *env, type_t *fn_type) {
DeclareMatch(fn_info, fn_type, FunctionType);
-
- env_t *main_env = fresh_scope(env);
-
- Text_t code = EMPTY_TEXT;
- binding_t *usage_binding = get_binding(env, "_USAGE");
- Text_t usage_code = usage_binding ? usage_binding->code : Text("usage");
- binding_t *help_binding = get_binding(env, "_HELP");
- Text_t help_code = help_binding ? help_binding->code : usage_code;
- if (!usage_binding) {
- bool explicit_help_flag = false;
- for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
- if (streq(arg->name, "help")) {
- explicit_help_flag = true;
- break;
- }
+ bool explicit_help_flag = false;
+ for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
+ if (streq(arg->name, "help")) {
+ explicit_help_flag = true;
+ break;
}
-
- Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]");
- for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
- usage = Texts(usage, " ");
- type_t *t = get_arg_type(main_env, arg);
- OptionalText_t flag = flagify(arg->name, arg->default_val != NULL);
- assert(flag.tag != TEXT_NONE);
- OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL);
- Text_t flags = Texts("\x1b[1m", flag, "\x1b[m");
- if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m");
+ }
+ env_t *main_env = fresh_scope(env);
+ Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]");
+ for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
+ usage = Texts(usage, " ");
+ type_t *t = get_arg_type(main_env, arg);
+ OptionalText_t flag = flagify(arg->name, arg->default_val != NULL);
+ assert(flag.tag != TEXT_NONE);
+ Text_t flags = Texts("\x1b[1m", flag, "\x1b[m");
+ if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
+ flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
+ if (arg->default_val || value_type(t)->tag == BoolType) {
if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
- if (arg->default_val || value_type(t)->tag == BoolType) {
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- usage = Texts(usage, "[", flags, "]");
- else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
- else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
- } else {
- usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m");
- }
+ usage = Texts(usage, "[", flags, "]");
+ 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");
}
- code = Texts(code,
- "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), "
- "Text$from_str(argv[0])",
- usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n");
}
- if (!help_binding) {
- Text_t help_text = fn_info->args ? Text("\n") : Text("\n\n\x1b[2;3m No arguments...\x1b[m");
-
- for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
- help_text = Texts(help_text, "\n");
- type_t *t = get_arg_type(main_env, arg);
- OptionalText_t flag = flagify(arg->name, true);
- assert(flag.tag != TEXT_NONE);
- OptionalText_t alias_flag = flagify(arg->alias, true);
- Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m");
- if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m");
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- help_text = Texts(help_text, " ", flags);
- else
- help_text = Texts(help_text, " ", flags, " \x1b[1;34m",
- get_flag_options(t, Text("\x1b[m | \x1b[1;34m")), "\x1b[m");
-
- if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m");
- if (arg->default_val) {
- Text_t default_text =
- Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start));
- help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m");
- }
+ return usage;
+}
+
+static Text_t generate_help(env_t *env, type_t *fn_type) {
+ DeclareMatch(fn_info, fn_type, FunctionType);
+ env_t *main_env = fresh_scope(env);
+ Text_t help_text = EMPTY_TEXT;
+
+ for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
+ help_text = Texts(help_text, "\n");
+ type_t *t = get_arg_type(main_env, arg);
+ OptionalText_t flag = flagify(arg->name, true);
+ assert(flag.tag != TEXT_NONE);
+ OptionalText_t alias_flag = flagify(arg->alias, true);
+ Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m");
+ if (alias_flag.tag != TEXT_NONE) flags = Texts("\x1b[33;1m", alias_flag, "\x1b[0;2m,\x1b[m ", flags);
+ if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
+ flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
+ if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
+ help_text = Texts(help_text, " ", flags);
+ else
+ help_text = Texts(help_text, " ", flags, " \x1b[1;34m", get_flag_options(t, Text("\x1b[m | \x1b[1;34m")),
+ "\x1b[m");
+
+ if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m");
+ if (arg->default_val) {
+ Text_t default_text =
+ Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start));
+ help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m");
}
- code = Texts(code, "Text_t help = Texts(usage, ", quoted_text(Texts(help_text, "\n")), ");\n");
- help_code = Text("help");
}
+ return help_text;
+}
+
+public
+Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version) {
+ DeclareMatch(fn_info, fn_type, FunctionType);
+
+ Text_t code = EMPTY_TEXT;
+ OptionalText_t usage = ast_metadata(ast, "USAGE");
+ if (usage.tag == TEXT_NONE) usage = generate_usage(env, fn_type);
+
+ OptionalText_t help = ast_metadata(ast, "HELP");
+ if (help.tag == TEXT_NONE) help = generate_help(env, fn_type);
+
+ code = Texts(code,
+ "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), "
+ "Text$from_str(argv[0]), Text(\" \")",
+ usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n",
+ "Text_t help = Texts(usage, Text(\"\\n\"", quoted_text(help), "\"\\n\"));\n");
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
code = Texts(code, compile_declaration(arg->type, Texts("_$", Text$from_str(arg->name))), " = ",
@@ -144,14 +151,14 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
code = Texts(code, "{", quoted_text(Text$replace(Text$from_str(arg->name), Text("_"), Text("-"))), ", &",
Texts("_$", Text$from_str(arg->name)), ", ", compile_type_info(arg->type),
arg->default_val ? Text("") : Text(", .required=true"),
- arg->alias ? Texts(", .short_flag=", quoted_text(Text$from_str(arg->name)),
+ arg->alias ? Texts(", .short_flag=", quoted_text(Text$from_str(arg->alias)),
"[0]") // TODO: escape char properly
: Text(""),
"},\n");
}
code = Texts(code, "};\n");
- code = Texts(code, "tomo_parse_args(argc, argv, ", usage_code, ", ", help_code, ", ", version_code,
+ code = Texts(code, "tomo_parse_args(argc, argv, usage, help, ", version_code,
", sizeof(cli_args)/sizeof(cli_args[0]), cli_args);\n");
// Lazily initialize default values to prevent side effects
@@ -161,7 +168,6 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
Text_t default_val;
if (arg->type) {
default_val = compile_to_type(env, arg->default_val, arg->type);
- if (arg->type->tag != OptionalType) default_val = promote_to_optional(arg->type, default_val);
} else {
default_val = compile(env, arg->default_val);
}
@@ -181,14 +187,21 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
}
public
-Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args) {
+Text_t compile_manpage(Text_t program, ast_t *ast, arg_t *args) {
+ OptionalText_t user_manpage = ast_metadata(ast, "MANPAGE");
+ if (user_manpage.tag != TEXT_NONE) {
+ return user_manpage;
+ }
+
+ OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS");
+ OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION");
Text_t date = Text(""); // TODO: use date
Text_t man = Texts(".\\\" Automatically generated by Tomo\n"
".TH \"",
Text$upper(program, Text("C")), "\" \"1\" \"", date,
"\" \"\" \"\"\n"
".SH NAME\n",
- program, " \\- ", synopsis.tag == TEXT_NONE ? Text("a Tomo program") : synopsis, "\n");
+ program, " \\- ", synopsys.tag == TEXT_NONE ? Text("a Tomo program") : synopsys, "\n");
if (description.tag != TEXT_NONE) {
man = Texts(man, ".SH DESCRIPTION\n", description, "\n");
diff --git a/src/compile/cli.h b/src/compile/cli.h
index fa60eccf..907554a6 100644
--- a/src/compile/cli.h
+++ b/src/compile/cli.h
@@ -2,9 +2,10 @@
#pragma once
+#include "../ast.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
#include "../types.h"
-Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version);
-Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args);
+Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version);
+Text_t compile_manpage(Text_t program, ast_t *ast, arg_t *args);
diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c
index bc927599..5e95459c 100644
--- a/src/compile/comparisons.c
+++ b/src/compile/comparisons.c
@@ -28,7 +28,9 @@ Text_t compile_comparison(env_t *env, ast_t *ast) {
type_t *lhs_t = get_type(env, binop.lhs);
type_t *rhs_t = get_type(with_enum_scope(env, lhs_t), binop.rhs);
type_t *operand_t;
- if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) {
+ if (type_eq(lhs_t, rhs_t)) {
+ operand_t = lhs_t;
+ } else if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) {
operand_t = rhs_t;
} else if (binop.rhs->tag == Int && is_numeric_type(lhs_t)) {
operand_t = lhs_t;
@@ -68,7 +70,9 @@ Text_t compile_comparison(env_t *env, ast_t *ast) {
type_t *lhs_t = get_type(env, cmp.lhs);
type_t *rhs_t = get_type(env, cmp.rhs);
type_t *operand_t;
- if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) {
+ if (type_eq(lhs_t, rhs_t)) {
+ operand_t = lhs_t;
+ } else if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) {
operand_t = rhs_t;
} else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) {
operand_t = lhs_t;
diff --git a/src/compile/conditionals.c b/src/compile/conditionals.c
index caebdbde..64be29fa 100644
--- a/src/compile/conditionals.c
+++ b/src/compile/conditionals.c
@@ -126,17 +126,22 @@ Text_t compile_if_expression(env_t *env, ast_t *ast) {
}
type_t *true_type = get_type(truthy_scope, if_->body);
- type_t *false_type = get_type(falsey_scope, if_->else_body);
+ ast_t *else_body = if_->else_body;
+ if (else_body && else_body->tag == Block && Match(else_body, Block)->statements
+ && !Match(else_body, Block)->statements->next)
+ else_body = Match(else_body, Block)->statements->ast;
+ if (else_body == NULL || else_body->tag == None) else_body = WrapAST(ast, None, .type = true_type);
+ type_t *false_type = get_type(falsey_scope, else_body);
if (true_type->tag == AbortType || true_type->tag == ReturnType)
return Texts("({ ", decl_code, "if (", condition_code, ") ", compile_statement(truthy_scope, if_->body), "\n",
- compile(falsey_scope, if_->else_body), "; })");
+ compile(falsey_scope, else_body), "; })");
else if (false_type->tag == AbortType || false_type->tag == ReturnType)
- return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, if_->else_body),
+ return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, else_body),
"\n", compile(truthy_scope, if_->body), "; })");
else if (decl_code.length > 0)
return Texts("({ ", decl_code, "(", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ",
- compile(falsey_scope, if_->else_body), ";})");
+ compile(falsey_scope, else_body), ";})");
else
return Texts("((", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ",
- compile(falsey_scope, if_->else_body), ")");
+ compile(falsey_scope, else_body), ")");
}
diff --git a/src/compile/enums.c b/src/compile/enums.c
index ec7a1755..24da79b8 100644
--- a/src/compile/enums.c
+++ b/src/compile/enums.c
@@ -12,8 +12,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) {
// Compile member types and constructors:
Text_t member_typeinfos = EMPTY_TEXT;
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
- if (!tag->fields) continue;
-
const char *tag_name = String(name, "$", tag->name);
type_t *tag_type = Table$str_get(*env->types, tag_name);
assert(tag_type && tag_type->tag == StructType);
@@ -38,9 +36,7 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) {
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
const char *tag_type_name = String(name, "$", tag->name);
type_t *tag_type = Table$str_get(*env->types, tag_type_name);
- if (tag_type && Match(tag_type, StructType)->fields)
- typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, ");
- else typeinfo = Texts(typeinfo, "{\"", tag->name, "\"}, ");
+ typeinfo = Texts(typeinfo, "{\"", tag->name, "\", ", compile_type_info(tag_type), "}, ");
}
typeinfo = Texts(typeinfo, "}}}};\n");
return Texts(member_typeinfos, typeinfo);
@@ -49,8 +45,6 @@ Text_t compile_enum_typeinfo(env_t *env, const char *name, tag_ast_t *tags) {
Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags) {
Text_t constructors = EMPTY_TEXT;
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
- if (!tag->fields) continue;
-
Text_t arg_sig = EMPTY_TEXT;
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
@@ -76,25 +70,16 @@ Text_t compile_enum_constructors(env_t *env, const char *name, tag_ast_t *tags)
Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) {
Text_t all_defs = EMPTY_TEXT;
Text_t none_name = namespace_name(env, env->namespace, Texts(name, "$none"));
- Text_t enum_name = namespace_name(env, env->namespace, Texts(name, "$$enum"));
Text_t enum_tags = Texts("{ ", none_name, "=0, ");
assert(Table$str_get(*env->types, name));
- bool has_any_tags_with_fields = false;
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
Text_t tag_name = namespace_name(env, env->namespace, Texts(name, "$tag$", tag->name));
enum_tags = Texts(enum_tags, tag_name);
if (tag->next) enum_tags = Texts(enum_tags, ", ");
- has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL);
}
enum_tags = Texts(enum_tags, " }");
- if (!has_any_tags_with_fields) {
- Text_t enum_def = Texts("enum ", enum_name, " ", enum_tags, ";\n");
- Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info"));
- return Texts(enum_def, "extern const TypeInfo_t ", info, ";\n");
- }
-
Text_t struct_name = namespace_name(env, env->namespace, Texts(name, "$$struct"));
Text_t enum_def = Texts("struct ", struct_name,
" {\n"
@@ -103,7 +88,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) {
" $tag;\n"
"union {\n");
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
- if (!tag->fields) continue;
Text_t field_def = compile_struct_header(env, NewAST(tag->file, tag->start, tag->end, StructDef,
.name = Text$as_c_string(Texts(name, "$", tag->name)),
.fields = tag->fields));
@@ -117,8 +101,6 @@ Text_t compile_enum_header(env_t *env, const char *name, tag_ast_t *tags) {
Text_t info = namespace_name(env, env->namespace, Texts(name, "$$info"));
all_defs = Texts(all_defs, "extern const TypeInfo_t ", info, ";\n");
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
- if (!tag->fields) continue;
-
Text_t arg_sig = EMPTY_TEXT;
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
@@ -140,11 +122,8 @@ Text_t compile_empty_enum(type_t *t) {
tag_t *tag = enum_->tags;
assert(tag);
assert(tag->type);
- if (Match(tag->type, StructType)->fields)
- return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type),
- "})");
- else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, "})");
- else return Texts("((", compile_type(t), ")", tag->tag_value, ")");
+ return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type),
+ "})");
}
public
@@ -156,16 +135,11 @@ Text_t compile_enum_field_access(env_t *env, ast_t *ast) {
for (tag_t *tag = e->tags; tag; tag = tag->next) {
if (streq(f->field, tag->name)) {
Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name));
- if (fielded_t->tag == PointerType) {
- Text_t fielded = compile_to_pointer_depth(env, f->fielded, 1, false);
- return Texts("((", fielded, ")->$tag == ", tag_name, ")");
- } else if (enum_has_fields(value_t)) {
- Text_t fielded = compile(env, f->fielded);
- return Texts("((", fielded, ").$tag == ", tag_name, ")");
- } else {
- Text_t fielded = compile(env, f->fielded);
- return Texts("((", fielded, ") == ", tag_name, ")");
- }
+ Text_t member =
+ compile_maybe_incref(env, WrapLiteralCode(ast, Texts("_e.", tag->name), .type = tag->type), tag->type);
+ return Texts("({ ", compile_declaration(value_t, Text("_e")), " = ",
+ compile_to_pointer_depth(env, f->fielded, 0, false), "; ", "_e.$tag == ", tag_name, " ? ",
+ promote_to_optional(tag->type, member), " : ", compile_none(tag->type), "; })");
}
}
code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(value_t));
diff --git a/src/compile/expressions.c b/src/compile/expressions.c
index c28185ce..f249993f 100644
--- a/src/compile/expressions.c
+++ b/src/compile/expressions.c
@@ -11,11 +11,41 @@
public
Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) {
- if (is_idempotent(ast) && can_be_mutated(env, ast)) {
- type_t *actual = get_type(with_enum_scope(env, t), ast);
- if (t->tag == ListType && type_eq(t, actual)) return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")");
- else if (t->tag == TableType && type_eq(t, actual))
- return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")");
+ if (!has_refcounts(t) || !can_be_mutated(env, ast)) {
+ return compile_to_type(env, ast, t);
+ }
+
+ // When using a struct as a value, we need to increment the refcounts of the inner fields as well:
+ if (t->tag == StructType) {
+ // If the struct is non-idempotent, we have to stash it in a local var first
+ if (is_idempotent(ast)) {
+ Text_t code = Texts("((", compile_type(t), "){");
+ for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
+ Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = ast, .field = field->name),
+ get_arg_type(env, field));
+ code = Texts(code, val);
+ if (field->next) code = Texts(code, ", ");
+ }
+ return Texts(code, "})");
+ } else {
+ static int64_t tmp_index = 1;
+ Text_t tmp_name = Texts("_tmp", tmp_index);
+ tmp_index += 1;
+ Text_t code = Texts("({ ", compile_declaration(t, tmp_name), " = ", compile_to_type(env, ast, t), "; ",
+ "((", compile_type(t), "){");
+ ast_t *tmp = WrapLiteralCode(ast, tmp_name, .type = t);
+ for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
+ Text_t val = compile_maybe_incref(env, WrapAST(ast, FieldAccess, .fielded = tmp, .field = field->name),
+ get_arg_type(env, field));
+ code = Texts(code, val);
+ if (field->next) code = Texts(code, ", ");
+ }
+ return Texts(code, "}); })");
+ }
+ } else if (t->tag == ListType && ast->tag != List && can_be_mutated(env, ast) && type_eq(get_type(env, ast), t)) {
+ return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")");
+ } else if (t->tag == TableType && ast->tag != Table && can_be_mutated(env, ast) && type_eq(get_type(env, ast), t)) {
+ return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")");
}
return compile_to_type(env, ast, t);
}
@@ -27,7 +57,6 @@ Text_t compile_empty(type_t *t) {
if (t->tag == OptionalType) return compile_none(t);
if (t == PATH_TYPE) return Text("NONE_PATH");
- else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_ABSOLUTE");
switch (t->tag) {
case BigIntType: return Text("I(0)");
@@ -66,7 +95,9 @@ Text_t compile_empty(type_t *t) {
Text_t compile(env_t *env, ast_t *ast) {
switch (ast->tag) {
case None: {
- code_err(ast, "I can't figure out what this `none`'s type is!");
+ type_t *type = Match(ast, None)->type;
+ if (type == NULL) code_err(ast, "I can't figure out what this `none`'s type is!");
+ return compile_none(non_optional(type));
}
case Bool: return Match(ast, Bool)->b ? Text("yes") : Text("no");
case Var: {
diff --git a/src/compile/files.c b/src/compile/files.c
index a4cc07fe..27c2e041 100644
--- a/src/compile/files.c
+++ b/src/compile/files.c
@@ -142,6 +142,7 @@ Text_t compile_top_level_code(env_t *env, ast_t *ast) {
}
return code;
}
+ case Metadata:
default: return EMPTY_TEXT;
}
}
@@ -151,7 +152,7 @@ typedef struct {
Text_t *code;
} compile_info_t;
-static void add_type_infos(type_ast_t *type_ast, void *userdata) {
+static visit_behavior_t add_type_infos(type_ast_t *type_ast, void *userdata) {
if (type_ast && type_ast->tag == EnumTypeAST) {
compile_info_t *info = (compile_info_t *)userdata;
// Force the type to get defined:
@@ -163,6 +164,7 @@ static void add_type_infos(type_ast_t *type_ast, void *userdata) {
compile_enum_constructors(info->env, String("enum$", (int64_t)(type_ast->start - type_ast->file->text)),
Match(type_ast, EnumTypeAST)->tags));
}
+ return VISIT_PROCEED;
}
public
@@ -193,7 +195,7 @@ 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"
+ "#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 a14c0455..46acd780 100644
--- a/src/compile/functions.c
+++ b/src/compile/functions.c
@@ -3,9 +3,12 @@
#include "../ast.h"
#include "../environment.h"
#include "../naming.h"
+#include "../stdlib/c_strings.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/floats.h"
#include "../stdlib/integers.h"
+#include "../stdlib/nums.h"
+#include "../stdlib/optionals.h"
#include "../stdlib/tables.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
@@ -163,12 +166,12 @@ Text_t compile_function_call(env_t *env, ast_t *ast) {
args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args);
REVERSE_LIST(args);
code_err(ast,
- "This function's signature doesn't match this call site.\n"
- "The signature is: ",
- type_to_text(fn_t),
- "\n"
- "But it's being called with: ",
- type_to_text(Type(FunctionType, .args = args)));
+ "This function's signature doesn't match this call site. \n"
+ " The function takes these args: (",
+ arg_types_to_text(Match(fn_t, FunctionType)->args, ", "),
+ ") \n"
+ " But it's being called with: (",
+ arg_types_to_text(args, ", "), ")");
}
}
return Texts(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")");
@@ -246,85 +249,6 @@ Text_t compile_function_call(env_t *env, ast_t *ast) {
}
}
-public
-Text_t compile_lambda(env_t *env, ast_t *ast) {
- DeclareMatch(lambda, ast, Lambda);
- Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id));
-
- env_t *body_scope = fresh_scope(env);
- body_scope->deferred = NULL;
- for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
- type_t *arg_type = get_arg_ast_type(env, arg);
- set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name));
- }
-
- body_scope->fn = ast;
-
- Table_t closed_vars = get_closed_vars(env, lambda->args, ast);
- if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata
- Text_t def = Text("typedef struct {");
- for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) {
- struct {
- const char *name;
- binding_t *b;
- } *entry = closed_vars.entries.data + closed_vars.entries.stride * i;
- if (has_stack_memory(entry->b->type))
- code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type),
- " stack memory in the variable `", entry->name,
- "`, but the function may outlive the stack memory");
- if (entry->b->type->tag == ModuleType) continue;
- set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name));
- def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; ");
- }
- def = Texts(def, "} ", name, "$userdata_t;");
- env->code->local_typedefs = Texts(env->code->local_typedefs, def);
- }
-
- type_t *ret_t = get_function_return_type(env, ast);
- Text_t code = Texts("static ", compile_type(ret_t), " ", name, "(");
- for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
- type_t *arg_type = get_arg_ast_type(env, arg);
- code = Texts(code, compile_type(arg_type), " _$", arg->name, ", ");
- }
-
- Text_t userdata;
- if (Table$length(closed_vars) == 0) {
- code = Texts(code, "void *_)");
- userdata = Text("NULL");
- } else {
- userdata = Texts("new(", name, "$userdata_t");
- for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) {
- struct {
- const char *name;
- binding_t *b;
- } *entry = closed_vars.entries.data + closed_vars.entries.stride * i;
- if (entry->b->type->tag == ModuleType) continue;
- binding_t *b = get_binding(env, entry->name);
- assert(b);
- Text_t binding_code = b->code;
- if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")");
- else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")");
- else userdata = Texts(userdata, ", ", binding_code);
- }
- userdata = Texts(userdata, ")");
- code = Texts(code, name, "$userdata_t *userdata)");
- }
-
- Text_t body = EMPTY_TEXT;
- for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) {
- if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType
- || get_type(body_scope, stmt->ast)->tag == ReturnType)
- body = Texts(body, compile_statement(body_scope, stmt->ast), "\n");
- else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n");
- bind_statement(body_scope, stmt->ast);
- }
- if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred)
- body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n");
-
- env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n");
- return Texts("((Closure_t){", name, ", ", userdata, "})");
-}
-
static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t *env, ast_t *ast) {
if (ast == NULL) return;
@@ -594,6 +518,206 @@ Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) {
return closed_vars;
}
+static visit_behavior_t find_used_variables(ast_t *ast, void *userdata) {
+ Table_t *vars = (Table_t *)userdata;
+ switch (ast->tag) {
+ case Var: {
+ const char *name = Match(ast, Var)->name;
+ Table$str_set(vars, name, ast);
+ return VISIT_STOP;
+ }
+ case Assign: {
+ for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) {
+ ast_t *var = target->ast;
+ for (;;) {
+ if (var->tag == Index) {
+ ast_t *index = Match(var, Index)->index;
+ if (index) ast_visit(index, find_used_variables, userdata);
+ var = Match(var, Index)->indexed;
+ } else if (var->tag == FieldAccess) {
+ var = Match(var, FieldAccess)->fielded;
+ } else {
+ break;
+ }
+ }
+ }
+ for (ast_list_t *val = Match(ast, Assign)->values; val; val = val->next) {
+ ast_visit(val->ast, find_used_variables, userdata);
+ }
+ return VISIT_STOP;
+ }
+ case UPDATE_CASES: {
+ binary_operands_t operands = BINARY_OPERANDS(ast);
+ ast_t *lhs = operands.lhs;
+ for (;;) {
+ if (lhs->tag == Index) {
+ ast_t *index = Match(lhs, Index)->index;
+ if (index) ast_visit(index, find_used_variables, userdata);
+ lhs = Match(lhs, Index)->indexed;
+ } else if (lhs->tag == FieldAccess) {
+ lhs = Match(lhs, FieldAccess)->fielded;
+ } else {
+ break;
+ }
+ }
+ ast_visit(operands.rhs, find_used_variables, userdata);
+ return VISIT_STOP;
+ }
+ case Declare: {
+ ast_visit(Match(ast, Declare)->value, find_used_variables, userdata);
+ return VISIT_STOP;
+ }
+ default: return VISIT_PROCEED;
+ }
+}
+
+static visit_behavior_t find_assigned_variables(ast_t *ast, void *userdata) {
+ Table_t *vars = (Table_t *)userdata;
+ switch (ast->tag) {
+ case Assign:
+ for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) {
+ ast_t *var = target->ast;
+ for (;;) {
+ if (var->tag == Index) var = Match(var, Index)->indexed;
+ else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded;
+ else break;
+ }
+ if (var->tag == Var) {
+ const char *name = Match(var, Var)->name;
+ Table$str_set(vars, name, var);
+ }
+ }
+ return VISIT_STOP;
+ case UPDATE_CASES: {
+ binary_operands_t operands = BINARY_OPERANDS(ast);
+ ast_t *var = operands.lhs;
+ for (;;) {
+ if (var->tag == Index) var = Match(var, Index)->indexed;
+ else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded;
+ else break;
+ }
+ if (var->tag == Var) {
+ const char *name = Match(var, Var)->name;
+ Table$str_set(vars, name, var);
+ }
+ return VISIT_STOP;
+ }
+ case Declare: {
+ ast_t *var = Match(ast, Declare)->var;
+ const char *name = Match(var, Var)->name;
+ Table$str_set(vars, name, var);
+ return VISIT_STOP;
+ }
+ default: return VISIT_PROCEED;
+ }
+}
+
+static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) {
+ Table_t used_vars = EMPTY_TABLE;
+ ast_visit(body, find_used_variables, &used_vars);
+ Table_t assigned_vars = EMPTY_TABLE;
+ ast_visit(body, find_assigned_variables, &assigned_vars);
+
+ for (arg_ast_t *arg = args; arg; arg = arg->next) {
+ type_t *arg_type = get_arg_ast_type(env, arg);
+ if (arg_type->tag == PointerType) {
+ Table$str_remove(&assigned_vars, arg->name);
+ }
+ }
+
+ Table_t unused = Table$without(assigned_vars, used_vars, Table$info(&CString$info, &Present$$info));
+ for (int64_t i = 0; i < (int64_t)unused.entries.length; i++) {
+ struct {
+ const char *name;
+ } *entry = unused.entries.data + i * unused.entries.stride;
+ if (streq(entry->name, "_")) continue;
+ ast_t *var = Table$str_get(assigned_vars, entry->name);
+ code_err(var, "This variable was assigned to, but never read from.");
+ }
+}
+
+public
+Text_t compile_lambda(env_t *env, ast_t *ast) {
+ DeclareMatch(lambda, ast, Lambda);
+ Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id));
+
+ env_t *body_scope = fresh_scope(env);
+ body_scope->deferred = NULL;
+ for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
+ type_t *arg_type = get_arg_ast_type(env, arg);
+ set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name));
+ }
+
+ body_scope->fn = ast;
+
+ Table_t closed_vars = get_closed_vars(env, lambda->args, ast);
+ if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata
+ Text_t def = Text("typedef struct {");
+ for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) {
+ struct {
+ const char *name;
+ binding_t *b;
+ } *entry = closed_vars.entries.data + closed_vars.entries.stride * i;
+ if (has_stack_memory(entry->b->type))
+ code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type),
+ " stack memory in the variable `", entry->name,
+ "`, but the function may outlive the stack memory");
+ if (entry->b->type->tag == ModuleType) continue;
+ set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name));
+ def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; ");
+ }
+ def = Texts(def, "} ", name, "$userdata_t;");
+ env->code->local_typedefs = Texts(env->code->local_typedefs, def);
+ }
+
+ type_t *ret_t = get_function_return_type(env, ast);
+ Text_t code = Texts("static ", compile_type(ret_t), " ", name, "(");
+ for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
+ type_t *arg_type = get_arg_ast_type(env, arg);
+ code = Texts(code, compile_type(arg_type), " _$", arg->name, ", ");
+ }
+
+ Text_t userdata;
+ if (Table$length(closed_vars) == 0) {
+ code = Texts(code, "void *_)");
+ userdata = Text("NULL");
+ } else {
+ userdata = Texts("new(", name, "$userdata_t");
+ for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) {
+ struct {
+ const char *name;
+ binding_t *b;
+ } *entry = closed_vars.entries.data + closed_vars.entries.stride * i;
+ if (entry->b->type->tag == ModuleType) continue;
+ binding_t *b = get_binding(env, entry->name);
+ assert(b);
+ Text_t binding_code = b->code;
+ if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")");
+ else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")");
+ else userdata = Texts(userdata, ", ", binding_code);
+ }
+ userdata = Texts(userdata, ")");
+ code = Texts(code, name, "$userdata_t *userdata)");
+ }
+
+ Text_t body = EMPTY_TEXT;
+ for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) {
+ if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType
+ || get_type(body_scope, stmt->ast)->tag == ReturnType)
+ body = Texts(body, compile_statement(body_scope, stmt->ast), "\n");
+ else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n");
+ bind_statement(body_scope, stmt->ast);
+ }
+ if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred)
+ body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n");
+
+ env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n");
+
+ check_unused_vars(env, lambda->args, lambda->body);
+
+ return Texts("((Closure_t){", name, ", ", userdata, "})");
+}
+
public
Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) {
bool is_private = false;
@@ -703,7 +827,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
definition = Texts(definition, wrapper);
} else if (cache && cache->tag == Int) {
assert(args);
- OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NULL);
+ OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NONE_INT, NULL);
Text_t pop_code = EMPTY_TEXT;
if (cache->tag == Int && cache_size.has_value && cache_size.value > 0) {
// FIXME: this currently just deletes the first entry, but this
@@ -784,6 +908,8 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
}
}
+ check_unused_vars(env, args, body);
+
return definition;
}
diff --git a/src/compile/headers.c b/src/compile/headers.c
index f132b312..e90556a1 100644
--- a/src/compile/headers.c
+++ b/src/compile/headers.c
@@ -79,28 +79,16 @@ static void _make_typedefs(compile_typedef_info_t *info, ast_t *ast) {
*info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");
} else if (ast->tag == EnumDef) {
DeclareMatch(def, ast, EnumDef);
- bool has_any_tags_with_fields = false;
- for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
- has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL);
- }
+ Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct"));
+ Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type"));
+ *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");
- if (has_any_tags_with_fields) {
- Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$struct"));
- Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type"));
- *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");
-
- for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
- if (!tag->fields) continue;
- Text_t tag_struct =
- namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct"));
- Text_t tag_type =
- namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type"));
- *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n");
- }
- } else {
- Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$enum"));
- Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(def->name, "$$type"));
- *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n");
+ for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
+ Text_t tag_struct =
+ namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$struct"));
+ Text_t tag_type =
+ namespace_name(info->env, info->env->namespace, Texts(def->name, "$", tag->name, "$$type"));
+ *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n");
}
} else if (ast->tag == LangDef) {
DeclareMatch(def, ast, LangDef);
@@ -116,41 +104,30 @@ static void _define_types_and_funcs(compile_typedef_info_t *info, ast_t *ast) {
compile_statement_namespace_header(info->env, info->header_path, ast));
}
-static void add_type_headers(type_ast_t *type_ast, void *userdata) {
- if (!type_ast) return;
+static visit_behavior_t add_type_headers(type_ast_t *type_ast, void *userdata) {
+ if (!type_ast) return VISIT_STOP;
if (type_ast->tag == EnumTypeAST) {
compile_typedef_info_t *info = (compile_typedef_info_t *)userdata;
// Force the type to get defined:
(void)parse_type_ast(info->env, type_ast);
DeclareMatch(enum_, type_ast, EnumTypeAST);
- bool has_any_tags_with_fields = false;
- for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) {
- has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL);
- }
-
const char *name = String("enum$", (int64_t)(type_ast->start - type_ast->file->text));
- if (has_any_tags_with_fields) {
- Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct"));
- Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type"));
- *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");
-
- for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) {
- if (!tag->fields) continue;
- Text_t tag_struct =
- namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct"));
- Text_t tag_type =
- namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type"));
- *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n");
- }
- } else {
- Text_t enum_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$enum"));
- Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type"));
- *info->header = Texts(*info->header, "typedef enum ", enum_name, " ", type_name, ";\n");
+ Text_t struct_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$struct"));
+ Text_t type_name = namespace_name(info->env, info->env->namespace, Texts(name, "$$type"));
+ *info->header = Texts(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n");
+
+ for (tag_ast_t *tag = enum_->tags; tag; tag = tag->next) {
+ Text_t tag_struct =
+ namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$struct"));
+ Text_t tag_type = namespace_name(info->env, info->env->namespace, Texts(name, "$", tag->name, "$$type"));
+ *info->header = Texts(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n");
}
*info->header = Texts(*info->header, compile_enum_header(info->env, name, enum_->tags));
}
+
+ return VISIT_PROCEED;
}
public
@@ -158,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});
@@ -183,8 +160,8 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast)
case USE_MODULE: {
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,
+ 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)
!= 0) {
if (!try_install_module(mod, true)) code_err(ast, "Could not find library");
diff --git a/src/compile/lists.c b/src/compile/lists.c
index 1f3590a9..f39f61d8 100644
--- a/src/compile/lists.c
+++ b/src/compile/lists.c
@@ -53,7 +53,7 @@ list_comprehension: {
// set_binding(scope, comprehension_name, list_type, comprehension_name);
for (ast_list_t *item = list->items; item; item = item->next) {
if (item->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, item->ast));
- else code = Texts(code, compile_statement(env, add_to_list_comprehension(item->ast, comprehension_var)));
+ else code = Texts(code, compile_statement(scope, add_to_list_comprehension(item->ast, comprehension_var)));
}
code = Texts(code, " ", comprehension_name, "; })");
return code;
@@ -254,7 +254,7 @@ Text_t compile_list_method_call(env_t *env, ast_t *ast) {
} else if (streq(call->name, "unique")) {
self = compile_to_pointer_depth(env, call->self, 0, false);
(void)compile_arguments(env, ast, NULL, call->args);
- return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Empty$$info))");
+ return Texts("Table$from_entries(", self, ", Table$info(", compile_type_info(item_t), ", &Present$$info))");
} else if (streq(call->name, "pop")) {
EXPECT_POINTER();
arg_t *arg_spec = new (arg_t, .name = "index", .type = INT_TYPE, .default_val = FakeAST(Int, "-1"));
diff --git a/src/compile/optionals.c b/src/compile/optionals.c
index 7da50b0b..9aca84d0 100644
--- a/src/compile/optionals.c
+++ b/src/compile/optionals.c
@@ -15,18 +15,14 @@ Text_t optional_into_nonnone(type_t *t, Text_t value) {
switch (t->tag) {
case IntType:
case ByteType: return Texts(value, ".value");
- case StructType:
- if (t == PATH_TYPE || t == PATH_TYPE_TYPE) return value;
- return Texts(value, ".value");
+ case StructType: return Texts(value, ".value");
default: return value;
}
}
public
Text_t promote_to_optional(type_t *t, Text_t code) {
- if (t == PATH_TYPE || t == PATH_TYPE_TYPE) {
- return code;
- } else if (t->tag == IntType) {
+ if (t->tag == IntType) {
switch (Match(t, IntType)->bits) {
case TYPE_IBITS8: return Texts("((OptionalInt8_t){.has_value=true, .value=", code, "})");
case TYPE_IBITS16: return Texts("((OptionalInt16_t){.has_value=true, .value=", code, "})");
@@ -53,7 +49,6 @@ Text_t compile_none(type_t *t) {
if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");
if (t == PATH_TYPE) return Text("NONE_PATH");
- else if (t == PATH_TYPE_TYPE) return Text("PATHTYPE_NONE");
switch (t->tag) {
case BigIntType: return Text("NONE_INT");
@@ -92,8 +87,6 @@ Text_t check_none(type_t *t, Text_t value) {
// NOTE: these use statement expressions ({...;}) because some compilers
// complain about excessive parens around equality comparisons
if (t->tag == PointerType || t->tag == FunctionType || t->tag == CStringType) return Texts("(", value, " == NULL)");
- else if (t == PATH_TYPE) return Texts("((", value, ").type.$tag == PATHTYPE_NONE)");
- else if (t == PATH_TYPE_TYPE) return Texts("((", value, ").$tag == PATHTYPE_NONE)");
else if (t->tag == BigIntType) return Texts("((", value, ").small == 0)");
else if (t->tag == ClosureType) return Texts("((", value, ").fn == NULL)");
else if (t->tag == FloatType)
@@ -103,10 +96,7 @@ Text_t check_none(type_t *t, Text_t value) {
else if (t->tag == BoolType) return Texts("((", value, ") == NONE_BOOL)");
else if (t->tag == TextType) return Texts("((", value, ").tag == TEXT_NONE)");
else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) return Texts("!(", value, ").has_value");
- else if (t->tag == EnumType) {
- if (enum_has_fields(t)) return Texts("((", value, ").$tag == 0)");
- else return Texts("((", value, ") == 0)");
- }
+ else if (t->tag == EnumType) return Texts("((", value, ").$tag == 0)");
print_err("Optional check not implemented for: ", type_to_text(t));
return EMPTY_TEXT;
}
@@ -115,12 +105,51 @@ public
Text_t compile_non_optional(env_t *env, ast_t *ast) {
ast_t *value = Match(ast, NonOptional)->value;
if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true);
- type_t *t = get_type(env, value);
- Text_t value_code = compile(env, value);
+ type_t *value_t = get_type(env, value);
+ if (value_t->tag == PointerType) {
+ // Dereference pointers automatically
+ return compile_non_optional(env, WrapAST(ast, NonOptional, WrapAST(ast, Index, .indexed = value)));
+ }
int64_t line = get_line_number(ast->file, ast->start);
- return Texts(
- "({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
- check_none(t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->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", optional_into_nonnone(t, Text("opt")), "; })");
+ if (value_t->tag == EnumType) {
+ // For this case:
+ // enum Foo(FirstField, SecondField(msg:Text))
+ // e := ...
+ // e!
+ // We desugar into `e.FirstField!` using the first enum field
+ tag_t *first_tag = Match(value_t, EnumType)->tags;
+ if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum");
+ return compile_non_optional(
+ env, WrapAST(ast, NonOptional, WrapAST(value, FieldAccess, .fielded = value, .field = first_tag->name)));
+ } else if (value->tag == FieldAccess
+ && value_type(get_type(env, Match(value, FieldAccess)->fielded))->tag == EnumType) {
+ type_t *enum_t = value_type(get_type(env, Match(value, FieldAccess)->fielded));
+ DeclareMatch(e, enum_t, EnumType);
+ DeclareMatch(f, value, FieldAccess);
+ for (tag_t *tag = e->tags; tag; tag = tag->next) {
+ if (streq(f->field, tag->name)) {
+ Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name));
+ return Texts(
+ "({ ", compile_declaration(enum_t, Text("_test_enum")), " = ",
+ 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",
+ compile_maybe_incref(
+ env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type),
+ "; })");
+ }
+ }
+ code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t));
+ } else {
+ Text_t value_code = compile(env, value);
+ return Texts("({ ", compile_declaration(value_t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
+ 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",
+ optional_into_nonnone(value_t, Text("opt")), "; })");
+ }
}
diff --git a/src/compile/pointers.c b/src/compile/pointers.c
index 11348330..98274cc8 100644
--- a/src/compile/pointers.c
+++ b/src/compile/pointers.c
@@ -55,13 +55,13 @@ Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type) {
type_t *pointed = Match(pointer_type, PointerType)->pointed;
switch (ast->tag) {
case HeapAllocate: {
- return Texts("heap(", compile_to_type(env, Match(ast, HeapAllocate)->value, pointed), ")");
+ return Texts("heap(", compile_maybe_incref(env, Match(ast, HeapAllocate)->value, pointed), ")");
}
case StackReference: {
ast_t *subject = Match(ast, StackReference)->value;
if (can_be_mutated(env, subject) && type_eq(pointed, get_type(env, subject)))
return Texts("(&", compile_lvalue(env, subject), ")");
- else return Texts("stack(", compile_to_type(env, subject, pointed), ")");
+ else return Texts("stack(", compile_maybe_incref(env, subject, pointed), ")");
}
default: code_err(ast, "Not an allocation!");
}
diff --git a/src/compile/promotions.c b/src/compile/promotions.c
index 2a346668..4b5458c9 100644
--- a/src/compile/promotions.c
+++ b/src/compile/promotions.c
@@ -26,18 +26,21 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede
if (more_complete) return true;
// Serialization/deserialization:
- if (type_eq(needed, Type(ListType, Type(ByteType)))) {
- *code = Texts("generic_serialize((", compile_declaration(actual, Text("[1]")), "){", *code, "}, ",
- compile_type_info(actual), ")");
- return true;
- } else if (type_eq(actual, Type(ListType, Type(ByteType)))) {
- *code = Texts("({ ", compile_declaration(needed, Text("deserialized")),
- ";\n"
- "generic_deserialize(",
- *code, ", &deserialized, ", compile_type_info(needed),
- ");\n"
- "deserialized; })");
- return true;
+ if (!type_eq(non_optional(value_type(needed)), Type(ListType, Type(ByteType)))
+ || !type_eq(non_optional(value_type(actual)), Type(ListType, Type(ByteType)))) {
+ if (type_eq(needed, Type(ListType, Type(ByteType)))) {
+ *code = Texts("generic_serialize((", compile_declaration(actual, Text("[1]")), "){", *code, "}, ",
+ compile_type_info(actual), ")");
+ return true;
+ } else if (type_eq(actual, Type(ListType, Type(ByteType)))) {
+ *code = Texts("({ ", compile_declaration(needed, Text("deserialized")),
+ ";\n"
+ "generic_deserialize(",
+ *code, ", &deserialized, ", compile_type_info(needed),
+ ");\n"
+ "deserialized; })");
+ return true;
+ }
}
// Optional promotion:
@@ -128,6 +131,10 @@ Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t) {
env = with_enum_scope(env, t);
}
+ if (ast->tag == Block && Match(ast, Block)->statements && !Match(ast, Block)->statements->next) {
+ ast = Match(ast, Block)->statements->ast;
+ }
+
if (ast->tag == Int && is_numeric_type(non_optional(t))) {
return compile_int_to_type(env, ast, t);
} else if (ast->tag == Num && t->tag == FloatType) {
diff --git a/src/compile/statements.c b/src/compile/statements.c
index f554263c..81a10ddd 100644
--- a/src/compile/statements.c
+++ b/src/compile/statements.c
@@ -196,8 +196,8 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
} else if (use->what == USE_MODULE) {
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,
+ 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)
!= 0) {
if (!try_install_module(mod, true)) code_err(ast, "Could not find library");
@@ -216,11 +216,12 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
return EMPTY_TEXT;
}
}
+ case Metadata: return EMPTY_TEXT;
default:
- // print("Is discardable: ", ast_to_sexp_str(ast), " ==> ",
- // is_discardable(env, ast));
if (!is_discardable(env, ast))
- code_err(ast, "The ", type_to_text(get_type(env, ast)), " result of this statement cannot be discarded");
+ code_err(
+ ast, "The ", type_to_text(get_type(env, ast)),
+ " value of this statement is implicitly ignored. \n Use `_ := ` if you want to explicitly discard it.");
return Texts("(void)", compile(env, ast), ";");
}
}
diff --git a/src/compile/tables.c b/src/compile/tables.c
index 54276c3b..e624f9fb 100644
--- a/src/compile/tables.c
+++ b/src/compile/tables.c
@@ -13,7 +13,7 @@ static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) {
return WrapAST(
entry, MethodCall, .name = "set", .self = subject,
.args = new (arg_ast_t, .value = e->key,
- .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "EMPTY"))));
+ .next = new (arg_ast_t, .value = e->value ? e->value : WrapAST(entry, Var, .name = "PRESENT"))));
}
Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) {
@@ -51,10 +51,10 @@ Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) {
for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
DeclareMatch(e, entry->ast, TableEntry);
- code = Texts(
- code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ",
- compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "EMPTY"), value_t),
- "}");
+ code = Texts(code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ",
+ compile_to_type(value_scope, e->value ? e->value : WrapAST(entry->ast, Var, .name = "PRESENT"),
+ value_t),
+ "}");
}
return Texts(code, ")");
}
diff --git a/src/compile/types.c b/src/compile/types.c
index 24790e46..a581fb61 100644
--- a/src/compile/types.c
+++ b/src/compile/types.c
@@ -12,7 +12,6 @@
public
Text_t compile_type(type_t *t) {
if (t == PATH_TYPE) return Text("Path_t");
- else if (t == PATH_TYPE_TYPE) return Text("PathType_t");
switch (t->tag) {
case ReturnType: errx(1, "Shouldn't be compiling ReturnType to a type");
@@ -73,7 +72,6 @@ Text_t compile_type(type_t *t) {
case TableType: return Texts("Optional", compile_type(nonnull));
case StructType: {
if (nonnull == PATH_TYPE) return Text("OptionalPath_t");
- if (nonnull == PATH_TYPE_TYPE) return Text("OptionalPathType_t");
DeclareMatch(s, nonnull, StructType);
return namespace_name(s->env, s->env->namespace->parent, Texts("$Optional", s->name, "$$type"));
}
@@ -90,7 +88,6 @@ public
Text_t compile_type_info(type_t *t) {
if (t == NULL) compiler_err(NULL, NULL, NULL, "Attempt to compile a NULL type");
if (t == PATH_TYPE) return Text("&Path$info");
- else if (t == PATH_TYPE_TYPE) return Text("&PathType$info");
switch (t->tag) {
case BoolType:
diff --git a/src/compile/whens.c b/src/compile/whens.c
index 4f6a2a40..618a667c 100644
--- a/src/compile/whens.c
+++ b/src/compile/whens.c
@@ -43,11 +43,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) {
DeclareMatch(enum_t, subject_t, EnumType);
- Text_t code;
- if (enum_has_fields(subject_t))
- code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n");
- else code = Texts("switch(", compile(env, when->subject), ") {\n");
-
+ Text_t code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n");
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
if (clause->pattern->tag == Var) {
const char *clause_tag_name = Match(clause->pattern, Var)->name;
@@ -83,8 +79,10 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) {
const char *var_name = Match(args->value, Var)->name;
if (!streq(var_name, "_")) {
Text_t var = Texts("_$", var_name);
- code = Texts(code, compile_declaration(tag_type, var), " = _when_subject.",
- valid_c_name(clause_tag_name), ";\n");
+ ast_t *member =
+ WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type);
+ code = Texts(code, compile_declaration(tag_type, var), " = ",
+ compile_maybe_incref(env, member, tag_type), ";\n");
scope = fresh_scope(scope);
set_binding(scope, Match(args->value, Var)->name, tag_type, EMPTY_TEXT);
}
@@ -101,8 +99,10 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) {
const char *var_name = Match(arg->value, Var)->name;
if (!streq(var_name, "_")) {
Text_t var = Texts("_$", var_name);
- code = Texts(code, compile_declaration(field->type, var), " = _when_subject.",
- valid_c_name(clause_tag_name), ".", valid_c_name(field->name), ";\n");
+ ast_t *member =
+ WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type);
+ code = Texts(code, compile_declaration(field->type, var), " = ",
+ compile_maybe_incref(env, member, tag_type), ".", valid_c_name(field->name), ";\n");
set_binding(scope, Match(arg->value, Var)->name, field->type, var);
}
field = field->next;
@@ -128,7 +128,7 @@ Text_t compile_when_statement(env_t *env, ast_t *ast) {
} else {
code = Texts(code, "default: errx(1, \"Invalid tag!\");\n");
}
- code = Texts(code, "\n}", enum_has_fields(subject_t) ? Text(")") : EMPTY_TEXT, "\n");
+ code = Texts(code, "\n}", Text(")"), "\n");
return code;
}
diff --git a/src/environment.c b/src/environment.c
index a5b30cb5..4cb7fbb0 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -17,9 +17,9 @@ type_t *TEXT_TYPE = NULL;
public
type_t *PATH_TYPE = NULL;
public
-type_t *PATH_TYPE_TYPE = NULL;
+type_t *PRESENT_TYPE = NULL;
public
-type_t *EMPTY_TYPE = NULL;
+type_t *RESULT_TYPE = NULL;
static type_t *declare_type(env_t *env, const char *def_str) {
ast_t *ast = parse_file_str(def_str);
@@ -67,10 +67,12 @@ env_t *global_env(bool source_mapping) {
(void)bind_type(env, "Int", Type(BigIntType));
(void)bind_type(env, "Int32", Type(IntType, .bits = TYPE_IBITS32));
(void)bind_type(env, "Memory", Type(MemoryType));
- PATH_TYPE_TYPE = declare_type(env, "enum PathType(Relative, Absolute, Home)");
- PATH_TYPE = declare_type(env, "struct Path(type:PathType, components:[Text])");
+ PATH_TYPE = declare_type(
+ env,
+ "enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))");
+ RESULT_TYPE = declare_type(env, "enum Result(Success, Failure(reason:Text))");
- EMPTY_TYPE = declare_type(env, "struct Empty()");
+ PRESENT_TYPE = declare_type(env, "struct Present()");
typedef struct {
const char *name, *code, *type_str;
@@ -90,10 +92,11 @@ env_t *global_env(bool source_mapping) {
MAKE_TYPE("Void", Type(VoidType), Text("void"), Text("Void$info")),
MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")),
MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")),
- MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")),
+ MAKE_TYPE("Present", PRESENT_TYPE, Text("Present$$type"), Text("Present$$info")),
+ MAKE_TYPE("Result", RESULT_TYPE, Text("Result_t"), Text("Result$$info")),
MAKE_TYPE( //
"Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"),
- {"parse", "Bool$parse", "func(text:Text, remainder:&Text? = none -> Bool?)"}),
+ {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}),
MAKE_TYPE( //
"Byte", Type(ByteType), Text("Byte_t"), Text("Byte$info"),
{"get_bit", "Byte$get_bit", "func(x:Byte, bit_index:Int -> Bool)"}, //
@@ -101,7 +104,7 @@ env_t *global_env(bool source_mapping) {
{"is_between", "Byte$is_between", "func(x:Byte, low:Byte, high:Byte -> Bool)"}, //
{"max", "Byte$max", "Byte"}, //
{"min", "Byte$min", "Byte"}, //
- {"parse", "Byte$parse", "func(text:Text, remainder:&Text? = none -> Byte?)"}, //
+ {"parse", "Byte$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Byte?)"}, //
{"to", "Byte$to", "func(first:Byte, last:Byte, step:Int8?=none -> func(->Byte?))"}),
MAKE_TYPE( //
"Int", Type(BigIntType), Text("Int_t"), Text("Int$info"), {"abs", "Int$abs", "func(x:Int -> Int)"}, //
@@ -126,7 +129,7 @@ env_t *global_env(bool source_mapping) {
{"next_prime", "Int$next_prime", "func(x:Int -> Int)"}, //
{"octal", "Int$octal", "func(i:Int, digits=0, prefix=yes -> Text)"}, //
{"onward", "Int$onward", "func(first:Int,step=1 -> func(->Int?))"}, //
- {"parse", "Int$parse", "func(text:Text, remainder:&Text? = none -> Int?)"}, //
+ {"parse", "Int$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int?)"}, //
{"plus", "Int$plus", "func(x,y:Int -> Int)"}, //
{"power", "Int$power", "func(base:Int,exponent:Int -> Int)"}, //
#if __GNU_MP_VERSION >= 6
@@ -145,7 +148,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int64$clamped", "func(x,low,high:Int64 -> Int64)"}, //
{"divided_by", "Int64$divided_by", "func(x,y:Int64 -> Int64)"}, //
{"gcd", "Int64$gcd", "func(x,y:Int64 -> Int64)"}, //
- {"parse", "Int64$parse", "func(text:Text, remainder:&Text? = none -> Int64?)"}, //
+ {"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)"}, //
@@ -167,7 +170,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int32$clamped", "func(x,low,high:Int32 -> Int32)"}, //
{"divided_by", "Int32$divided_by", "func(x,y:Int32 -> Int32)"}, //
{"gcd", "Int32$gcd", "func(x,y:Int32 -> Int32)"}, //
- {"parse", "Int32$parse", "func(text:Text, remainder:&Text? = none -> Int32?)"}, //
+ {"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)"}, //
@@ -189,7 +192,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int16$clamped", "func(x,low,high:Int16 -> Int16)"}, //
{"divided_by", "Int16$divided_by", "func(x,y:Int16 -> Int16)"}, //
{"gcd", "Int16$gcd", "func(x,y:Int16 -> Int16)"}, //
- {"parse", "Int16$parse", "func(text:Text, remainder:&Text? = none -> Int16?)"}, //
+ {"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)"}, //
@@ -211,7 +214,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int8$clamped", "func(x,low,high:Int8 -> Int8)"}, //
{"divided_by", "Int8$divided_by", "func(x,y:Int8 -> Int8)"}, //
{"gcd", "Int8$gcd", "func(x,y:Int8 -> Int8)"}, //
- {"parse", "Int8$parse", "func(text:Text, remainder:&Text? = none -> Int8?)"}, //
+ {"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)"}, //
@@ -302,15 +305,11 @@ env_t *global_env(bool source_mapping) {
{"as_text", "Text$from_str", "func(str:CString -> Text)"},
{"join", "CString$join", "func(glue:CString, pieces:[CString] -> CString)"}),
MAKE_TYPE( //
- "PathType", PATH_TYPE_TYPE, Text("PathType_t"), Text("PathType$info"), //
- {"Relative", "PATHTYPE_RELATIVE", "PathType"}, //
- {"Absolute", "PATHTYPE_ABSOLUTE", "PathType"}, //
- {"Home", "PATHTYPE_HOME", "PathType"}),
- MAKE_TYPE( //
"Path", PATH_TYPE, Text("Path_t"), Text("Path$info"), //
{"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Int64?)"}, //
- {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"}, //
- {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, //
+ {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, //
+ {"append_bytes", "Path$append_bytes",
+ "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, //
{"base_name", "Path$base_name", "func(path:Path -> Text)"}, //
{"by_line", "Path$by_line", "func(path:Path -> func(->Text?)?)"}, //
{"can_execute", "Path$can_execute", "func(path:Path -> Bool)"}, //
@@ -320,7 +319,9 @@ env_t *global_env(bool source_mapping) {
{"child", "Path$child", "func(path:Path, child:Text -> Path)"}, //
{"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, //
{"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, //
- {"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755))"}, //
+ {"components", "Path$components", "func(path:Path -> [Text])"}, //
+ {"create_directory", "Path$create_directory",
+ "func(path:Path, permissions=Int32(0o755), recursive=yes -> Result)"}, //
{"current_dir", "Path$current_dir", "func(->Path)"}, //
{"exists", "Path$exists", "func(path:Path -> Bool)"}, //
{"expand_home", "Path$expand_home", "func(path:Path -> Path)"}, //
@@ -335,23 +336,24 @@ env_t *global_env(bool source_mapping) {
{"is_pipe", "Path$is_pipe", "func(path:Path, follow_symlinks=yes -> Bool)"}, //
{"is_socket", "Path$is_socket", "func(path:Path, follow_symlinks=yes -> Bool)"}, //
{"is_symlink", "Path$is_symlink", "func(path:Path -> Bool)"}, //
+ {"lines", "Path$lines", "func(path:Path -> [Text]?)"}, //
{"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Int64?)"}, //
{"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, //
- {"parent", "Path$parent", "func(path:Path -> Path)"}, //
+ {"parent", "Path$parent", "func(path:Path -> Path?)"}, //
{"read", "Path$read", "func(path:Path -> Text?)"}, //
{"read_bytes", "Path$read_bytes", "func(path:Path, limit:Int?=none -> [Byte]?)"}, //
{"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"}, //
- {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, //
+ {"remove", "Path$remove", "func(path:Path, ignore_missing=no -> Result)"}, //
{"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"}, //
- {"set_owner", "Path$set_owner", //
- "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes)"}, //
+ {"set_owner", "Path$set_owner",
+ "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes -> Result)"}, //
{"sibling", "Path$sibling", "func(path:Path, name:Text -> Path)"}, //
{"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))"}, //
- {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, //
- {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path)"}, //
- {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path)"}),
+ {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> 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?)"}),
MAKE_TYPE( //
"Text", TEXT_TYPE, Text("Text_t"), Text("Text$info"), //
{"as_c_string", "Text$as_c_string", "func(text:Text -> CString)"}, //
@@ -362,6 +364,7 @@ env_t *global_env(bool source_mapping) {
{"caseless_equals", "Text$equal_ignoring_case", "func(a,b:Text, language='C' -> Bool)"}, //
{"codepoint_names", "Text$codepoint_names", "func(text:Text -> [Text])"}, //
{"ends_with", "Text$ends_with", "func(text,suffix:Text, remainder:&Text? = none -> Bool)"}, //
+ {"find", "Text$find", "func(text,target:Text, start=1 -> Int?)"}, //
{"from", "Text$from", "func(text:Text, first:Int -> Text)"}, //
{"from_c_string", "Text$from_str", "func(str:CString -> Text?)"}, //
{"from_codepoint_names", "Text$from_codepoint_names", "func(codepoint_names:[Text] -> Text?)"}, //
@@ -537,17 +540,18 @@ env_t *global_env(bool source_mapping) {
struct {
const char *name, *code, *type_str;
} global_vars[] = {
- {"USE_COLOR", "USE_COLOR", "Bool"},
+ {"PRESENT", "PRESENT", "Present"},
{"TOMO_VERSION", "TOMO_VERSION_TEXT", "Text"},
- {"say", "say", "func(text:Text, newline=yes)"},
- {"print", "say", "func(text:Text, newline=yes)"},
- {"getenv", "getenv_text", "func(name:Text -> Text?)"},
- {"setenv", "setenv_text", "func(name:Text, value:Text -> Text?)"},
+ {"USE_COLOR", "USE_COLOR", "Bool"},
{"ask", "ask", "func(prompt:Text, bold=yes, force_tty=yes -> Text?)"},
+ {"at_cleanup", "tomo_at_cleanup", "func(fn:func())"},
{"exit", "tomo_exit", "func(message:Text?=none, code=Int32(1) -> Abort)"},
{"fail", "fail_text", "func(message:Text -> Abort)"},
- {"sleep", "sleep_float64", "func(seconds:Float64)"},
- {"EMPTY", "EMPTY", "Empty"},
+ {"getenv", "getenv_text", "func(name:Text -> Text?)"},
+ {"print", "say", "func(text:Text, newline=yes)"},
+ {"say", "say", "func(text:Text, newline=yes)"},
+ {"setenv", "setenv_text", "func(name:Text, value:Text?)"},
+ {"sleep", "sleep_seconds", "func(seconds:Float64)"},
};
for (size_t i = 0; i < sizeof(global_vars) / sizeof(global_vars[0]); i++) {
diff --git a/src/environment.h b/src/environment.h
index 6389cc7a..ba036f2e 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -86,5 +86,5 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name);
#define code_err(ast, ...) compiler_err((ast)->file, (ast)->start, (ast)->end, __VA_ARGS__)
extern type_t *TEXT_TYPE;
extern type_t *PATH_TYPE;
-extern type_t *PATH_TYPE_TYPE;
-extern type_t *EMPTY_TYPE;
+extern type_t *PRESENT_TYPE;
+extern type_t *RESULT_TYPE;
diff --git a/src/modules.c b/src/modules.c
index 9ebdca09..36952ec8 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -28,7 +28,7 @@ const char *get_library_version(Path_t lib_dir) {
Path_t changes_file = Path$child(lib_dir, Text("CHANGES.md"));
OptionalText_t changes = Path$read(changes_file);
if (changes.length <= 0) {
- return "v0.0";
+ return "v0";
}
const char *changes_str = Text$as_c_string(Texts(Text("\n"), changes));
const char *version_line = strstr(changes_str, "\n## ");
@@ -41,7 +41,7 @@ Text_t get_library_name(Path_t lib_dir) {
Text_t name = Path$base_name(lib_dir);
name = Text$without_prefix(name, Text("tomo-"));
name = Text$without_suffix(name, Text("-tomo"));
- Text_t suffix = Texts(Text("_"), Text$from_str(get_library_version(lib_dir)));
+ Text_t suffix = Texts(Text("@"), Text$from_str(get_library_version(lib_dir)));
if (!Text$ends_with(name, suffix, NULL)) name = Texts(name, suffix);
return name;
}
@@ -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);
@@ -129,6 +129,7 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) {
}
print("Installing ", mod.name, " from git...");
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);
xsystem("tomo -L ", dest);
return true;
@@ -152,10 +153,10 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) {
const char *extension = p + 1;
Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX"));
tmpdir = Path$child(tmpdir, Text$from_str(mod.name));
- Path$create_directory(tmpdir, 0755);
+ Path$create_directory(tmpdir, 0755, true);
xsystem("curl ", mod.url, " -o ", tmpdir);
- Path$create_directory(dest, 0755);
+ Path$create_directory(dest, 0755, true);
if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename, " -d ", dest);
else if (streq(extension, ".tar.gz") || streq(extension, ".tar"))
xsystem("tar xf ", tmpdir, "/", filename, " -C ", dest);
diff --git a/src/naming.c b/src/naming.c
index 484e1998..d7bb0fb9 100644
--- a/src/naming.c
+++ b/src/naming.c
@@ -96,8 +96,9 @@ Text_t valid_c_name(const char *name) {
public
Text_t CONSTFUNC namespace_name(env_t *env, namespace_t *ns, Text_t name) {
- for (; ns; ns = ns->parent)
+ for (; ns; ns = ns->parent) {
name = Texts(ns->name, "$", name);
+ }
if (env->id_suffix.length > 0) name = Texts(name, env->id_suffix);
return name;
}
@@ -113,5 +114,6 @@ Text_t get_id_suffix(const char *filename) {
Path_t id_file = Path$child(build_dir, Texts(Path$base_name(path), Text$from_str(".id")));
OptionalText_t id = Path$read(id_file);
if (id.tag == TEXT_NONE) err(1, "Could not read ID file: %s", Path$as_c_string(id_file));
+ id = Text$trim(id, Text(" \r\n"), true, true);
return Texts("$", id);
}
diff --git a/src/parse/expressions.c b/src/parse/expressions.c
index b43e4f3a..d031c49f 100644
--- a/src/parse/expressions.c
+++ b/src/parse/expressions.c
@@ -168,7 +168,7 @@ ast_t *parse_term_no_suffix(parse_ctx_t *ctx, const char *pos) {
(void)(false || (term = parse_none(ctx, pos)) || (term = parse_num(ctx, pos)) // Must come before int
|| (term = parse_int(ctx, pos)) || (term = parse_negative(ctx, pos)) // Must come after num/int
|| (term = parse_heap_alloc(ctx, pos)) || (term = parse_stack_reference(ctx, pos))
- || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos)) || (term = parse_path(ctx, pos))
+ || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos, true)) || (term = parse_path(ctx, pos))
|| (term = parse_lambda(ctx, pos)) || (term = parse_parens(ctx, pos)) || (term = parse_table(ctx, pos))
|| (term = parse_var(ctx, pos)) || (term = parse_list(ctx, pos)) || (term = parse_reduction(ctx, pos))
|| (term = parse_pass(ctx, pos)) || (term = parse_defer(ctx, pos)) || (term = parse_skip(ctx, pos))
diff --git a/src/parse/files.c b/src/parse/files.c
index 23e940e9..f5d9554a 100644
--- a/src/parse/files.c
+++ b/src/parse/files.c
@@ -7,8 +7,11 @@
#include <string.h>
#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/paths.h"
#include "../stdlib/stdlib.h"
#include "../stdlib/tables.h"
+#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "context.h"
#include "errors.h"
@@ -31,6 +34,35 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) {
return declaration;
}
+static ast_t *parse_metadata(parse_ctx_t *ctx, const char *pos) {
+ const char *start = pos;
+ const char *key = get_id(&pos);
+ if (!key) return NULL;
+ spaces(&pos);
+ if (!match(&pos, ":")) return NULL;
+ spaces(&pos);
+ ast_t *value = parse_text(ctx, pos, false);
+ Text_t value_text = EMPTY_TEXT;
+ if (value) {
+ for (ast_list_t *child = Match(value, TextJoin)->children; child; child = child->next) {
+ if (child->ast->tag != TextLiteral)
+ parser_err(ctx, child->ast->start, child->ast->end, "Text interpolations are not allowed in metadata");
+ value_text = Texts(value_text, Match(child->ast, TextLiteral)->text);
+ }
+ } else {
+ value = parse_path(ctx, pos);
+ if (!value) return NULL;
+ Path_t path = Path$from_str(Match(value, Path)->path);
+ path = Path$resolved(path, Path$parent(Path$from_str(ctx->file->filename)));
+ OptionalText_t contents = Path$read(path);
+ if (contents.tag == TEXT_NONE) parser_err(ctx, value->start, value->end, "File not found: ", path);
+ value_text = Text$trim(contents, Text("\r\n\t "), true, true);
+ }
+ pos = value->end;
+
+ return NewAST(ctx->file, start, pos, Metadata, .key = Text$from_str(key), .value = value_text);
+}
+
ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
whitespace(ctx, &pos);
@@ -40,10 +72,11 @@ ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) {
whitespace(ctx, &next);
if (get_indent(ctx, next) != 0) break;
ast_t *stmt;
- if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def))
- || (stmt = optional(ctx, &pos, parse_enum_def)) || (stmt = optional(ctx, &pos, parse_lang_def))
- || (stmt = optional(ctx, &pos, parse_convert_def)) || (stmt = optional(ctx, &pos, parse_use))
- || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_top_declaration))) {
+ if ((stmt = optional(ctx, &pos, parse_metadata)) || (stmt = optional(ctx, &pos, parse_struct_def))
+ || (stmt = optional(ctx, &pos, parse_func_def)) || (stmt = optional(ctx, &pos, parse_enum_def))
+ || (stmt = optional(ctx, &pos, parse_lang_def)) || (stmt = optional(ctx, &pos, parse_convert_def))
+ || (stmt = optional(ctx, &pos, parse_use)) || (stmt = optional(ctx, &pos, parse_inline_c))
+ || (stmt = optional(ctx, &pos, parse_top_declaration))) {
statements = new (ast_list_t, .ast = stmt, .next = statements);
pos = stmt->end;
whitespace(ctx, &pos); // TODO: check for newline
diff --git a/src/parse/text.c b/src/parse/text.c
index 7650955c..e23b8417 100644
--- a/src/parse/text.c
+++ b/src/parse/text.c
@@ -17,7 +17,7 @@
#include "types.h"
#include "utils.h"
-static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) {
+static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, bool allow_interps) {
const char *pos = *out_pos;
int64_t starting_indent = get_indent(ctx, pos);
@@ -41,6 +41,8 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) {
parser_err(ctx, pos, pos, "I expected a valid text here");
}
+ if (!allow_interps) interp = NULL;
+
ast_list_t *chunks = NULL;
Text_t chunk = EMPTY_TEXT;
const char *chunk_start = pos;
@@ -123,9 +125,9 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) {
return chunks;
}
-ast_t *parse_text(parse_ctx_t *ctx, const char *pos) {
+ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps) {
// ('"' ... '"' / "'" ... "'" / "`" ... "`")
- // "$" [name] [interp-char] quote-char ... close-quote
+ // "$" [name] quote-char ... close-quote
const char *start = pos;
const char *lang = NULL;
@@ -136,7 +138,7 @@ ast_t *parse_text(parse_ctx_t *ctx, const char *pos) {
if (!(*pos == '"' || *pos == '\'' || *pos == '`')) return NULL;
- ast_list_t *chunks = _parse_text_helper(ctx, &pos);
+ ast_list_t *chunks = _parse_text_helper(ctx, &pos, allow_interps);
bool colorize = match(&pos, "~") && match_word(&pos, "colorized");
return NewAST(ctx->file, start, pos, TextJoin, .lang = lang, .children = chunks, .colorize = colorize);
}
@@ -157,7 +159,7 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) {
parser_err(ctx, pos, pos + 1,
"This is not a valid string quotation character. Valid characters are: \"'`|/;([{<");
- ast_list_t *chunks = _parse_text_helper(ctx, &pos);
+ ast_list_t *chunks = _parse_text_helper(ctx, &pos, true);
return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type);
}
diff --git a/src/parse/text.h b/src/parse/text.h
index 6ab3cab2..865bc5a4 100644
--- a/src/parse/text.h
+++ b/src/parse/text.h
@@ -1,9 +1,11 @@
// Logic for parsing text literals
#pragma once
+#include <stdbool.h>
+
#include "../ast.h"
#include "context.h"
-ast_t *parse_text(parse_ctx_t *ctx, const char *pos);
+ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps);
ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos);
ast_t *parse_path(parse_ctx_t *ctx, const char *pos);
diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c
index 41e8e6db..2d145bd5 100644
--- a/src/stdlib/bigint.c
+++ b/src/stdlib/bigint.c
@@ -393,52 +393,98 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) {
}
public
-OptionalInt_t Int$from_str(const char *str) {
- mpz_t i;
- int result;
- if (strncmp(str, "0x", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 16);
- } else if (strncmp(str, "0o", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 8);
- } else if (strncmp(str, "0b", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 2);
- } else {
- result = mpz_init_set_str(i, str, 10);
- }
- if (result != 0) return NONE_INT;
- return Int$from_mpz(i);
-}
+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, Text_t *remainder) {
+OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
const char *str = Text$as_c_string(text);
- mpz_t i;
- int result;
- if (strncmp(str, "0x", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "0123456789abcdefABCDEF");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str + 2, (size_t)(end - (str + 2)))), 16);
+ bool negative = (*str == '-');
+ if (negative || *str == '+') str += 1;
+ const char *end = str;
+ int32_t base32;
+ if (base.small != 0) {
+ base32 = Int32$from_int(base, false);
+ switch (base32) {
+ case 16:
+ if (strncmp(str, "0x", 2) == 0) {
+ base16_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "0123456789abcdefABCDEF");
+ break;
+ case 10:
+ base10:
+ end = str + strspn(str, "0123456789");
+ break;
+ case 8:
+ if (strncmp(str, "0o", 2) == 0) {
+ base8_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "01234567");
+ break;
+ case 2:
+ if (strncmp(str, "0b", 2) == 0) {
+ base2_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "01");
+ break;
+ case 1: {
+ str += strspn(str, "0");
+ size_t n = strspn(str, "1");
+ end = str + n;
+ if (remainder) *remainder = Text$from_str(end);
+ else if (*end != '\0') return NONE_INT;
+ return Int$from_int64((int64_t)n);
+ }
+ default: {
+ if (base32 < 1 || base32 > 36) {
+ if (remainder) *remainder = text;
+ return NONE_INT;
+ }
+ for (; *end; end++) {
+ char c = *end;
+ int32_t digit;
+ if ('0' <= c && c <= '9') {
+ digit = (c - (int)'0');
+ } else if ('a' <= c && c <= 'z') {
+ digit = (c - (int)'a');
+ } else if ('A' <= c && c <= 'Z') {
+ digit = (c - (int)'A');
+ } else {
+ break;
+ }
+ if (digit >= base32) break;
+ }
+ }
+ }
+ } else if (strncmp(str, "0x", 2) == 0) {
+ base32 = 16;
+ goto base16_prefix;
} else if (strncmp(str, "0o", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "01234567");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str + 2, (size_t)(end - (str + 2)))), 8);
+ base32 = 8;
+ goto base8_prefix;
} else if (strncmp(str, "0b", 2) == 0) {
- const char *end = str + 2 + strspn(str + 2, "01");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str + 2, (size_t)(end - (str + 2)))), 2);
+ base32 = 2;
+ goto base2_prefix;
} else {
- const char *end = str + strspn(str, "0123456789");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 10);
+ base32 = 10;
+ goto base10;
}
+
+ if (remainder) *remainder = Text$from_str(end);
+ else if (*end != '\0') return NONE_INT;
+
+ mpz_t i;
+ int result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), base32);
if (result != 0) {
if (remainder) *remainder = text;
return NONE_INT;
}
+ if (negative) {
+ mpz_neg(i, i);
+ }
return Int$from_mpz(i);
}
diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h
index e50a6847..b57844a4 100644
--- a/src/stdlib/bigint.h
+++ b/src/stdlib/bigint.h
@@ -24,7 +24,7 @@ Text_t Int$octal(Int_t i, Int_t digits, bool prefix);
PUREFUNC Closure_t Int$to(Int_t first, Int_t last, OptionalInt_t step);
PUREFUNC Closure_t Int$onward(Int_t first, Int_t step);
OptionalInt_t Int$from_str(const char *str);
-OptionalInt_t Int$parse(Text_t text, Text_t *remainder);
+OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder);
Int_t Int$abs(Int_t x);
Int_t Int$power(Int_t base, Int_t exponent);
Int_t Int$gcd(Int_t x, Int_t y);
diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c
index ab689ae4..4416d804 100644
--- a/src/stdlib/bytes.c
+++ b/src/stdlib/bytes.c
@@ -33,8 +33,8 @@ public
CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { return low <= x && x <= high; }
public
-OptionalByte_t Byte$parse(Text_t text, Text_t *remainder) {
- OptionalInt_t full_int = Int$parse(text, remainder);
+OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
+ OptionalInt_t full_int = Int$parse(text, base, remainder);
if (full_int.small != 0L && Int$compare_value(full_int, I(0)) >= 0 && Int$compare_value(full_int, I(255)) <= 0) {
return (OptionalByte_t){.has_value = true, .value = Byte$from_int(full_int, true)};
} else {
diff --git a/src/stdlib/bytes.h b/src/stdlib/bytes.h
index 2f948177..6581f300 100644
--- a/src/stdlib/bytes.h
+++ b/src/stdlib/bytes.h
@@ -18,7 +18,7 @@ Byte_t Byte$from_int(Int_t i, bool truncate);
Byte_t Byte$from_int64(int64_t i, bool truncate);
Byte_t Byte$from_int32(int32_t i, bool truncate);
Byte_t Byte$from_int16(int16_t i, bool truncate);
-OptionalByte_t Byte$parse(Text_t text, Text_t *remainder);
+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; }
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c
index 8301a2c2..cc2fa0b8 100644
--- a/src/stdlib/cli.c
+++ b/src/stdlib/cli.c
@@ -202,7 +202,7 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
if ((type->tag == TextInfo || type == &CString$info) && arg[0] == '\\' && arg[1] == '-') {
arg = arg + 1;
} else if (arg[0] == '-') {
- print_err("Not a valid argument for flag ", flag, ": ", arg);
+ print_err("Not a valid flag: ", arg);
}
}
@@ -215,7 +215,10 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
return List$from(args, I(2));
} else {
args = parse_arg_list(args, flag, dest, nonnull, allow_dashes);
- if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true;
+ if (nonnull == &Int$info || nonnull == &Path$info || nonnull == &Num$info || nonnull == &Num32$info
+ || nonnull->tag == TextInfo || nonnull->tag == EnumInfo)
+ return args;
+ else if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true;
else if (nonnull == &Int32$info) ((OptionalInt32_t *)dest)->has_value = true;
else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true;
else if (nonnull == &Int8$info) ((OptionalInt8_t *)dest)->has_value = true;
@@ -235,23 +238,23 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
if (parsed.small == 0) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int_t *)dest = parsed;
} else if (type == &Int64$info) {
- OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NULL);
+ OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int64_t *)dest = parsed.value;
} else if (type == &Int32$info) {
- OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NULL);
+ OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int32_t *)dest = parsed.value;
} else if (type == &Int16$info) {
- OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NULL);
+ OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int16_t *)dest = parsed.value;
} else if (type == &Int8$info) {
- OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NULL);
+ OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int8_t *)dest = parsed.value;
} else if (type == &Byte$info) {
- OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NULL);
+ OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Byte_t *)dest = parsed.value;
} else if (type == &Bool$info) {
@@ -322,7 +325,8 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
// Case: --flag values...
if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: ", flag);
List_t values = List$slice(*args, I(i + 2), I(-1));
- *args = parse_arg_list(values, flag, dest, type, false);
+ List_t remaining_args = parse_arg_list(values, flag, dest, type, false);
+ *args = List$concat(List$to(*args, I(i)), remaining_args, sizeof(const char *));
return true;
} else if (starts_with(arg + 2, flag) && arg[2 + strlen(flag)] == '=') {
// Case: --flag=...
@@ -338,7 +342,8 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
} else {
values = List(arg_value);
}
- *args = parse_arg_list(values, flag, dest, type, false);
+ List_t remaining_args = parse_arg_list(values, flag, dest, type, false);
+ *args = List$concat(List$to(*args, I(i)), remaining_args, sizeof(const char *));
return true;
}
} else if (short_flag && arg[0] == '-' && arg[1] != '-' && strchr(arg + 1, short_flag)) {
@@ -397,7 +402,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
List_t texts = Text$split(Text$from_str(arg_value), Text(","));
values = EMPTY_LIST;
for (int64_t j = 0; j < (int64_t)texts.length; j++)
- List$insert_value(&texts, Text$as_c_string(*(Text_t *)(texts.data + j * texts.stride)), I(0),
+ List$insert_value(&values, Text$as_c_string(*(Text_t *)(texts.data + j * texts.stride)), I(0),
sizeof(const char *));
} else {
// Case: -fVALUE
diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h
index c177a0a5..7c829cac 100644
--- a/src/stdlib/datatypes.h
+++ b/src/stdlib/datatypes.h
@@ -86,13 +86,18 @@ typedef struct table_s {
struct table_s *fallback;
} Table_t;
-typedef struct Empty$$struct {
-} Empty$$type;
+typedef struct Present$$struct {
+} Present$$type;
+
+#define PRESENT_STRUCT ((Present$$type){})
typedef struct {
+ Present$$type value;
bool has_value;
- Empty$$type value;
-} $OptionalEmpty$$type;
+} $OptionalPresent$$type;
+
+#define NONE_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = false})
+#define OPTIONAL_PRESENT_STRUCT (($OptionalPresent$$type){.has_value = true})
typedef struct {
void *fn, *userdata;
@@ -123,15 +128,82 @@ typedef struct Text_s {
};
} Text_t;
-typedef enum PathEnum { PATHTYPE_NONE, PATHTYPE_RELATIVE, PATHTYPE_ABSOLUTE, PATHTYPE_HOME } PathType_t;
-#define OptionalPathType_t PathType_t
+typedef struct Path$AbsolutePath$$struct {
+ List_t components;
+} Path$AbsolutePath$$type;
+
+typedef struct {
+ Path$AbsolutePath$$type value;
+ bool has_value;
+} $OptionalPath$AbsolutePath$$type;
+
+typedef struct Path$RelativePath$$struct {
+ List_t components;
+} Path$RelativePath$$type;
typedef struct {
- PathType_t type;
+ Path$RelativePath$$type value;
+ bool has_value;
+} $OptionalPath$RelativePath$$type;
+
+typedef struct Path$HomePath$$struct {
List_t components;
+} Path$HomePath$$type;
+
+typedef struct {
+ Path$HomePath$$type value;
+ bool has_value;
+} $OptionalPath$HomePath$$type;
+
+#define Path$tagged$AbsolutePath(comps) ((Path_t){.$tag = Path$tag$AbsolutePath, .AbsolutePath.components = comps})
+#define Path$tagged$RelativePath(comps) ((Path_t){.$tag = Path$tag$RelativePath, .RelativePath.components = comps})
+#define Path$tagged$HomePath(comps) ((Path_t){.$tag = Path$tag$HomePath, .HomePath.components = comps})
+
+typedef struct {
+ enum { Path$tag$none, Path$tag$AbsolutePath, Path$tag$RelativePath, Path$tag$HomePath } $tag;
+ union {
+ Path$RelativePath$$type RelativePath;
+ Path$AbsolutePath$$type AbsolutePath;
+ Path$HomePath$$type HomePath;
+ List_t components;
+ };
} Path_t;
+
+#define $OptionalPath$$type Path_t
#define OptionalPath_t Path_t
+typedef struct Result$Success$$struct {
+} Result$Success$$type;
+
+typedef struct {
+ Result$Success$$type value;
+ bool has_value;
+} $OptionalResult$Success$$type;
+
+typedef struct Result$Failure$$struct {
+ Text_t reason;
+} Result$Failure$$type;
+
+typedef struct {
+ Result$Failure$$type value;
+ bool has_value;
+} $OptionalResult$Failure$$type;
+
+#define Result$Success ((Result$$type){.$tag = Result$tag$Success})
+#define SuccessResult Result$Success
+#define Result$tagged$Failure(msg) ((Result$$type){.$tag = Result$tag$Failure, .Failure.reason = msg})
+#define FailureResult(...) Result$tagged$Failure(Texts(__VA_ARGS__))
+
+typedef struct Result$$struct {
+ enum { Result$tag$none, Result$tag$Success, Result$tag$Failure } $tag;
+ union {
+ Result$Success$$type Success;
+ Result$Failure$$type Failure;
+ };
+} Result$$type;
+
+#define Result_t Result$$type
+
#define OptionalBool_t uint8_t
#define OptionalList_t List_t
#define OptionalTable_t Table_t
diff --git a/src/stdlib/floatX.c.h b/src/stdlib/floatX.c.h
index 0961631c..54477ee3 100644
--- a/src/stdlib/floatX.c.h
+++ b/src/stdlib/floatX.c.h
@@ -67,6 +67,7 @@ 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); }
+public
PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo_t *info) {
(void)info;
if (!x) return Text(TYPE_STR);
diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h
index 0e665591..0910c7f1 100644
--- a/src/stdlib/intX.c.h
+++ b/src/stdlib/intX.c.h
@@ -188,8 +188,8 @@ Closure_t NAMESPACED(onward)(INT_T first, INT_T step) {
return (Closure_t){.fn = _next_int, .userdata = range};
}
public
-PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder) {
- OptionalInt_t full_int = Int$parse(text, remainder);
+PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder) {
+ OptionalInt_t full_int = Int$parse(text, base, remainder);
if (full_int.small == 0L) return (OPT_T){.has_value = false};
if (Int$compare_value(full_int, I(NAMESPACED(min))) < 0) {
return (OPT_T){.has_value = false};
diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h
index 03aa7247..4d8f8e3d 100644
--- a/src/stdlib/intX.h
+++ b/src/stdlib/intX.h
@@ -46,7 +46,7 @@ List_t NAMESPACED(bits)(INTX_T x);
bool NAMESPACED(get_bit)(INTX_T x, Int_t bit_index);
Closure_t NAMESPACED(to)(INTX_T first, INTX_T last, OPT_T step);
Closure_t NAMESPACED(onward)(INTX_T first, INTX_T step);
-PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder);
+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; }
diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c
index 2ae47c36..fd396463 100644
--- a/src/stdlib/memory.c
+++ b/src/stdlib/memory.c
@@ -17,7 +17,7 @@ public
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, ">"));
+ Text_t text = Text$from_str(String("Memory<", (void *)p, ">"));
return colorize ? Texts(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text;
}
diff --git a/src/stdlib/metamethods.c b/src/stdlib/metamethods.c
index 3eff2dd3..70b8e4e1 100644
--- a/src/stdlib/metamethods.c
+++ b/src/stdlib/metamethods.c
@@ -85,12 +85,6 @@ void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type) {
fclose(input);
}
-public
-int generic_print(const void *obj, bool colorize, const TypeInfo_t *type) {
- Text_t text = generic_as_text(obj, colorize, type);
- return Text$print(stdout, text) + fputc('\n', stdout);
-}
-
__attribute__((noreturn)) public
void cannot_serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) {
(void)obj, (void)out, (void)pointers;
diff --git a/src/stdlib/metamethods.h b/src/stdlib/metamethods.h
index 05d91c5c..7db041e7 100644
--- a/src/stdlib/metamethods.h
+++ b/src/stdlib/metamethods.h
@@ -16,6 +16,5 @@ void _serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t
List_t generic_serialize(const void *x, const TypeInfo_t *type);
void _deserialize(FILE *input, void *outval, List_t *pointers, const TypeInfo_t *type);
void generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type);
-int generic_print(const void *obj, bool colorize, const TypeInfo_t *type);
void cannot_serialize(const void *, FILE *, Table_t *, const TypeInfo_t *type);
void cannot_deserialize(FILE *, void *, List_t *, const TypeInfo_t *type);
diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h
index d067ec94..700a4ada 100644
--- a/src/stdlib/optionals.h
+++ b/src/stdlib/optionals.h
@@ -15,7 +15,7 @@
#define NONE_TABLE ((OptionalTable_t){.entries.data = NULL})
#define NONE_CLOSURE ((OptionalClosure_t){.fn = NULL})
#define NONE_TEXT ((OptionalText_t){.tag = TEXT_NONE})
-#define NONE_PATH ((Path_t){.type = PATHTYPE_NONE})
+#define NONE_PATH ((OptionalPath_t){.$tag = Path$tag$none})
PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type);
PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type);
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index 810f98b1..ed8383fd 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -29,11 +29,8 @@
#include "types.h"
#include "util.h"
-// Use inline version of the siphash code for performance:
-#include "siphash-internals.h"
-
-static const Path_t HOME_PATH = {.type = PATHTYPE_HOME}, ROOT_PATH = {.type = PATHTYPE_ABSOLUTE},
- CURDIR_PATH = {.type = PATHTYPE_RELATIVE};
+static const Path_t HOME_PATH = Path$tagged$HomePath(EMPTY_LIST), ROOT_PATH = Path$tagged$AbsolutePath(EMPTY_LIST),
+ CURDIR_PATH = Path$tagged$RelativePath(EMPTY_LIST);
static void clean_components(List_t *components) {
for (int64_t i = 0; i < (int64_t)components->length;) {
@@ -62,40 +59,41 @@ Path_t Path$from_str(const char *str) {
if (strchr(str, ';') != NULL) fail("Path has illegal character (semicolon): ", str);
- Path_t result = {.components = {}};
+ Path_t result = {};
if (str[0] == '/') {
- result.type = PATHTYPE_ABSOLUTE;
+ result.$tag = Path$tag$AbsolutePath;
str += 1;
} else if (str[0] == '~' && str[1] == '/') {
- result.type = PATHTYPE_HOME;
+ result.$tag = Path$tag$HomePath;
str += 2;
} else if (str[0] == '.' && str[1] == '/') {
- result.type = PATHTYPE_RELATIVE;
+ result.$tag = Path$tag$RelativePath;
str += 2;
} else {
- result.type = PATHTYPE_RELATIVE;
+ result.$tag = Path$tag$RelativePath;
}
+ List_t components = EMPTY_LIST;
while (str && *str) {
size_t component_len = strcspn(str, "/");
if (component_len > 0) {
if (component_len == 1 && str[0] == '.') {
// ignore /./
- } else if (component_len == 2 && strncmp(str, "..", 2) == 0 && result.components.length > 1
+ } else if (component_len == 2 && strncmp(str, "..", 2) == 0 && components.length > 1
&& !Text$equal_values(
Text(".."),
- *(Text_t *)(result.components.data
- + result.components.stride * ((int64_t)result.components.length - 1)))) {
+ *(Text_t *)(components.data + components.stride * ((int64_t)components.length - 1)))) {
// Pop off /foo/baz/.. -> /foo
- List$remove_at(&result.components, I((int64_t)result.components.length), I(1), sizeof(Text_t));
+ List$remove_at(&components, I((int64_t)components.length), I(1), sizeof(Text_t));
} else {
Text_t component = Text$from_strn(str, component_len);
- List$insert_value(&result.components, component, I(0), sizeof(Text_t));
+ List$insert_value(&components, component, I(0), sizeof(Text_t));
}
str += component_len;
}
str += strspn(str, "/");
}
+ result.components = components;
return result;
}
@@ -104,12 +102,12 @@ 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) {
- if (path.type == PATHTYPE_HOME) {
+ if (path.$tag == Path$tag$HomePath) {
Path_t pwd = Path$from_str(getenv("HOME"));
- List_t components = List$concat(pwd.components, path.components, sizeof(Text_t));
- assert(components.length == path.components.length + pwd.components.length);
+ List_t components = List$concat(pwd.AbsolutePath.components, path.HomePath.components, sizeof(Text_t));
+ assert(components.length == path.HomePath.components.length + pwd.AbsolutePath.components.length);
clean_components(&components);
- path = (Path_t){.type = PATHTYPE_ABSOLUTE, .components = components};
+ path = Path$tagged$AbsolutePath(components);
}
return path;
}
@@ -120,7 +118,7 @@ Path_t Path$_concat(int n, Path_t items[n]) {
Path_t result = items[0];
LIST_INCREF(result.components);
for (int i = 1; i < n; i++) {
- if (items[i].type != PATHTYPE_RELATIVE)
+ if (items[i].$tag != Path$tag$RelativePath)
fail("Cannot concatenate an absolute or home-based path onto another path: (", items[i], ")");
List$insert_all(&result.components, items[i].components, I(0), sizeof(Text_t));
}
@@ -130,10 +128,14 @@ Path_t Path$_concat(int n, Path_t items[n]) {
public
Path_t Path$resolved(Path_t path, Path_t relative_to) {
- if (path.type == PATHTYPE_RELATIVE
- && !(relative_to.type == PATHTYPE_RELATIVE && relative_to.components.length == 0)) {
- Path_t result = {.type = relative_to.type};
- result.components = relative_to.components;
+ if (path.$tag == Path$tag$HomePath) {
+ return Path$expand_home(path);
+ } else if (path.$tag == Path$tag$RelativePath
+ && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) {
+ Path_t result = {
+ .$tag = relative_to.$tag,
+ .components = relative_to.components,
+ };
LIST_INCREF(result.components);
List$insert_all(&result.components, path.components, I(0), sizeof(Text_t));
clean_components(&result.components);
@@ -144,16 +146,18 @@ Path_t Path$resolved(Path_t path, Path_t relative_to) {
public
Path_t Path$relative_to(Path_t path, Path_t relative_to) {
- if (path.type != relative_to.type)
- fail("Cannot create a path relative to a different path with a mismatching type: (", path, ") relative to (",
- relative_to, ")");
+ if (path.$tag != relative_to.$tag) {
+ path = Path$resolved(path, Path$current_dir());
+ relative_to = Path$resolved(relative_to, Path$current_dir());
+ }
- Path_t result = {.type = PATHTYPE_RELATIVE};
+ Path_t result = Path$tagged$RelativePath(EMPTY_LIST);
int64_t shared = 0;
- for (; shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length; shared++) {
+ while (shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length) {
Text_t *p = (Text_t *)(path.components.data + shared * path.components.stride);
Text_t *r = (Text_t *)(relative_to.components.data + shared * relative_to.components.stride);
if (!Text$equal_values(*p, *r)) break;
+ shared += 1;
}
for (int64_t i = shared; i < (int64_t)relative_to.components.length; i++)
@@ -163,7 +167,6 @@ Path_t Path$relative_to(Path_t path, Path_t relative_to) {
Text_t *p = (Text_t *)(path.components.data + i * path.components.stride);
List$insert(&result.components, p, I(0), sizeof(Text_t));
}
- // clean_components(&result.components);
return result;
}
@@ -277,7 +280,7 @@ OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks) {
return (OptionalInt64_t){.value = (int64_t)sb.st_ctime};
}
-static void _write(Path_t path, List_t bytes, int mode, int permissions) {
+static Result_t _write(Path_t path, List_t bytes, int mode, int permissions) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
int fd = open(path_str, mode, permissions);
@@ -287,36 +290,38 @@ static void _write(Path_t path, List_t bytes, int mode, int permissions) {
// be closed by GC finalizers.
GC_gcollect();
fd = open(path_str, mode, permissions);
- if (fd == -1) fail("Could not write to file: ", path_str, "\n", strerror(errno));
}
+ if (fd == -1) return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")");
}
if (bytes.stride != 1) List$compact(&bytes, 1);
ssize_t written = write(fd, bytes.data, (size_t)bytes.length);
- if (written != (ssize_t)bytes.length) fail("Could not write to file: ", path_str, "\n", strerror(errno));
+ if (written != (ssize_t)bytes.length)
+ return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")");
close(fd);
+ return SuccessResult;
}
public
-void Path$write(Path_t path, Text_t text, int permissions) {
+Result_t Path$write(Path_t path, Text_t text, int permissions) {
List_t bytes = Text$utf8(text);
- _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
+ return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
}
public
-void Path$write_bytes(Path_t path, List_t bytes, int permissions) {
- _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
+Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions) {
+ return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
}
public
-void Path$append(Path_t path, Text_t text, int permissions) {
+Result_t Path$append(Path_t path, Text_t text, int permissions) {
List_t bytes = Text$utf8(text);
- _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
+ return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
public
-void Path$append_bytes(Path_t path, List_t bytes, int permissions) {
- _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
+Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) {
+ return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
public
@@ -347,8 +352,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
memcpy(content, mem, (size_t)sb.st_size);
content[sb.st_size] = '\0';
close(fd);
- if (count.small && (int64_t)sb.st_size < target_count)
- fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)sb.st_size, ")");
+ if (count.small && (int64_t)sb.st_size < target_count) return NONE_LIST;
int64_t len = count.small ? target_count : (int64_t)sb.st_size;
return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len};
} else {
@@ -376,8 +380,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
len += (size_t)just_read;
}
close(fd);
- if (count.small != 0 && (int64_t)len < target_count)
- fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)len, ")");
+ if (count.small != 0 && (int64_t)len < target_count) return NONE_LIST;
return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len};
}
}
@@ -408,23 +411,24 @@ OptionalText_t Path$group(Path_t path, bool follow_symlinks) {
}
public
-void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) {
+Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) {
uid_t owner_id = (uid_t)-1;
if (owner.tag == TEXT_NONE) {
struct passwd *pwd = getpwnam(Text$as_c_string(owner));
- if (pwd == NULL) fail("Not a valid user: ", owner);
+ if (pwd == NULL) return FailureResult("Not a valid user: ", owner);
owner_id = pwd->pw_uid;
}
gid_t group_id = (gid_t)-1;
if (group.tag == TEXT_NONE) {
struct group *grp = getgrnam(Text$as_c_string(group));
- if (grp == NULL) fail("Not a valid group: ", group);
+ if (grp == NULL) return FailureResult("Not a valid group: ", group);
group_id = grp->gr_gid;
}
const char *path_str = Path$as_c_string(path);
int result = follow_symlinks ? chown(path_str, owner_id, group_id) : lchown(path_str, owner_id, group_id);
- if (result < 0) fail("Could not set owner!");
+ if (result < 0) return FailureResult("Could not set owner!");
+ return SuccessResult;
}
static int _remove_files(const char *path, const struct stat *sbuf, int type, struct FTW *ftwb) {
@@ -446,43 +450,53 @@ static int _remove_files(const char *path, const struct stat *sbuf, int type, st
}
public
-void Path$remove(Path_t path, bool ignore_missing) {
+Result_t Path$remove(Path_t path, bool ignore_missing) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
struct stat sb;
if (lstat(path_str, &sb) != 0) {
- if (!ignore_missing) fail("Could not remove file: ", path_str, " (", strerror(errno), ")");
- return;
+ if (!ignore_missing) return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")");
+ return SuccessResult;
}
if ((sb.st_mode & S_IFMT) == S_IFREG || (sb.st_mode & S_IFMT) == S_IFLNK) {
if (unlink(path_str) != 0 && !ignore_missing)
- fail("Could not remove file: ", path_str, " (", strerror(errno), ")");
+ return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")");
} else if ((sb.st_mode & S_IFMT) == S_IFDIR) {
const int num_open_fd = 10;
if (nftw(path_str, _remove_files, num_open_fd, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0)
- fail("Could not remove directory: %s (%s)", path_str, strerror(errno));
+ return FailureResult("Could not remove directory: ", path_str, " (", strerror(errno), ")");
} else {
- fail("Could not remove path: ", path_str, " (not a file or directory)");
+ return FailureResult("Could not remove path: ", path_str, " (not a file or directory)");
}
+ return SuccessResult;
}
public
-void Path$create_directory(Path_t path, int permissions) {
+Result_t Path$create_directory(Path_t path, int permissions, bool recursive) {
+retry:
path = Path$expand_home(path);
const char *c_path = Path$as_c_string(path);
int status = mkdir(c_path, (mode_t)permissions);
- if (status != 0 && errno != EEXIST) fail("Could not create directory: ", c_path, " (", strerror(errno), ")");
+ if (status != 0) {
+ if (recursive && errno == ENOENT) {
+ Path$create_directory(Path$parent(path), permissions, recursive);
+ goto retry;
+ } else if (errno != EEXIST) {
+ return FailureResult("Could not create directory: ", c_path, " (", strerror(errno), ")");
+ }
+ }
+ return SuccessResult;
}
-static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) {
+static OptionalList_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) {
path = Path$expand_home(path);
struct dirent *dir;
List_t children = EMPTY_LIST;
const char *path_str = Path$as_c_string(path);
size_t path_len = strlen(path_str);
DIR *d = opendir(path_str);
- if (!d) fail("Could not open directory: ", path, " (", strerror(errno), ")");
+ if (!d) return NONE_LIST;
if (path_str[path_len - 1] == '/') --path_len;
@@ -503,18 +517,22 @@ static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter
}
public
-List_t Path$children(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, (mode_t)-1); }
+OptionalList_t Path$children(Path_t path, bool include_hidden) {
+ return _filtered_children(path, include_hidden, (mode_t)-1);
+}
public
-List_t Path$files(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, S_IFREG); }
+OptionalList_t Path$files(Path_t path, bool include_hidden) {
+ return _filtered_children(path, include_hidden, S_IFREG);
+}
public
-List_t Path$subdirectories(Path_t path, bool include_hidden) {
+OptionalList_t Path$subdirectories(Path_t path, bool include_hidden) {
return _filtered_children(path, include_hidden, S_IFDIR);
}
public
-Path_t Path$unique_directory(Path_t path) {
+OptionalPath_t Path$unique_directory(Path_t path) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
size_t len = strlen(path_str);
@@ -524,12 +542,12 @@ Path_t Path$unique_directory(Path_t path) {
buf[len] = '\0';
if (buf[len - 1] == '/') buf[--len] = '\0';
char *created = mkdtemp(buf);
- if (!created) fail("Failed to create temporary directory: ", path_str, " (", strerror(errno), ")");
+ if (!created) return NONE_PATH;
return Path$from_str(created);
}
public
-Path_t Path$write_unique_bytes(Path_t path, List_t bytes) {
+OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
size_t len = strlen(path_str);
@@ -545,30 +563,30 @@ Path_t Path$write_unique_bytes(Path_t path, List_t bytes) {
++suffixlen;
int fd = mkstemps(buf, suffixlen);
- if (fd == -1) fail("Could not write to unique file: ", buf, "\n", strerror(errno));
+ if (fd == -1) return NONE_PATH;
if (bytes.stride != 1) List$compact(&bytes, 1);
ssize_t written = write(fd, bytes.data, (size_t)bytes.length);
- if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, "\n", strerror(errno));
+ if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, " (", strerror(errno), ")");
close(fd);
return Path$from_str(buf);
}
public
-Path_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
-Path_t Path$parent(Path_t path) {
- if (path.type == PATHTYPE_ABSOLUTE && path.components.length == 0) {
- return path;
+OptionalPath_t Path$parent(Path_t path) {
+ if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) {
+ return NONE_PATH;
} else if (path.components.length > 0
&& !Text$equal_values(
*(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1)),
Text(".."))) {
- return (Path_t){.type = path.type, .components = List$slice(path.components, I(1), I(-2))};
+ return (Path_t){.$tag = path.$tag, .components = List$slice(path.components, I(1), I(-2))};
} else {
- Path_t result = {.type = path.type, .components = path.components};
+ Path_t result = {.$tag = path.$tag, .components = path.components};
LIST_INCREF(result.components);
List$insert_value(&result.components, Text(".."), I(0), sizeof(Text_t));
return result;
@@ -579,8 +597,8 @@ public
PUREFUNC Text_t Path$base_name(Path_t path) {
if (path.components.length >= 1)
return *(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1));
- else if (path.type == PATHTYPE_HOME) return Text("~");
- else if (path.type == PATHTYPE_RELATIVE) return Text(".");
+ else if (path.$tag == Path$tag$HomePath) return Text("~");
+ else if (path.$tag == Path$tag$RelativePath) return Text(".");
else return EMPTY_TEXT;
}
@@ -610,7 +628,7 @@ public
Path_t Path$child(Path_t path, Text_t name) {
if (Text$has(name, Text("/")) || Text$has(name, Text(";"))) fail("Path name has invalid characters: ", name);
Path_t result = {
- .type = path.type,
+ .$tag = path.$tag,
.components = path.components,
};
LIST_INCREF(result.components);
@@ -623,14 +641,13 @@ public
Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); }
public
-Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) {
- if (path.components.length == 0) fail("A path with no components can't have an extension!");
+OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) {
+ if (path.components.length == 0) return NONE_PATH;
- if (Text$has(extension, Text("/")) || Text$has(extension, Text(";")))
- fail("Path extension has invalid characters: ", extension);
+ if (Text$has(extension, Text("/")) || Text$has(extension, Text(";"))) return NONE_PATH;
Path_t result = {
- .type = path.type,
+ .$tag = path.$tag,
.components = path.components,
};
LIST_INCREF(result.components);
@@ -704,6 +721,28 @@ OptionalClosure_t Path$by_line(Path_t path) {
}
public
+OptionalList_t Path$lines(Path_t path) {
+ const char *path_str = Path$as_c_string(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.
+ GC_gcollect();
+ f = fopen(path_str, "r");
+ }
+ }
+
+ if (f == NULL) return NONE_LIST;
+
+ List_t lines = EMPTY_LIST;
+ for (OptionalText_t line; (line = _next_line(&f)).tag != TEXT_NONE;) {
+ List$insert(&lines, &line, I(0), sizeof(line));
+ }
+ return lines;
+}
+
+public
List_t Path$glob(Path_t path) {
glob_t glob_result;
int status = glob(Path$as_c_string(path), GLOB_BRACE | GLOB_TILDE, NULL, &glob_result);
@@ -730,55 +769,19 @@ Path_t Path$current_dir(void) {
}
public
-PUREFUNC uint64_t Path$hash(const void *obj, const TypeInfo_t *type) {
- (void)type;
- Path_t *path = (Path_t *)obj;
- siphash sh;
- siphashinit(&sh, (uint64_t)path->type);
- for (int64_t i = 0; i < (int64_t)path->components.length; i++) {
- uint64_t item_hash = Text$hash(path->components.data + i * path->components.stride, &Text$info);
- siphashadd64bits(&sh, item_hash);
- }
- return siphashfinish_last_part(&sh, (uint64_t)path->components.length);
-}
-
-public
-PUREFUNC int32_t Path$compare(const void *va, const void *vb, const TypeInfo_t *type) {
- (void)type;
- Path_t *a = (Path_t *)va, *b = (Path_t *)vb;
- int diff = ((int)a->type - (int)b->type);
- if (diff != 0) return diff;
- return List$compare(&a->components, &b->components, List$info(&Text$info));
-}
-
-public
-PUREFUNC bool Path$equal(const void *va, const void *vb, const TypeInfo_t *type) {
- (void)type;
- Path_t *a = (Path_t *)va, *b = (Path_t *)vb;
- if (a->type != b->type) return false;
- return List$equal(&a->components, &b->components, List$info(&Text$info));
-}
-
-public
-PUREFUNC bool Path$equal_values(Path_t a, Path_t b) {
- if (a.type != b.type) return false;
- return List$equal(&a.components, &b.components, List$info(&Text$info));
-}
-
-public
int Path$print(FILE *f, Path_t path) {
if (path.components.length == 0) {
- if (path.type == PATHTYPE_ABSOLUTE) return fputs("/", f);
- else if (path.type == PATHTYPE_RELATIVE) return fputs(".", f);
- else if (path.type == PATHTYPE_HOME) return fputs("~", f);
+ if (path.$tag == Path$tag$AbsolutePath) return fputs("/", f);
+ else if (path.$tag == Path$tag$RelativePath) return fputs(".", f);
+ else if (path.$tag == Path$tag$HomePath) return fputs("~", f);
}
int n = 0;
- if (path.type == PATHTYPE_ABSOLUTE) {
+ if (path.$tag == Path$tag$AbsolutePath) {
n += fputc('/', f);
- } else if (path.type == PATHTYPE_HOME) {
+ } else if (path.$tag == Path$tag$HomePath) {
n += fputs("~/", f);
- } else if (path.type == PATHTYPE_RELATIVE) {
+ } else if (path.$tag == Path$tag$RelativePath) {
if (!Text$equal_values(*(Text_t *)path.components.data, Text(".."))) n += fputs("./", f);
}
@@ -799,9 +802,9 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
if (!obj) return Text("Path");
Path_t *path = (Path_t *)obj;
Text_t text = Text$join(Text("/"), path->components);
- if (path->type == PATHTYPE_HOME) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text);
- else if (path->type == PATHTYPE_ABSOLUTE) text = Text$concat(Text("/"), text);
- else if (path->type == PATHTYPE_RELATIVE
+ if (path->$tag == Path$tag$HomePath) text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text);
+ else if (path->$tag == Path$tag$AbsolutePath) text = Text$concat(Text("/"), text);
+ else if (path->$tag == Path$tag$RelativePath
&& (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text(".."))))
text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text);
@@ -811,52 +814,80 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
}
public
-CONSTFUNC bool Path$is_none(const void *obj, const TypeInfo_t *type) {
- (void)type;
- return ((Path_t *)obj)->type == PATHTYPE_NONE;
-}
+const TypeInfo_t Path$AbsolutePath$$info = {
+ .size = sizeof(Path$AbsolutePath$$type),
+ .align = __alignof__(Path$AbsolutePath$$type),
+ .metamethods = Struct$metamethods,
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "AbsolutePath",
+ .num_fields = 1,
+ .fields = (NamedType_t[1]){{
+ .name = "components",
+ .type = List$info(&Text$info),
+ }},
+ },
+};
public
-void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) {
- (void)type;
- Path_t *path = (Path_t *)obj;
- fputc((int)path->type, out);
- List$serialize(&path->components, out, pointers, List$info(&Text$info));
-}
+const TypeInfo_t Path$RelativePath$$info = {
+ .size = sizeof(Path$RelativePath$$type),
+ .align = __alignof__(Path$RelativePath$$type),
+ .metamethods = Struct$metamethods,
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "RelativePath",
+ .num_fields = 1,
+ .fields = (NamedType_t[1]){{
+ .name = "components",
+ .type = List$info(&Text$info),
+ }},
+ },
+};
public
-void Path$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type) {
- (void)type;
- Path_t path = {};
- path.type = fgetc(in);
- List$deserialize(in, &path.components, pointers, List$info(&Text$info));
- *(Path_t *)obj = path;
-}
-
-public
-const TypeInfo_t Path$info = {.size = sizeof(Path_t),
- .align = __alignof__(Path_t),
- .tag = OpaqueInfo,
- .metamethods = {
- .as_text = Path$as_text,
- .hash = Path$hash,
- .compare = Path$compare,
- .equal = Path$equal,
- .is_none = Path$is_none,
- .serialize = Path$serialize,
- .deserialize = Path$deserialize,
- }};
-
-public
-const TypeInfo_t PathType$info = {
- .size = sizeof(PathType_t),
- .align = __alignof__(PathType_t),
- .metamethods = PackedDataEnum$metamethods,
+const TypeInfo_t Path$HomePath$$info = {
+ .size = sizeof(Path$HomePath$$type),
+ .align = __alignof__(Path$HomePath$$type),
+ .metamethods = Struct$metamethods,
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "HomePath",
+ .num_fields = 1,
+ .fields = (NamedType_t[1]){{
+ .name = "components",
+ .type = List$info(&Text$info),
+ }},
+ },
+};
+
+public
+const TypeInfo_t Path$info = {
+ .size = sizeof(Path_t),
+ .align = __alignof__(Path_t),
.tag = EnumInfo,
.EnumInfo =
{
- .name = "PathType",
+ .name = "Path",
.num_tags = 3,
- .tags = ((NamedType_t[3]){{.name = "Relative"}, {.name = "Absolute"}, {.name = "Home"}}),
+ .tags =
+ (NamedType_t[3]){
+ {.name = "AbsolutePath", &Path$AbsolutePath$$info},
+ {.name = "RelativePath", &Path$RelativePath$$info},
+ {.name = "HomePath", &Path$HomePath$$info},
+ },
+ },
+ .metamethods =
+ {
+ .as_text = Path$as_text,
+ .compare = Enum$compare,
+ .equal = Enum$equal,
+ .hash = Enum$hash,
+ .is_none = Enum$is_none,
+ .serialize = Enum$serialize,
+ .deserialize = Enum$deserialize,
},
};
diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h
index ce6de1c8..881a3c78 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -11,7 +11,11 @@
Path_t Path$from_str(const char *str);
Path_t Path$from_text(Text_t text);
+// This function is defined as an extern in `src/stdlib/print.h`
// int Path$print(FILE *f, Path_t path);
+// UNSAFE: this works because each type of path has a .components in the same place
+#define Path$components(path) ((path).components)
+// END UNSAFE
const char *Path$as_c_string(Path_t path);
#define Path(str) Path$from_str(str)
Path_t Path$_concat(int n, Path_t items[n]);
@@ -31,24 +35,24 @@ bool Path$can_execute(Path_t path);
OptionalInt64_t Path$modified(Path_t path, bool follow_symlinks);
OptionalInt64_t Path$accessed(Path_t path, bool follow_symlinks);
OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks);
-void Path$write(Path_t path, Text_t text, int permissions);
-void Path$write_bytes(Path_t path, List_t bytes, int permissions);
-void Path$append(Path_t path, Text_t text, int permissions);
-void Path$append_bytes(Path_t path, List_t bytes, int permissions);
+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);
OptionalText_t Path$read(Path_t path);
OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t limit);
-void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks);
+Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks);
OptionalText_t Path$owner(Path_t path, bool follow_symlinks);
OptionalText_t Path$group(Path_t path, bool follow_symlinks);
-void Path$remove(Path_t path, bool ignore_missing);
-void Path$create_directory(Path_t path, int permissions);
+Result_t Path$remove(Path_t path, bool ignore_missing);
+Result_t Path$create_directory(Path_t path, int permissions, bool recursive);
List_t Path$children(Path_t path, bool include_hidden);
List_t Path$files(Path_t path, bool include_hidden);
List_t Path$subdirectories(Path_t path, bool include_hidden);
-Path_t Path$unique_directory(Path_t path);
-Path_t Path$write_unique(Path_t path, Text_t text);
-Path_t Path$write_unique_bytes(Path_t path, List_t bytes);
-Path_t Path$parent(Path_t path);
+OptionalPath_t Path$unique_directory(Path_t path);
+OptionalPath_t Path$write_unique(Path_t path, Text_t text);
+OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes);
+OptionalPath_t Path$parent(Path_t path);
Text_t Path$base_name(Path_t path);
Text_t Path$extension(Path_t path, bool full);
bool Path$has_extension(Path_t path, Text_t extension);
@@ -57,6 +61,7 @@ Path_t Path$sibling(Path_t path, Text_t name);
Path_t Path$with_extension(Path_t path, Text_t extension, bool replace);
Path_t Path$current_dir(void);
Closure_t Path$by_line(Path_t path);
+OptionalList_t Path$lines(Path_t path);
List_t Path$glob(Path_t path);
uint64_t Path$hash(const void *obj, const TypeInfo_t *);
@@ -68,5 +73,7 @@ bool Path$is_none(const void *obj, const TypeInfo_t *type);
void Path$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type);
void Path$deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type);
+extern const TypeInfo_t Path$AbsolutePath$$info;
+extern const TypeInfo_t Path$RelativePath$$info;
+extern const TypeInfo_t Path$HomePath$$info;
extern const TypeInfo_t Path$info;
-extern const TypeInfo_t PathType$info;
diff --git a/src/stdlib/print.c b/src/stdlib/print.c
index ef570f94..7da1343f 100644
--- a/src/stdlib/print.c
+++ b/src/stdlib/print.c
@@ -43,6 +43,7 @@ int _print_hex(FILE *f, hex_format_t hex) {
int printed = 0;
if (!hex.no_prefix) printed += fputs("0x", f);
if (hex.digits > 0) {
+ hex.digits -= (hex.n == 0); // Don't need a leading zero for a zero
for (uint64_t n = hex.n; n > 0 && hex.digits > 0; n /= 16) {
hex.digits -= 1;
}
@@ -68,6 +69,7 @@ int _print_oct(FILE *f, oct_format_t oct) {
int printed = 0;
if (!oct.no_prefix) printed += fputs("0o", f);
if (oct.digits > 0) {
+ oct.digits -= (oct.n == 0); // Don't need a leading zero for a zero
for (uint64_t n = oct.n; n > 0 && oct.digits > 0; n /= 8)
oct.digits -= 1;
for (; oct.digits > 0; oct.digits -= 1)
diff --git a/src/stdlib/result.c b/src/stdlib/result.c
new file mode 100644
index 00000000..8fd2ca1e
--- /dev/null
+++ b/src/stdlib/result.c
@@ -0,0 +1,65 @@
+// Result (Success/Failure) type info
+#include <err.h>
+#include <gc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/param.h>
+
+#include "enums.h"
+#include "structs.h"
+#include "text.h"
+#include "util.h"
+
+public
+const TypeInfo_t Result$Success$$info = {
+ .size = sizeof(Result$Success$$type),
+ .align = __alignof__(Result$Success$$type),
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "Success",
+ .num_fields = 0,
+ },
+ .metamethods = Struct$metamethods,
+};
+
+public
+const TypeInfo_t Result$Failure$$info = {
+ .size = sizeof(Result$Failure$$type),
+ .align = __alignof__(Result$Failure$$type),
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "Failure",
+ .num_fields = 1,
+ .fields =
+ (NamedType_t[1]){
+ {.name = "reason", .type = &Text$info},
+ },
+ },
+ .metamethods = Struct$metamethods,
+};
+
+public
+const TypeInfo_t Result$$info = {
+ .size = sizeof(Result_t),
+ .align = __alignof__(Result_t),
+ .tag = EnumInfo,
+ .EnumInfo =
+ {
+ .name = "Result",
+ .num_tags = 2,
+ .tags =
+ (NamedType_t[2]){
+ {
+ .name = "Success",
+ .type = &Result$Success$$info,
+ },
+ {
+ .name = "Failure",
+ .type = &Result$Failure$$info,
+ },
+ },
+ },
+ .metamethods = Enum$metamethods,
+};
diff --git a/src/stdlib/result.h b/src/stdlib/result.h
new file mode 100644
index 00000000..328480e7
--- /dev/null
+++ b/src/stdlib/result.h
@@ -0,0 +1,9 @@
+#pragma once
+
+// Result type for Success/Failure
+
+#include "types.h"
+
+extern const TypeInfo_t Result$Success$$info;
+extern const TypeInfo_t Result$Failure$$info;
+extern const TypeInfo_t Result$$info;
diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c
index b21d0cbc..ea939f62 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 c3ea1d36..f4e6d678 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <gc.h>
#include <locale.h>
+#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
@@ -72,6 +73,7 @@ void tomo_init(void) {
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigaction(SIGILL, &sigact, (struct sigaction *)NULL);
+ atexit(tomo_cleanup);
}
public
@@ -145,7 +147,7 @@ void say(Text_t text, bool newline) {
public
_Noreturn void tomo_exit(Text_t text, int32_t status) {
if (text.length > 0) print(text);
- _exit(status);
+ exit(status);
}
public
@@ -204,11 +206,16 @@ cleanup:
}
public
-void sleep_float64(double seconds) {
+void sleep_seconds(double seconds) {
+ if (seconds < 0) fail("Cannot sleep for a negative amount of time: ", seconds);
+ else if (isnan(seconds)) fail("Cannot sleep for a time that is NaN");
struct timespec ts;
ts.tv_sec = (time_t)seconds;
ts.tv_nsec = (long)((seconds - (double)ts.tv_sec) * 1e9);
- nanosleep(&ts, NULL);
+ while (nanosleep(&ts, NULL) != 0) {
+ if (errno == EINTR) continue;
+ fail("Failed to sleep for the requested time (", strerror(errno), ")");
+ }
}
public
@@ -218,4 +225,37 @@ OptionalText_t getenv_text(Text_t name) {
}
public
-void setenv_text(Text_t name, Text_t value) { setenv(Text$as_c_string(name), Text$as_c_string(value), 1); }
+void setenv_text(Text_t name, OptionalText_t value) {
+ int status;
+ if (value.tag == TEXT_NONE) {
+ status = unsetenv(Text$as_c_string(name));
+ } else {
+ status = setenv(Text$as_c_string(name), Text$as_c_string(value), 1);
+ }
+ if (status != 0) {
+ if (errno == EINVAL) fail("Invalid environment variable name: ", Text$quoted(name, false, Text("\"")));
+ else fail("Failed to set environment variable (", strerror(errno));
+ }
+}
+
+typedef struct cleanup_s {
+ Closure_t cleanup_fn;
+ struct cleanup_s *next;
+} cleanup_t;
+
+static cleanup_t *cleanups = NULL;
+
+public
+void tomo_at_cleanup(Closure_t fn) { cleanups = new (cleanup_t, .cleanup_fn = fn, .next = cleanups); }
+
+public
+void tomo_cleanup(void) {
+ while (cleanups) {
+ // NOTE: we *must* remove the cleanup function from the stack before calling it,
+ // otherwise it will cause an infinite loop if the cleanup function fails or exits.
+ void (*run_cleanup)(void *) = cleanups->cleanup_fn.fn;
+ void *userdata = cleanups->cleanup_fn.userdata;
+ cleanups = cleanups->next;
+ run_cleanup(userdata);
+ }
+}
diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h
index e52b5cd1..3afe3529 100644
--- a/src/stdlib/stdlib.h
+++ b/src/stdlib/stdlib.h
@@ -16,9 +16,12 @@ extern bool USE_COLOR;
extern Text_t TOMO_VERSION_TEXT;
void tomo_init(void);
+void tomo_at_cleanup(Closure_t fn);
+void tomo_cleanup(void);
#define fail(...) \
({ \
+ tomo_cleanup(); \
fflush(stdout); \
if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \
else fputs("==================== ERROR ====================\n\n", stderr); \
@@ -30,11 +33,12 @@ void tomo_init(void);
else fputs("\n", stderr); \
fflush(stderr); \
raise(SIGABRT); \
- _exit(1); \
+ exit(1); \
})
#define fail_source(filename, start, end, ...) \
({ \
+ tomo_cleanup(); \
fflush(stdout); \
if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \n\n\x1b[0;1m", stderr); \
else fputs("==================== ERROR ====================\n\n", stderr); \
@@ -50,7 +54,7 @@ void tomo_init(void);
if (USE_COLOR) fputs("\x1b[m", stderr); \
fflush(stderr); \
raise(SIGABRT); \
- _exit(1); \
+ exit(1); \
})
_Noreturn void fail_text(Text_t message);
@@ -75,6 +79,6 @@ Text_t ask(Text_t prompt, bool bold, bool force_tty);
_Noreturn void tomo_exit(Text_t text, int32_t status);
Closure_t spawn(Closure_t fn);
-void sleep_num(double seconds);
+void sleep_seconds(double seconds);
OptionalText_t getenv_text(Text_t name);
void setenv_text(Text_t name, Text_t value);
diff --git a/src/stdlib/structs.c b/src/stdlib/structs.c
index 89b5581b..da8f1461 100644
--- a/src/stdlib/structs.c
+++ b/src/stdlib/structs.c
@@ -215,14 +215,3 @@ void Struct$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo
}
}
}
-
-public
-const TypeInfo_t Empty$$info = {.size = 0,
- .align = 0,
- .tag = StructInfo,
- .metamethods = Struct$metamethods,
- .StructInfo.name = "Empty",
- .StructInfo.num_fields = 0};
-
-public
-const Empty$$type EMPTY = {};
diff --git a/src/stdlib/structs.h b/src/stdlib/structs.h
index 83904377..a582e9fb 100644
--- a/src/stdlib/structs.h
+++ b/src/stdlib/structs.h
@@ -15,8 +15,6 @@ PUREFUNC bool PackedData$equal(const void *x, const void *y, const TypeInfo_t *t
PUREFUNC Text_t Struct$as_text(const void *obj, bool colorize, const TypeInfo_t *type);
void Struct$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type);
void Struct$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *type);
-extern const TypeInfo_t Empty$$info;
-extern const Empty$$type EMPTY;
#define Struct$metamethods \
{ \
diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c
index 6e774c53..a801957f 100644
--- a/src/stdlib/tables.c
+++ b/src/stdlib/tables.c
@@ -18,6 +18,7 @@
#include "metamethods.h"
#include "pointers.h"
#include "siphash.h"
+#include "structs.h"
#include "tables.h"
#include "text.h"
#include "types.h"
@@ -757,3 +758,14 @@ void Table$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_
*(Table_t *)outval = t;
}
+
+public
+const TypeInfo_t Present$$info = {.size = 0,
+ .align = 0,
+ .tag = StructInfo,
+ .metamethods = Struct$metamethods,
+ .StructInfo.name = "Present",
+ .StructInfo.num_fields = 0};
+
+public
+const Present$$type PRESENT = {};
diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h
index cc2b3b91..cf1c3625 100644
--- a/src/stdlib/tables.h
+++ b/src/stdlib/tables.h
@@ -130,6 +130,8 @@ void Table$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_
#define Table$length(t) ((t).entries.length)
+extern const TypeInfo_t Present$$info;
+extern const Present$$type PRESENT;
extern const TypeInfo_t CStrToVoidStarTable;
#define Table$metamethods \
diff --git a/src/stdlib/text.c b/src/stdlib/text.c
index f323d88d..b4b27fed 100644
--- a/src/stdlib/text.c
+++ b/src/stdlib/text.c
@@ -763,8 +763,10 @@ static Text_t Text$from_components(List_t graphemes, Table_t unique_clusters) {
public
OptionalText_t Text$from_strn(const char *str, size_t len) {
int64_t ascii_span = 0;
- for (size_t i = 0; i < len && isascii(str[i]); i++)
+ for (size_t i = 0; i < len && isascii(str[i]); i++) {
ascii_span++;
+ if (str[i] == 0) return NONE_TEXT;
+ }
if (ascii_span == (int64_t)len) { // All ASCII
char *copy = GC_MALLOC_ATOMIC(len);
@@ -786,12 +788,15 @@ OptionalText_t Text$from_strn(const char *str, size_t len) {
uint32_t buf[256];
size_t u32_len = sizeof(buf) / sizeof(buf[0]);
uint32_t *u32s = u8_to_u32(pos, (size_t)(next - pos), buf, &u32_len);
+ if (u32s == NULL) return NONE_TEXT;
uint32_t buf2[256];
size_t u32_normlen = sizeof(buf2) / sizeof(buf2[0]);
uint32_t *u32s_normalized = u32_normalize(UNINORM_NFC, u32s, u32_len, buf2, &u32_normlen);
+ if (u32s_normalized == NULL) return NONE_TEXT;
int32_t g = get_synthetic_grapheme(u32s_normalized, (int64_t)u32_normlen);
+ if (g == 0) return NONE_TEXT;
List$insert(&graphemes, &g, I(0), sizeof(int32_t));
Table$get_or_setdefault(&unique_clusters, int32_t, uint8_t, g, (uint8_t)unique_clusters.entries.length,
Table$info(&Int32$info, &Byte$info));
@@ -1057,8 +1062,8 @@ PUREFUNC public int32_t Text$compare(const void *va, const void *vb, const TypeI
bool _matches(TextIter_t *text_state, TextIter_t *target_state, int64_t pos) {
for (int64_t i = 0; i < (int64_t)target_state->stack[0].text.length; i++) {
int32_t text_i = Text$get_grapheme_fast(text_state, pos + i);
- int32_t prefix_i = Text$get_grapheme_fast(target_state, i);
- if (text_i != prefix_i) return false;
+ int32_t target_i = Text$get_grapheme_fast(target_state, i);
+ if (text_i != target_i) return false;
}
return true;
}
@@ -1107,6 +1112,19 @@ static bool _has_grapheme(TextIter_t *text, int32_t g) {
}
public
+OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start) {
+ if (text.length < target.length) return NONE_INT;
+ if (target.length <= 0) return I(1);
+ TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target);
+ for (int64_t i = Int64$from_int(start, false) - 1; i < text.length - target.length + 1; i++) {
+ if (_matches(&text_state, &target_state, i)) {
+ return Int$from_int64(i + 1);
+ }
+ }
+ return NONE_INT;
+}
+
+public
Text_t Text$trim(Text_t text, Text_t to_trim, bool left, bool right) {
int64_t first = 0;
TextIter_t text_state = NEW_TEXT_ITER_STATE(text), trim_state = NEW_TEXT_ITER_STATE(to_trim);
@@ -1135,6 +1153,7 @@ Text_t Text$translate(Text_t text, Table_t translations) {
struct {
Text_t target, replacement;
} *entry = replacement_list.data + r * replacement_list.stride;
+ if (entry->target.length <= 0) continue;
TextIter_t target_state = NEW_TEXT_ITER_STATE(entry->target);
if (_matches(&text_state, &target_state, i)) {
if (i > span_start) result = concat2(result, Text$slice(text, I(span_start + 1), I(i)));
@@ -1156,6 +1175,7 @@ Text_t Text$translate(Text_t text, Table_t translations) {
public
Text_t Text$replace(Text_t text, Text_t target, Text_t replacement) {
+ if (target.length <= 0) return text;
TextIter_t text_state = NEW_TEXT_ITER_STATE(text), target_state = NEW_TEXT_ITER_STATE(target);
Text_t result = EMPTY_TEXT;
int64_t span_start = 0;
@@ -1605,6 +1625,7 @@ static INLINE const char *codepoint_name(ucs4_t c) {
char *found_name = unicode_character_name(c, name);
if (found_name) return found_name;
const uc_block_t *block = uc_block(c);
+ if (!block) return "???";
assert(block);
return String(block->name, "-", hex(c, .no_prefix = true, .uppercase = true));
}
diff --git a/src/stdlib/text.h b/src/stdlib/text.h
index 37ed056a..8c0c43b3 100644
--- a/src/stdlib/text.h
+++ b/src/stdlib/text.h
@@ -50,6 +50,7 @@ static inline Text_t Text_from_text(Text_t t) { return t; }
Text_t Text$_concat(int n, Text_t items[n]);
#define Text$concat(...) Text$_concat(sizeof((Text_t[]){__VA_ARGS__}) / sizeof(Text_t), (Text_t[]){__VA_ARGS__})
#define Texts(...) Text$concat(MAP_LIST(convert_to_text, __VA_ARGS__))
+// This function is defined as an extern in `src/stdlib/print.h`
// int Text$print(FILE *stream, Text_t t);
Text_t Text$slice(Text_t text, Int_t first_int, Int_t last_int);
Text_t Text$from(Text_t text, Int_t first);
@@ -84,6 +85,7 @@ PUREFUNC bool Text$starts_with(Text_t text, Text_t prefix, Text_t *remainder);
PUREFUNC bool Text$ends_with(Text_t text, Text_t suffix, Text_t *remainder);
Text_t Text$without_prefix(Text_t text, Text_t prefix);
Text_t Text$without_suffix(Text_t text, Text_t suffix);
+OptionalInt_t Text$find(Text_t text, Text_t target, Int_t start);
Text_t Text$replace(Text_t text, Text_t target, Text_t replacement);
Text_t Text$translate(Text_t text, Table_t translations);
PUREFUNC bool Text$has(Text_t text, Text_t target);
diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h
index b6166890..6a9ba621 100644
--- a/src/stdlib/tomo.h
+++ b/src/stdlib/tomo.h
@@ -26,6 +26,7 @@
#include "pointers.h" // IWYU pragma: export
#include "print.h" // IWYU pragma: export
#include "reals.h" // IWYU pragma: export
+#include "result.h" // IWYU pragma: export
#include "siphash.h" // IWYU pragma: export
#include "stacktrace.h" // IWYU pragma: export
#include "structs.h" // IWYU pragma: export
diff --git a/src/tomo.c b/src/tomo.c
index fab725db..04f0289a 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -28,6 +28,7 @@
#include "stdlib/c_strings.h"
#include "stdlib/cli.h"
#include "stdlib/datatypes.h"
+#include "stdlib/enums.h"
#include "stdlib/lists.h"
#include "stdlib/optionals.h"
#include "stdlib/paths.h"
@@ -91,7 +92,7 @@ static OptionalText_t show_codegen = NONE_TEXT,
" -D_BSD_SOURCE"
#endif
" -DGC_THREADS"),
- ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo_" TOMO_VERSION), ldflags = Text(""),
+ ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo@" TOMO_VERSION), ldflags = Text(""),
optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER);
static Text_t config_summary,
@@ -116,6 +117,7 @@ static bool is_stale_for_any(Path_t path, List_t relative_to, bool ignore_missin
static Path_t build_file(Path_t path, const char *extension);
static void wait_for_child_success(pid_t child);
static bool is_config_outdated(Path_t path);
+static Path_t get_exe_path(Path_t path);
typedef struct {
bool h : 1, c : 1, o : 1;
@@ -166,7 +168,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");
@@ -185,7 +187,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]);
@@ -194,8 +196,8 @@ int main(int argc, char *argv[]) {
Text_t usage = Texts("\x1b[33;4;1mUsage:\x1b[m\n"
"\x1b[1mRun a program:\x1b[m tomo file.tm [-- args...]\n"
"\x1b[1mTranspile files:\x1b[m tomo -t file.tm\n"
- "\x1b[1mCompile object file:\x1b[m tomo -c file.tm\n"
- "\x1b[1mCompile executable:\x1b[m tomo -e file.tm\n"
+ "\x1b[1mCompile object file:\x1b[m tomo -c file.tm\n"
+ "\x1b[1mCompile executable:\x1b[m tomo -e file.tm\n"
"\x1b[1mBuild libraries:\x1b[m tomo -L lib...\n"
"\x1b[1mUninstall libraries:\x1b[m tomo -u lib...\n"
"\x1b[1mOther flags:\x1b[m\n"
@@ -217,7 +219,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'}, //
@@ -291,7 +293,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);
}
@@ -368,7 +370,9 @@ int main(int argc, char *argv[]) {
for (int64_t i = 0; i < (int64_t)compile_executables.length; i++) {
Path_t path = *(Path_t *)(compile_executables.data + i * compile_executables.stride);
- Path_t exe_path = Path$with_extension(path, Text(""), true);
+ Path_t exe_path = get_exe_path(path);
+ // Put executable as a sibling to the .tm file instead of in the .build directory
+ exe_path = Path$sibling(path, Path$base_name(exe_path));
pid_t child = fork();
if (child == 0) {
env_t *env = global_env(source_mapping);
@@ -399,7 +403,7 @@ int main(int argc, char *argv[]) {
// 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);
- Path_t exe_path = build_file(Path$with_extension(path, Text(""), true), "");
+ Path_t exe_path = get_exe_path(path);
pid_t child = fork();
if (child == 0) {
env_t *env = global_env(source_mapping);
@@ -418,12 +422,13 @@ int main(int argc, char *argv[]) {
// After parallel compilation, do serial execution:
for (int64_t i = 0; i < (int64_t)run_files.length; i++) {
Path_t path = *(Path_t *)(run_files.data + i * run_files.stride);
- Path_t exe_path = build_file(Path$with_extension(path, Text(""), true), "");
+ Path_t exe_path = get_exe_path(path);
// Don't fork for the last program
pid_t child = i == (int64_t)run_files.length - 1 ? 0 : fork();
if (child == 0) {
const char *prog_args[1 + args.length + 1];
- prog_args[0] = (char *)Path$as_c_string(exe_path);
+ Path_t relative_exe = Path$relative_to(exe_path, Path$current_dir());
+ prog_args[0] = (char *)Path$as_c_string(relative_exe);
for (int64_t j = 0; j < (int64_t)args.length; j++)
prog_args[j + 1] = *(const char **)(args.data + j * args.stride);
prog_args[1 + args.length] = NULL;
@@ -448,6 +453,18 @@ void wait_for_child_success(pid_t child) {
}
}
+Path_t get_exe_path(Path_t path) {
+ ast_t *ast = parse_file(Path$as_c_string(path), NULL);
+ OptionalText_t exe_name = ast_metadata(ast, "EXECUTABLE");
+ if (exe_name.tag == TEXT_NONE) exe_name = Path$base_name(Path$with_extension(path, Text(""), true));
+
+ Path_t build_dir = Path$sibling(path, Text(".build"));
+ if (mkdir(Path$as_c_string(build_dir), 0755) != 0) {
+ if (!Path$is_directory(build_dir, true)) err(1, "Could not make .build directory");
+ }
+ return Path$child(build_dir, exe_name);
+}
+
Path_t build_file(Path_t path, const char *extension) {
Path_t build_dir = Path$sibling(path, Text(".build"));
if (mkdir(Path$as_c_string(build_dir), 0755) != 0) {
@@ -475,7 +492,7 @@ void build_library(Path_t lib_dir) {
FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs),
#ifdef __APPLE__
- " -Wl,-install_name,@rpath/'lib", Path$base_name(lib_dir), SHARED_SUFFIX,
+ " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX,
"'"
#else
" -Wl,-soname,'lib", lib_name, SHARED_SUFFIX,
@@ -493,9 +510,9 @@ void build_library(Path_t lib_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 (!Path$equal_values(lib_dir, dest)) {
+ if (!Enum$equal(&lib_dir, &dest, &Path$info)) {
if (verbose) whisper("Clearing out any pre-existing version of ", lib_name);
xsystem(as_owner, "rm -rf '", dest, "'");
if (verbose) whisper("Moving files to ", dest);
@@ -513,7 +530,7 @@ void install_library(Path_t lib_dir) {
"' "
">/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) {
@@ -674,14 +691,14 @@ 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",
+ 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 "'");
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")));
+ 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};
@@ -841,7 +858,7 @@ void transpile_code(env_t *base_env, Path_t path) {
namespace_name(module_env, module_env->namespace, Text("$initialize")),
"();\n"
"\n",
- compile_cli_arg_call(module_env, main_binding->code, main_binding->type, version),
+ compile_cli_arg_call(module_env, ast, main_binding->code, main_binding->type, version),
"return 0;\n"
"}\n"));
}
@@ -877,10 +894,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t
Path_t manpage_file = build_file(Path$with_extension(path, Text(".1"), true), "");
if (clean_build || !Path$is_file(manpage_file, true) || is_stale(manpage_file, path, true)) {
- Text_t manpage = compile_manpage(Path$base_name(exe_path), NONE_TEXT, NONE_TEXT,
- Match(main_binding->type, FunctionType)->args);
- if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir()));
+ Text_t manpage = compile_manpage(Path$base_name(exe_path), ast, Match(main_binding->type, FunctionType)->args);
Path$write(manpage_file, manpage, 0644);
+ if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir()));
} else {
if (verbose) whisper("Unchanged: ", manpage_file);
}
diff --git a/src/typecheck.c b/src/typecheck.c
index 10cb9cba..9ba7284e 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -14,6 +14,7 @@
#include "naming.h"
#include "parse/files.h"
#include "parse/types.h"
+#include "stdlib/optionals.h"
#include "stdlib/paths.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
@@ -73,7 +74,7 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) {
if (has_stack_memory(key_type))
code_err(key_type_ast, "Tables can't have stack references because the list may outlive the stack frame.");
- type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : EMPTY_TYPE;
+ type_t *val_type = table_type->value ? parse_type_ast(env, table_type->value) : PRESENT_TYPE;
if (!val_type) code_err(ast, "I can't figure out what the value type for this entry is.");
if (table_type->value && has_stack_memory(val_type))
@@ -124,11 +125,6 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) {
Table$str_set(env->types, enum_name, enum_type);
- bool has_any_tags_with_fields = false;
- for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) {
- has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields;
- }
-
for (tag_ast_t *tag_ast = tag_asts; tag_ast; tag_ast = tag_ast->next) {
arg_t *fields = NULL;
for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) {
@@ -150,14 +146,11 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) {
set_binding(ns_env, tag_ast->name, constructor_t, tagged_name);
binding_t binding = {.type = constructor_t, .code = tagged_name};
List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding));
- } else if (has_any_tags_with_fields) { // Empty singleton value:
+ } else { // Empty singleton value:
Text_t code =
Texts("((", namespace_name(env, env->namespace, Texts(enum_name, "$$type")), "){",
namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name)), "})");
set_binding(ns_env, tag_ast->name, enum_type, code);
- } else {
- Text_t code = namespace_name(env, env->namespace, Texts(enum_name, "$tag$", tag_ast->name));
- set_binding(ns_env, tag_ast->name, enum_type, code);
}
Table$str_set(env->types, String(enum_name, "$", tag_ast->name), tag_type);
@@ -231,8 +224,8 @@ static env_t *load_module(env_t *env, ast_t *use_ast) {
case USE_MODULE: {
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)
+ 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)
!= 0) {
if (!try_install_module(mod, true)) code_err(use_ast, "Couldn't find or install library: ", folder);
}
@@ -440,9 +433,7 @@ void bind_statement(env_t *env, ast_t *statement) {
ns_env->current_type = type;
tag_t *tags = NULL;
int64_t next_tag = 1;
- bool has_any_tags_with_fields = false;
for (tag_ast_t *tag_ast = def->tags; tag_ast; tag_ast = tag_ast->next) {
- has_any_tags_with_fields = has_any_tags_with_fields || tag_ast->fields;
arg_t *fields = NULL;
for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) {
type_t *field_t = get_arg_ast_type(env, field_ast);
@@ -495,13 +486,10 @@ void bind_statement(env_t *env, ast_t *statement) {
set_binding(ns_env, tag->name, constructor_t, tagged_name);
binding_t binding = {.type = constructor_t, .code = tagged_name};
List$insert(&ns_env->namespace->constructors, &binding, I(1), sizeof(binding));
- } else if (has_any_tags_with_fields) { // Empty singleton value:
+ } else { // Empty singleton value:
Text_t code = Texts("((", namespace_name(env, env->namespace, Texts(def->name, "$$type")), "){",
namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name)), "})");
set_binding(ns_env, tag->name, type, code);
- } else {
- Text_t code = namespace_name(env, env->namespace, Texts(def->name, "$tag$", tag->name));
- set_binding(ns_env, tag->name, type, code);
}
Table$str_set(env->types, String(def->name, "$", tag->name), tag->type);
}
@@ -754,7 +742,12 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
case NonOptional: {
ast_t *value = Match(ast, NonOptional)->value;
- type_t *t = get_type(env, value);
+ type_t *t = value_type(get_type(env, value));
+ if (t->tag == EnumType) {
+ tag_t *first_tag = Match(t, EnumType)->tags;
+ if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum");
+ return first_tag->type;
+ }
if (t->tag != OptionalType)
code_err(value, "This value is not optional. Only optional values can use the '!' operator.");
return Match(t, OptionalType)->type;
@@ -816,7 +809,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
DeclareMatch(e, entry_ast, TableEntry);
type_t *key_t = get_type(scope, e->key);
- type_t *value_t = e->value ? get_type(scope, e->value) : EMPTY_TYPE;
+ type_t *value_t = e->value ? get_type(scope, e->value) : PRESENT_TYPE;
type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t;
if (!key_merged) ambiguous_key_type = true;
@@ -849,7 +842,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
} else if (comp->expr->tag == TableEntry) {
DeclareMatch(e, comp->expr, TableEntry);
return Type(TableType, .key_type = get_type(scope, e->key),
- .value_type = e->value ? get_type(scope, e->value) : EMPTY_TYPE, .env = env);
+ .value_type = e->value ? get_type(scope, e->value) : PRESENT_TYPE, .env = env);
} else {
return Type(ListType, .item_type = get_type(scope, comp->expr));
}
@@ -971,7 +964,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
else if (streq(call->name, "sorted")) return self_value_t;
else if (streq(call->name, "to")) return self_value_t;
else if (streq(call->name, "unique"))
- return Type(TableType, .key_type = item_type, .value_type = EMPTY_TYPE);
+ return Type(TableType, .key_type = item_type, .value_type = PRESENT_TYPE);
else code_err(ast, "There is no '", call->name, "' method for lists");
}
case TableType: {
@@ -1218,6 +1211,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
binary_operands_t binop = BINARY_OPERANDS(ast);
type_t *lhs_t = get_type(env, binop.lhs);
type_t *rhs_t = get_type(env, binop.rhs);
+ if (type_eq(lhs_t, rhs_t)) return ast->tag == Compare ? Type(IntType, .bits = TYPE_IBITS32) : Type(BoolType);
if ((binop.lhs->tag == Int && is_numeric_type(rhs_t)) || (binop.rhs->tag == Int && is_numeric_type(lhs_t))
|| can_compile_to_type(env, binop.rhs, lhs_t) || can_compile_to_type(env, binop.lhs, rhs_t))
@@ -1376,7 +1370,6 @@ type_t *get_type(env_t *env, ast_t *ast) {
case If: {
DeclareMatch(if_, ast, If);
- if (!if_->else_body) return Type(VoidType);
env_t *truthy_scope = env;
env_t *falsey_scope = env;
@@ -1401,6 +1394,11 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
type_t *true_t = get_type(truthy_scope, if_->body);
+ ast_t *else_body = if_->else_body;
+ if (!else_body) {
+ if (true_t->tag == AbortType) return Type(VoidType);
+ else_body = WrapAST(ast, None, .type = true_t);
+ }
type_t *false_t = get_type(falsey_scope, if_->else_body);
type_t *t_either = type_or_type(true_t, false_t);
if (!t_either)
@@ -1509,6 +1507,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
case Unknown: code_err(ast, "I can't figure out the type of: ", ast_to_sexp_str(ast));
case ExplicitlyTyped: return Match(ast, ExplicitlyTyped)->type;
+ case Metadata: return Type(VoidType);
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
@@ -1527,10 +1526,14 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) {
case StructDef:
case EnumDef:
case LangDef:
- case Use: return true;
+ case Use:
+ 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);
}
@@ -1586,8 +1589,9 @@ bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, call_opts
for (; unused_args; unused_args = unused_args->next) {
if (unused_args->name) continue; // Already handled the keyword args
if (options.promotion) {
- if (!can_compile_to_type(arg_scope, unused_args->value, spec_type))
+ if (!can_compile_to_type(arg_scope, unused_args->value, spec_type)) {
return false; // Positional arg trying to fill in
+ }
} else {
type_t *call_type = get_arg_ast_type(arg_scope, unused_args);
type_t *complete_call_type =
@@ -1651,7 +1655,7 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) {
case None: return true;
case Int: {
DeclareMatch(info, ast, Int);
- Int_t int_val = Int$parse(Text$from_str(info->str), NULL);
+ Int_t int_val = Int$parse(Text$from_str(info->str), NONE_INT, NULL);
if (int_val.small == 0) return false; // Failed to parse
return (Int$compare_value(int_val, I(BIGGEST_SMALL_INT)) <= 0);
}
@@ -1683,7 +1687,8 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) {
default: return is_constant(env, binop.lhs) && is_constant(env, binop.rhs);
}
}
- case Use: return true;
+ case Use:
+ case Metadata: return true;
case FunctionCall: return false;
case InlineCCode: return true;
default: return false;
@@ -1695,40 +1700,44 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) {
if (needed->tag == OptionalType && ast->tag == None) return true;
- type_t *actual = get_type(env, ast);
- if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed);
-
+ env = with_enum_scope(env, needed);
if (is_numeric_type(needed) && ast->tag == Int) return true;
if (needed->tag == FloatType && ast->tag == Num) return true;
- needed = non_optional(needed);
- if (needed->tag == ListType && ast->tag == List) {
- type_t *item_type = Match(needed, ListType)->item_type;
+ type_t *non_optional_needed = non_optional(needed);
+ if (non_optional_needed->tag == ListType && ast->tag == List) {
+ type_t *item_type = Match(non_optional_needed, ListType)->item_type;
for (ast_list_t *item = Match(ast, List)->items; item; item = item->next) {
if (!can_compile_to_type(env, item->ast, item_type)) return false;
}
return true;
- } else if (needed->tag == TableType && ast->tag == Table) {
- type_t *key_type = Match(needed, TableType)->key_type;
- type_t *value_type = Match(needed, TableType)->value_type;
+ } else if (non_optional_needed->tag == TableType && ast->tag == Table) {
+ type_t *key_type = Match(non_optional_needed, TableType)->key_type;
+ type_t *value_type = Match(non_optional_needed, TableType)->value_type;
for (ast_list_t *entry = Match(ast, Table)->entries; entry; entry = entry->next) {
if (entry->ast->tag != TableEntry) continue; // TODO: fix this
DeclareMatch(e, entry->ast, TableEntry);
if (!can_compile_to_type(env, e->key, key_type)
- || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, EMPTY_TYPE)))
+ || !(e->value ? can_compile_to_type(env, e->value, value_type) : type_eq(value_type, PRESENT_TYPE)))
return false;
}
return true;
- } else if (needed->tag == PointerType) {
- DeclareMatch(ptr, needed, PointerType);
+ }
+
+ type_t *actual = get_type(env, ast);
+ if (type_eq(actual, needed)) return true;
+ if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed);
+
+ if (non_optional_needed->tag == PointerType) {
+ DeclareMatch(ptr, non_optional_needed, PointerType);
if (ast->tag == HeapAllocate)
return !ptr->is_stack && can_compile_to_type(env, Match(ast, HeapAllocate)->value, ptr->pointed);
else if (ast->tag == StackReference)
return ptr->is_stack && can_compile_to_type(env, Match(ast, StackReference)->value, ptr->pointed);
- else return can_promote(actual, needed);
- } else if (actual->tag == OptionalType && needed->tag != OptionalType) {
+ else return can_promote(actual, non_optional_needed);
+ } else if (actual->tag == OptionalType && non_optional_needed->tag != OptionalType) {
return false;
} else {
- return can_promote(actual, needed);
+ return can_promote(actual, non_optional_needed);
}
}
diff --git a/src/types.c b/src/types.c
index 492a2a22..aeadbceb 100644
--- a/src/types.c
+++ b/src/types.c
@@ -9,10 +9,20 @@
#include "environment.h"
#include "stdlib/datatypes.h"
#include "stdlib/integers.h"
+#include "stdlib/tables.h"
#include "stdlib/text.h"
#include "stdlib/util.h"
#include "types.h"
+Text_t arg_types_to_text(arg_t *args, const char *separator) {
+ Text_t text = EMPTY_TEXT;
+ for (arg_t *arg = args; arg; arg = arg->next) {
+ text = Texts(text, type_to_text(arg->type));
+ if (arg->next) text = Texts(text, separator);
+ }
+ return text;
+}
+
Text_t type_to_text(type_t *t) {
if (!t) return Text("(Unknown type)");
@@ -39,7 +49,7 @@ Text_t type_to_text(type_t *t) {
}
case TableType: {
DeclareMatch(table, t, TableType);
- return (table->value_type && table->value_type != EMPTY_TYPE)
+ return (table->value_type && table->value_type != PRESENT_TYPE)
? Texts("{", type_to_text(table->key_type), ":", type_to_text(table->value_type), "}")
: Texts("{", type_to_text(table->key_type), "}");
}
@@ -47,19 +57,15 @@ Text_t type_to_text(type_t *t) {
return type_to_text(Match(t, ClosureType)->fn);
}
case FunctionType: {
- Text_t c = Text("func(");
DeclareMatch(fn, t, FunctionType);
- for (arg_t *arg = fn->args; arg; arg = arg->next) {
- c = Texts(c, type_to_text(arg->type));
- if (arg->next) c = Texts(c, ",");
- }
- if (fn->ret && fn->ret->tag != VoidType) c = Texts(c, fn->args ? " -> " : "-> ", type_to_text(fn->ret));
- c = Texts(c, ")");
- return c;
+ Text_t text = Texts("func(", arg_types_to_text(fn->args, ","));
+ if (fn->ret && fn->ret->tag != VoidType) text = Texts(text, fn->args ? " -> " : "-> ", type_to_text(fn->ret));
+ text = Texts(text, ")");
+ return text;
}
case StructType: {
DeclareMatch(struct_, t, StructType);
- return Text$from_str(struct_->name);
+ return Text$replace(Text$from_str(struct_->name), Text("$"), Text("."));
}
case PointerType: {
DeclareMatch(ptr, t, PointerType);
@@ -69,7 +75,7 @@ Text_t type_to_text(type_t *t) {
case EnumType: {
DeclareMatch(enum_, t, EnumType);
if (enum_->name != NULL && strncmp(enum_->name, "enum$", strlen("enum$")) != 0)
- return Text$from_str(enum_->name);
+ return Text$replace(Text$from_str(enum_->name), Text("$"), Text("."));
Text_t text = Text("enum(");
for (tag_t *tag = enum_->tags; tag; tag = tag->next) {
text = Texts(text, Text$from_str(tag->name));
@@ -227,23 +233,59 @@ PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b) {
else return NUM_PRECISION_INCOMPARABLE;
}
-PUREFUNC bool has_heap_memory(type_t *t) {
+bool _has_heap_memory(type_t *t, Table_t *visited) {
+ if (!t) return false;
+ Text_t type_text = type_to_text(t);
+ if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false;
+ Table$set(visited, &type_text, NULL, Set$info(&Text$info));
+
switch (t->tag) {
case ListType: return true;
case TableType: return true;
case PointerType: return true;
- case OptionalType: return has_heap_memory(Match(t, OptionalType)->type);
+ case OptionalType: return _has_heap_memory(Match(t, OptionalType)->type, visited);
case BigIntType: return true;
case RealType: return true;
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
- if (has_heap_memory(field->type)) return true;
+ if (_has_heap_memory(field->type, visited)) return true;
+ }
+ return false;
+ }
+ case EnumType: {
+ for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
+ if (tag->type && _has_heap_memory(tag->type, visited)) return true;
+ }
+ return false;
+ }
+ default: return false;
+ }
+}
+
+bool has_heap_memory(type_t *t) {
+ Table_t visited = EMPTY_TABLE;
+ return _has_heap_memory(t, &visited);
+}
+
+bool _has_refcounts(type_t *t, Table_t *visited) {
+ if (!t) return false;
+ Text_t type_text = type_to_text(t);
+ if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false;
+ Table$set(visited, &type_text, NULL, Set$info(&Text$info));
+
+ switch (t->tag) {
+ case ListType: return true;
+ case TableType: return true;
+ case OptionalType: return _has_refcounts(Match(t, OptionalType)->type, visited);
+ case StructType: {
+ for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
+ if (_has_refcounts(field->type, visited)) return true;
}
return false;
}
case EnumType: {
for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
- if (tag->type && has_heap_memory(tag->type)) return true;
+ if (tag->type && _has_refcounts(tag->type, visited)) return true;
}
return false;
}
@@ -251,15 +293,45 @@ PUREFUNC bool has_heap_memory(type_t *t) {
}
}
-PUREFUNC bool has_stack_memory(type_t *t) {
+bool has_refcounts(type_t *t) {
+ Table_t visited = EMPTY_TABLE;
+ return _has_refcounts(t, &visited);
+}
+
+bool _has_stack_memory(type_t *t, Table_t *visited) {
if (!t) return false;
+ Text_t type_text = type_to_text(t);
+ if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false;
+ Table$set(visited, &type_text, NULL, Set$info(&Text$info));
+
switch (t->tag) {
case PointerType: return Match(t, PointerType)->is_stack;
- case OptionalType: return has_stack_memory(Match(t, OptionalType)->type);
+ case OptionalType: return _has_stack_memory(Match(t, OptionalType)->type, visited);
+ case ListType: return _has_stack_memory(Match(t, ListType)->item_type, visited);
+ case TableType:
+ return _has_stack_memory(Match(t, TableType)->key_type, visited)
+ || _has_stack_memory(Match(t, TableType)->value_type, visited);
+ case StructType: {
+ for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
+ if (_has_stack_memory(field->type, visited)) return true;
+ }
+ return false;
+ }
+ case EnumType: {
+ for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
+ if (tag->type && _has_stack_memory(tag->type, visited)) return true;
+ }
+ return false;
+ }
default: return false;
}
}
+bool has_stack_memory(type_t *t) {
+ Table_t visited = EMPTY_TABLE;
+ return _has_stack_memory(t, &visited);
+}
+
PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t) {
const char *found = NULL;
for (tag_t *tag = Match(enum_type, EnumType)->tags; tag; tag = tag->next) {
@@ -397,15 +469,25 @@ PUREFUNC bool is_packed_data(type_t *t) {
|| t->tag == FunctionType) {
return true;
} else if (t->tag == StructType) {
+ size_t offset = 0;
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
if (!is_packed_data(field->type)) return false;
+ size_t align = type_align(field->type);
+ if (align > 0 && offset % align != 0) return false;
+ offset += type_size(field->type);
}
- return true;
+ size_t overall_align = type_align(t);
+ return overall_align == 0 || (offset % overall_align == 0);
} else if (t->tag == EnumType) {
+ size_t offset = sizeof(int32_t);
for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
if (!is_packed_data(tag->type)) return false;
+ size_t align = type_align(tag->type);
+ if (align > 0 && offset % align != 0) return false;
+ offset += type_size(tag->type);
}
- return true;
+ size_t overall_align = type_align(t);
+ return overall_align == 0 || (offset % overall_align == 0);
} else {
return false;
}
@@ -444,7 +526,6 @@ PUREFUNC size_t unpadded_struct_size(type_t *t) {
PUREFUNC size_t type_size(type_t *t) {
if (t == PATH_TYPE) return sizeof(Path_t);
- if (t == PATH_TYPE_TYPE) return sizeof(PathType_t);
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-default"
@@ -534,7 +615,6 @@ PUREFUNC size_t type_size(type_t *t) {
PUREFUNC size_t type_align(type_t *t) {
if (t == PATH_TYPE) return __alignof__(Path_t);
- if (t == PATH_TYPE_TYPE) return __alignof__(PathType_t);
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-default"
@@ -630,7 +710,8 @@ type_t *get_field_type(type_t *t, const char *field_name) {
case EnumType: {
DeclareMatch(e, t, EnumType);
for (tag_t *tag = e->tags; tag; tag = tag->next) {
- if (streq(field_name, tag->name)) return Type(BoolType);
+ if (!streq(field_name, tag->name)) continue;
+ return Type(OptionalType, tag->type);
}
return NULL;
}
@@ -763,10 +844,3 @@ type_t *_make_function_type(type_t *ret, int n, arg_t args[n]) {
}
return Type(FunctionType, .ret = ret, .args = &arg_pointers[0]);
}
-
-PUREFUNC bool enum_has_fields(type_t *t) {
- for (tag_t *e_tag = Match(t, EnumType)->tags; e_tag; e_tag = e_tag->next) {
- if (e_tag->type != NULL && Match(e_tag->type, StructType)->fields) return true;
- }
- return false;
-}
diff --git a/src/types.h b/src/types.h
index a7a6ffcf..9f468c1e 100644
--- a/src/types.h
+++ b/src/types.h
@@ -140,6 +140,7 @@ struct type_s {
_make_function_type(ret, sizeof((arg_t[]){__VA_ARGS__}) / sizeof(arg_t), (arg_t[]){__VA_ARGS__})
Text_t type_to_text(type_t *t);
+Text_t arg_types_to_text(arg_t *args, const char *separator);
const char *get_type_name(type_t *t);
PUREFUNC bool type_eq(type_t *a, type_t *b);
PUREFUNC bool type_is_a(type_t *t, type_t *req);
@@ -152,8 +153,9 @@ typedef enum {
NUM_PRECISION_INCOMPARABLE
} precision_cmp_e;
PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b);
-PUREFUNC bool has_heap_memory(type_t *t);
-PUREFUNC bool has_stack_memory(type_t *t);
+bool has_heap_memory(type_t *t);
+bool has_refcounts(type_t *t);
+bool has_stack_memory(type_t *t);
PUREFUNC bool can_promote(type_t *actual, type_t *needed);
PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t);
PUREFUNC bool is_int_type(type_t *t);
@@ -168,4 +170,3 @@ PUREFUNC type_t *non_optional(type_t *t);
type_t *get_field_type(type_t *t, const char *field_name);
PUREFUNC type_t *get_iterated_type(type_t *t);
type_t *_make_function_type(type_t *ret, int n, arg_t args[n]);
-PUREFUNC bool enum_has_fields(type_t *t);