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;