diff --git a/releasenotes.md b/releasenotes.md index 85c0a0ed5..9b91ac2e0 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -17,6 +17,7 @@ - Permit foreach values to be optional. - Add `--show-backtrace` option to disable backtrace for even smaller binary. - Untested Xtensa support. +- `$expand` macro, to expand a string into code. ### Fixes @@ -25,6 +26,7 @@ - `dbghelp.lib` was linked even on nolibc on Windows. - Fix incorrect linker selection on some platforms. - Struct members declared in a single line declaration were not sharing attributes. #1266 +- `opt` project setting now properly documented. ### Stdlib changes diff --git a/src/build/project.c b/src/build/project.c index 623a9df04..cac1ae031 100644 --- a/src/build/project.c +++ b/src/build/project.c @@ -33,7 +33,7 @@ const char *project_default_keys[][2] = { {"macossdk", "Set the directory for the MacOS SDK for cross compilation."}, {"memory-env", "Set the memory environment: normal, small, tiny, none."}, {"no-entry", "Do not generate (or require) a main function."}, - {"opt", "Link libc (default: true)."}, + {"opt", "Optimization setting: O0, O1, O2, O3, O4, O5, Os, Oz."}, {"optlevel", "Code optimization level: none, less, more, max."}, {"optsize", "Code size optimization: none, small, tiny."}, {"output", "Output location, relative to project file."}, @@ -98,7 +98,7 @@ const char* project_target_keys[][2] = { {"macossdk", "Set the directory for the MacOS SDK for cross compilation."}, {"memory-env", "Set the memory environment: normal, small, tiny, none."}, {"no-entry", "Do not generate (or require) a main function."}, - {"opt", "Link libc (default: true)."}, + {"opt", "Optimization setting: O0, O1, O2, O3, O4, O5, Os, Oz."}, {"optlevel", "Code optimization level: none, less, more, max."}, {"optsize", "Code size optimization: none, small, tiny."}, {"output", "Output location, relative to project file."}, diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 7a69225db..3e76609b2 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -113,6 +113,7 @@ const char *decl_to_a_name(Decl *decl) case DECL_CT_ASSERT: return "a compile time assert"; case DECL_CT_ECHO: return "a compile time echo"; case DECL_CT_EXEC: return "compile time exec include"; + case DECL_CT_EXPAND: return "compile time expand"; case DECL_CT_INCLUDE: return "an include"; case DECL_DECLARRAY: return "a declarray"; case DECL_DEFINE: case DECL_TYPEDEF: return "a define"; diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 818a30ca7..c4fc0d971 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -739,6 +739,7 @@ typedef struct Decl_ DefineDecl define_decl; EnumConstantDecl enum_constant; ExecDecl exec_decl; + Expr* expand_decl; Signature fntype_decl; FuncDecl func_decl; ImportDecl import; @@ -1533,6 +1534,7 @@ typedef struct Ast_ Decl **decls_stmt; AstDeferStmt defer_stmt; // 8 Expr *expr_stmt; // 8 + Expr *expand_stmt; // 8 AstForStmt for_stmt; // 32 AstForeachStmt foreach_stmt; // 40 AstIfStmt if_stmt; // 32 @@ -2329,6 +2331,7 @@ const char *module_create_object_file_name(Module *module); bool parse_file(File *file); Decl **parse_include_file(File *file, CompilationUnit *unit); +Ast *parse_include_file_stmts(File *file, CompilationUnit *unit); bool parse_stdin(void); Path *path_create_from_string(const char *string, uint32_t len, SourceSpan span); diff --git a/src/compiler/context.c b/src/compiler/context.c index 963a57d8c..d78bc0f44 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -126,6 +126,7 @@ void decl_register(Decl *decl) case DECL_CT_ASSERT: case DECL_CT_ECHO: case DECL_CT_EXEC: + case DECL_CT_EXPAND: case DECL_ENUM_CONSTANT: case DECL_FAULTVALUE: case DECL_IMPORT: @@ -233,6 +234,7 @@ void unit_register_global_decl(CompilationUnit *unit, Decl *decl) UNREACHABLE case DECL_CT_EXEC: case DECL_CT_INCLUDE: + case DECL_CT_EXPAND: vec_add(unit->ct_includes, decl); return; case DECL_CT_ECHO: diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 4e5b58c1b..b74be6275 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -575,6 +575,9 @@ RETRY: case AST_DECLS_STMT: MACRO_COPY_DECL_LIST(ast->decls_stmt); break; + case AST_CT_EXPAND_STMT: + MACRO_COPY_EXPR(ast->expr_stmt); + break; case AST_CONTRACT_FAULT: if (ast->contract_fault.resolved) { @@ -891,6 +894,9 @@ Decl *copy_decl(CopyStruct *c, Decl *decl) MACRO_COPY_DECL_LIST(copy->methods); MACRO_COPY_DECL_LIST(copy->interface_methods); break; + case DECL_CT_EXPAND: + MACRO_COPY_EXPR(copy->expand_decl); + break; case DECL_CT_EXEC: MACRO_COPY_EXPR(copy->exec_decl.filename); MACRO_COPY_EXPR_LIST(copy->exec_decl.args); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 72d0eff7e..707a4aa46 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -69,6 +69,7 @@ typedef enum AST_CT_ASSERT, AST_CT_ECHO_STMT, AST_CT_ELSE_STMT, + AST_CT_EXPAND_STMT, AST_CT_FOREACH_STMT, AST_CT_FOR_STMT, AST_CT_IF_STMT, @@ -91,6 +92,10 @@ typedef enum AST_CONTRACT_FAULT, } AstKind; +#define CT_AST \ + AST_CT_ASSERT: case AST_CT_ECHO_STMT: case AST_CT_ELSE_STMT: \ + case AST_CT_EXPAND_STMT: case AST_CT_FOREACH_STMT: case AST_CT_FOR_STMT: \ + case AST_CT_IF_STMT: case AST_CT_SWITCH_STMT typedef enum { @@ -147,6 +152,7 @@ typedef enum DECL_CT_ASSERT, DECL_CT_ECHO, DECL_CT_EXEC, + DECL_CT_EXPAND, DECL_CT_INCLUDE, DECL_DECLARRAY, DECL_DEFINE, @@ -174,7 +180,7 @@ typedef enum case DECL_DEFINE: case DECL_CT_ASSERT: case DECL_CT_EXEC: \ case DECL_CT_ECHO: case DECL_CT_INCLUDE: case DECL_GLOBALS: \ case DECL_BODYPARAM: case DECL_VAR: case DECL_ENUM_CONSTANT: case DECL_FAULTVALUE: \ - case DECL_POISONED + case DECL_POISONED: case DECL_CT_EXPAND #define NON_RUNTIME_EXPR EXPR_DESIGNATOR: case EXPR_POISONED: \ case EXPR_CT_DEFINED: case EXPR_CT_AND_OR:\ @@ -601,6 +607,7 @@ typedef enum TOKEN_CT_EVALTYPE, // $evaltype TOKEN_CT_ERROR, // $error TOKEN_CT_EXEC, // $exec + TOKEN_CT_EXPAND, // $expand TOKEN_CT_EXTNAMEOF, // $extnameof TOKEN_CT_FEATURE, // $feature TOKEN_CT_FOR, // $for diff --git a/src/compiler/json_output.c b/src/compiler/json_output.c index f85146b0b..f38f60892 100644 --- a/src/compiler/json_output.c +++ b/src/compiler/json_output.c @@ -42,6 +42,7 @@ static inline const char *decl_type_to_string(Decl *type) case DECL_CT_ASSERT: return "$assert"; case DECL_CT_ECHO: return "$echo"; case DECL_CT_EXEC: return "$exec"; + case DECL_CT_EXPAND: return "$expand"; case DECL_CT_INCLUDE: return "$include"; case DECL_DEFINE: return "def"; case DECL_DISTINCT: return "distinct"; diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index d7cf6fe6c..b0ef23b13 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -1260,6 +1260,7 @@ LLVMValueRef llvm_get_ref(GenContext *c, Decl *decl) case DECL_BODYPARAM: case DECL_CT_ECHO: case DECL_CT_EXEC: + case DECL_CT_EXPAND: case DECL_CT_INCLUDE: case DECL_GLOBALS: case DECL_INTERFACE: diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index c08b242b9..99ef0c455 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -1597,6 +1597,9 @@ void llvm_emit_stmt(GenContext *c, Ast *ast) case AST_CONTRACT: case AST_ASM_STMT: case AST_CONTRACT_FAULT: + case AST_CASE_STMT: + case AST_DEFAULT_STMT: + case CT_AST: UNREACHABLE case AST_EXPR_STMT: llvm_emit_expr_stmt(c, ast); @@ -1652,16 +1655,6 @@ void llvm_emit_stmt(GenContext *c, Ast *ast) case AST_ASSERT_STMT: llvm_emit_assert_stmt(c, ast); break; - case AST_CT_ASSERT: - case AST_CT_IF_STMT: - case AST_CT_ELSE_STMT: - case AST_CT_FOR_STMT: - case AST_CT_SWITCH_STMT: - case AST_CASE_STMT: - case AST_DEFAULT_STMT: - case AST_CT_ECHO_STMT: - case AST_CT_FOREACH_STMT: - UNREACHABLE case AST_SWITCH_STMT: llvm_emit_switch(c, ast); break; diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index f300cc2a7..32e070fd0 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -2636,6 +2636,18 @@ static Decl *parse_include(ParseContext *c) return decl; } +static Decl *parse_expand(ParseContext *c) +{ + SourceSpan loc = c->span; + Decl *decl = decl_new(DECL_CT_EXPAND, NULL, loc); + advance_and_verify(c, TOKEN_CT_EXPAND); + CONSUME_OR_RET(TOKEN_LPAREN, poisoned_decl); + ASSIGN_EXPR_OR_RET(decl->expand_decl, parse_constant_expr(c), poisoned_decl); + CONSUME_OR_RET(TOKEN_RPAREN, poisoned_decl); + if (!parse_attributes_for_global(c, decl)) return poisoned_decl; + CONSUME_EOS_OR_RET(poisoned_decl); + return decl; +} static Decl *parse_exec(ParseContext *c) { SourceSpan loc = c->span; @@ -2772,6 +2784,10 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) if (contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_exec(c); break; + case TOKEN_CT_EXPAND: + if (contracts) goto CONTRACT_NOT_ALLOWED; + decl = parse_expand(c); + break; case TOKEN_BITSTRUCT: if (contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_bitstruct_declaration(c); diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index d1a521d17..c237150b2 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -1029,7 +1029,19 @@ static inline Ast *parse_return_stmt(ParseContext *c) return ast; } - +/** + * ct_expand_stmt :: CT_EXPAND '(' expr ')' + */ +static inline Ast* parse_ct_expand_stmt(ParseContext *c) +{ + Ast *ast = ast_new_curr(c, AST_CT_EXPAND_STMT); + advance_and_verify(c, TOKEN_CT_EXPAND); + CONSUME_OR_RET(TOKEN_LPAREN, poisoned_ast); + ASSIGN_EXPR_OR_RET(ast->expand_stmt, parse_expr(c), poisoned_ast); + CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); + CONSUME_EOS_OR_RET(poisoned_ast); + return ast; +} /** * ct_foreach_stmt ::= CT_FOREACH '(' CT_IDENT (',' CT_IDENT)? ':' expr ')' statement* CT_ENDFOREACH */ @@ -1280,6 +1292,8 @@ Ast *parse_stmt(ParseContext *c) return parse_ct_if_stmt(c); case TOKEN_CT_SWITCH: return parse_ct_switch_stmt(c); + case TOKEN_CT_EXPAND: + return parse_ct_expand_stmt(c); case TOKEN_CT_FOREACH: return parse_ct_foreach_stmt(c); case TOKEN_CT_FOR: diff --git a/src/compiler/parser.c b/src/compiler/parser.c index 44d2ba67b..cf7fcdb6b 100644 --- a/src/compiler/parser.c +++ b/src/compiler/parser.c @@ -132,6 +132,38 @@ Decl **parse_include_file(File *file, CompilationUnit *unit) return list; } +Ast *parse_include_file_stmts(File *file, CompilationUnit *unit) +{ + ParseContext parse_context = { .tok = TOKEN_INVALID_TOKEN }; + ParseContext *c = &parse_context; + c->unit = unit; + parse_context.lexer = (Lexer){ .file = file, .context = c }; + lexer_init(&parse_context.lexer); + // Prime everything + advance(c); + advance(c); + Ast *first = NULL; + Ast *current = NULL; + while (!tok_is(c, TOKEN_EOF)) + { + Ast *stmt = parse_stmt(c); + if (!stmt) continue; + if (!ast_ok(stmt)) + { + ast_poison(stmt); + return poisoned_ast; + } + if (!first) + { + first = current = stmt; + continue; + } + current->next = astid(stmt); + current = stmt; + } + return first; +} + File stdin_file; /** diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 8ea308cc9..ac6e605bf 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -4277,6 +4277,7 @@ bool sema_analyse_decl(SemaContext *context, Decl *decl) case DECL_BODYPARAM: case DECL_CT_INCLUDE: case DECL_CT_EXEC: + case DECL_CT_EXPAND: case DECL_GLOBALS: UNREACHABLE } diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index c6b753d2b..2c116d170 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -779,6 +779,7 @@ static inline bool sema_cast_ident_rvalue(SemaContext *context, Expr *expr) case DECL_CT_EXEC: case DECL_GLOBALS: case DECL_ERASED: + case DECL_CT_EXPAND: UNREACHABLE case DECL_POISONED: return expr_poison(expr); @@ -7617,6 +7618,7 @@ static inline bool sema_expr_analyse_ct_nameof(SemaContext *context, Expr *expr) case DECL_CT_ASSERT: case DECL_CT_ECHO: case DECL_CT_EXEC: + case DECL_CT_EXPAND: case DECL_CT_INCLUDE: case DECL_DECLARRAY: case DECL_ERASED: diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index 97f2dbb16..cbfe2756d 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -99,13 +99,7 @@ static void sema_trace_stmt_liveness(Ast *ast) switch (ast->ast_kind) { case AST_POISONED: - case AST_CT_ECHO_STMT: - case AST_CT_ELSE_STMT: - case AST_CT_FOREACH_STMT: - case AST_CT_FOR_STMT: - case AST_CT_IF_STMT: - case AST_CT_ASSERT: - case AST_CT_SWITCH_STMT: + case CT_AST: case AST_CONTRACT: case AST_FOREACH_STMT: case AST_CONTRACT_FAULT: @@ -567,6 +561,7 @@ RETRY: case DECL_CT_ASSERT: case DECL_CT_ECHO: case DECL_CT_EXEC: + case DECL_CT_EXPAND: case DECL_IMPORT: case DECL_CT_INCLUDE: case DECL_LABEL: diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index 31aca217d..3f7e8fd36 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -184,6 +184,34 @@ static Decl **sema_load_include(CompilationUnit *unit, Decl *decl) return parse_include_file(file, unit); } +static Decl **sema_interpret_expand(CompilationUnit *unit, Decl *decl) +{ + SemaContext context; + sema_context_init(&context, unit); + FOREACH(Attr *, attr, decl->attributes) + { + if (attr->attr_kind != ATTRIBUTE_IF) + { + RETURN_PRINT_ERROR_AT(NULL, attr, "Invalid attribute for '$expand'."); + } + } + Expr *string = decl->expand_decl; + bool success = sema_analyse_ct_expr(&context, string); + sema_context_destroy(&context); + if (!success) return NULL; + if (!expr_is_const_string(string)) + { + RETURN_PRINT_ERROR_AT(NULL, string, "Expected a constant string for '$expand'."); + } + scratch_buffer_clear(); + scratch_buffer_printf("%s.%d", unit->file->full_path, string->span.row); + File *file = source_file_text_load(scratch_buffer_to_string(), string->const_expr.bytes.ptr); + ParseContext parse_context = { .tok = TOKEN_INVALID_TOKEN }; + ParseContext *c = &parse_context; + c->unit = unit; + return parse_include_file(file, unit); +} + static Decl **sema_run_exec(CompilationUnit *unit, Decl *decl) { if (active_target.trust_level < TRUST_FULL) @@ -299,9 +327,21 @@ INLINE void register_includes(CompilationUnit *unit, Decl **decls) { FOREACH(Decl *, include, decls) { - Decl **include_decls = include->decl_kind == DECL_CT_EXEC - ? sema_run_exec(unit, include) - : sema_load_include(unit, include); + Decl **include_decls; + switch (include->decl_kind) + { + case DECL_CT_EXEC: + include_decls = sema_run_exec(unit, include); + break; + case DECL_CT_INCLUDE: + include_decls = sema_load_include(unit, include); + break; + case DECL_CT_EXPAND: + include_decls = sema_interpret_expand(unit, include); + break; + default: + UNREACHABLE + } FOREACH(Decl *, decl, include_decls) { if (decl->is_cond) diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index cc3658b2b..c07915991 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -2851,6 +2851,21 @@ bool sema_analyse_ct_assert_stmt(SemaContext *context, Ast *statement) return true; } +bool sema_analyse_ct_expand_stmt(SemaContext *context, Ast *stmt) +{ + Expr *string = stmt->expand_stmt; + if (!sema_analyse_ct_expr(context, string)) return false; + if (!expr_is_const_string(string)) RETURN_SEMA_ERROR(string, "Expected a constant string to '$expand'."); + scratch_buffer_clear(); + scratch_buffer_printf("%s.%d", context->unit->file->full_path, string->span.row); + File *file = source_file_text_load(scratch_buffer_to_string(), string->const_expr.bytes.ptr); + Ast *result = parse_include_file_stmts(file, context->unit); + stmt->ast_kind = AST_NOP_STMT; + if (!result) return true; + if (!ast_ok(result)) return ast_poison(stmt); + return sema_analyse_then_overwrite(context, stmt, astid(result)); +} + bool sema_analyse_ct_echo_stmt(SemaContext *context, Ast *statement) { Expr *message = statement->expr_stmt; @@ -3012,6 +3027,8 @@ static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *state return sema_analyse_ct_echo_stmt(context, statement); case AST_DECLARE_STMT: return sema_analyse_declare_stmt(context, statement); + case AST_CT_EXPAND_STMT: + return sema_analyse_ct_expand_stmt(context, statement); case AST_DEFAULT_STMT: RETURN_SEMA_ERROR(statement, "Unexpected 'default' outside of switch"); case AST_DEFER_STMT: diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 83b73fdaf..d17c62983 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -262,6 +262,7 @@ static bool sema_resolve_type_identifier(SemaContext *context, TypeInfo *type_in case DECL_DECLARRAY: case DECL_BODYPARAM: case DECL_CT_INCLUDE: + case DECL_CT_EXPAND: case DECL_CT_EXEC: case DECL_GLOBALS: UNREACHABLE diff --git a/src/compiler/semantic_analyser.c b/src/compiler/semantic_analyser.c index c6a24f7b3..daf170aa7 100644 --- a/src/compiler/semantic_analyser.c +++ b/src/compiler/semantic_analyser.c @@ -206,6 +206,7 @@ static void register_generic_decls(CompilationUnit *unit, Decl **decls) case DECL_FNTYPE: case DECL_CT_INCLUDE: case DECL_CT_EXEC: + case DECL_CT_EXPAND: continue; case DECL_ATTRIBUTE: break; diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index eb3916f2c..080c3da40 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -362,6 +362,8 @@ const char *token_type_to_string(TokenType type) return "$error"; case TOKEN_CT_EXEC: return "$exec"; + case TOKEN_CT_EXPAND: + return "$expand"; case TOKEN_CT_EXTNAMEOF: return "$extnameof"; case TOKEN_CT_FEATURE: diff --git a/test/test_suite/compile_time/ct_expand.c3t b/test/test_suite/compile_time/ct_expand.c3t new file mode 100644 index 000000000..9ceebce8d --- /dev/null +++ b/test/test_suite/compile_time/ct_expand.c3t @@ -0,0 +1,42 @@ +// #target: macos-x64 +module test; +import std; + +$expand(test()); + +struct Foo +{ + int a; + int b; +} +macro test() +{ + var $out = "struct Copy {"; + $foreach ($member : Foo.membersof) + $out = $concat($out, " ", $member.typeid.nameof, " ", $member.nameof, ";"); + $endforeach; + return $concat($out, "}"); +} + +fn void main() +{ + var $a = "a = 4;"; + $expand($concat("int a = 1;", $a)); + Copy x = { 1, 3 }; + x.a = 4; +} + +/* #expect: test.ll + +%Copy = type { i32, i32 } + +define void @test.main() #0 { +entry: + %a = alloca i32, align 4 + %x = alloca %Copy, align 4 + store i32 1, ptr %a, align 4 + store i32 4, ptr %a, align 4 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x, ptr align 4 @.__const, i32 8, i1 false) + store i32 4, ptr %x, align 4 + ret void +}