diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-12-11 13:50:01 -0500 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-12-11 13:52:46 -0500 |
| commit | 7f8f2117799cdfa6b62909a9182b5adade1d0bd2 (patch) | |
| tree | 1db466db870768e952f50572453660e090e434e0 /src | |
| parent | 630f910563b6f27dd34a4a0496a43d32539eadcb (diff) | |
| parent | 02886fab651d3f64d2c8ded5597e6c075dc69b5f (diff) | |
Merge branch 'dev' into constructive-reals
Diffstat (limited to 'src')
61 files changed, 1399 insertions, 764 deletions
@@ -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; +} @@ -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 @@ -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); |
