/*
Copyright (C) 2019, 2020 Leo Tenenbaum.
This file is part of toc. toc is distributed under version 3 of the GNU General Public License, without any warranty whatsoever.
You should have received a copy of the GNU General Public License along with toc. If not, see .
*/
#define TEXT_ERR_START "\x1b[91m"
#define TEXT_ERR_END "\x1b[0m"
#define TEXT_INFO_START "\x1b[94m"
#define TEXT_INFO_END "\x1b[0m"
#define TEXT_WARN_START "\x1b[93m"
#define TEXT_WARN_END "\x1b[0m"
#define TEXT_IMPORTANT_START "\x1b[1m"
#define TEXT_IMPORTANT_END "\x1b[0m"
#define TEXT_INDICATOR_START "\x1b[96m"
#define TEXT_INDICATOR_END "\x1b[0m"
#if defined(TOC_DEBUG) && __STDC_VERSION__ >= 199901
#define ERR_SHOW_SOURCE_LOCATION 1
#endif
static void print_pos_highlight(FILE *out, ErrCtx *ctx, File *file, U32 start_pos, U32 end_pos) {
char *str = file->contents;
if (!str) {
return;
}
char *start = str + start_pos;
char *line_start = start;
char *end = str + end_pos;
/* go back to last newline / 0 byte */
while (*line_start != '\0' && *line_start != '\n') --line_start;
if (line_start < start) ++line_start;
/* skip space at start of line */
while (isspace(*line_start) && line_start < end)
++line_start;
char *line_end = strchr(start, '\n');
int has_newline = line_end != NULL;
if (!has_newline)
line_end = strchr(start, '\0');
assert(line_end);
if (!line_start[0])
fprintf(out, "");
else {
/* write up to start of error */
fwrite(line_start, 1, (size_t)(start - line_start), out);
if (ctx->color_enabled)
fprintf(out, TEXT_INDICATOR_START);
if (line_end < end) {
/* write error part (only go to end of line) */
fwrite(start, 1, (size_t)(line_end - start), out);
if (ctx->color_enabled)
fprintf(out, TEXT_INDICATOR_END);
} else {
/* write error part */
fwrite(start, 1, (size_t)(end - start), out);
if (ctx->color_enabled)
fprintf(out, TEXT_INDICATOR_END);
/* write rest of line */
fwrite(end, 1, (size_t)(line_end - end), out);
}
}
fprintf(out, "\n");
}
static FILE *err_ctx_file(ErrCtx *ctx) {
(void)ctx;
return stderr;
}
static void err_fprint(ErrCtx *ctx, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(err_ctx_file(ctx), fmt, args);
va_end(args);
}
static void print_location_highlight(FILE *out, Location where) {
assert(where.end >= where.start);
File *f = where.file;
ErrCtx *ctx = f->ctx;
if (where.start == 0 && where.end == 0) { err_fprint(ctx, "\n"); return; } /* null location */
Token *first = &f->tokens[where.start];
Token *last = &f->tokens[where.end-1];
print_pos_highlight(out, ctx, f, first->pos.start, last->pos.end);
}
/* for debugging */
static void fprint_location(FILE *out, Location location) {
File *f = location.file;
fprintf(out, "Line %ld of %s: ", (long)f->tokens[location.start].pos.line, f->filename);
print_location_highlight(out, location);
}
static void print_location(Location location) {
fprint_location(stdout, location);
}
static inline const char *ordinals(size_t x) {
switch (x % 10) {
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}
static void err_text_err(ErrCtx *ctx, const char *s) {
if (ctx->color_enabled)
err_fprint(ctx, TEXT_ERR_START "%s" TEXT_ERR_END, s);
else
err_fprint(ctx, "%s", s);
}
static void err_text_warn(ErrCtx *ctx, const char *s) {
if (ctx->color_enabled)
err_fprint(ctx, TEXT_WARN_START "%s" TEXT_WARN_END, s);
else
err_fprint(ctx, "%s", s);
}
static void err_text_info(ErrCtx *ctx, const char *s) {
if (ctx->color_enabled)
err_fprint(ctx, TEXT_INFO_START "%s" TEXT_INFO_END, s);
else
err_fprint(ctx, "%s", s);
}
static void err_text_important(ErrCtx *ctx, const char *s) {
if (ctx->color_enabled)
err_fprint(ctx, TEXT_IMPORTANT_START "%s" TEXT_IMPORTANT_END, s);
else
err_fprint(ctx, "%s", s);
}
static void err_vfprint(ErrCtx *ctx, const char *fmt, va_list args) {
vfprintf(err_ctx_file(ctx), fmt, args);
}
static void err_print_line_file(Location where) {
File *f = where.file;
ErrCtx *ctx = f->ctx;
err_fprint(ctx, " at line %lu of %s:\n", (unsigned long)f->tokens[where.start].pos.line, f->filename);
}
static void err_print_header_(Location where) {
ErrCtx *ctx = where.file->ctx;
err_text_err(ctx, "error");
if (where.start == 0 && where.end == 0) { err_fprint(ctx, ": "); return; }
err_print_line_file(where);
}
static void info_print_header_(Location where) {
ErrCtx *ctx = where.file->ctx;
err_text_info(ctx, "info");
if (where.start == 0 && where.end == 0) { err_fprint(ctx, ": "); return; }
err_print_line_file(where);
}
static void warn_print_header_(Location where) {
ErrCtx *ctx = where.file->ctx;
err_text_warn(ctx, "warning");
if (where.start == 0 && where.end == 0) { err_fprint(ctx, ": "); return; }
err_print_line_file(where);
}
static void err_print_footer_(Location where, bool show_ctx_stack) {
ErrCtx *ctx = where.file->ctx;
err_fprint(ctx, "\n\t");
print_location_highlight(err_ctx_file(ctx), where);
if (ctx && show_ctx_stack) {
arr_foreach(ctx->instance_stack, Location, inst) {
err_fprint(ctx, "While generating this instance of a function or struct:\n\t");
print_location_highlight(err_ctx_file(ctx), *inst);
}
}
}
/* Write nicely-formatted errors to the error file */
static void err_vprint(Location where, const char *fmt, va_list args) {
ErrCtx *ctx = where.file->ctx;
if (!ctx->enabled) return;
ctx->have_errored = true;
err_print_header_(where);
err_vfprint(ctx, fmt, args);
err_print_footer_(where, true);
}
static void err_print_(
#if ERR_SHOW_SOURCE_LOCATION
int line, const char *file,
#endif
Location where, const char *fmt, ...) {
va_list args;
#if ERR_SHOW_SOURCE_LOCATION
ErrCtx *ctx = where.file->ctx;
if (!ctx->enabled) return;
if (file)
err_fprint(ctx, "Generated by line %d of %s:\n", line, file);
#endif
va_start(args, fmt);
err_vprint(where, fmt, args);
va_end(args);
}
#if ERR_SHOW_SOURCE_LOCATION
#define err_print(...) err_print_(__LINE__, __FILE__, __VA_ARGS__)
#else
#define err_print err_print_
#endif
static void info_print(Location where, const char *fmt, ...) {
va_list args;
ErrCtx *ctx = where.file->ctx;
if (!ctx->enabled) return;
va_start(args, fmt);
info_print_header_(where);
err_vfprint(ctx, fmt, args);
err_print_footer_(where, false);
va_end(args);
}
static void warn_print_(
#if ERR_SHOW_SOURCE_LOCATION
int line, const char *file,
#endif
Location where, const char *fmt, ...) {
va_list args;
ErrCtx *ctx = where.file->ctx;
if (!ctx->enabled) return;
#if ERR_SHOW_SOURCE_LOCATION
if (file)
err_fprint(ctx, "Generated by line %d of %s:\n", line, file);
#endif
va_start(args, fmt);
warn_print_header_(where);
err_vfprint(ctx, fmt, args);
err_print_footer_(where, true);
va_end(args);
}
#if ERR_SHOW_SOURCE_LOCATION
#define warn_print(...) warn_print_(__LINE__, __FILE__, __VA_ARGS__)
#else
#define warn_print warn_print_
#endif
static void *err_malloc(size_t size) {
if (size == 0) return NULL;
void *ret = malloc(size);
if (!ret) {
fprintf(stderr, "Error: Out of memory.\n");
exit(EXIT_FAILURE);
}
#ifdef MALLOC_FILL
memset(ret, MALLOC_FILL, size);
#endif
return ret;
}
static void *err_calloc(size_t n, size_t size) {
if (n == 0 || size == 0) return NULL;
void *ret = calloc(n, size);
if (!ret) {
fprintf(stderr, "Error: Out of memory.\n");
exit(EXIT_FAILURE);
}
return ret;
}
static void *err_realloc(void *data, size_t new_size) {
if (new_size == 0) {
free(data);
return NULL;
}
void *ret = realloc(data, new_size);
if (!ret) {
fprintf(stderr, "Error: Out of memory.\n");
exit(EXIT_FAILURE);
}
return ret;
}