Template
feat(error): add typed errors + source-span diagnostics
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
#include "error.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* C23 only: ISO/IEC 9899:2024. */
|
||||
|
||||
static const char *const category_names[] = {
|
||||
[ST_ERR_KDL_PARSE] = "kdl-parse",
|
||||
[ST_ERR_KDL_SCHEMA] = "kdl-schema",
|
||||
[ST_ERR_IO] = "io",
|
||||
[ST_ERR_USAGE] = "usage",
|
||||
[ST_ERR_INTERNAL] = "internal",
|
||||
};
|
||||
|
||||
struct st_error *
|
||||
st_error_new(enum st_error_category category, const char *message)
|
||||
{
|
||||
struct st_error *e;
|
||||
size_t len;
|
||||
|
||||
if (message == NULL) {
|
||||
message = "";
|
||||
}
|
||||
e = calloc(1, sizeof(*e));
|
||||
if (e == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
len = strlen(message);
|
||||
e->message = malloc(len + 1);
|
||||
if (e->message == NULL) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(e->message, message, len + 1);
|
||||
e->category = category;
|
||||
return e;
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_error_kdl_parse(const char *message)
|
||||
{
|
||||
return st_error_new(ST_ERR_KDL_PARSE, message);
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_error_kdl_schema(const char *message)
|
||||
{
|
||||
return st_error_new(ST_ERR_KDL_SCHEMA, message);
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_error_io(const char *message)
|
||||
{
|
||||
return st_error_new(ST_ERR_IO, message);
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_error_usage(const char *message)
|
||||
{
|
||||
return st_error_new(ST_ERR_USAGE, message);
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_error_internal(const char *message)
|
||||
{
|
||||
return st_error_new(ST_ERR_INTERNAL, message);
|
||||
}
|
||||
|
||||
struct st_error *
|
||||
st_error_at(struct st_error *e, const struct st_span *span)
|
||||
{
|
||||
if (e != NULL) {
|
||||
e->span = span;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
void
|
||||
st_error_free(struct st_error *e)
|
||||
{
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
free(e->message);
|
||||
free(e);
|
||||
}
|
||||
|
||||
enum st_error_category
|
||||
st_error_category_of(const struct st_error *e)
|
||||
{
|
||||
return e != NULL ? e->category : ST_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
const char *
|
||||
st_error_message(const struct st_error *e)
|
||||
{
|
||||
return e != NULL ? e->message : "";
|
||||
}
|
||||
|
||||
const char *
|
||||
st_error_category_name(enum st_error_category category)
|
||||
{
|
||||
if ((size_t)category >= sizeof(category_names) / sizeof(category_names[0])) {
|
||||
return "unknown";
|
||||
}
|
||||
return category_names[category];
|
||||
}
|
||||
|
||||
void
|
||||
st_error_print(FILE *out, const struct st_error *e)
|
||||
{
|
||||
const char *cat;
|
||||
char *prefixed;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
if (out == NULL || e == NULL) {
|
||||
return;
|
||||
}
|
||||
cat = st_error_category_name(e->category);
|
||||
len = strlen(cat) + strlen(e->message) + 4; /* "[", "]", " ", NUL */
|
||||
prefixed = malloc(len);
|
||||
if (prefixed == NULL) {
|
||||
fprintf(out, "%s\n", e->message);
|
||||
return;
|
||||
}
|
||||
rc = snprintf(prefixed, len, "[%s] %s", cat, e->message);
|
||||
if (rc < 0) {
|
||||
prefixed[0] = '\0';
|
||||
}
|
||||
if (e->span != NULL) {
|
||||
st_span_print(out, e->span, prefixed);
|
||||
} else {
|
||||
fprintf(out, "%s\n", prefixed);
|
||||
}
|
||||
free(prefixed);
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#ifndef ST_ERROR_H
|
||||
#define ST_ERROR_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "span.h"
|
||||
|
||||
/*
|
||||
* Typed error for all library code paths. Library code RETURNS these;
|
||||
* it never calls exit()/abort() on user input (only main.c exits).
|
||||
*/
|
||||
enum st_error_category {
|
||||
ST_ERR_KDL_PARSE = 0,
|
||||
ST_ERR_KDL_SCHEMA,
|
||||
ST_ERR_IO,
|
||||
ST_ERR_USAGE,
|
||||
ST_ERR_INTERNAL,
|
||||
};
|
||||
|
||||
struct st_error {
|
||||
enum st_error_category category;
|
||||
char *message; /* owned copy; never NULL after construction */
|
||||
const struct st_span *span; /* borrowed, may be NULL */
|
||||
};
|
||||
|
||||
/* Constructors. All duplicate `message`; NULL is treated as "". They
|
||||
* return NULL only on allocation failure. */
|
||||
struct st_error *st_error_new(enum st_error_category category,
|
||||
const char *message);
|
||||
struct st_error *st_error_kdl_parse(const char *message);
|
||||
struct st_error *st_error_kdl_schema(const char *message);
|
||||
struct st_error *st_error_io(const char *message);
|
||||
struct st_error *st_error_usage(const char *message);
|
||||
struct st_error *st_error_internal(const char *message);
|
||||
|
||||
/* Attaches a borrowed span and returns `e` for chaining:
|
||||
* err = st_error_at(st_error_kdl_parse("bad token"), &span); */
|
||||
struct st_error *st_error_at(struct st_error *e, const struct st_span *span);
|
||||
|
||||
/* Frees the error (message + struct). NULL is a safe no-op. */
|
||||
void st_error_free(struct st_error *e);
|
||||
|
||||
enum st_error_category st_error_category_of(const struct st_error *e);
|
||||
const char *st_error_message(const struct st_error *e);
|
||||
|
||||
/* Stable display name, e.g. "kdl-parse". Unknown categories yield
|
||||
* "unknown" rather than indexing out of range. */
|
||||
const char *st_error_category_name(enum st_error_category category);
|
||||
|
||||
/* Prints the diagnostic. With a span: the canonical two-line form with
|
||||
* the category-prefixed message ("file:line:col: [cat] <msg>\n" +
|
||||
* caret line). Without a span: "[cat] <msg>\n". NULL is tolerated. */
|
||||
void st_error_print(FILE *out, const struct st_error *e);
|
||||
|
||||
#endif
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#include "span.h"
|
||||
|
||||
/* C23 only: ISO/IEC 9899:2024. */
|
||||
#define ST_SPAN_CARET_PAD_CAP 60
|
||||
|
||||
void
|
||||
st_span_print(FILE *out, const struct st_span *span, const char *message)
|
||||
{
|
||||
size_t pad;
|
||||
|
||||
if (out == NULL || span == NULL || span->file == NULL ||
|
||||
message == NULL) {
|
||||
return;
|
||||
}
|
||||
fprintf(out, "%s:%zu:%zu: %s\n", span->file, span->line, span->col,
|
||||
message);
|
||||
/*
|
||||
* Caret line: 4-space indent, "..." elision, caret. The caret sits
|
||||
* three cells past the reported column (the elision marker occupies
|
||||
* those cells), so col 5 renders exactly " ...^". Columns below
|
||||
* 5 collapse onto the elision; columns above 5 push the caret right,
|
||||
* capped to keep the line bounded.
|
||||
*/
|
||||
fputs(" ...", out);
|
||||
pad = span->col > 5 ? span->col - 5 : 0;
|
||||
if (pad > ST_SPAN_CARET_PAD_CAP) {
|
||||
pad = ST_SPAN_CARET_PAD_CAP;
|
||||
}
|
||||
while (pad-- > 0) {
|
||||
fputc(' ', out);
|
||||
}
|
||||
fputs("^\n", out);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#ifndef ST_SPAN_H
|
||||
#define ST_SPAN_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* A source span: 1-based line/column into a source file.
|
||||
* `file` is borrowed (never freed); typically points into stable
|
||||
* storage such as a file-name arena or a string literal.
|
||||
*/
|
||||
struct st_span {
|
||||
const char *file;
|
||||
size_t line;
|
||||
size_t col;
|
||||
};
|
||||
|
||||
/*
|
||||
* Renders the canonical two-line diagnostic:
|
||||
*
|
||||
* <file>:<line>:<col>: <message>
|
||||
* ...^
|
||||
*
|
||||
* The caret line is a fixed 4-space indent followed by a "..."
|
||||
* elision marker and the caret. For the canonical span (col 5)
|
||||
* this is exactly " ...^"; the caret shifts right for larger
|
||||
* columns and is capped so hostile spans cannot force unbounded
|
||||
* output. NULL arguments are tolerated and render nothing.
|
||||
*/
|
||||
void st_span_print(FILE *out, const struct st_span *span,
|
||||
const char *message);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user