From dfedf3f2bb434065da3ddbc931e87a4017535f80 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 30 Apr 2025 20:42:31 -0400 Subject: Update compiler to use randomly generated unique-per-file symbol suffixes instead of needing to rename symbols with objcopy --- docs/libraries.md | 70 ++++++++++++++++--------- docs/namespacing.md | 77 +++++++++++++++------------- lib/commands/commands.tm | 16 +++--- lib/time/time.tm | 49 +++++++++--------- src/ast.h | 1 + src/compile.c | 130 ++++++++++++++++++++++++----------------------- src/enums.c | 33 +++++++----- src/environment.c | 33 ++++++++---- src/environment.h | 5 +- src/parse.c | 9 +++- src/stdlib/files.c | 17 ------- src/stdlib/files.h | 2 - src/stdlib/nums.c | 4 +- src/stdlib/util.h | 2 +- src/structs.c | 11 ++-- src/tomo.c | 114 +++++++++++++++++------------------------ src/typecheck.c | 41 +++++++-------- 17 files changed, 319 insertions(+), 295 deletions(-) diff --git a/docs/libraries.md b/docs/libraries.md index 1409bca5..7febb2e1 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -24,7 +24,8 @@ produces the following C header file and C source file: #pragma once #include -extern Text_t foo$my_variable; +extern Text_t my_variable$foo_C3zxCsha; +void $initialize$foo_C3zxCsha(void); ``` ```c @@ -32,14 +33,19 @@ extern Text_t foo$my_variable; #include #include "foo.tm.h" -Text_t foo$my_variable = "hello"; +public Text_t my_variable$foo_C3zxCsha = Text("hello"); +public void $initialize$foo_C3zxCsha(void) { + static bool initialized = false; + if (initialized) return; + initialized = true; +} ``` -Notice that the symbols defined here (`foo$my_variable`) use a file-based -prefix that includes a dollar sign. C compilers support an extension that -allows dollar signs in identifiers, and this allows us to use guaranteed-unique -prefixes so symbols from one file don't have naming collisions with symbols -in another file. +Notice that the symbols defined here (`my_variable$foo_C3zxCsha`) use a +filename-based suffix with a random bit at the end that includes a dollar sign. +C compilers support an extension that allows dollar signs in identifiers, and +this allows us to use guaranteed-unique prefixes so symbols from one file don't +have naming collisions with symbols in another file. The C file is compiled by invoking the C compiler with something like: `cc -c foo.tm.c -o foo.tm.o` @@ -65,8 +71,9 @@ If I want to run `baz.tm` with `tomo baz.tm` then this transpiles to: #include #include "./foo.tm.h" -void baz$say_stuff(); -void baz$main(); +void say_stuff$baz_VEDjfzDs(); +void main$baz_VEDjfzDs(); +void $initialize$baz_VEDjfzDs(void); ``` ```c @@ -74,23 +81,44 @@ void baz$main(); #include #include "baz.tm.h" -public void baz$say_stuff() -{ - say(Texts(Text("I got "), foo$my_variable, Text(" from foo"))); +public void say_stuff$baz_VEDjfzDs() { + say(Texts(Text("I got "), my_variable$foo_C3zxCsha, Text(" from foo")), yes); } -public void baz$main() -{ - baz$say_stuff(); +public void main$foo_VEDjfzDs() { + say_stuff$foo_VEDjfzDs(); +} + +public void $initialize$foo_VEDjfzDs(void) { + static bool initialized = false; + if (initialized) return; + initialized = true; + + $initialize$foo_C3zxCsha(); + ... +} + +int main$baz_VEDjfzDs$parse_and_run(int argc, char *argv[]) { + tomo_init(); + $initialize$baz_VEDjfzDs(); + + Text_t usage = Texts(Text("Usage: "), Text$from_str(argv[0]), Text(" [--help]")); + tomo_parse_args(argc, argv, usage, usage); + main$baz_VEDjfzDs(); + return 0; } ``` +The automatically generated function `main$baz_VEDjfzDs$parse_and_run` is in +charge of parsing the command line arguments to `main()` (in this case there +aren't any) and printing out any help/usage errors, then calling `main()`. + Then `baz.tm.o` is compiled to a static object with `cc -c baz.tm.c -o baz.tm.o`. -Next, we need to create an actual executable file that will invoke `baz$main()` -(with any command line arguments). To do that, we create a small wrapper -program: +Next, we need to create an actual executable file that will invoke +`main$baz_VEDjfzDs$parse_and_run()` (with any command line arguments). To do +that, we create a small wrapper program: ```c // File: /tmp/program.c @@ -99,11 +127,7 @@ program: int main(int argc, char *argv[]) { - tomo_init(); - if (argc > 1) - errx(1, "This program doesn't take any arguments."); - baz$main(); - return 0; + return main$baz_VEDjfzDs$parse_and_run(argc, argv); } ``` diff --git a/docs/namespacing.md b/docs/namespacing.md index e9dc428d..42bdd984 100644 --- a/docs/namespacing.md +++ b/docs/namespacing.md @@ -1,15 +1,30 @@ # Namespacing In order to work with C's namespace limitations, I've designed the following -system: +system, which makes use of a C language extension `-fdollars-in-identifiers` +that lets you use dollar signs in identifiers. This extension is supported by +GCC, TinyCC, and Clang. + +## Unique File Suffixes + +Each file gets a unique suffix with the format `$_XXXXXXXX`, where the +Xs are 8 randomly chosen identifier characters and `` includes only +valid identifier characters up to the first period. + +For example, in a file called `hello-world.tm`, a variable like `foo` would +become `foo$helloworld_VEDjfzDs`. This helps avoid namespace conflicts between +two files that define the same symbol. ## Namespaces -In C, there is a GCC extension (also supported by clang and TCC) to allow for -dollar signs in identifiers. This provides a way to have compiled C code which -segments its imports into different namespaces. For example `Foo$Baz` would be -the identifier `Baz` in the namespace `Foo`, and would be guaranteed to not -collide with a user-chosen name like `FooBaz`. +Dollar signs in identifiers provide a way to have compiled C code which segments +its imports into different namespaces. For example `Foo$Baz` would be the +identifier `Baz` in the namespace `Foo`, and would be guaranteed to not collide +with a user-chosen name like `FooBaz` or `Foo_Baz`. + +## Example + +For this Tomo code: ```tomo // File: foo.tm @@ -19,46 +34,40 @@ struct Baz(x:Int) member := 5 func frob(b:Baz -> Int) return b.x + +func main() pass ``` +The generated C source code will look like this: + ```C -// File: foo.tm.h +// File: .build/foo.tm.h ... -typedef struct foo$Baz_s foo$Baz_t; -struct foo$Baz_s { - Int_t $x; +typedef struct Baz$$struct$foo_VEDjfzDs Baz$$type$foo_VEDjfzDs; +struct Baz$$struct$foo_VEDjfzDs { + Int_t x; }; - -extern Int_t foo$my_var; -extern const TypeInfo_t foo$Baz; - -extern Int_t foo$Baz$member; -Int_t foo$Baz$frob(struct foo$Baz_s $b); -void foo$main(); +DEFINE_OPTIONAL_TYPE(struct Baz$$struct$foo_VEDjfzDs, 8,$OptionalBaz$$type$foo_VEDjfzDs); +extern const TypeInfo_t Baz$$info$foo_VEDjfzDs; +extern Int_t Baz$member$foo_VEDjfzDs; +Int_t Baz$frob$foo_VEDjfzDs(struct Baz$$struct$foo_VEDjfzDs _$b); +extern Int_t my_var$foo_VEDjfzDs; +void main$foo_VEDjfzDs(); ... ``` ```C -// File: foo.tm.c +// File: .build/foo.tm.c ... -Int_t foo$my_var = I_small(123); -Int_t foo$Baz$member = I_small(5); - -static Text_t foo$Baz$as_text(foo$Baz_t *obj, bool use_color) -{ - if (!obj) - return "Baz"; - return Texts(use_color ? Text("\x1b[0;1mBaz\x1b[m(") : Text("Baz("), - Int$as_text(stack(obj->$x), use_color, &Int$info), Text(")")); +public Int_t my_var$foo_VEDjfzDs = I_small(123); +public const TypeInfo_t Baz$$info$foo_VEDjfzDs = {...}; +public Int_t Baz$member$foo_VEDjfzDs = I_small(5); + +public Int_t Baz$frob$foo_VEDjfzDs(struct Baz$$struct$foo_VEDjfzDs _$b) { + return (_$b).x; } -public Int_t foo$Baz$frob(struct foo$Baz_s $b) -{ - return ($b).x; +public void main$foo_VEDjfzDs() { } ... ``` - -And on the usage site, the code `include ./foo.tm` compiles to `#include -"./foo.tm.h"` in the header and `use$foo()` in the code that is executed. - diff --git a/lib/commands/commands.tm b/lib/commands/commands.tm index cb1d48e3..8a131042 100644 --- a/lib/commands/commands.tm +++ b/lib/commands/commands.tm @@ -57,22 +57,22 @@ struct Command(command:Text, args:[Text]=[], env:{Text=Text}={}) errors : [Byte] status := run_command(command.command, command.args, command.env, input_bytes, &output, &errors) - if C_code:Bool(WIFEXITED(_$status)) - return ProgramResult(output, errors, ExitType.Exited(C_code:Int32(WEXITSTATUS(_$status)))) + if C_code:Bool(WIFEXITED(@status)) + return ProgramResult(output, errors, ExitType.Exited(C_code:Int32(WEXITSTATUS(@status)))) - if C_code:Bool(WIFSIGNALED(_$status)) - return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32(WTERMSIG(_$status)))) + if C_code:Bool(WIFSIGNALED(@status)) + return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32(WTERMSIG(@status)))) return ProgramResult(output, errors, ExitType.Failed) func run(command:Command, -> ExitType) status := run_command(command.command, command.args, command.env, none, none, none) - if C_code:Bool(WIFEXITED(_$status)) - return ExitType.Exited(C_code:Int32(WEXITSTATUS(_$status))) + if C_code:Bool(WIFEXITED(@status)) + return ExitType.Exited(C_code:Int32(WEXITSTATUS(@status))) - if C_code:Bool(WIFSIGNALED(_$status)) - return ExitType.Signaled(C_code:Int32(WTERMSIG(_$status))) + if C_code:Bool(WIFSIGNALED(@status)) + return ExitType.Signaled(C_code:Int32(WTERMSIG(@status))) return ExitType.Failed diff --git a/lib/time/time.tm b/lib/time/time.tm index 1796654d..db0b9538 100644 --- a/lib/time/time.tm +++ b/lib/time/time.tm @@ -129,22 +129,22 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) return t.format("%F") func info(t:Time, timezone=Time.local_timezone() -> TimeInfo) - return C_code : TimeInfo ( + ret : TimeInfo + C_code { struct tm info = {}; WITH_TIMEZONE(@timezone, localtime_r(&@t.tv_sec, &info)); - (_$time$TimeInfo$$type){ - .year = I(info.tm_year + 1900), - .month = I(info.tm_mon + 1), - .day = I(info.tm_mday), - .hour = I(info.tm_hour), - .minute = I(info.tm_min), - .second = I(info.tm_sec), - .nanosecond = I(@t.tv_usec), - .weekday = (_$time$Weekday$$type){.$tag=info.tm_wday + 1}, - .day_of_year = I(info.tm_yday), - .timezone = @timezone, - } - ) + @ret.year = I(info.tm_year + 1900); + @ret.month = I(info.tm_mon + 1); + @ret.day = I(info.tm_mday); + @ret.hour = I(info.tm_hour); + @ret.minute = I(info.tm_min); + @ret.second = I(info.tm_sec); + @ret.nanosecond = I(@t.tv_usec); + @ret.weekday.$tag = info.tm_wday + 1; + @ret.day_of_year = I(info.tm_yday); + @ret.timezone = @timezone; + } + return ret func after(t:Time, seconds=0.0, minutes=0.0, hours=0.0, days=0, weeks=0, months=0, years=0, timezone=Time.local_timezone() -> Time) return C_code : Time ( @@ -166,7 +166,8 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) ) func parse(text:Text, format="%Y-%m-%dT%H:%M:%S%z", timezone=Time.local_timezone() -> Time?) - return C_code : Time? ( + ret : Time? + C_code { struct tm info = {.tm_isdst=-1}; const char *str = Text$as_c_string(@text); const char *fmt = Text$as_c_string(@format); @@ -175,14 +176,16 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) char *invalid; WITH_TIMEZONE(@timezone, invalid = strptime(str, fmt, &info)); - if (!invalid || invalid[0] != '\0') - return (_$time$$OptionalTime$$type){.is_none=true}; - - long offset = info.tm_gmtoff; // Need to cache this because mktime() mutates it to local tz >:( - time_t t; - WITH_TIMEZONE(@timezone, t = mktime(&info)); - (_$time$$OptionalTime$$type){.value={.tv_sec=t + offset - info.tm_gmtoff}}; - ) + if (!invalid || invalid[0] != '\0') { + @ret.is_none = true; + } else { + long offset = info.tm_gmtoff; // Need to cache this because mktime() mutates it to local tz >:( + time_t t; + WITH_TIMEZONE(@timezone, t = mktime(&info)); + @ret.value.tv_sec = t + offset - info.tm_gmtoff; + } + } + return ret func _run_tests() >> Time.now().format() diff --git a/src/ast.h b/src/ast.h index 2de9f007..47ff3caf 100644 --- a/src/ast.h +++ b/src/ast.h @@ -189,6 +189,7 @@ struct ast_s { ast_t *var; type_ast_t *type; ast_t *value; + bool top_level:1; } Declare; struct { ast_list_t *targets, *values; diff --git a/src/compile.c b/src/compile.c index 52c8a736..02d4795c 100644 --- a/src/compile.c +++ b/src/compile.c @@ -799,7 +799,7 @@ CORD compile_type(type_t *t) if (!text->lang || streq(text->lang, "Text")) return "Text_t"; else - return CORD_all(namespace_prefix(text->env, text->env->namespace->parent), text->lang, "$$type"); + return namespace_name(text->env, text->env->namespace, "$type"); } case ListType: return "List_t"; case SetType: return "Table_t"; @@ -820,11 +820,11 @@ CORD compile_type(type_t *t) case StructType: { DeclareMatch(s, t, StructType); if (s->external) return s->name; - return CORD_all("struct ", namespace_prefix(s->env, s->env->namespace->parent), s->name, "$$struct"); + return CORD_all("struct ", namespace_name(s->env, s->env->namespace, "$struct")); } case EnumType: { DeclareMatch(e, t, EnumType); - return CORD_all(namespace_prefix(e->env, e->env->namespace->parent), e->name, "$$type"); + return namespace_name(e->env, e->env->namespace, "$type"); } case OptionalType: { type_t *nonnull = Match(t, OptionalType)->type; @@ -843,7 +843,7 @@ CORD compile_type(type_t *t) if (nonnull == PATH_TYPE_TYPE) return "OptionalPathType_t"; DeclareMatch(s, nonnull, StructType); - return CORD_all(namespace_prefix(s->env, s->env->namespace->parent), "$Optional", s->name, "$$type"); + return namespace_name(s->env, s->env->namespace->parent, CORD_all("$Optional", s->name, "$$type")); } default: compiler_err(NULL, NULL, NULL, "Optional types are not supported for: ", type_to_str(t)); @@ -874,7 +874,7 @@ CORD compile_lvalue(env_t *env, ast_t *ast) DeclareMatch(index, ast, Index); type_t *container_t = get_type(env, index->indexed); if (container_t->tag == OptionalType) - code_err(index->indexed, "This value might be null, so it can't be safely used as an assignment target"); + code_err(index->indexed, "This value might be none, so it can't be safely used as an assignment target"); if (!index->index && container_t->tag == PointerType) return compile(env, ast); @@ -1060,7 +1060,7 @@ static CORD _compile_statement(env_t *env, ast_t *ast) 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; - code = CORD_all(code, "case ", namespace_prefix(enum_t->env, enum_t->env->namespace), "tag$", clause_tag_name, ": {\n", + code = CORD_all(code, "case ", namespace_name(enum_t->env, enum_t->env->namespace, CORD_all("tag$", clause_tag_name)), ": {\n", compile_statement(env, clause->body), "break;\n" "}\n"); @@ -1071,7 +1071,7 @@ static CORD _compile_statement(env_t *env, ast_t *ast) code_err(clause->pattern, "This is not a valid pattern for a ", type_to_str(subject_t), " enum type"); const char *clause_tag_name = Match(Match(clause->pattern, FunctionCall)->fn, Var)->name; - code = CORD_all(code, "case ", namespace_prefix(enum_t->env, enum_t->env->namespace), "tag$", clause_tag_name, ": {\n"); + code = CORD_all(code, "case ", namespace_name(enum_t->env, enum_t->env->namespace, CORD_all("tag$", clause_tag_name)), ": {\n"); type_t *tag_type = NULL; for (tag_t *tag = enum_t->tags; tag; tag = tag->next) { if (streq(tag->name, clause_tag_name)) { @@ -1089,7 +1089,8 @@ static CORD _compile_statement(env_t *env, ast_t *ast) code_err(args->value, "This is not a valid variable to bind to"); const char *var_name = Match(args->value, Var)->name; if (!streq(var_name, "_")) { - code = CORD_all(code, compile_declaration(tag_type, compile(env, args->value)), " = _when_subject.", clause_tag_name, ";\n"); + CORD var = CORD_all("_$", var_name); + code = CORD_all(code, compile_declaration(tag_type, var), " = _when_subject.", clause_tag_name, ";\n"); scope = fresh_scope(scope); set_binding(scope, Match(args->value, Var)->name, tag_type, CORD_EMPTY); } @@ -1106,8 +1107,9 @@ static CORD _compile_statement(env_t *env, ast_t *ast) const char *var_name = Match(arg->value, Var)->name; if (!streq(var_name, "_")) { - code = CORD_all(code, compile_declaration(field->type, compile(env, arg->value)), " = _when_subject.", clause_tag_name, ".", field->name, ";\n"); - set_binding(scope, Match(arg->value, Var)->name, field->type, CORD_EMPTY); + CORD var = CORD_cat("_$", var_name); + code = CORD_all(code, compile_declaration(field->type, var), " = _when_subject.", clause_tag_name, ".", field->name, ";\n"); + set_binding(scope, Match(arg->value, Var)->name, field->type, var); } field = field->next; } @@ -1859,7 +1861,7 @@ static CORD _compile_statement(env_t *env, ast_t *ast) } if (fn->ret->tag == OptionalType) { - // Use an optional variable `cur` for each iteration step, which will be checked for null + // Use an optional variable `cur` for each iteration step, which will be checked for none code = CORD_all(code, compile_declaration(fn->ret, "cur"), ";\n"); get_next = CORD_all("(cur=", get_next, ", !", check_none(fn->ret, "cur"), ")"); if (for_->vars) { @@ -1967,8 +1969,11 @@ static CORD _compile_statement(env_t *env, ast_t *ast) case Use: { DeclareMatch(use, ast, Use); if (use->what == USE_LOCAL) { - CORD name = file_base_id(Match(ast, Use)->path); - return with_source_info(env, ast, CORD_all("_$", name, "$$initialize();\n")); + Path_t path = Path$from_str(Match(ast, Use)->path); + Path_t in_file = Path$from_str(ast->file->filename); + path = Path$resolved(path, Path$parent(in_file)); + CORD suffix = get_id_suffix(Path$as_c_string(path)); + return with_source_info(env, ast, CORD_all("$initialize", suffix, "();\n")); } else if (use->what == USE_MODULE) { glob_t tm_files; if (glob(String(TOMO_HOME"/installed/", use->path, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) @@ -1976,17 +1981,11 @@ static CORD _compile_statement(env_t *env, ast_t *ast) CORD initialization = CORD_EMPTY; - char *lib_id = String(use->path); - for (char *p = lib_id; *p; p++) { - if (!isalnum(*p) && *p != '_') - *p = '_'; - } - for (size_t i = 0; i < tm_files.gl_pathc; i++) { const char *filename = tm_files.gl_pathv[i]; initialization = CORD_all( initialization, - with_source_info(env, ast, CORD_all("_$", lib_id, "$", file_base_id(filename), "$$initialize();\n"))); + with_source_info(env, ast, CORD_all("$initialize", get_id_suffix(filename), "();\n"))); } globfree(&tm_files); return initialization; @@ -2582,11 +2581,11 @@ CORD compile_none(type_t *t) case CStringType: return "NULL"; case PointerType: return CORD_all("((", compile_type(t), ")NULL)"); case ClosureType: return "NONE_CLOSURE"; - case NumType: return "nan(\"null\")"; + case NumType: return "nan(\"none\")"; case StructType: return CORD_all("((", compile_type(Type(OptionalType, .type=t)), "){.is_none=true})"); case EnumType: { env_t *enum_env = Match(t, EnumType)->env; - return CORD_all("((", compile_type(t), "){", namespace_prefix(enum_env, enum_env->namespace), "null})"); + return CORD_all("((", compile_type(t), "){", namespace_name(enum_env, enum_env->namespace, "none"), "})"); } default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_str(t)); } @@ -2712,8 +2711,8 @@ CORD compile(env_t *env, ast_t *ast) binding_t *b = get_binding(env, Match(ast, Var)->name); if (b) return b->code ? b->code : CORD_cat("_$", Match(ast, Var)->name); - return CORD_cat("_$", Match(ast, Var)->name); - // code_err(ast, "I don't know of any variable by this name"); + // return CORD_cat("_$", Match(ast, Var)->name); + code_err(ast, "I don't know of any variable by this name"); } case Int: { const char *str = Match(ast, Int)->str; @@ -2899,7 +2898,7 @@ CORD compile(env_t *env, ast_t *ast) if (!lang || streq(lang, "Text")) lang_constructor = "Text"; else - lang_constructor = CORD_all(namespace_prefix(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent), lang); + lang_constructor = namespace_name(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent, lang); ast_list_t *chunks = Match(ast, TextJoin)->children; if (!chunks) { @@ -3038,7 +3037,7 @@ CORD compile(env_t *env, ast_t *ast) } case Lambda: { DeclareMatch(lambda, ast, Lambda); - CORD name = CORD_all(namespace_prefix(env, env->namespace), "lambda$", String(lambda->id)); + CORD name = namespace_name(env, env->namespace, CORD_all("lambda$", String(lambda->id))); env->code->function_naming = CORD_all( env->code->function_naming, @@ -3817,13 +3816,13 @@ CORD compile(env_t *env, ast_t *ast) DeclareMatch(e, value_t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { if (streq(f->field, tag->name)) { - CORD prefix = namespace_prefix(e->env, e->env->namespace); + CORD tag_name = namespace_name(e->env, e->env->namespace, CORD_all("tag$", tag->name)); if (fielded_t->tag == PointerType) { CORD fielded = compile_to_pointer_depth(env, f->fielded, 1, false); - return CORD_all("((", fielded, ")->$tag == ", prefix, "tag$", tag->name, ")"); + return CORD_all("((", fielded, ")->$tag == ", tag_name, ")"); } else { CORD fielded = compile(env, f->fielded); - return CORD_all("((", fielded, ").$tag == ", prefix, "tag$", tag->name, ")"); + return CORD_all("((", fielded, ").$tag == ", tag_name, ")"); } } } @@ -3963,15 +3962,15 @@ CORD compile_type_info(type_t *t) DeclareMatch(text, t, TextType); if (!text->lang || streq(text->lang, "Text")) return "&Text$info"; - return CORD_all("(&", namespace_prefix(text->env, text->env->namespace->parent), text->lang, "$$info)"); + return CORD_all("(&", namespace_name(text->env, text->env->namespace, "$info"), ")"); } case StructType: { DeclareMatch(s, t, StructType); - return CORD_all("(&", namespace_prefix(s->env, s->env->namespace->parent), s->name, "$$info)"); + return CORD_all("(&", namespace_name(s->env, s->env->namespace, "$info"), ")"); } case EnumType: { DeclareMatch(e, t, EnumType); - return CORD_all("(&", namespace_prefix(e->env, e->env->namespace->parent), e->name, "$$info)"); + return CORD_all("(&", namespace_name(e->env, e->env->namespace, "$info"), ")"); } case ListType: { type_t *item_t = Match(t, ListType)->item_type; @@ -4182,13 +4181,12 @@ CORD compile_function(env_t *env, CORD name_code, ast_t *ast, CORD *staticdefs) } env_t *body_scope = fresh_scope(env); - while (body_scope->namespace && body_scope->namespace->parent) { + while (body_scope->namespace) { body_scope->locals->fallback = body_scope->locals->fallback->fallback; body_scope->namespace = body_scope->namespace->parent; } body_scope->deferred = NULL; - body_scope->namespace = NULL; for (arg_ast_t *arg = args; arg; arg = arg->next) { type_t *arg_type = get_arg_ast_type(env, arg); set_binding(body_scope, arg->name, arg_type, CORD_cat("_$", arg->name)); @@ -4330,7 +4328,7 @@ CORD compile_top_level_code(env_t *env, ast_t *ast) case Declare: { DeclareMatch(decl, ast, Declare); const char *decl_name = Match(decl->var, Var)->name; - CORD full_name = CORD_all(namespace_prefix(env, env->namespace), decl_name); + CORD full_name = namespace_name(env, env->namespace, decl_name); type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); if (t->tag == FunctionType) t = Type(ClosureType, t); CORD val_code = compile_declared_value(env, ast); @@ -4341,17 +4339,19 @@ CORD compile_top_level_code(env_t *env, ast_t *ast) is_private ? "static " : "public ", compile_declaration(t, full_name), " = ", val_code, ";\n"); } else { - CORD checked_access = CORD_all("check_initialized(", full_name, ", \"", decl_name, "\")"); + CORD init_var = namespace_name(env, env->namespace, CORD_all(decl_name, "$$initialized")); + CORD checked_access = CORD_all("check_initialized(", full_name, ", ", init_var, ", \"", decl_name, "\")"); set_binding(env, decl_name, t, checked_access); + CORD initialized_name = namespace_name(env, env->namespace, CORD_all(decl_name, "$$initialized")); return CORD_all( - "static bool ", full_name, "$initialized = false;\n", + "static bool ", initialized_name, " = false;\n", is_private ? "static " : "public ", compile_declaration(t, full_name), ";\n"); } } case FunctionDef: { - CORD name_code = CORD_all(namespace_prefix(env, env->namespace), Match(Match(ast, FunctionDef)->name, Var)->name); + CORD name_code = namespace_name(env, env->namespace, Match(Match(ast, FunctionDef)->name, Var)->name); return compile_function(env, name_code, ast, &env->code->staticdefs); } case ConvertDef: { @@ -4359,7 +4359,7 @@ CORD compile_top_level_code(env_t *env, ast_t *ast) const char *name = get_type_name(Match(type, FunctionType)->ret); if (!name) code_err(ast, "Conversions are only supported for text, struct, and enum types, not ", type_to_str(Match(type, FunctionType)->ret)); - CORD name_code = CORD_all(namespace_prefix(env, env->namespace), name, "$", String(get_line_number(ast->file, ast->start))); + CORD name_code = namespace_name(env, env->namespace, CORD_all(name, "$", String(get_line_number(ast->file, ast->start)))); return compile_function(env, name_code, ast, &env->code->staticdefs); } case StructDef: { @@ -4379,8 +4379,8 @@ CORD compile_top_level_code(env_t *env, ast_t *ast) } case LangDef: { DeclareMatch(def, ast, LangDef); - CORD code = CORD_all("public const TypeInfo_t ", namespace_prefix(env, env->namespace), - def->name, "$$info = {", String((int64_t)sizeof(Text_t)), ", ", String((int64_t)__alignof__(Text_t)), + CORD code = CORD_all("public const TypeInfo_t ", namespace_name(env, env->namespace, CORD_all(def->name, "$$info")), + " = {", String((int64_t)sizeof(Text_t)), ", ", String((int64_t)__alignof__(Text_t)), ", .metamethods=Text$metamethods, .tag=TextInfo, .TextInfo={", CORD_quoted(def->name), "}};\n"); env_t *ns_env = namespace_env(env, def->name); return CORD_all(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : CORD_EMPTY); @@ -4392,7 +4392,7 @@ CORD compile_top_level_code(env_t *env, ast_t *ast) *extended = *ns_env; extended->locals = new(Table_t, .fallback=env->locals); extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings); - extended->libname = env->libname; + extended->id_suffix = env->id_suffix; return compile_top_level_code(extended, extend->body); } case Extern: return CORD_EMPTY; @@ -4418,18 +4418,19 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast) } else if (stmt->ast->tag == Declare) { DeclareMatch(decl, stmt->ast, Declare); const char *decl_name = Match(decl->var, Var)->name; - CORD full_name = CORD_all(namespace_prefix(env, env->namespace), decl_name); + CORD full_name = namespace_name(env, env->namespace, decl_name); type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); if (t->tag == FunctionType) t = Type(ClosureType, t); CORD val_code = compile_declared_value(env, stmt->ast); if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) { + CORD initialized_name = namespace_name(env, env->namespace, CORD_all(decl_name, "$$initialized")); env->code->variable_initializers = CORD_all( env->code->variable_initializers, with_source_info( env, stmt->ast, CORD_all( full_name, " = ", val_code, ",\n", - full_name, "$initialized = true;\n"))); + initialized_name, " = true;\n"))); } } else if (stmt->ast->tag == StructDef) { initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, StructDef)->name), @@ -4485,7 +4486,7 @@ CORD compile_file(env_t *env, ast_t *ast) env->code->lambdas, "\n", env->code->staticdefs, "\n", top_level_code, - "public void ", namespace_prefix(env, env->namespace), "$initialize(void) {\n", + "public void ", namespace_name(env, env->namespace, "$initialize"), "(void) {\n", "static bool initialized = false;\n", "if (initialized) return;\n", "initialized = true;\n", @@ -4532,13 +4533,13 @@ CORD compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) } case LangDef: { DeclareMatch(def, ast, LangDef); - CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name); + CORD full_name = namespace_name(env, env->namespace, def->name); return CORD_all( // Constructor macro: - "#define ", namespace_prefix(env, env->namespace), def->name, - "(text) ((", namespace_prefix(env, env->namespace), def->name, "$$type){.length=sizeof(text)-1, .tag=TEXT_ASCII, .ascii=\"\" text})\n" - "#define ", namespace_prefix(env, env->namespace), def->name, - "s(...) ((", namespace_prefix(env, env->namespace), def->name, "$$type)Texts(__VA_ARGS__))\n" + "#define ", namespace_name(env, env->namespace, def->name), + "(text) ((", namespace_name(env, env->namespace, CORD_all(def->name, "$$type")), "){.length=sizeof(text)-1, .tag=TEXT_ASCII, .ascii=\"\" text})\n" + "#define ", namespace_name(env, env->namespace, def->name), + "s(...) ((", namespace_name(env, env->namespace, CORD_all(def->name, "$$type")), ")Texts(__VA_ARGS__))\n" "extern const TypeInfo_t ", full_name, "$$info;\n" ); } @@ -4569,7 +4570,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a *extended = *ns_env; extended->locals = new(Table_t, .fallback=env->locals); extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings); - extended->libname = env->libname; + extended->id_suffix = env->id_suffix; ns_env = extended; block = extend->body; @@ -4621,7 +4622,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a return CORD_all( decl->value ? compile_statement_type_header(env, header_path, decl->value) : CORD_EMPTY, - "extern ", compile_declaration(t, CORD_cat(namespace_prefix(env, env->namespace), decl_name)), ";\n"); + "extern ", compile_declaration(t, namespace_name(env, env->namespace, decl_name)), ";\n"); } case FunctionDef: { DeclareMatch(fndef, ast, FunctionDef); @@ -4640,9 +4641,9 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a CORD ret_type_code = compile_type(ret_t); if (ret_t->tag == AbortType) ret_type_code = CORD_all("__attribute__((noreturn)) _Noreturn ", ret_type_code); - CORD name = CORD_all(namespace_prefix(env, env->namespace), decl_name); + CORD name = namespace_name(env, env->namespace, decl_name); if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name)) - name = CORD_all(namespace_prefix(env, env->namespace), String(get_line_number(ast->file, ast->start))); + name = namespace_name(env, env->namespace, String(get_line_number(ast->file, ast->start))); return CORD_all(ret_type_code, " ", name, arg_signature, ";\n"); } case ConvertDef: { @@ -4661,8 +4662,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a CORD name = get_type_name(ret_t); if (!name) code_err(ast, "Conversions are only supported for text, struct, and enum types, not ", type_to_str(ret_t)); - name = CORD_all(namespace_prefix(env, env->namespace), name); - CORD name_code = CORD_all(name, "$", String(get_line_number(ast->file, ast->start))); + CORD name_code = namespace_name(env, env->namespace, CORD_all(name, "$", String(get_line_number(ast->file, ast->start)))); return CORD_all(ret_type_code, " ", name_code, arg_signature, ";\n"); } default: return CORD_EMPTY; @@ -4686,20 +4686,24 @@ static void _make_typedefs(compile_typedef_info_t *info, ast_t *ast) if (ast->tag == StructDef) { DeclareMatch(def, ast, StructDef); if (def->external) return; - CORD full_name = CORD_cat(namespace_prefix(info->env, info->env->namespace), def->name); - *info->header = CORD_all(*info->header, "typedef struct ", full_name, "$$struct ", full_name, "$$type;\n"); + CORD struct_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$struct")); + CORD type_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$type")); + *info->header = CORD_all(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); } else if (ast->tag == EnumDef) { DeclareMatch(def, ast, EnumDef); - CORD full_name = CORD_cat(namespace_prefix(info->env, info->env->namespace), def->name); - *info->header = CORD_all(*info->header, "typedef struct ", full_name, "$$struct ", full_name, "$$type;\n"); + CORD struct_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$struct")); + CORD type_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$type")); + *info->header = CORD_all(*info->header, "typedef struct ", struct_name, " ", type_name, ";\n"); for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { if (!tag->fields) continue; - *info->header = CORD_all(*info->header, "typedef struct ", full_name, "$", tag->name, "$$struct ", full_name, "$", tag->name, "$$type;\n"); + CORD tag_struct = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$", tag->name, "$$struct")); + CORD tag_type = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$", tag->name, "$$type")); + *info->header = CORD_all(*info->header, "typedef struct ", tag_struct, " ", tag_type, ";\n"); } } else if (ast->tag == LangDef) { DeclareMatch(def, ast, LangDef); - *info->header = CORD_all(*info->header, "typedef Text_t ", namespace_prefix(info->env, info->env->namespace), def->name, "$$type;\n"); + *info->header = CORD_all(*info->header, "typedef Text_t ", namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$type")), ";\n"); } } @@ -4721,7 +4725,7 @@ CORD compile_file_header(env_t *env, Path_t header_path, ast_t *ast) visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn=(void*)_make_typedefs, &info}); visit_topologically(Match(ast, Block)->statements, (Closure_t){.fn=(void*)_define_types_and_funcs, &info}); - header = CORD_all(header, "void ", namespace_prefix(env, env->namespace), "$initialize(void);\n"); + header = CORD_all(header, "void ", namespace_name(env, env->namespace, "$initialize"), "(void);\n"); return header; } diff --git a/src/enums.c b/src/enums.c index 9676918c..e391086e 100644 --- a/src/enums.c +++ b/src/enums.c @@ -16,7 +16,6 @@ CORD compile_enum_typeinfo(env_t *env, ast_t *ast) { DeclareMatch(def, ast, EnumDef); - CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name); // Compile member types and constructors: CORD member_typeinfos = CORD_EMPTY; @@ -36,7 +35,8 @@ CORD compile_enum_typeinfo(env_t *env, ast_t *ast) type_t *t = Table$str_get(*env->types, def->name); const char *metamethods = is_packed_data(t) ? "PackedDataEnum$metamethods" : "Enum$metamethods"; - CORD typeinfo = CORD_all("public const TypeInfo_t ", full_name, "$$info = {", + CORD info = namespace_name(env, env->namespace, CORD_all(def->name, "$$info")); + CORD typeinfo = CORD_all("public const TypeInfo_t ", info, " = {", String((int64_t)type_size(t)), "u, ", String((int64_t)type_align(t)), "u, .metamethods=", metamethods, ", {.tag=EnumInfo, .EnumInfo={.name=\"", def->name, "\", " ".num_tags=", String((int64_t)num_tags), ", .tags=(NamedType_t[]){"); @@ -56,8 +56,6 @@ CORD compile_enum_typeinfo(env_t *env, ast_t *ast) CORD compile_enum_constructors(env_t *env, ast_t *ast) { DeclareMatch(def, ast, EnumDef); - CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name); - CORD constructors = CORD_EMPTY; for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { if (!tag->fields) continue; @@ -69,8 +67,11 @@ CORD compile_enum_constructors(env_t *env, ast_t *ast) if (field->next) arg_sig = CORD_cat(arg_sig, ", "); } if (arg_sig == CORD_EMPTY) arg_sig = "void"; - CORD constructor_impl = CORD_all("public inline ", full_name, "$$type ", full_name, "$tagged$", tag->name, "(", arg_sig, ") { return (", - full_name, "$$type){.$tag=", full_name, "$tag$", tag->name, ", .", tag->name, "={"); + CORD type_name = namespace_name(env, env->namespace, CORD_all(def->name, "$$type")); + CORD tagged_name = namespace_name(env, env->namespace, CORD_all(def->name, "$tagged$", tag->name)); + CORD tag_name = namespace_name(env, env->namespace, CORD_all(def->name, "$tag$", tag->name)); + CORD constructor_impl = CORD_all("public inline ", type_name, " ", tagged_name, "(", arg_sig, ") { return (", + type_name, "){.$tag=", tag_name, ", .", tag->name, "={"); for (arg_ast_t *field = tag->fields; field; field = field->next) { constructor_impl = CORD_all(constructor_impl, "$", field->name); if (field->next) constructor_impl = CORD_cat(constructor_impl, ", "); @@ -84,14 +85,16 @@ CORD compile_enum_constructors(env_t *env, ast_t *ast) CORD compile_enum_header(env_t *env, ast_t *ast) { DeclareMatch(def, ast, EnumDef); - CORD full_name = CORD_all(namespace_prefix(env, env->namespace), def->name); CORD all_defs = CORD_EMPTY; - CORD enum_def = CORD_all("struct ", full_name, "$$struct {\n" - "\tenum { ", full_name, "$null=0, "); + CORD struct_name = namespace_name(env, env->namespace, CORD_all(def->name, "$$struct")); + CORD none_name = namespace_name(env, env->namespace, CORD_all(def->name, "$none")); + CORD enum_def = CORD_all("struct ", struct_name, " {\n" + "\tenum { ", none_name, "=0, "); bool has_any_tags_with_fields = false; for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { - enum_def = CORD_all(enum_def, full_name, "$tag$", tag->name); + CORD tag_name = namespace_name(env, env->namespace, CORD_all(def->name, "$tag$", tag->name)); + enum_def = CORD_all(enum_def, tag_name); if (tag->next) enum_def = CORD_all(enum_def, ", "); has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL); } @@ -103,14 +106,16 @@ CORD compile_enum_header(env_t *env, ast_t *ast) if (!tag->fields) continue; CORD field_def = compile_struct_header(env, WrapAST(ast, StructDef, .name=CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)), .fields=tag->fields)); all_defs = CORD_all(all_defs, field_def); - enum_def = CORD_all(enum_def, full_name, "$", tag->name, "$$type ", tag->name, ";\n"); + CORD tag_type = namespace_name(env, env->namespace, CORD_all(def->name, "$", tag->name, "$$type")); + enum_def = CORD_all(enum_def, tag_type, " ", tag->name, ";\n"); } enum_def = CORD_all(enum_def, "};\n"); } enum_def = CORD_all(enum_def, "};\n"); all_defs = CORD_all(all_defs, enum_def); - all_defs = CORD_all(all_defs, "extern const TypeInfo_t ", full_name, "$$info;\n"); + CORD info = namespace_name(env, env->namespace, CORD_all(def->name, "$$info")); + all_defs = CORD_all(all_defs, "extern const TypeInfo_t ", info, ";\n"); for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { if (!tag->fields) continue; @@ -121,7 +126,9 @@ CORD compile_enum_header(env_t *env, ast_t *ast) if (field->next) arg_sig = CORD_all(arg_sig, ", "); } if (arg_sig == CORD_EMPTY) arg_sig = "void"; - CORD constructor_def = CORD_all(full_name, "$$type ", full_name, "$tagged$", tag->name, "(", arg_sig, ");\n"); + CORD enum_type = namespace_name(env, env->namespace, CORD_all(def->name, "$$type")); + CORD tagged_name = namespace_name(env, env->namespace, CORD_all(def->name, "$tagged$", tag->name)); + CORD constructor_def = CORD_all(enum_type, " ", tagged_name, "(", arg_sig, ");\n"); all_defs = CORD_all(all_defs, constructor_def); } return all_defs; diff --git a/src/environment.c b/src/environment.c index b950cf9e..227af572 100644 --- a/src/environment.c +++ b/src/environment.c @@ -2,11 +2,13 @@ // (variable bindings, code sections, etc.) #include #include +#include #include "cordhelpers.h" #include "environment.h" #include "parse.h" #include "stdlib/datatypes.h" +#include "stdlib/paths.h" #include "stdlib/tables.h" #include "stdlib/text.h" #include "stdlib/util.h" @@ -531,18 +533,28 @@ env_t *global_env(bool source_mapping) return env; } -CORD CONSTFUNC namespace_prefix(env_t *env, namespace_t *ns) +CORD CONSTFUNC namespace_name(env_t *env, namespace_t *ns, CORD name) { - CORD prefix = CORD_EMPTY; for (; ns; ns = ns->parent) - prefix = CORD_all(ns->name, "$", prefix); - if (env->locals != env->globals) { - if (env->libname) - prefix = CORD_all("_$", env->libname, "$", prefix); - else - prefix = CORD_all("_$", prefix); + name = CORD_all(ns->name, "$", name); + if (env->id_suffix) + name = CORD_all(name, env->id_suffix); + return name; +} + +CORD get_id_suffix(const char *filename) +{ + assert(filename); + Path_t path = Path$from_str(filename); + Path_t build_dir = Path$with_component(Path$parent(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 prefix; + Path_t id_file = Path$with_component(build_dir, Texts(Path$base_name(path), Text$from_str(".id"))); + OptionalText_t id = Path$read(id_file); + if (id.length < 0) err(1, "Could not read ID file: ", id_file); + return Text$as_c_string(Texts(Text("$"), id)); } env_t *load_module_env(env_t *env, ast_t *ast) @@ -552,8 +564,9 @@ env_t *load_module_env(env_t *env, ast_t *ast) if (cached) return cached; env_t *module_env = fresh_scope(env); module_env->code = new(compilation_unit_t); - module_env->namespace = new(namespace_t, .name=file_base_id(name)); module_env->namespace_bindings = module_env->locals; + module_env->id_suffix = get_id_suffix(ast->file->filename); + Table$str_set(module_env->imports, name, module_env); ast_list_t *statements = Match(ast, Block)->statements; diff --git a/src/environment.h b/src/environment.h index 1c6d52a5..74de3449 100644 --- a/src/environment.h +++ b/src/environment.h @@ -48,7 +48,7 @@ typedef struct env_s { type_t *fn_ret; loop_ctx_t *loop_ctx; deferral_t *deferred; - CORD libname; // Currently compiling library name (if any) + const char *id_suffix; namespace_t *namespace; Closure_t *comprehension_action; bool do_source_mapping:1; @@ -61,7 +61,8 @@ typedef struct { env_t *global_env(bool source_mapping); env_t *load_module_env(env_t *env, ast_t *ast); -CORD namespace_prefix(env_t *env, namespace_t *ns); +CORD namespace_name(env_t *env, namespace_t *ns, CORD name); +CORD get_id_suffix(const char *filename); env_t *get_namespace_by_type(env_t *env, type_t *t); env_t *namespace_scope(env_t *env); env_t *fresh_scope(env_t *env); diff --git a/src/parse.c b/src/parse.c index 12fafc0c..fcbf7e6b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1723,6 +1723,13 @@ PARSER(parse_declaration) { return NewAST(ctx->file, start, pos, Declare, .var=var, .type=type, .value=val); } +PARSER(parse_top_declaration) { + ast_t *declaration = parse_declaration(ctx, pos); + if (declaration) + declaration->__data.Declare.top_level = true; + return declaration; +} + PARSER(parse_update) { const char *start = pos; ast_t *lhs = optional(ctx, &pos, parse_expr); @@ -1948,7 +1955,7 @@ PARSER(parse_file_body) { ||(stmt=optional(ctx, &pos, parse_use)) ||(stmt=optional(ctx, &pos, parse_extern)) ||(stmt=optional(ctx, &pos, parse_inline_c)) - ||(stmt=optional(ctx, &pos, parse_declaration))) + ||(stmt=optional(ctx, &pos, parse_top_declaration))) { statements = new(ast_list_t, .ast=stmt, .next=statements); pos = stmt->end; diff --git a/src/stdlib/files.c b/src/stdlib/files.c index 900129f8..87b0205c 100644 --- a/src/stdlib/files.c +++ b/src/stdlib/files.c @@ -79,23 +79,6 @@ public char *file_base_name(const char *path) return buf; } -public char *file_base_id(const char *path) -{ - const char *slash = strrchr(path, '/'); - if (slash) path = slash + 1; - assert(!isdigit(*path)); - const char *end = path + strcspn(path, "."); - size_t len = (size_t)(end - path); - char *buf = GC_MALLOC_ATOMIC(len+1); - strncpy(buf, path, len); - buf[len] = '\0'; - for (char *p = buf; *p; p++) { - if (!isalnum(*p)) - *p = '_'; - } - return buf; -} - static file_t *_load_file(const char* filename, FILE *file) { if (!file) return NULL; diff --git a/src/stdlib/files.h b/src/stdlib/files.h index 68827c2a..f650f78e 100644 --- a/src/stdlib/files.h +++ b/src/stdlib/files.h @@ -18,8 +18,6 @@ typedef struct { char *resolve_path(const char *path, const char *relative_to, const char *system_path); __attribute__((pure, nonnull)) char *file_base_name(const char *path); -__attribute__((pure, nonnull)) -char *file_base_id(const char *path); __attribute__((nonnull)) file_t *load_file(const char *filename); __attribute__((nonnull, returns_nonnull)) diff --git a/src/stdlib/nums.c b/src/stdlib/nums.c index 3213fd2f..34fbb162 100644 --- a/src/stdlib/nums.c +++ b/src/stdlib/nums.c @@ -105,7 +105,7 @@ public OptionalNum_t Num$parse(Text_t text) { if (end > str && end[0] == '\0') return d; else - return nan("null"); + return nan("none"); } static bool Num$is_none(const void *n, const TypeInfo_t *info) @@ -210,7 +210,7 @@ public OptionalNum32_t Num32$parse(Text_t text) { if (end > str && end[0] == '\0') return d; else - return nan("null"); + return nan("none"); } static bool Num32$is_none(const void *n, const TypeInfo_t *info) diff --git a/src/stdlib/util.h b/src/stdlib/util.h index 25cd49f9..3b00e6e9 100644 --- a/src/stdlib/util.h +++ b/src/stdlib/util.h @@ -14,7 +14,7 @@ #define new(t, ...) ((t*)memcpy(GC_MALLOC(sizeof(t)), &(t){__VA_ARGS__}, sizeof(t))) #define heap(x) (__typeof(x)*)memcpy(GC_MALLOC(sizeof(x)), (__typeof(x)[1]){x}, sizeof(x)) #define stack(x) (__typeof(x)*)((__typeof(x)[1]){x}) -#define check_initialized(var, name) *({ if (!var ## $initialized) fail("The variable " name " is being accessed before it has been initialized!"); \ +#define check_initialized(var, init_var, name) *({ if (!init_var) fail("The variable " name " is being accessed before it has been initialized!"); \ &var; }) #define IF_DECLARE(decl, expr, block) if (({ decl; expr ? ({ block; 1; }) : 0; })) {} diff --git a/src/structs.c b/src/structs.c index cad6fea2..b398b871 100644 --- a/src/structs.c +++ b/src/structs.c @@ -14,8 +14,8 @@ CORD compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_t *fields, bool is_secret, bool is_opaque) { - CORD typeinfo_name = CORD_all(namespace_prefix(env, env->namespace), name, "$$info"); - CORD type_code = Match(t, StructType)->external ? name : CORD_all("struct ", namespace_prefix(env, env->namespace), name, "$$struct"); + CORD typeinfo_name = namespace_name(env, env->namespace, CORD_cat(name, "$$info")); + CORD type_code = Match(t, StructType)->external ? name : CORD_all("struct ", namespace_name(env, env->namespace, CORD_cat(name, "$$struct"))); int num_fields = 0; for (arg_ast_t *f = fields; f; f = f->next) @@ -47,8 +47,8 @@ CORD compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_t CORD compile_struct_header(env_t *env, ast_t *ast) { DeclareMatch(def, ast, StructDef); - CORD typeinfo_name = CORD_all(namespace_prefix(env, env->namespace), def->name, "$$info"); - CORD type_code = def->external ? def->name : CORD_all("struct ", namespace_prefix(env, env->namespace), def->name, "$$struct"); + CORD typeinfo_name = namespace_name(env, env->namespace, CORD_all(def->name, "$$info")); + CORD type_code = def->external ? def->name : CORD_all("struct ", namespace_name(env, env->namespace, CORD_all(def->name, "$$struct"))); CORD fields = CORD_EMPTY; for (arg_ast_t *field = def->fields; field; field = field->next) { @@ -70,9 +70,8 @@ CORD compile_struct_header(env_t *env, ast_t *ast) CORD optional_code = CORD_EMPTY; if (!def->opaque) { optional_code = CORD_all("DEFINE_OPTIONAL_TYPE(", compile_type(t), ", ", unpadded_size, ",", - namespace_prefix(env, env->namespace), "$Optional", def->name, "$$type);\n"); + namespace_name(env, env->namespace, CORD_all("$Optional", def->name, "$$type")), ");\n"); } return CORD_all(struct_code, optional_code, typeinfo_code); } - // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/tomo.c b/src/tomo.c index dd61891e..ba6c3995 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -91,7 +91,6 @@ static void transpile_code(env_t *base_env, Path_t path); static void compile_object_file(Path_t path); static Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t object_files, List_t extra_ldlibs); static void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_link); -static Text_t escape_lib_name(Text_t lib_name); static void build_library(Path_t lib_dir); static void install_library(Path_t lib_dir); static void compile_files(env_t *env, List_t files, List_t *object_files, List_t *ldlibs); @@ -336,16 +335,6 @@ void wait_for_child_success(pid_t child) } } -Text_t escape_lib_name(Text_t lib_name) -{ - char *libname_id = String(lib_name); - for (char *p = libname_id; *p; p++) { - if (!isalnum(*p) && *p != '_') - *p = '_'; - } - return Text$from_str(libname_id); -} - Path_t build_file(Path_t path, const char *extension) { Path_t build_dir = Path$with_component(Path$parent(path), Text(".build")); @@ -386,20 +375,24 @@ static void _make_typedefs_for_library(libheader_info_t *info, ast_t *ast) { if (ast->tag == StructDef) { DeclareMatch(def, ast, StructDef); - CORD full_name = CORD_cat(namespace_prefix(info->env, info->env->namespace), def->name); - CORD_put(CORD_all("typedef struct ", full_name, "$$struct ", full_name, "$$type;\n"), info->output); + CORD struct_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$struct")); + CORD type_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$type")); + CORD_put(CORD_all("typedef struct ", struct_name, " ", type_name, ";\n"), info->output); } else if (ast->tag == EnumDef) { DeclareMatch(def, ast, EnumDef); - CORD full_name = CORD_cat(namespace_prefix(info->env, info->env->namespace), def->name); - CORD_put(CORD_all("typedef struct ", full_name, "$$struct ", full_name, "$$type;\n"), info->output); + CORD struct_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$struct")); + CORD type_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$type")); + CORD_put(CORD_all("typedef struct ", struct_name, " ", type_name, ";\n"), info->output); for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { if (!tag->fields) continue; - CORD_put(CORD_all("typedef struct ", full_name, "$", tag->name, "$$struct ", full_name, "$", tag->name, "$$type;\n"), info->output); + CORD tag_struct_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$", tag->name, "$$struct")); + CORD tag_type_name = namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$", tag->name, "$$type")); + CORD_put(CORD_all("typedef struct ", tag_struct_name, " ", tag_type_name, ";\n"), info->output); } } else if (ast->tag == LangDef) { DeclareMatch(def, ast, LangDef); - CORD_put(CORD_all("typedef Text_t ", namespace_prefix(info->env, info->env->namespace), def->name, "$$type;\n"), info->output); + CORD_put(CORD_all("typedef Text_t ", namespace_name(info->env, info->env->namespace, CORD_all(def->name, "$$type")), ";\n"), info->output); } } @@ -438,7 +431,7 @@ static void _compile_file_header_for_library(env_t *env, Path_t header_path, Pat visit_topologically( Match(file_ast, Block)->statements, (Closure_t){.fn=(void*)_compile_statement_header_for_library, &info}); - CORD_put(CORD_all("void ", namespace_prefix(module_env, module_env->namespace), "$initialize(void);\n"), output); + CORD_put(CORD_all("void ", namespace_name(module_env, module_env->namespace, "$initialize"), "(void);\n"), output); } void build_library(Path_t lib_dir) @@ -455,10 +448,6 @@ void build_library(Path_t lib_dir) compile_files(env, tm_files, &object_files, &extra_ldlibs); - // Library name replaces all stretchs of non-alphanumeric chars with an underscore - // So e.g. https://github.com/foo/baz --> https_github_com_foo_baz - env->libname = Text$as_c_string(escape_lib_name(lib_dir_name)); - // Build a "whatever.h" header that loads all the headers: Path_t header_path = Path$with_component(lib_dir, Texts(lib_dir_name, Text(".h"))); FILE *header = fopen(String(header_path), "w"); @@ -473,28 +462,8 @@ void build_library(Path_t lib_dir) if (fclose(header) == -1) print_err("Failed to write header file: ", header_path); - // Build up a list of symbol renamings: - Path_t build_dir = Path$with_component(lib_dir, Text(".build")); - Path_t symbol_renames = Path$with_component(build_dir, Text("symbol_renames.txt")); - if (is_stale_for_any(symbol_renames, object_files)) { - Path$remove(symbol_renames, true); - FILE *prog; - for (int64_t i = 0; i < object_files.length; i++) { - Path_t obj = *(Path_t*)(object_files.data + i*object_files.stride); - prog = run_cmd("nm -g '", obj, "' | awk '$2~/^[DTB]$/ && $3~/^_\\$/{print $3 \" _$", - CORD_to_const_char_star(env->libname), "\" substr($3,2)}' " - ">>", symbol_renames); - if (!prog) print_err("Could not find symbols!"); - int status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - errx(WEXITSTATUS(status), "Failed to create symbol rename table with `nm` and `awk`"); - } - } else { - if (verbose) whisper("Unchanged: ", symbol_renames); - } - Path_t shared_lib = Path$with_component(lib_dir, Texts(Text("lib"), lib_dir_name, Text(SHARED_SUFFIX))); - if (!is_stale_for_any(shared_lib, object_files) && !is_stale(shared_lib, symbol_renames)) { + if (!is_stale_for_any(shared_lib, object_files)) { if (verbose) whisper("Unchanged: ", shared_lib); return; } @@ -515,28 +484,6 @@ void build_library(Path_t lib_dir) if (!quiet) print("Compiled library:\t", shared_lib); - - prog = run_cmd("objcopy --redefine-syms='", symbol_renames, "' '", shared_lib, "'"); - status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - errx(WEXITSTATUS(status), "Failed to run `objcopy` to add library prefix to symbols"); - -#if defined(__ELF__) - prog = run_cmd("patchelf --rename-dynamic-symbols '", symbol_renames, "' '", shared_lib, "'"); - status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - errx(WEXITSTATUS(status), "Failed to run `patchelf` to rename dynamic symbols with library prefix"); -#elif defined(__MACH__) - prog = run_cmd("llvm-objcopy --redefine-syms='", symbol_renames, "' '", shared_lib, "'"); - status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - errx(WEXITSTATUS(status), "Failed to run `llvm-objcopy` to rename dynamic symbols with library prefix"); -#else -#error "Unknown platform (not ELF or MACH)" -#endif - - if (verbose) - print("Successfully renamed symbols with library prefix!"); } void install_library(Path_t lib_dir) @@ -549,6 +496,7 @@ void install_library(Path_t lib_dir) if (verbose) whisper("Moving files to ", dest); xsystem("mkdir -p '", dest, "'"); xsystem("cp -r '", lib_dir, "'/* '", dest, "/'"); + xsystem("cp -r '", lib_dir, "'/.build '", dest, "/'"); } if (verbose) whisper("Linking "TOMO_HOME"/lib/lib", lib_dir_name, SHARED_SUFFIX); xsystem("mkdir -p '"TOMO_HOME"'/lib/"); @@ -564,6 +512,8 @@ void install_library(Path_t lib_dir) print("Installed \033[1m", lib_dir_name, "\033[m to "TOMO_HOME"/installed"); } +#include "stdlib/random.h" + void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs) { Table_t to_link = {}; @@ -578,6 +528,33 @@ void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t * build_file_dependency_graph(filename, &dependency_files, &to_link); } + // Make sure all files and dependencies have a .id file: + for (int64_t i = 0; i < dependency_files.entries.length; i++) { + struct { + Path_t filename; + staleness_t staleness; + } *entry = (dependency_files.entries.data + i*dependency_files.entries.stride); + + Path_t id_file = build_file(entry->filename, ".id"); + if (!Path$exists(id_file)) { + static const char id_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + char id_str[8]; + for (int j = 0; j < (int)sizeof(id_str); j++) { + id_str[j] = id_chars[random_range(0, sizeof(id_chars)-1)]; + } + Text_t filename_id = Text(""); + Text_t base = Path$base_name(entry->filename); + TextIter_t state = NEW_TEXT_ITER_STATE(base); + for (int64_t j = 0; j < base.length; j++) { + uint32_t c = Text$get_main_grapheme_fast(&state, j); + if (c == '.') break; + if (isalpha(c) || isdigit(c) || c == '_') + filename_id = Texts(filename_id, Text$from_strn((char[]){(char)c}, 1)); + } + Path$write(id_file, Texts(filename_id, Text("_"), Text$from_strn(id_str, sizeof(id_str))), 0644); + } + } + // (Re)compile header files, eagerly for explicitly passed in files, lazily // for downstream dependencies: for (int64_t i = 0; i < dependency_files.entries.length; i++) { @@ -585,6 +562,7 @@ void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t * Path_t filename; staleness_t staleness; } *entry = (dependency_files.entries.data + i*dependency_files.entries.stride); + if (entry->staleness.h || clean_build) { transpile_header(env, entry->filename); entry->staleness.o = true; @@ -661,8 +639,8 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l return; staleness_t staleness = { - .h=is_stale(build_file(path, ".h"), path), - .c=is_stale(build_file(path, ".c"), path), + .h=is_stale(build_file(path, ".h"), path) || is_stale(build_file(path, ".h"), build_file(path, ".id")), + .c=is_stale(build_file(path, ".c"), path) || is_stale(build_file(path, ".c"), build_file(path, ".id")), }; staleness.o = staleness.c || staleness.h || is_stale(build_file(path, ".o"), build_file(path, ".c")) @@ -807,7 +785,7 @@ void transpile_code(env_t *base_env, Path_t path) "int ", main_binding->code, "$parse_and_run(int argc, char *argv[]) {\n", module_env->do_source_mapping ? "#line 1\n" : CORD_EMPTY, "tomo_init();\n", - namespace_prefix(module_env, module_env->namespace), "$initialize();\n" + namespace_name(module_env, module_env->namespace, "$initialize"), "();\n" "\n", compile_cli_arg_call(module_env, main_binding->code, main_binding->type), "return 0;\n" diff --git a/src/typecheck.c b/src/typecheck.c index 6c483511..0247c926 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -188,19 +188,13 @@ static env_t *load_module(env_t *env, ast_t *module_ast) env_t *module_env = fresh_scope(env); Table$str_set(env->imports, use->path, module_env); - char *libname_id = String(use->path); - for (char *p = libname_id; *p; p++) { - if (!isalnum(*p) && *p != '_') - *p = '_'; - } - module_env->libname = libname_id; + for (size_t i = 0; i < tm_files.gl_pathc; i++) { const char *filename = tm_files.gl_pathv[i]; ast_t *ast = parse_file(filename, NULL); if (!ast) print_err("Could not compile file ", filename); env_t *module_file_env = fresh_scope(module_env); - char *file_prefix = file_base_id(filename); - module_file_env->namespace = new(namespace_t, .name=file_prefix); + module_file_env->namespace = NULL; env_t *subenv = load_module_env(module_file_env, ast); for (int64_t j = 0; j < subenv->locals->entries.length; j++) { struct { @@ -236,7 +230,7 @@ void prebind_statement(env_t *env, ast_t *statement) type_t *type = Type(StructType, .name=def->name, .opaque=true, .external=def->external, .env=ns_env); // placeholder Table$str_set(env->types, def->name, type); set_binding(env, def->name, Type(TypeInfoType, .name=def->name, .type=type, .env=ns_env), - CORD_all(namespace_prefix(env, env->namespace), def->name, "$$info")); + namespace_name(env, env->namespace, CORD_all(def->name, "$$info"))); for (ast_list_t *stmt = def->namespace ? Match(def->namespace, Block)->statements : NULL; stmt; stmt = stmt->next) prebind_statement(ns_env, stmt->ast); break; @@ -250,7 +244,7 @@ void prebind_statement(env_t *env, ast_t *statement) type_t *type = Type(EnumType, .name=def->name, .opaque=true, .env=ns_env); // placeholder Table$str_set(env->types, def->name, type); set_binding(env, def->name, Type(TypeInfoType, .name=def->name, .type=type, .env=ns_env), - CORD_all(namespace_prefix(env, env->namespace), def->name, "$$info")); + namespace_name(env, env->namespace, CORD_all(def->name, "$$info"))); for (ast_list_t *stmt = def->namespace ? Match(def->namespace, Block)->statements : NULL; stmt; stmt = stmt->next) prebind_statement(ns_env, stmt->ast); break; @@ -264,7 +258,7 @@ void prebind_statement(env_t *env, ast_t *statement) type_t *type = Type(TextType, .lang=def->name, .env=ns_env); Table$str_set(env->types, def->name, type); set_binding(env, def->name, Type(TypeInfoType, .name=def->name, .type=type, .env=ns_env), - CORD_all(namespace_prefix(env, env->namespace), def->name, "$$info")); + namespace_name(env, env->namespace, CORD_all(def->name, "$$info"))); for (ast_list_t *stmt = def->namespace ? Match(def->namespace, Block)->statements : NULL; stmt; stmt = stmt->next) prebind_statement(ns_env, stmt->ast); break; @@ -276,7 +270,7 @@ void prebind_statement(env_t *env, ast_t *statement) *extended = *ns_env; extended->locals = new(Table_t, .fallback=env->locals); extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings); - extended->libname = env->libname; + extended->id_suffix = env->id_suffix; for (ast_list_t *stmt = extend->body ? Match(extend->body, Block)->statements : NULL; stmt; stmt = stmt->next) prebind_statement(extended, stmt->ast); List_t new_bindings = extended->locals->entries; @@ -320,8 +314,11 @@ void bind_statement(env_t *env, ast_t *statement) code_err(statement, "I couldn't figure out the type of this value"); if (type->tag == FunctionType) type = Type(ClosureType, type); - CORD prefix = namespace_prefix(env, env->namespace); - CORD code = CORD_cat(prefix ? prefix : "$", name); + CORD code; + if (name[0] != '_' && (env->namespace || decl->top_level)) + code = namespace_name(env, env->namespace, name); + else + code = CORD_all("_$", name); set_binding(env, name, type, code); break; } @@ -329,8 +326,7 @@ void bind_statement(env_t *env, ast_t *statement) DeclareMatch(def, statement, FunctionDef); const char *name = Match(def->name, Var)->name; type_t *type = get_function_def_type(env, statement); - CORD code = CORD_all(namespace_prefix(env, env->namespace), name); - set_binding(env, name, type, code); + set_binding(env, name, type, namespace_name(env, env->namespace, name)); break; } case ConvertDef: { @@ -340,8 +336,8 @@ void bind_statement(env_t *env, ast_t *statement) if (!name) code_err(statement, "Conversions are only supported for text, struct, and enum types, not ", type_to_str(ret_t)); - CORD code = CORD_all(namespace_prefix(env, env->namespace), name, "$", - String(get_line_number(statement->file, statement->start))); + CORD code = namespace_name(env, env->namespace, + CORD_all(name, "$", String(get_line_number(statement->file, statement->start)))); binding_t binding = {.type=type, .code=code}; env_t *type_ns = get_namespace_by_type(env, ret_t); List$insert(&type_ns->namespace->constructors, &binding, I(0), sizeof(binding)); @@ -435,9 +431,10 @@ void bind_statement(env_t *env, ast_t *statement) for (tag_t *tag = tags; tag; tag = tag->next) { if (Match(tag->type, StructType)->fields) { // Constructor: type_t *constructor_t = Type(FunctionType, .args=Match(tag->type, StructType)->fields, .ret=type); - set_binding(ns_env, tag->name, constructor_t, CORD_all(namespace_prefix(env, env->namespace), def->name, "$tagged$", tag->name)); + set_binding(ns_env, tag->name, constructor_t, namespace_name(env, env->namespace, CORD_all(def->name, "$tagged$", tag->name))); } else { // Empty singleton value: - CORD code = CORD_all("((", namespace_prefix(env, env->namespace), def->name, "$$type){", namespace_prefix(env, env->namespace), def->name, "$tag$", tag->name, "})"); + CORD code = CORD_all("((", namespace_name(env, env->namespace, CORD_all(def->name, "$$type")), "){", + namespace_name(env, env->namespace, CORD_all(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); @@ -455,7 +452,7 @@ void bind_statement(env_t *env, ast_t *statement) Table$str_set(env->types, def->name, type); set_binding(ns_env, "from_text", NewFunctionType(type, {.name="text", .type=TEXT_TYPE}), - CORD_all("(", namespace_prefix(env, env->namespace), def->name, "$$type)")); + CORD_all("(", namespace_name(env, env->namespace, CORD_all(def->name, "$$type")), ")")); for (ast_list_t *stmt = def->namespace ? Match(def->namespace, Block)->statements : NULL; stmt; stmt = stmt->next) bind_statement(ns_env, stmt->ast); @@ -468,7 +465,7 @@ void bind_statement(env_t *env, ast_t *statement) *extended = *ns_env; extended->locals = new(Table_t, .fallback=env->locals); extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings); - extended->libname = env->libname; + extended->id_suffix = env->id_suffix; for (ast_list_t *stmt = extend->body ? Match(extend->body, Block)->statements : NULL; stmt; stmt = stmt->next) bind_statement(extended, stmt->ast); List_t new_bindings = extended->locals->entries; -- cgit v1.2.3