Function comments are stored and displayed with -P.

This commit is contained in:
Christoffer Lerno
2025-01-10 23:39:57 +01:00
parent 1f29110271
commit 48923a2237
8 changed files with 226 additions and 43 deletions

View File

@@ -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 <arg1>`
- 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.

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -594,6 +594,7 @@ typedef enum
typedef enum
{
CONTRACT_UNKNOWN,
CONTRACT_COMMENT,
CONTRACT_PURE,
CONTRACT_REQUIRE,
CONTRACT_PARAM,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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:

View File

@@ -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;