From 0605a8c5005a89334b3358cd07b2d07f9da2fab7 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sun, 30 May 2021 15:11:12 +0200 Subject: [PATCH] Introduce "private" keyword and private modules / private import. --- resources/testfragments/parsertest.c3 | 2 +- src/compiler/compiler.c | 4 +- src/compiler/compiler_internal.h | 5 +- src/compiler/context.c | 45 +++++--- src/compiler/enums.h | 1 + src/compiler/lexer.c | 41 ------- src/compiler/parse_global.c | 28 ++--- src/compiler/parse_stmt.c | 1 + src/compiler/sema_decls.c | 2 +- src/compiler/sema_passes.c | 12 +- src/compiler/symtab.c | 2 +- src/compiler/tokens.c | 4 +- src/compiler_tests/tests.c | 115 +------------------ test/test_suite/generic/generic_idents.c3t | 91 +++++++++++++++ test/test_suite/visibility/private_import.c3 | 21 ++++ test/test_suite/visibility/private_module.c3 | 17 +++ 16 files changed, 192 insertions(+), 199 deletions(-) create mode 100644 test/test_suite/generic/generic_idents.c3t create mode 100644 test/test_suite/visibility/private_import.c3 create mode 100644 test/test_suite/visibility/private_module.c3 diff --git a/resources/testfragments/parsertest.c3 b/resources/testfragments/parsertest.c3 index 481f69952..31f671b22 100644 --- a/resources/testfragments/parsertest.c3 +++ b/resources/testfragments/parsertest.c3 @@ -1,4 +1,4 @@ -module foo ($foo, #bar, Integer); +module foo <$foo, #bar, Integer>; import bar as eok local; import bar2 as eok2; import bar3 local; diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index fd7ea830c..e4c325b29 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -281,6 +281,7 @@ void compiler_compile(void) if (!global_context.module_list) { + if (global_context.errors_found) exit(EXIT_FAILURE); error_exit("No modules to compile."); } VECEACH(global_context.generic_module_list, i) @@ -496,7 +497,7 @@ Module *global_context_find_module(const char *name) return stable_get(&global_context.modules, name); } -Module *compiler_find_or_create_module(Path *module_name, TokenId *parameters) +Module *compiler_find_or_create_module(Path *module_name, TokenId *parameters, bool is_private) { Module *module = global_context_find_module(module_name->module); if (module) return module; @@ -507,6 +508,7 @@ Module *compiler_find_or_create_module(Path *module_name, TokenId *parameters) module->name = module_name; module->stage = ANALYSIS_NOT_BEGUN; module->parameters = parameters; + module->is_private = is_private; stable_init(&module->symbols, 0x10000); stable_set(&global_context.modules, module_name->module, module); if (parameters) diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index a08e35ecf..a7a6aece5 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1159,6 +1159,7 @@ typedef struct Module_ bool is_c_library : 1; bool is_exported : 1; bool is_generic : 1; + bool is_private : 1; AnalysisStage stage : 6; Ast **files; // Asts @@ -1555,7 +1556,7 @@ void header_gen(Module *module); void global_context_add_type(Type *type); Decl *compiler_find_symbol(const char *name); -Module *compiler_find_or_create_module(Path *module_name, TokenId *parameters); +Module *compiler_find_or_create_module(Path *module_name, TokenId *parameters, bool is_private); Module *global_context_find_module(const char *name); void compiler_register_public_symbol(Decl *decl); @@ -1564,7 +1565,7 @@ void context_register_global_decl(Context *context, Decl *decl); void context_register_external_symbol(Context *context, Decl *decl); bool context_add_import(Context *context, Path *path, Token symbol, Token alias, bool private_import); bool context_set_module_from_filename(Context *context); -bool context_set_module(Context *context, Path *path, TokenId *generic_parameters); +bool context_set_module(Context *context, Path *path, TokenId *generic_parameters, bool is_private); void context_print_ast(Context *context, FILE *file); #pragma mark --- Decl functions diff --git a/src/compiler/context.c b/src/compiler/context.c index 0dc095e23..2a849eb52 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -15,24 +15,39 @@ Context *context_create(File *file) } -static inline bool create_module_or_check_name(Context *context, Path *module_name, TokenId *parameters) +static inline bool create_module_or_check_name(Context *context, Path *module_name, TokenId *parameters, bool is_private) { context->module_name = module_name; - if (context->module == NULL) - { - context->module = compiler_find_or_create_module(module_name, parameters); - vec_add(context->module->contexts, context); - return true; - } + if (context->module == NULL) + { + context->module = compiler_find_or_create_module(module_name, parameters, is_private); + } + else + { + if (context->module->name->module != module_name->module) + { + SEMA_ERROR(module_name, + "Module name here '%s' did not match actual module '%s'.", + module_name->module, + context->module->name->module); + return false; + } + } - if (context->module->name->module != module_name->module) + if (context->module->is_private != is_private) { - SEMA_ERROR(module_name, "Module name here '%s' did not match actual module '%s'.", module_name->module, context->module->name->module); - return false; - } + if (is_private) + { + SEMA_ERROR(module_name, "The module is declared as private here, but was declared as public elsewhere."); + } + else + { + SEMA_ERROR(module_name, "The module is declared as public here, but was declared as private elsewhere."); + } + return false; + } vec_add(context->module->contexts, context); - return true; } @@ -58,10 +73,10 @@ bool context_set_module_from_filename(Context *context) path->span = INVALID_RANGE; path->module = module_name; path->len = len; - return create_module_or_check_name(context, path, NULL); + return create_module_or_check_name(context, path, NULL, true); } -bool context_set_module(Context *context, Path *path, TokenId *generic_parameters) +bool context_set_module(Context *context, Path *path, TokenId *generic_parameters, bool is_private) { // Note that we allow the illegal name for now, to be able to parse further. context->module_name = path; @@ -71,7 +86,7 @@ bool context_set_module(Context *context, Path *path, TokenId *generic_parameter return false; } - return create_module_or_check_name(context, path, generic_parameters); + return create_module_or_check_name(context, path, generic_parameters, is_private); } diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 7e69a9f53..5249b29e4 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -426,6 +426,7 @@ typedef enum TOKEN_MODULE, TOKEN_NEXTCASE, TOKEN_NULL, + TOKEN_PRIVATE, TOKEN_RETURN, TOKEN_STATIC, TOKEN_STRUCT, diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index b694989d9..f3f5921e1 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -1138,44 +1138,3 @@ void lexer_init_with_file(Lexer *lexer, File *file) } -#pragma mark --- Test methods - -void lexer_init_for_test(Lexer *lexer, const char *text, size_t len) -{ - static File helper; - lexer->current_line = 1; - lexer->line_start = lexer->current; - lexer->lexing_start = text; - lexer->current = text; - lexer->file_begin = text; - lexer->current_file = &helper; - lexer->current_file->start_id = 0; - lexer->current_file->contents = text; - lexer->current_file->end_id = len; - lexer->current_file->name = "Test"; -} - -bool lexer_scan_ident_test(Lexer *lexer, const char *scan) -{ - static File helper; - lexer->lexing_start = scan; - lexer->current = scan; - lexer->file_begin = scan; - lexer->current_file = &helper; - lexer->current_file->start_id = 0; - lexer->current_file->contents = scan; - lexer->current_file->end_id = 1000; - lexer->current_file->name = "Foo"; - - if (scan[0] == '$') - { - next(lexer); - return scan_ident(lexer, TOKEN_CT_IDENT, TOKEN_CT_CONST_IDENT, TOKEN_CT_TYPE_IDENT, '$'); - } - if (scan[0] == '#') - { - next(lexer); - return scan_ident(lexer, TOKEN_HASH_IDENT, TOKEN_HASH_CONST_IDENT, TOKEN_HASH_TYPE_IDENT, '#'); - } - return scan_ident(lexer, TOKEN_IDENT, TOKEN_CONST_IDENT, TOKEN_TYPE_IDENT, 0); -} diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 179a26a8a..d7012e0de 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -342,6 +342,8 @@ bool parse_module(Context *context) return context_set_module_from_filename(context); } + bool is_private = try_consume(context, TOKEN_PRIVATE); + if (!TOKEN_IS(TOKEN_IDENT)) { SEMA_TOKEN_ERROR(context->tok, "Module statement should be followed by the name of the module."); @@ -357,7 +359,7 @@ bool parse_module(Context *context) path->len = strlen("INVALID"); path->module = "INVALID"; path->span = INVALID_RANGE; - context_set_module(context, path, NULL); + context_set_module(context, path, NULL, false); recover_top_level(context); return false; } @@ -366,11 +368,11 @@ bool parse_module(Context *context) TokenId *generic_parameters = NULL; if (!parse_optional_module_params(context, &generic_parameters)) { - context_set_module(context, path, NULL); + context_set_module(context, path, NULL, is_private); recover_top_level(context); return true; } - context_set_module(context, path, generic_parameters); + context_set_module(context, path, generic_parameters, is_private); TRY_CONSUME_EOS_OR(false); return true; } @@ -2065,6 +2067,8 @@ static inline bool parse_import(Context *context) { advance_and_verify(context, TOKEN_IMPORT); + bool private = try_consume(context, TOKEN_PRIVATE); + if (!TOKEN_IS(TOKEN_IDENT)) { SEMA_TOKEN_ERROR(context->tok, "Import statement should be followed by the name of the module to import."); @@ -2072,23 +2076,7 @@ static inline bool parse_import(Context *context) } Path *path = parse_module_path(context); - if (TOKEN_IS(TOKEN_COLON)) - { - while (1) - { - if (!parse_specified_import(context, path)) return false; - if (!try_consume(context, TOKEN_COMMA)) break; - } - } - else - { - bool private = try_consume(context, TOKEN_AS); - if (private) - { - CONSUME_OR(TOKEN_MODULE, false); - } - context_add_import(context, path, NO_TOKEN, NO_TOKEN, private); - } + context_add_import(context, path, NO_TOKEN, NO_TOKEN, private); TRY_CONSUME_EOS_OR(false); return true; } diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index e49411d05..a085278ee 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -1166,6 +1166,7 @@ Ast *parse_stmt(Context *context) case TOKEN_RBRAPIPE: case TOKEN_BANGBANG: case TOKEN_UNDERSCORE: + case TOKEN_PRIVATE: SEMA_TOKEN_ERROR(context->tok, "Unexpected '%s' found when expecting a statement.", token_type_to_string(context->tok.type)); advance(context); return poisoned_ast; diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 30f6446f4..64ff08bcb 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -1018,7 +1018,7 @@ static Context *copy_context(Module *module, Context *c) static Module *sema_instantiate_module(Context *context, Module *module, Path *path, TypeInfo **parms) { - Module *new_module = compiler_find_or_create_module(path, NULL); + Module *new_module = compiler_find_or_create_module(path, NULL, module->is_private); new_module->is_generic = true; Context **contexts = module->contexts; VECEACH(contexts, i) diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index 58a7518e2..096234ee2 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -57,12 +57,20 @@ void sema_analysis_pass_process_imports(Module *module) continue; } - // 7. Assign the module. + // 7. Importing private is not allowed. + if (import_module->is_private && !import->import.private) + { + SEMA_ERROR(import, "Importing a private module is not allowed (unless 'import private' is used)."); + decl_poison(import); + continue; + } + + // 8. Assign the module. DEBUG_LOG("* Import of %s.", path->module); import->module = import_module; for (unsigned j = 0; j < i; j++) { - // 7. We might run into multiple imports of the same package. + // 9. We might run into multiple imports of the same package. if (import->module == context->imports[j]->module) { SEMA_ERROR(import, "Module '%s' was imported more than once, please remove the duplicates.", path->module); diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index 0d5412713..a0f250289 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -78,7 +78,7 @@ void symtab_init(uint32_t capacity) symtab.capacity = capacity; // Add keywords. - for (int i = 0; i < TOKEN_EOF; i++) + for (int i = 0; i < TOKEN_LAST; i++) { const char* name = token_type_to_string(i); // Skip non-keywords diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index af46837b3..84fd73dac 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -232,6 +232,8 @@ const char *token_type_to_string(TokenType type) return "nextcase"; case TOKEN_NULL: return "null"; + case TOKEN_PRIVATE: + return "private"; case TOKEN_RETURN: return "return"; case TOKEN_STATIC: @@ -307,7 +309,7 @@ const char *token_type_to_string(TokenType type) case TOKEN_DOCS_END: return "*/"; case TOKEN_DOCS_DIRECTIVE: - return "directive"; + return "DIRECTIVE"; case TOKEN_DOCS_LINE: return "DOCS_LINE"; diff --git a/src/compiler_tests/tests.c b/src/compiler_tests/tests.c index c0aa1c67d..f606e4272 100644 --- a/src/compiler_tests/tests.c +++ b/src/compiler_tests/tests.c @@ -9,119 +9,7 @@ #include "compiler/compiler_internal.h" #include "benchmark.h" -static void test_lexer(void) -{ -#ifdef __OPTIMIZE__ - printf("--- RUNNING OPTIMIZED ---\n"); -#endif - run_arena_allocator_tests(); - printf("Begin lexer testing.\n"); - printf("-- Check number of keywords...\n"); - int tokens_found = 0; - const int EXPECTED_TOKENS = TOKEN_CT_SWITCH - TOKEN_ALIAS + 1 + TOKEN_TYPEID - TOKEN_VOID + 1; - const char* tokens[TOKEN_EOF]; - int len[TOKEN_EOF]; - Lexer lexer; - for (int i = 1; i < TOKEN_EOF; i++) - { - const char* token = token_type_to_string((TokenType)i); - tokens[i] = token; - len[i] = (int)strlen(token); - TokenType lookup = TOKEN_IDENT; - const char* interned = symtab_add(token, len[i], fnv1a(token, len[i]), &lookup); - if (lookup != TOKEN_IDENT) - { - if (!lexer_scan_ident_test(&lexer, token)) - { - TEST_ASSERT(false, "Failed to scan token %s", token); - } - int index = toktype_arena.allocated; - TokenType type_scanned = (TokenType)(toktypeptr(index - 1))[0]; - TEST_ASSERT(type_scanned == (TokenType)i, "Mismatch scanning: was '%s', expected '%s' - lookup: %s - interned: %s.", - token_type_to_string(type_scanned), - token_type_to_string(i), - token_type_to_string(lookup), - interned); - tokens_found++; - } - else - { - tokens[i] = "casi"; - len[i] = 4; - } - } - printf("-> %d keywords found.\n", tokens_found); - EXPECT("Keywords", tokens_found, EXPECTED_TOKENS); - const int BENCH_REPEATS = 10000; - - printf("-- Test keyword lexing speed...\n"); - bench_begin(); - for (int b = 0; b < BENCH_REPEATS; b++) - { - for (int i = 1; i < TOKEN_EOF; i++) - { - volatile bool t = lexer_scan_ident_test(&lexer, tokens[i]); - (void)t; - } - } - - printf("-> Test complete in %fs, %.0f kkeywords/s\n", bench_mark(), (BENCH_REPEATS * (TOKEN_EOF - 1)) / (1000 * bench_mark())); - -#include "shorttest.c" - - printf("-- Test token lexing speed...\n"); - const char *pointer = test_parse; - int loc = 0; - while (*pointer != '\0') - { - if (*(pointer++) == '\n') loc++; - } - - bench_begin(); - int tokens_parsed = 0; - size_t test_len = strlen(test_parse); - for (int b = 0; b < BENCH_REPEATS; b++) - { - lexer_init_for_test(&lexer, test_parse, test_len); - Token token; - while (1) - { - token = lexer_advance(&lexer); - if (token.type == TOKEN_EOF) break; - TEST_ASSERT(token.type != TOKEN_INVALID_TOKEN, "Got invalid token"); - - tokens_parsed++; - } - } - - printf("-> Test complete in %fs, %.0f kloc/s, %.0f ktokens/s\n", bench_mark(), - loc * BENCH_REPEATS / (1000 * bench_mark()), tokens_parsed / (1000 * bench_mark())); -} - -void test_compiler(void) -{ - const char **files = NULL; - file_add_wildcard_files(&files, "tests", true); - if (!vec_size(files)) - { - error_exit("No test files could be found."); - } - - const char **single_file = VECNEW(const char *, 1); - vec_add(single_file, files[0]); - - VECEACH(files, i) - { - printf("Running %s...\n", files[i]); - char *res = NULL; - asprintf(&res, "tests/%s", files[i]); - single_file[0] = res; - active_target = (BuildTarget) { .type = TARGET_TYPE_EXECUTABLE, .sources = single_file, .name = "a.out" }; - free(res); - } - -} void test_file(void) { @@ -155,8 +43,7 @@ void compiler_tests(void) symtab_init(0x100000); test_file(); - test_lexer(); - test_compiler(); + run_arena_allocator_tests(); exit(0); } \ No newline at end of file diff --git a/test/test_suite/generic/generic_idents.c3t b/test/test_suite/generic/generic_idents.c3t new file mode 100644 index 000000000..14cb03b22 --- /dev/null +++ b/test/test_suite/generic/generic_idents.c3t @@ -0,0 +1,91 @@ +module gen ; + +func Type mult(Type x) +{ + return x * x; +} + +func Type addMult(Type x, Type a, Type b) +{ + return x * a + b; +} + +module test; +import gen; + +define intMult = gen::mult; +define doubleAddMult = gen::addMult; + +func int getIt(int i) +{ + return intMult(i) + 1; +} + +func double getIt2(double i) +{ + return doubleAddMult(i, 2, 3); +} + +// #expect: gen.int.ll + +define i32 @gen.int.mult(i32 %0) + %x = alloca i32, align 4 + store i32 %0, i32* %x, align 4 + %1 = load i32, i32* %x, align 4 + %2 = load i32, i32* %x, align 4 + %mul = mul i32 %1, %2 + ret i32 %mul + +; Function Attrs: nounwind +define i32 @gen.int.addMult(i32 %0, i32 %1, i32 %2) + %x = alloca i32, align 4 + %a = alloca i32, align 4 + %b = alloca i32, align 4 + store i32 %0, i32* %x, align 4 + store i32 %1, i32* %a, align 4 + store i32 %2, i32* %b, align 4 + %3 = load i32, i32* %x, align 4 + %4 = load i32, i32* %a, align 4 + %mul = mul i32 %3, %4 + %5 = load i32, i32* %b, align 4 + %add = add i32 %mul, %5 + ret i32 %add + +// #expect: test.ll + +declare i32 @gen.int.mult(i32) +declare double @gen.double.addMult(double, double, double) + +define i32 @test.getIt(i32 %0) + + %i = alloca i32, align 4 + store i32 %0, i32* %i, align 4 + %1 = load i32, i32* %i, align 4 + %2 = call i32 @gen.int.mult(i32 %1) + %add = add i32 %2, 1 + ret i32 %add + +define double @test.getIt2(double %0) + + %i = alloca double, align 8 + store double %0, double* %i, align 8 + %1 = load double, double* %i, align 8 + %2 = call double @gen.double.addMult(double %1, double 2.000000e+00, double 3.000000e+00) + ret double %2 +} + +// #expect: gen.double.ll + +define double @gen.double.addMult(double %0, double %1, double %2) + %x = alloca double, align 8 + %a = alloca double, align 8 + %b = alloca double, align 8 + store double %0, double* %x, align 8 + store double %1, double* %a, align 8 + store double %2, double* %b, align 8 + %3 = load double, double* %x, align 8 + %4 = load double, double* %a, align 8 + %fmul = fmul double %3, %4 + %5 = load double, double* %b, align 8 + %fadd = fadd double %fmul, %5 + ret double %fadd diff --git a/test/test_suite/visibility/private_import.c3 b/test/test_suite/visibility/private_import.c3 new file mode 100644 index 000000000..e5d22719a --- /dev/null +++ b/test/test_suite/visibility/private_import.c3 @@ -0,0 +1,21 @@ +module foo; + +static func void hidden() +{ +} + +module bar; +import foo; + +func void test() +{ + foo::hidden(); // #error: The function 'hidden' is not visible from this module. +} + +module baz; +import private foo; + +func void test() +{ + foo::hidden(); +} \ No newline at end of file diff --git a/test/test_suite/visibility/private_module.c3 b/test/test_suite/visibility/private_module.c3 new file mode 100644 index 000000000..631dafdf9 --- /dev/null +++ b/test/test_suite/visibility/private_module.c3 @@ -0,0 +1,17 @@ +module private foo; + +func void hidden() +{ +} + +module bar; +import foo; // #error: Importing a private module is not allowed (unless 'import private' is used). + + +module baz; +import private foo; + +func void test() +{ + foo::hidden(); +} \ No newline at end of file