diff --git a/include/gwion_util.h b/include/gwion_util.h index 2bc1285..86d8368 100644 --- a/include/gwion_util.h +++ b/include/gwion_util.h @@ -22,6 +22,7 @@ #include "gwion_text.h" #include "mp_string.h" #include "carg.h" +#include "prettyerr.h" #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__WIN32)) #include "windows_missing.h" diff --git a/include/prettyerr.h b/include/prettyerr.h new file mode 100644 index 0000000..8219240 --- /dev/null +++ b/include/prettyerr.h @@ -0,0 +1,125 @@ +// libprettyerr: error.h +// Copyright (C) 2021 Ethan Uppal +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef _LIBPRETTYERR_ERROR_H +#define _LIBPRETTYERR_ERROR_H + +#include +#include +#include + +struct libprettyerr_str { + size_t line; + const char* start; +}; + +#define PERR_Str(line_, start_) \ + (struct libprettyerr_str){ .line = line_, .start = start_ } +#define PERR_Str_None() \ + PERR_Str(0, NULL) + +struct libprettyerr_pos { + size_t index; + size_t length; +}; + +#define PERR_Pos(index_, length_) \ + (struct libprettyerr_pos){ .index = index_, .length = length_ } + +enum libprettyerr_errtype { + PERR_SUCCESS, // bold green + PERR_ERROR, // bold red + PERR_WARNING, // red + PERR_INFO, // bold white + PERR_HINT // bold yellow +}; + +enum libprettyerr_boxtype { + PERR_BOX_THICK_VERT, + PERR_BOX_THIN_HIGH, + PERR_BOX_THIN_UL, + PERR_BOX_THIN_UL_ROUNDED, + PERR_BOX_THIN_HORIZ, + PERR_BOX_THIN_BL, + PERR_BOX_THIN_BL_ROUNDED, + PERR_BOX_THIN_VERT, + PERR_BOX_MAX, +}; + +struct libprettyerr_error { + enum libprettyerr_errtype type; + + struct libprettyerr_str primary; + struct libprettyerr_pos error_position; + + const char* main; + const char* explain; + short error_code; + + const char* filename; +}; + +#define PERR_Error(type_, primary_, error_position_, main_, explain_, error_code_, filename_) \ + (struct libprettyerr_error){ \ + .type = type_, .primary = primary_, .error_position = error_position_, \ + .main = main_, .explain = explain_, .error_code = error_code_, \ + .filename = filename_ \ + } + +#define PERR_Secondary(type_, primary_, error_position_, main_, filename_) \ + (struct libprettyerr_error){ \ + .type = type_, .primary = primary_, .error_position = error_position_, \ + .main = main_, .filename = filename_ \ + } + +// TODO: tertiary for warnings +struct libprettyerr_printer; +typedef void (*libprettyerr_runner_t)(const struct libprettyerr_printer*, const struct libprettyerr_error*); +struct libprettyerr_printer { + const char* source; + FILE* stream; + const char** box_lookup; + libprettyerr_runner_t runner; + bool color; + bool utf8; + bool rounded; +}; + +#define PRETTYERR_EXPORT + +// Initializes a printer +PRETTYERR_EXPORT +void perr_printer_init(struct libprettyerr_printer* printer, FILE* stream, + const char* source, bool utf8, libprettyerr_runner_t style); + +// Uses the printer to display the provided error in +PRETTYERR_EXPORT +void perr_print_error(const struct libprettyerr_printer* printer, + const struct libprettyerr_error* err); + +// prints source filename and position +PRETTYERR_EXPORT +void perr_print_line_number(const struct libprettyerr_printer* printer, + const struct libprettyerr_error* err, + const char *color); + +typedef struct libprettyerr_error perr_t; +typedef struct libprettyerr_printer perr_printer_t; +typedef libprettyerr_runner_t perr_runner_t; +extern PRETTYERR_EXPORT perr_runner_t perr_runner_basic_style; +extern PRETTYERR_EXPORT perr_runner_t perr_runner_secondary_style; + +#endif /* _LIBPRETTYERR_PRETTYERR_H */ diff --git a/src/prettyerr.c b/src/prettyerr.c new file mode 100644 index 0000000..99af27e --- /dev/null +++ b/src/prettyerr.c @@ -0,0 +1,246 @@ +// libprettyerr: error.c +// Copyright (C) 2021 Ethan Uppal +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include + +#include +#include + +// Used to declare isatty if on Unix +#if defined(__unix__) || defined(__unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +#include +#endif + +#define _PRINTF(...) tcol_fprintf(printer->stream, __VA_ARGS__) +#define _PUTCHR(...) fputc(__VA_ARGS__, printer->stream) + +static char const _tcol_lookup[5] = {'G', 'R', 'M', 'B', 'Y'}; + +static char const *_ascii_box_lookup[8] = {"|", "|", "+", "-", "+", "|"}; +// static char const* _utf8_box_lookup[8] = { "┃", "╵", "┌", "─", "└", "│"}; +static char const *_utf8_box_lookup[8] = {"┃", "╵", "┌", "╭", + "─", "└", "╰", "│"}; + +static const char *_errtype_lookup[5] = {"succes", "error", "warning", "info", + "hint"}; + +void perr_printer_init(perr_printer_t *printer, FILE *stream, + const char *source, bool utf8, perr_runner_t style) { + printer->source = source; + printer->stream = stream; + +// Enables ANSI colors only in Unix terminals. False by default on Windows. +#if defined(__unix__) || defined(__unix) || \ + (defined(__APPLE__) && defined(__MACH__)) + const int fd = fileno(stream); + // if (fd < 0) { + // perror("fileno"); + // } + printer->color = isatty(fd); +#else + printer->color = false; +#endif + + if ((printer->utf8 = utf8)) { + printer->box_lookup = _utf8_box_lookup; + } else { + printer->box_lookup = _ascii_box_lookup; + } + printer->runner = style; +} + +static void perr_print_column(const perr_printer_t *printer, const char *color, + const size_t column) { + _PRINTF(" %s%s{0} %*s", color, printer->box_lookup[PERR_BOX_THICK_VERT], + (int)column, ""); +} + +static inline void _perr_print_filename(const perr_printer_t *printer, + const perr_t * err, + const size_t column) { + _PRINTF("{0}{+}%s{0}:%zu:%zu: ", err->filename, err->primary.line, column); +} + +static void _perr_print_error(const perr_printer_t *printer, const perr_t *err, + const char *color) { + _PRINTF("%s%s", color, _errtype_lookup[err->type]); + if (err->error_code) + _PRINTF("[%c%04d]", toupper(_errtype_lookup[err->type][0]), + err->error_code); + _PRINTF("{0}: "); +} + +// Print the line number and a | to denote the start of the line. +void perr_print_line_number(const perr_printer_t *printer, const perr_t *err, + const char *color) { + _PRINTF("{-}%5zu{0} %s%s{0} ", err->primary.line, color, + printer->box_lookup[PERR_BOX_THICK_VERT]); +} + +// Print the line. +static void _perr_print_offending_line(const perr_printer_t *printer, + const perr_t * err, + const char * error_line, + const char *color, const size_t column) { + _PRINTF("%.*s", (int)column, error_line); + const char *bug = error_line + column; + _PRINTF("%s{-}%.*s{0}", color, (int)err->error_position.length, bug); + const char *end = bug + err->error_position.length; + char * nl = strstr(end, "\n"); + ptrdiff_t nl_index = nl ? nl - end : (ptrdiff_t)strlen(end) - 1; + _PRINTF("%.*s\n", (int)nl_index, end); +} + +// Print a series of '^' showing where the error occurs. +static inline void _perr_print_highlight_error(const perr_printer_t *printer, + const perr_t * err, + const char * error_line, + const char * color, + const size_t column, + const bool small) { + perr_print_column(printer, color, 0); + char* line = (char*)error_line; + size_t i = 0; + while(i < column) { + if(*line =='\t') _PRINTF("\t"); + else _PRINTF(" "); + i++; + line++; + } + const enum libprettyerr_boxtype type = + (!small ? PERR_BOX_THIN_UL : PERR_BOX_THIN_BL) + printer->rounded; + if (err->error_position.length > 1) { + _PRINTF("%s{-}%s", color, printer->box_lookup[type]); + { + size_t i; + for (i = 1; i < err->error_position.length; i++) { + _PRINTF(printer->box_lookup[PERR_BOX_THIN_HORIZ]); + } + } + } else + _PRINTF("%s{-}%s", color, printer->box_lookup[PERR_BOX_THIN_VERT]); + _PRINTF("{0}\n"); +} + +// Print the fix, dimmed +// TODO: this should be tertiary +static inline void _perr_print_fix(const perr_printer_t *printer, + const char * fix) { + _PRINTF("{-}"); + _PRINTF(fix); + _PRINTF("{0}\n"); +} + +static void lookup_color(char *color, enum libprettyerr_errtype type) { + char _color[16]; + sprintf(_color, "+%c", _tcol_lookup[type]); + size_t len; + const int status = tcol_color_parse(color, 16, _color, 2, &len); + if (status != TermColorErrorNone) { + color[0] = 0; + // error + } else + color[len] = 0; +} + +static inline void perr_print_basic_style(const perr_printer_t *printer, + const perr_t * err) { + // Normal errors: + // + // $filename:$line:$col: $type: $msg + // $line | + // | ^^^^ + + // Here we scan backwards until we reach the start of the line (or the + // start of the source code). This allows us to retrieve the line that has + // the error. + size_t idx_cpy = err->error_position.index; + while (idx_cpy > 0 && printer->source[idx_cpy - 1] != '\n') { idx_cpy--; } + const char *error_line = (printer->source + idx_cpy); + error_line += (printer->source[idx_cpy] == '\n') ? 1 : 0; + + // The column is how far the error's index is from the start-of-line's + // index. + const size_t column = err->error_position.index - idx_cpy; + + char color[17]; + lookup_color(color, err->type); + + // Here we print the first row of the error message which provides general + // information such as filename, line, column, error type and a message. + _perr_print_filename(printer, err, column); + _perr_print_error(printer, err, color); + _PRINTF(err->main); + _PUTCHR('\n'); + + perr_print_line_number(printer, err, color); + _perr_print_offending_line(printer, err, error_line, color, column); + _perr_print_highlight_error(printer, err, error_line, color, column, !err->explain); + + // Adds a subsidiary error note, if applicable + if (err->explain) { + perr_print_column(printer, color, column); + _PRINTF("{-}%s%s{0}\n", color, printer->box_lookup[PERR_BOX_THIN_HIGH]); + perr_print_column(printer, color, column); + _PRINTF(err->explain); + _PUTCHR('\n'); + } +} + +/* +static inline void perr_print_complex(const perr_printer_t* printer, + const perr_t* err) { + // IDK COPY BRENDANZAB (DONT) +} +*/ + +static inline void perr_print_secondary_style(const perr_printer_t *printer, + const perr_t * err) { + size_t idx_cpy = err->error_position.index; + while (idx_cpy > 0 && printer->source[idx_cpy - 1] != '\n') { idx_cpy--; } + const char *error_line = (printer->source + idx_cpy); + error_line += (printer->source[idx_cpy] == '\n') ? 1 : 0; + + // The column is how far the error's index is from the start-of-line's + // index. + const size_t column = err->error_position.index - idx_cpy; + + char color[17]; + lookup_color(color, err->type); + + _perr_print_filename(printer, err, column); + _PUTCHR('\n'); + + perr_print_line_number(printer, err, color); + _perr_print_offending_line(printer, err, error_line, color, column); + _perr_print_highlight_error(printer, err, error_line, color, column, false); + + perr_print_column(printer, color, column); + _PRINTF("{-}%s%s%.*s{0} ", color, + printer->box_lookup[PERR_BOX_THIN_BL + printer->rounded], 3, + printer->box_lookup[PERR_BOX_THIN_HORIZ]); + _PRINTF(err->main); + _PUTCHR('\n'); +} + +void perr_print_error(const perr_printer_t *printer, const perr_t *err) { + printer->runner(printer, err); +} + +perr_runner_t perr_runner_basic_style = perr_print_basic_style; +perr_runner_t perr_runner_secondary_style = perr_print_secondary_style;