diff --git a/releasenotes.md b/releasenotes.md index c90533698..5844cdf5b 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -29,6 +29,7 @@ - Improve the error message when running out of memory. - Allowed passing arguments to @test / @benchmark runners via `c3c test[benchmark] -- -o --opt1 ` - Handle bytes and string literals the same way in terms of zero termination. +- Function comments are stored and displayed with -P. ### Fixes - Fix case trying to initialize a `char[*]*` from a String. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index ac86af145..9b4ada64a 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1473,6 +1473,11 @@ typedef struct AstDocDirective_ const char *directive_name; const char *rest_of_line; } generic; + struct + { + const char *string; + size_t strlen; + }; }; } AstContractStmt; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 8878106e8..e5f729dac 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -615,6 +615,7 @@ void doc_ast_copy(CopyStruct *c, AstContractStmt *doc) case CONTRACT_PARAM: case CONTRACT_PURE: case CONTRACT_UNKNOWN: + case CONTRACT_COMMENT: break; } } diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 6f1746eb6..b40df380b 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -594,6 +594,7 @@ typedef enum typedef enum { CONTRACT_UNKNOWN, + CONTRACT_COMMENT, CONTRACT_PURE, CONTRACT_REQUIRE, CONTRACT_PARAM, diff --git a/src/compiler/json_output.c b/src/compiler/json_output.c index 5d9938409..29ad9b0d1 100644 --- a/src/compiler/json_output.c +++ b/src/compiler/json_output.c @@ -10,21 +10,63 @@ unsigned decl_count_ = vec_size(unit->global_decls); \ for (unsigned k_ = 0; k_ < decl_count_; k_++) { \ a__ = unit->global_decls[k_]; -#define PRINTF(string, ...) fprintf(file, string, ##__VA_ARGS__) /* NOLINT */ +#define PRINTF(string__, ...) fprintf(file, string__, ##__VA_ARGS__) /* NOLINT */ +#define PRINT(string__) fputs(string__, file) /* NOLINT */ #define FOREACH_DECL_END } } } #define INSERT_COMMA do { if (first) { first = false; } else { fputs(",\n", file); } } while(0) +static bool emit_docs(FILE *file, AstId contracts, int tabs) +{ + if (!contracts) return false; + Ast *ast = astptr(contracts); + if (ast->contract_stmt.kind != CONTRACT_COMMENT) return false; + for (int i = 0; i < tabs; i++) PRINT("\t"); + PRINT("\"comment\": \""); + bool last_is_whitespace = true; + for (size_t i = 0; i < ast->contract_stmt.strlen; i++) + { + unsigned char c = ast->contract_stmt.string[i]; + if (char_is_whitespace(c) || c < 31) + { + if (last_is_whitespace) continue; + fputc(' ', file); + last_is_whitespace = true; + continue; + } + last_is_whitespace = false; + switch (c) + { + case '"': + PRINT("\\\""); + break; + case '\\': + PRINT("\\\\"); + break; + default: + if (c > 127) + { + PRINTF("\\x%02x", c); + } + else + { + fputc(c, file); + } + } + } + PRINT("\""); + return true; +} static inline void emit_modules(FILE *file) { - fputs("\t\"modules\": [\n", file); + PRINT("\t\"modules\": [\n"); FOREACH_IDX(i, Module *, module, compiler.context.module_list) { if (i != 0) fputs(",\n", file); PRINTF("\t\t\"%s\"", module->name->module); } - fputs("\n\t],\n", file); - fputs("\t\"generic_modules\": [\n", file); + PRINT("\n\t],\n"); + PRINT("\t\"generic_modules\": [\n"); FOREACH_IDX(j, Module *, module, compiler.context.generic_module_list) { if (j != 0) fputs(",\n", file); @@ -192,45 +234,83 @@ void print_var_expr(FILE *file, Expr *expr) } } +INLINE void print_indent(FILE *file, int indent) +{ + for (int j = 0; j < indent; j++) PRINT("\t"); +} +static inline void emit_members(FILE *file, Decl **members, int indent) +{ + FOREACH_IDX(i, Decl *, member, members) + { + if (i != 0) fputs(",\n", file); + print_indent(file, indent); + PRINTF("\t\t\t\t{\n"); + if (member->name) + { + print_indent(file, indent); + PRINTF("\t\t\t\t\t\"name\": \"%s\",\n", member->name); + } + if (member->decl_kind == DECL_VAR) + { + print_indent(file, indent); + PRINTF("\t\t\t\t\t\"type\": \""); + ASSERT0(member->var.type_info); + print_type(file, type_infoptr(member->var.type_info)); + PRINT("\"\n"); + print_indent(file, indent); + PRINTF("\t\t\t\t}"); + continue; + } + print_indent(file, indent); + PRINTF("\t\t\t\t\t\"inner\": "); + PRINT(member->decl_kind == DECL_STRUCT ? "\"struct\"" : "\"union\""); + PRINT(",\n"); + print_indent(file, indent); + PRINT("\t\t\t\t\t\"members\": [\n"); + emit_members(file, member->strukt.members, indent + 2); + PRINT("\n"); + print_indent(file, indent); + PRINT("\t\t\t\t\t]\n"); + print_indent(file, indent); + PRINTF("\t\t\t\t}"); + continue; + } + +} static inline void emit_type_data(FILE *file, Module *module, Decl *type) { PRINTF("\t\t\"%s::%s\": {\n", module->name->module, type->name); PRINTF("\t\t\t\"kind\": \"%s\"", decl_type_to_string(type)); - if (type->decl_kind == DECL_STRUCT || type->decl_kind == DECL_UNION) + switch (type->decl_kind) { - fputs(",\n\t\t\t\"members\": [\n", file); - FOREACH_IDX(i, Decl *, member, type->strukt.members) - { - if (i != 0) fputs(",\n", file); - PRINTF("\t\t\t\t{\n"); - if (member->name) - { - PRINTF("\t\t\t\t\t\"name\": \"%s\",\n", member->name); - } - // TODO, extend this - PRINTF("\t\t\t\t\t\"type\": \""); - if (member->var.type_info) - { - print_type(file, type_infoptr(member->var.type_info)); - } - else - { - fputs("", file); - } - PRINTF("\"\n\t\t\t\t}"); - } - fputs("\n\t\t\t]", file); + case DECL_UNION: + case DECL_STRUCT: + fputs(",\n\t\t\t\"members\": [\n", file); + emit_members(file, type->strukt.members, 0); + fputs("\n\t\t\t]", file); + break; + case DECL_DISTINCT: + PRINT(",\n\t\t\t\"type\": \""); + print_type(file, type->distinct); + PRINTF(",\n\t\t\t\"inline\": \"%s\"", type->is_substruct ? "true" : "false"); + break; + default: + break; } - fputs("\n\t\t}", file); + PRINT("\n\t\t}"); } static inline void emit_func_data(FILE *file, Module *module, Decl *func) { PRINTF("\t\t\"%s::%s\": {\n", module->name->module, func->name); + if (emit_docs(file, func->func_decl.docs, 3)) + { + PRINTF(",\n"); + } PRINTF("\t\t\t\"rtype\": \""); print_type(file, type_infoptr(func->func_decl.signature.rtype)); PRINTF("\",\n"); - fputs("\t\t\t\"params\": [\n", file); + PRINT("\t\t\t\"params\": [\n"); FOREACH_IDX(i, Decl *, decl, func->func_decl.signature.params) { if (!decl) continue; @@ -254,6 +334,43 @@ static inline void emit_func_data(FILE *file, Module *module, Decl *func) fputs("\n\t\t}", file); } +static inline void emit_macro_data(FILE *file, Module *module, Decl *macro) +{ + PRINTF("\t\t\"%s::%s\": {\n", module->name->module, macro->name); + if (emit_docs(file, macro->func_decl.docs, 3)) + { + PRINTF(",\n"); + } + if (macro->func_decl.signature.rtype) + { + PRINTF("\t\t\t\"rtype\": \""); + print_type(file, type_infoptr(macro->func_decl.signature.rtype)); + PRINTF("\",\n"); + } + fputs("\t\t\t\"params\": [\n", file); + FOREACH_IDX(i, Decl *, decl, macro->func_decl.signature.params) + { + if (!decl) continue; + if (i != 0) fputs(",\n", file); + fputs("\t\t\t\t{\n", file); + PRINTF("\t\t\t\t\t\"name\": \"%s\",\n", decl->name ? decl->name : ""); + PRINTF("\t\t\t\t\t\"type\": \""); + if (decl->var.type_info) + { + print_type(file, type_infoptr(decl->var.type_info)); + } + else + { + fputs("", file); + } + fputs("\"\n", file); + fputs("\t\t\t\t}", file); + } + fputs("\n\t\t\t]\n", file); + + fputs("\n\t\t}", file); +} + static inline bool decl_is_hidden(Decl *decl) { return decl->visibility > VISIBLE_PUBLIC; @@ -355,6 +472,17 @@ static inline void emit_functions(FILE *file) FOREACH_DECL_END; } fputs("\n\t},\n", file); + fputs("\t\"macros\": {\n", file); + { + bool first = true; + FOREACH_DECL(Decl *func, compiler.context.module_list) + if (func->decl_kind != DECL_MACRO) continue; + if (decl_is_hidden(func)) continue; + INSERT_COMMA; + emit_macro_data(file, module, func); + FOREACH_DECL_END; + } + fputs("\n\t},\n", file); fputs("\t\"generic_functions\": {\n", file); { @@ -367,6 +495,19 @@ static inline void emit_functions(FILE *file) FOREACH_DECL_END; } fputs("\n\t},\n", file); + + fputs("\t\"generic_macros\": {\n", file); + { + bool first = true; + FOREACH_DECL(Decl *func, compiler.context.generic_module_list) + if (func->decl_kind != DECL_MACRO) continue; + if (decl_is_hidden(func)) continue; + INSERT_COMMA; + emit_macro_data(file, module, func); + FOREACH_DECL_END; + } + fputs("\n\t},\n", file); + } static inline void emit_json_to_file(FILE *file) diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index e1659f6da..56c6b7a76 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -1158,6 +1158,7 @@ static inline bool scan_base64(Lexer *lexer) **/ static bool parse_doc_start(Lexer *lexer) { + const char *comment_start = NULL; bool may_have_contract = true; // Let's loop until we find the end or the contract. while (!reached_end(lexer)) @@ -1188,6 +1189,10 @@ static bool parse_doc_start(Lexer *lexer) FALLTHROUGH; default: may_have_contract = false; + if (!comment_start) + { + comment_start = lexer->current; + } next(lexer); continue; } @@ -1200,7 +1205,13 @@ EXIT:; // // In any case we can consider this having reached "the contracts" lexer->mode = LEX_CONTRACTS; - return new_token(lexer, TOKEN_DOCS_START, lexer->lexing_start); + lexer->data.strlen = 0; + if (!comment_start) return new_token(lexer, TOKEN_DOCS_START, "<*"); + new_token(lexer, TOKEN_DOCS_START, comment_start); + const char *last = lexer->current - 1; + while (last > comment_start && char_is_whitespace(*last)) last--; + lexer->data.strlen = last - comment_start + 1; + return true; } static bool lexer_scan_token_inner(Lexer *lexer) diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 5f29f9dcd..55b0056cd 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -246,6 +246,7 @@ bool parse_module(ParseContext *c, AstId contracts) case CONTRACT_ENSURE: break; case CONTRACT_REQUIRE: + case CONTRACT_COMMENT: continue; } RETURN_PRINT_ERROR_AT(false, current, "Invalid constraint - only '@require' is valid for modules."); @@ -2677,9 +2678,21 @@ static inline bool parse_doc_optreturn(ParseContext *c, AstId *docs, AstId **doc static bool parse_contracts(ParseContext *c, AstId *contracts_ref) { *contracts_ref = 0; - if (!try_consume(c, TOKEN_DOCS_START)) return true; + if (!tok_is(c, TOKEN_DOCS_START)) return true; AstId **next = &contracts_ref; + if (c->data.strlen > 0) + { + Ast *ast = ast_new_curr(c, AST_CONTRACT); + ast->contract_stmt.kind = CONTRACT_COMMENT; + ast->contract_stmt.string = symstr(c); + ast->contract_stmt.strlen = c->data.strlen; + ast->span = c->span; + append_docs(next, contracts_ref, ast); + } + + advance_and_verify(c, TOKEN_DOCS_START); + while (!try_consume(c, TOKEN_DOCS_END)) { // Skip empty lines. @@ -2796,6 +2809,15 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) if (!parse_contracts(c, &contracts)) return poisoned_decl; Decl *decl; + bool has_real_contracts = false; + if (contracts) + { + Ast *contract_start = astptr(contracts); + if (contract_start->contract_stmt.kind != CONTRACT_COMMENT || contract_start->next) + { + has_real_contracts = true; + } + } TokenType tok = c->tok; if (tok != TOKEN_MODULE && !c->unit->module) { @@ -2815,13 +2837,13 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) decl = parse_func_definition(c, contracts, FUNC_PARSE_EXTERN); break; case TOKEN_CONST: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_top_level_const_declaration(c, true); break; case TOKEN_IDENT: case TOKEN_TLOCAL: case TYPELIKE_TOKENS: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_global_declaration(c); break; default: @@ -2852,7 +2874,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) PRINT_ERROR_HERE("There are more than one doc comment in a row, that is not allowed."); return poisoned_decl; case TOKEN_DEF: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_def(c); break; case TOKEN_FN: @@ -2900,39 +2922,39 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) decl = parse_exec(c); break; case TOKEN_BITSTRUCT: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_bitstruct_declaration(c); break; case TOKEN_INTERFACE: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_interface_declaration(c); break; case TOKEN_DISTINCT: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_distinct_declaration(c); break; case TOKEN_CONST: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_top_level_const_declaration(c, false); break; case TOKEN_STRUCT: case TOKEN_UNION: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_struct_declaration(c); break; case TOKEN_MACRO: decl = parse_macro_declaration(c, contracts); break; case TOKEN_ENUM: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_enum_declaration(c); break; case TOKEN_FAULT: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_fault_declaration(c); break; case TOKEN_IDENT: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_global_declaration(c); break; case TOKEN_EOF: @@ -2953,7 +2975,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **c_ref) return poisoned_decl; case TOKEN_TLOCAL: case TYPELIKE_TOKENS: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_global_declaration(c); break; case TOKEN_EOS: diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index c2faf80fe..8decd6faa 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -3179,6 +3179,7 @@ bool sema_analyse_contracts(SemaContext *context, AstId doc, AstId **asserts, So { case CONTRACT_UNKNOWN: case CONTRACT_PURE: + case CONTRACT_COMMENT: break; case CONTRACT_REQUIRE: if (!sema_analyse_require(context, directive, asserts, call_span)) return false;