/* SPDX-License-Identifier: copyleft-next-0.3.1 */ /* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ /** * @file debug.c * * Debugging and information printing helper implementations. */ #include #include #include #include #include #include #include #include /** * Get string representation of issue_level. * * @param level issue_level to get string representation for. * @return \p level as a string. */ const char *issue_level_str(enum issue_level level) { switch (level) { case SRC_INFO: return "info"; case SRC_WARN: return "warn"; case SRC_ERROR: return "error"; } return "unknown"; } /** * Find position in file buffer where line number \p no * starts. Lines are assumed to be one-indexed, with * \p no = \c 0 and \p no = \c 1 both considered the first line. * * @param buf Buffer to look in. * @param no Line number whose start to look for. * @return Pointer to location in buffer where line number \p no * starts. */ static const char *find_lineno(const char *buf, size_t no) { if (no == 0 || no == 1) return buf; char c; while ((c = *buf)) { buf++; if (c == '\n') no--; if (no == 1) break; } return buf; } /** * Helper for printing out an issue. * * @param issue Issue context. * @param fmt Format string. Follows standard printf() formatting. * @param args Arguments for \p fmt. */ static void _issue(struct src_issue issue, const char *fmt, va_list args) { /* get start and end of current line in buffer */ const char *line_start = find_lineno(issue.fctx.fbuf, (size_t)issue.loc.first_line); const char *line_end = strchr(line_start, '\n'); if (!line_end) line_end = strchr(line_start, 0); const int line_len = (int)(line_end - line_start); fprintf(stderr, "%s:%i:%i: %s: ", issue.fctx.fname, issue.loc.first_line, issue.loc.first_col, issue_level_str(issue.level)); vfprintf(stderr, fmt, args); fputc('\n', stderr); int lineno_len = snprintf(NULL, 0, "%i", issue.loc.first_line); fputc(' ', stderr); fprintf(stderr, "%i | ", issue.loc.first_line); fprintf(stderr, "%.*s\n", line_len, line_start); for (int i = 0; i < lineno_len + 2; ++i) fputc(' ', stderr); fprintf(stderr, "| "); for (int i = 0; i < issue.loc.first_col - 1; ++i) fputc(line_start[i] == '\t' ? '\t' : ' ', stderr); for (int i = issue.loc.first_col; i < issue.loc.last_col; ++i) { if (i == issue.loc.first_col) fputc('^', stderr); else fputc('~', stderr); } fputc('\n', stderr); } void src_issue(struct src_issue issue, const char *err_msg, ...) { va_list args; va_start(args, err_msg); _issue(issue, err_msg, args); va_end(args); } void semantic_error(struct scope *scope, struct ast *node, const char *fmt, ...) { va_list args; va_start(args, fmt); struct src_issue issue; issue.level = SRC_ERROR; issue.loc = node->loc; issue.fctx = scope->fctx; _issue(issue, fmt, args); va_end(args); } void type_mismatch(struct scope *scope, struct ast *node, struct type *l, struct type *r) { const char *ls = type_str(l); const char *rs = type_str(r); semantic_error(scope, node, "type mismatch: %s vs %s\n", ls, rs); free((void *)ls); free((void *)rs); } void move_error(struct ast *new_use, struct ast *prev_use) { semantic_error(new_use->scope, new_use, "using moved value"); semantic_info(prev_use->scope, prev_use, "previously moved here"); } void reference_error(struct ast *new_use, struct ast *prev_use) { semantic_error(new_use->scope, new_use, "using referenced value"); semantic_info(prev_use->scope, prev_use, "previously referenced here"); } static void _type_str(FILE *f, struct type *t); static void _type_list_str(FILE *f, struct type *types) { if (!types) return; _type_str(f, types); foreach_type(type, types->n) { fprintf(f, ", "); _type_str(f, type); } } static void _type_str(FILE *f, struct type *type) { if (!type) return; switch (type->k) { case TYPE_ERR: fprintf(f, "err"); break; case TYPE_VOID: fprintf(f, "void"); break; case TYPE_PTR: fprintf(f, "*"); _type_list_str(f, tptr_base(type)); break; case TYPE_REF: fprintf(f, "&"); _type_list_str(f, tref_base(type)); break; case TYPE_ID: fprintf(f, "%s", type->id); break; case TYPE_CLOSURE: fprintf(f, "("); _type_list_str(f, type->t0); fprintf(f, ")"); break; case TYPE_PURE_CLOSURE: fprintf(f, "&("); _type_list_str(f, type->t0); fprintf(f, ")"); break; case TYPE_FUNC_PTR: fprintf(f, "*("); _type_list_str(f, type->t0); fprintf(f, ")"); break; case TYPE_CONSTRUCT: fprintf(f, "%s![", type->id); _type_list_str(f, type->t0); fprintf(f, "]"); break; } } const char *type_str(struct type *t) { if (!t) return strdup("NULL"); char *buf = NULL; size_t size = 0; FILE *memstream = open_memstream(&buf, &size); if (!memstream) return NULL; _type_str(memstream, t); fclose(memstream); return buf; } void loc_error(struct scope *scope, struct src_loc loc, const char *fmt, ...) { va_list args; va_start(args, fmt); struct src_issue issue; issue.level = SRC_ERROR; issue.loc = loc; issue.fctx = scope->fctx; _issue(issue, fmt, args); va_end(args); } void semantic_warn(struct scope *scope, struct ast *node, const char *fmt, ...) { va_list args; va_start(args, fmt); struct src_issue issue; issue.level = SRC_WARN; issue.loc = node->loc; issue.fctx = scope->fctx; _issue(issue, fmt, args); va_end(args); } void semantic_info(struct scope *scope, struct ast *node, const char *fmt, ...) { va_list args; va_start(args, fmt); struct src_issue issue; issue.level = SRC_INFO; issue.loc = node->loc; issue.fctx = scope->fctx; _issue(issue, fmt, args); va_end(args); } void internal_error(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "internal error: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); } void internal_warn(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "internal warning: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); }