aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-24 18:18:23 +0200
committerMel <mel@rnrd.eu>2026-05-24 18:18:23 +0200
commita76fd9b5af153773273121c7aec2a9cbeed1dfb7 (patch)
treea6c619e44df4f38f60c457b9122e207b0a3c09f0 /boot
parent8c4c219c945dfb41415e04654f488f7880e58ca2 (diff)
downloadcatskill-a76fd9b5af153773273121c7aec2a9cbeed1dfb7.tar.zst
catskill-a76fd9b5af153773273121c7aec2a9cbeed1dfb7.zip
Make diagnostics generic over passes, condensing different error handling
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot')
-rw-r--r--boot/catboot.c25
-rw-r--r--boot/common.c316
-rw-r--r--boot/ir.c36
-rw-r--r--boot/lex.c93
-rw-r--r--boot/lower.c378
-rw-r--r--boot/parse.c79
-rw-r--r--boot/tests/lower/shadowing_error.cskt4
-rw-r--r--boot/tests/lower/type_cycle_error.cskt7
-rw-r--r--boot/visit/ir.c81
9 files changed, 709 insertions, 310 deletions
diff --git a/boot/catboot.c b/boot/catboot.c
index 3919a2a..e340d48 100644
--- a/boot/catboot.c
+++ b/boot/catboot.c
@@ -130,12 +130,11 @@ debug_lower_pass(struct Source_File source_file)
}
struct Unit unit = { 0 };
- lower_tree(&tree, source_file, &unit);
+ lower_tree(&tree, &unit);
printer(&unit);
- // diagnostics are part of the printed unit, so the caller can read
- // them from the dump. always returning success keeps snapshot tests
- // viable for both the green and the diagnostic paths.
+ // always succeed. errors live in the printed unit and snapshot tests
+ // pick them up from there.
return 0;
}
@@ -156,11 +155,12 @@ debug_transpile_pass(struct Source_File source_file)
}
struct Unit unit = { 0 };
- lower_tree(&tree, source_file, &unit);
- if (unit.had_error) {
- log_error("lowering finished with errors\n");
- return 1;
+ lower_tree(&tree, &unit);
+ FOR_EACH_ARRAY (struct Lower_Error, err, &unit.lower_errors) {
+ struct Diagnostic d = lower_error_to_diagnostic(err, &unit, source_file);
+ diagnostic_display(source_file, &d, stderr);
}
+ if (unit.had_error) return 1;
struct Transpile_Output output = transpile_output_from_file(stdout);
@@ -294,11 +294,12 @@ default_command(struct Command_Arguments* arguments)
}
struct Unit unit = { 0 };
- lower_tree(&tree, source_file, &unit);
- if (unit.had_error) {
- log_error("lowering finished with errors\n");
- return COMMAND_FAIL;
+ lower_tree(&tree, &unit);
+ FOR_EACH_ARRAY (struct Lower_Error, err, &unit.lower_errors) {
+ struct Diagnostic d = lower_error_to_diagnostic(err, &unit, source_file);
+ diagnostic_display(source_file, &d, stderr);
}
+ if (unit.had_error) return COMMAND_FAIL;
struct Transpiler transpiler;
transpiler_new(&transpiler, transpile_output_from_string());
diff --git a/boot/common.c b/boot/common.c
index 0524b3b..85b1422 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -938,6 +938,54 @@ string_buffer_append(struct String_Buffer* buffer, struct String s)
buffer->data[buffer->length] = '\0';
}
+// pairs with `%S` in the printf-like formatters below.
+#define STR(s) (int)(s).length, (s).data
+
+void
+_format_translate_format(ascii* dst, const ascii* src, uint dst_size)
+{
+ uint si = 0, di = 0;
+ while (di + 4 < dst_size) {
+ ascii c = src[si];
+ if (c == '\0') break;
+ if (c == '%' && src[si + 1] == 'S') {
+ dst[di++] = '%';
+ dst[di++] = '.';
+ dst[di++] = '*';
+ dst[di++] = 's';
+ si += 2;
+ } else {
+ dst[di++] = c;
+ si++;
+ }
+ }
+ dst[di] = '\0';
+}
+
+#define MAX_FORMATTED_STRING_SIZE 1024
+
+struct String
+string_vformat(const ascii* format, va_list args)
+{
+ ascii translated[MAX_FORMAT_STRING_SIZE];
+ _format_translate_format(translated, format, sizeof translated);
+
+ ascii buffer[MAX_FORMATTED_STRING_SIZE];
+ int written = vsnprintf(buffer, sizeof buffer, translated, args);
+ check(written >= 0, "vsnprintf failed");
+ return string_new(buffer, (uint)written);
+}
+
+struct String
+string_format(const ascii* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ struct String result = string_vformat(format, args);
+ va_end(args);
+ return result;
+}
+
// appends a C-style string to the string buffer.
void
string_buffer_append_c_str(struct String_Buffer* buffer, const ascii* c_str)
@@ -954,6 +1002,17 @@ string_buffer_append_c_str(struct String_Buffer* buffer, const ascii* c_str)
buffer->data[buffer->length] = '\0';
}
+// appends a formatted string to the buffer.
+void
+string_buffer_appendf(struct String_Buffer* buffer, const ascii* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ struct String formatted = string_vformat(format, args);
+ va_end(args);
+ string_buffer_append(buffer, formatted);
+}
+
// converts the string buffer to an immutable string.
// allocates a new string in the string region.
struct String
@@ -977,6 +1036,263 @@ struct Source_File
struct String path;
};
+// byte position within a file.
+typedef uint64 Pos;
+
+// an extent of text within a file,
+// defined by its start and end byte positions.
+struct Span
+{
+ Pos start;
+ Pos end;
+};
+
+struct Span
+span_new(Pos start, Pos end)
+{
+ return (struct Span){ .start = start, .end = end };
+}
+
+struct Span
+span_empty(void)
+{
+ return (struct Span){ 0, 0 };
+}
+
+struct Span
+span_width(Pos start, uint width)
+{
+ return (struct Span){ .start = start, .end = start + width };
+}
+
+// create a span which encompasses two spans.
+struct Span
+span_merge(struct Span a, struct Span b)
+{
+ return (struct Span){
+ .start = a.start < b.start ? a.start : b.start,
+ .end = a.end > b.end ? a.end : b.end,
+ };
+}
+
+// expand a span by a number of bytes.
+// negative number expands the span to the left,
+// positive number expands the span to the right.
+struct Span
+span_expand(struct Span span, integer by)
+{
+ uint start_expand = by < 0 ? -by : 0;
+ uint end_expand = by > 0 ? by : 0;
+ return (struct Span){
+ .start = span.start - start_expand,
+ .end = span.end + end_expand,
+ };
+}
+
+// check if two spans are equal.
+bool
+span_equals(struct Span a, struct Span b)
+{
+ return a.start == b.start && a.end == b.end;
+}
+
+// check if span equals = { 0, 0 }.
+bool
+span_is_empty(struct Span span)
+{
+ return span_equals(span, (struct Span){ 0, 0 });
+}
+
+uint
+span_length(struct Span span)
+{
+ uint length = span.end - span.start;
+ return length == 0 ? 1 : length;
+}
+
+// a cursor position placed within a text file.
+struct Cursor
+{
+ uint line;
+ uint column;
+};
+
+struct Cursor
+cursor_new(uint line, uint column)
+{
+ return (struct Cursor){ .line = line, .column = column };
+}
+
+struct Cursor
+cursor_empty(void)
+{
+ return cursor_new(0, 0);
+}
+
+enum Diagnostic_Severity
+{
+ DIAGNOSTIC_NOTE,
+ DIAGNOSTIC_WARNING,
+ DIAGNOSTIC_ERROR,
+};
+
+struct Diagnostic
+{
+ enum Diagnostic_Severity severity;
+ struct Span span;
+ // first line of the rendered diagnostic. may contain `\n` to introduce
+ // extra body lines, each indented under the headline.
+ struct String message;
+ // optional. rendered as `hint: <hint>` under the body when non-empty.
+ struct String hint;
+ // NOTE(mel): probably overkill for bootstrapping, but i kind of want to figure
+ // the error format out for future iterations, and want to see it in action,
+ // to have more thoughts about improving it later on.
+};
+
+const ascii*
+diagnostic_severity_label(enum Diagnostic_Severity severity)
+{
+ switch (severity) {
+ case DIAGNOSTIC_NOTE:
+ return "note.";
+ case DIAGNOSTIC_WARNING:
+ return "warning?";
+ case DIAGNOSTIC_ERROR:
+ return "error!";
+ default:
+ return "diagnostic";
+ }
+}
+
+const ascii*
+diagnostic_severity_color(enum Diagnostic_Severity severity)
+{
+ switch (severity) {
+ case DIAGNOSTIC_NOTE:
+ return ANSI_BLUE;
+ case DIAGNOSTIC_WARNING:
+ return ANSI_YELLOW;
+ case DIAGNOSTIC_ERROR:
+ return ANSI_RED;
+ default:
+ return ANSI_WHITE;
+ }
+}
+
+// line and column are 1-based.
+void
+source_position_from_span(struct String source, struct Span span, uint* out_line, uint* out_column)
+{
+ uint line = 1, column = 1;
+ uint upto = span.start;
+ if (upto > string_length(source)) upto = string_length(source);
+ for (uint i = 0; i < upto; ++i) {
+ if (string_at(source, i) == '\n') {
+ line++;
+ column = 1;
+ } else {
+ column++;
+ }
+ }
+ *out_line = line;
+ *out_column = column;
+}
+
+struct Span
+source_line_span(struct String source, struct Span span)
+{
+ Pos line_start = span.start, line_end = span.start;
+ while (line_start > 0 && string_at(source, line_start - 1) != '\n') line_start--;
+ while (line_end < string_length(source) && string_at(source, line_end) != '\n') line_end++;
+ return span_new(line_start, line_end);
+}
+
+// render a diagnostic to a stream.
+void
+diagnostic_display(struct Source_File source, struct Diagnostic* d, FILE* out)
+{
+ uint line, column;
+ source_position_from_span(source.source, d->span, &line, &column);
+
+ STRING_FORMAT_TO(source.path, out, ANSI_WHITE "%s:%lu:%lu:\n", line, column);
+
+ // first line of the message is the headline. anything after a `\n` is body.
+ uint headline_end = 0;
+ while (headline_end < d->message.length && d->message.data[headline_end] != '\n')
+ headline_end++;
+
+ fprintf(out, ANSI_BOLD "%s%s " ANSI_WHITE, diagnostic_severity_color(d->severity),
+ diagnostic_severity_label(d->severity));
+ fprintf(out, "%.*s", (int)headline_end, d->message.data);
+ if (d->severity == DIAGNOSTIC_ERROR) fprintf(out, " :(");
+ fprintf(out, "\n" ANSI_NO_BOLD);
+
+ if (headline_end < d->message.length) {
+ uint body_start = headline_end + 1;
+ fprintf(out, "%.*s\n", (int)(d->message.length - body_start), d->message.data + body_start);
+ }
+
+ if (!string_is_empty(d->hint)) STRING_FORMAT_TO(d->hint, out, " hint: %S\n");
+
+ struct Span line_span = source_line_span(source.source, d->span);
+ struct String_View source_line =
+ string_substring(source.source, line_span.start, line_span.end);
+
+ fprintf(out, ANSI_WHITE "%lu| ", line);
+ STRING_FORMAT_TO(source_line, out, "%S\n");
+
+ uint line_number_length = ceil(log10(line + 1));
+ fprintf(out, "%s%*s", diagnostic_severity_color(d->severity),
+ (int)(column + 1 + line_number_length), " ");
+ uint width = span_length(d->span);
+ if (width == 0) width = 1;
+ for (uint w = 0; w < width; w++) fprintf(out, "^");
+ fprintf(out, "\n" ANSI_RESET);
+}
+
+void
+diagnostic_push_str(
+ Array(struct Diagnostic) * into, bool* out_had_error, enum Diagnostic_Severity severity,
+ struct Span span, struct String message)
+{
+ struct Diagnostic d = { .severity = severity, .span = span, .message = message };
+ array_push(into, &d);
+ if (out_had_error && severity == DIAGNOSTIC_ERROR) *out_had_error = true;
+}
+
+void
+diagnostic_push(
+ Array(struct Diagnostic) * into, bool* out_had_error, enum Diagnostic_Severity severity,
+ struct Span span, const ascii* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ struct String message = string_vformat(format, args);
+ va_end(args);
+ diagnostic_push_str(into, out_had_error, severity, span, message);
+}
+
+#define diagnostic_emit(unit, severity, span, ...) \
+ diagnostic_push(&(unit)->diagnostics, &(unit)->had_error, severity, span, __VA_ARGS__)
+
+#define diagnostic_emit_str(unit, severity, span, message) \
+ diagnostic_push_str(&(unit)->diagnostics, &(unit)->had_error, severity, span, message)
+
+void
+diagnostic_render(
+ struct Source_File source, FILE* out, enum Diagnostic_Severity severity, struct Span span,
+ const ascii* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ struct String message = string_vformat(format, args);
+ va_end(args);
+
+ struct Diagnostic d = { .severity = severity, .span = span, .message = message };
+ diagnostic_display(source, &d, out);
+}
+
// single iteration of the CRC32 checksum algorithm
// described in POSIX.
// see: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/cksum.html
diff --git a/boot/ir.c b/boot/ir.c
index 4bdfc6e..6ea055e 100644
--- a/boot/ir.c
+++ b/boot/ir.c
@@ -444,19 +444,33 @@ struct Import
struct Span span;
};
-enum Diagnostic_Severity
+enum Lower_Error_Kind
{
- DIAGNOSTIC_NOTE,
- DIAGNOSTIC_WARNING,
- DIAGNOSTIC_ERROR,
+ LOWER_ERROR_NONE,
+ LOWER_ERROR_UNDEFINED_TYPE,
+ LOWER_ERROR_DUPLICATE_TYPE,
+ LOWER_ERROR_DUPLICATE_FUNCTION,
+ LOWER_ERROR_NAME_SHADOWS,
+ LOWER_ERROR_TYPE_CYCLE,
+ LOWER_ERROR_ASSIGNMENT_AS_EXPRESSION,
+ LOWER_ERROR_RANGE_OUTSIDE_LOOP,
+ LOWER_ERROR_CONSTRUCT_SUBJECT_NOT_NAME,
+ LOWER_ERROR_TYPE_EXPRESSION_IN_BODY,
+ LOWER_ERROR_UNSUPPORTED_TOP_LEVEL,
+ LOWER_ERROR_UNKNOWN_LOOP_STYLE,
+ LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN,
+ LOWER_ERROR_UNIMPLEMENTED,
};
-// TODO: we might want to take this out to be used for other passes.
-struct Diagnostic
+struct Lower_Error
{
- enum Diagnostic_Severity severity;
+ enum Lower_Error_Kind kind;
struct Span span;
- struct String message;
+
+ // per-error details, meaning depends on each error kind!
+ struct String name;
+ struct String detail;
+ Array(Type_Id) cycle_chain;
};
// a single translation unit.
@@ -472,7 +486,7 @@ struct Unit
Array(Type_Id) type_emission_order;
bool had_error;
- Array(struct Diagnostic) diagnostics;
+ Array(struct Lower_Error) lower_errors;
};
REGION(struct Type, type)
@@ -655,10 +669,6 @@ ir_make_construct(Type_Id type_id, Array(struct Construct_Field) fields, struct
return expression_new(EXPRESSION_CONSTRUCT, v, span);
}
-// type-ref constructors.
-// `bare` is the no-modifier form; `pointer` wraps one `&` modifier; `with_mods`
-// takes a fully-formed mods array (caller owns it).
-
struct Type_Ref
type_ref_bare(Type_Id type_id)
{
diff --git a/boot/lex.c b/boot/lex.c
index 594905f..18f8690 100644
--- a/boot/lex.c
+++ b/boot/lex.c
@@ -13,99 +13,6 @@
#define MAX_CHAR_BUFFER_SIZE 256
-// byte position within a file.
-typedef uint64 Pos;
-
-// an extent of text within a file,
-// defined by its start and end byte positions.
-struct Span
-{
- Pos start;
- Pos end;
-};
-
-struct Span
-span_new(Pos start, Pos end)
-{
- return (struct Span){ .start = start, .end = end };
-}
-
-struct Span
-span_empty(void)
-{
- return (struct Span){ 0, 0 };
-}
-
-struct Span
-span_width(Pos start, uint width)
-{
- return (struct Span){ .start = start, .end = start + width };
-}
-
-// create a span which encompasses two spans.
-struct Span
-span_merge(struct Span a, struct Span b)
-{
- return (struct Span){
- .start = a.start < b.start ? a.start : b.start,
- .end = a.end > b.end ? a.end : b.end,
- };
-}
-
-// expand a span by a number of bytes.
-// negative number expands the span to the left,
-// positive number expands the span to the right.
-struct Span
-span_expand(struct Span span, integer by)
-{
- uint start_expand = by < 0 ? -by : 0;
- uint end_expand = by > 0 ? by : 0;
- return (struct Span){
- .start = span.start - start_expand,
- .end = span.end + end_expand,
- };
-}
-
-// check if two spans are equal.
-bool
-span_equals(struct Span a, struct Span b)
-{
- return a.start == b.start && a.end == b.end;
-}
-
-// check if span equals = { 0, 0 }.
-bool
-span_is_empty(struct Span span)
-{
- return span_equals(span, (struct Span){ 0, 0 });
-}
-
-uint
-span_length(struct Span span)
-{
- uint length = span.end - span.start;
- return length == 0 ? 1 : length;
-}
-
-// a cursor position placed within a text file.
-struct Cursor
-{
- uint line;
- uint column;
-};
-
-struct Cursor
-cursor_new(uint line, uint column)
-{
- return (struct Cursor){ .line = line, .column = column };
-}
-
-struct Cursor
-cursor_empty(void)
-{
- return cursor_new(0, 0);
-}
-
// what kind of token is it?
enum Token_Kind
{
diff --git a/boot/lower.c b/boot/lower.c
index 01762a5..65eb18c 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -73,10 +73,6 @@ struct Lower_Context
// the translation unit being created
struct Unit* unit;
- // text source of the unit in translation
- // used for attaching diagnostics to correct lines
- struct Source_File source;
-
// monotonic counter of synthesized types
uint synthetic_type_counter;
@@ -89,21 +85,89 @@ struct Lower_Context
};
void
-lower_emit_error(struct Unit* unit, struct Span span, struct String message)
+lower_push_error(struct Unit* unit, struct Lower_Error err)
{
- struct Diagnostic d = {
- .severity = DIAGNOSTIC_ERROR,
- .span = span,
- .message = message,
- };
- array_push(&unit->diagnostics, &d);
+ array_push(&unit->lower_errors, &err);
unit->had_error = true;
}
+// render the cycle's chain to a nice human-y message.
void
-lower_emit_error_c(struct Unit* unit, struct Span span, const ascii* message)
+lower_format_cycle_chain(
+ struct String_Buffer* buf, struct Unit* unit, struct Source_File source, Array(Type_Id) * chain)
+{
+ uint n = array_length(chain);
+ for (uint k = 0; k < n; ++k) {
+ Type_Id current_id = *array_at(Type_Id, chain, k);
+ Type_Id next_id = *array_at(Type_Id, chain, (k + 1) % n);
+ struct Type* current = *array_at(struct Type*, &unit->types.entries, current_id);
+ struct Type* next = *array_at(struct Type*, &unit->types.entries, next_id);
+ uint line, column;
+ source_position_from_span(source.source, current->span, &line, &column);
+
+ if (k > 0) string_buffer_appendf(buf, "\n");
+ string_buffer_appendf(
+ buf, " %S (line %lu) depends on %S", STR(current->name), line, STR(next->name));
+ }
+}
+
+// turn a structured lower error into a displayable diagnostic.
+struct Diagnostic
+lower_error_to_diagnostic(struct Lower_Error* err, struct Unit* unit, struct Source_File source)
{
- lower_emit_error(unit, span, string_from_c_string(message));
+ struct Diagnostic d = { .severity = DIAGNOSTIC_ERROR, .span = err->span };
+ switch (err->kind) {
+ case LOWER_ERROR_UNDEFINED_TYPE:
+ d.message = string_format("undefined type '%S'", STR(err->name));
+ return d;
+ case LOWER_ERROR_DUPLICATE_TYPE:
+ d.message = string_format("duplicate type '%S'", STR(err->name));
+ return d;
+ case LOWER_ERROR_DUPLICATE_FUNCTION:
+ d.message = string_format("duplicate function '%S'", STR(err->name));
+ return d;
+ case LOWER_ERROR_NAME_SHADOWS:
+ d.message = string_format("name '%S' shadows an existing binding", STR(err->name));
+ return d;
+ case LOWER_ERROR_TYPE_CYCLE: {
+ struct String_Buffer buf = string_buffer_new(512);
+ string_buffer_appendf(&buf, "type cycle detected\n");
+ lower_format_cycle_chain(&buf, unit, source, &err->cycle_chain);
+ d.message = string_buffer_to_string(&buf);
+ d.hint = string_from_static_c_string(
+ "break the cycle with a reference (use `&T` instead of `T`)");
+ return d;
+ }
+ case LOWER_ERROR_ASSIGNMENT_AS_EXPRESSION:
+ d.message = string_from_static_c_string("assignment cannot appear as an expression");
+ return d;
+ case LOWER_ERROR_RANGE_OUTSIDE_LOOP:
+ d.message =
+ string_from_static_c_string("range expressions are only valid in loop initializers");
+ return d;
+ case LOWER_ERROR_CONSTRUCT_SUBJECT_NOT_NAME:
+ d.message = string_from_static_c_string("construction subject must be a type name");
+ return d;
+ case LOWER_ERROR_TYPE_EXPRESSION_IN_BODY:
+ d.message =
+ string_from_static_c_string("type expression not allowed inside a function body");
+ return d;
+ case LOWER_ERROR_UNSUPPORTED_TOP_LEVEL:
+ d.message = string_from_static_c_string("unsupported top-level statement");
+ return d;
+ case LOWER_ERROR_UNKNOWN_LOOP_STYLE:
+ d.message = string_from_static_c_string("unknown loop style");
+ return d;
+ case LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN:
+ d.message = string_from_static_c_string("unknown compound assignment operator");
+ return d;
+ case LOWER_ERROR_UNIMPLEMENTED:
+ d.message = string_format("unimplemented: %S", STR(err->detail));
+ return d;
+ default:
+ d.message = string_from_static_c_string("unknown lower error");
+ return d;
+ }
}
bool
@@ -419,11 +483,13 @@ lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type)
if (lower_type_lookup_by_name(ctx->unit, current->value.name.name, &id)) {
ref.type_id = id;
} else {
- lower_emit_error(
- ctx->unit, current->span,
- string_concatenate(
- ARG_ASCII, "undefined type '", ARG_STRING, current->value.name.name, ARG_ASCII,
- "'", ARG_END));
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNDEFINED_TYPE,
+ .span = current->span,
+ .name = current->value.name.name,
+ });
}
return ref;
}
@@ -438,10 +504,22 @@ lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type)
return ref;
}
case TREE_TYPE_MAP:
- lower_emit_error_c(ctx->unit, current->span, "unimplemented: map types");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = current->span,
+ .detail = string_from_static_c_string("map types"),
+ });
return ref;
default:
- lower_emit_error_c(ctx->unit, current->span, "unimplemented: this type form");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = current->span,
+ .detail = string_from_static_c_string("this type form"),
+ });
return ref;
}
}
@@ -494,10 +572,10 @@ lower_register_function_shell(struct Unit* unit, struct String name, struct Span
{
Function_Id existing;
if (lower_function_lookup_by_name(unit, name, &existing)) {
- lower_emit_error(
- unit, span,
- string_concatenate(
- ARG_ASCII, "duplicate function '", ARG_STRING, name, ARG_ASCII, "'", ARG_END));
+ lower_push_error(
+ unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_DUPLICATE_FUNCTION, .span = span, .name = name });
return false;
}
@@ -517,10 +595,9 @@ lower_register_type_shell(struct Unit* unit, struct String name, struct Span spa
{
Type_Id existing;
if (lower_type_lookup_by_name(unit, name, &existing)) {
- lower_emit_error(
- unit, span,
- string_concatenate(
- ARG_ASCII, "duplicate type '", ARG_STRING, name, ARG_ASCII, "'", ARG_END));
+ lower_push_error(
+ unit,
+ (struct Lower_Error){ .kind = LOWER_ERROR_DUPLICATE_TYPE, .span = span, .name = name });
return false;
}
@@ -570,11 +647,13 @@ lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_T
// aliases always need their target's full definition.
array_push(&type->depends_on, &target_id);
} else {
- lower_emit_error(
- ctx->unit, tree_type->span,
- string_concatenate(
- ARG_ASCII, "undefined type '", ARG_STRING, tree_type->value.name.name,
- ARG_ASCII, "'", ARG_END));
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNDEFINED_TYPE,
+ .span = tree_type->span,
+ .name = tree_type->value.name.name,
+ });
}
break;
}
@@ -628,7 +707,13 @@ lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_T
break;
}
case TREE_TYPE_CLASS:
- lower_emit_error_c(ctx->unit, tree_type->span, "unimplemented: class types");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_type->span,
+ .detail = string_from_static_c_string("class types"),
+ });
break;
default: {
// this is a type alias assigning a name to a structural type.
@@ -698,11 +783,19 @@ lower_pass_1_fill_bodies(struct Lower_Context* ctx, struct Tree* tree)
}
if (stmt->kind == TREE_STATEMENT_PRAGMA) {
- lower_emit_error_c(ctx->unit, stmt->span, "unimplemented: top-level pragmas");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = stmt->span,
+ .detail = string_from_static_c_string("top-level pragmas"),
+ });
continue;
}
- lower_emit_error_c(ctx->unit, stmt->span, "unsupported top-level statement");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){ .kind = LOWER_ERROR_UNSUPPORTED_TOP_LEVEL, .span = stmt->span });
}
}
@@ -778,12 +871,19 @@ lower_expression_binary_operation(struct Lower_Context* ctx, struct Tree_Express
enum Binary_Operation op = tree_expr->value.binary_operator.operation;
// TODO: maybe we want to support assignment expressions some day. today they aren't.
if (op >= BINARY_ASSIGN) {
- lower_emit_error_c(ctx->unit, tree_expr->span, "assignment cannot appear as an expression");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_ASSIGNMENT_AS_EXPRESSION,
+ .span = tree_expr->span,
+ });
return nil;
}
if (op == BINARY_RANGE) {
- lower_emit_error_c(
- ctx->unit, tree_expr->span, "range expressions are only valid in loop initializers");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_RANGE_OUTSIDE_LOOP, .span = tree_expr->span });
return nil;
}
return ir_make_binary(
@@ -800,7 +900,13 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
// table to reorder, we only handle positional arguments today.
FOR_EACH_ARRAY (struct String, name, &group->argument_names) {
if (name->length > 0) {
- lower_emit_error_c(ctx->unit, tree_expr->span, "unimplemented: named call arguments");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_expr->span,
+ .detail = string_from_static_c_string("named call arguments"),
+ });
return nil;
}
}
@@ -819,16 +925,23 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
{
struct Tree_Expression* subject = tree_expr->value.construct.subject;
if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
- lower_emit_error_c(ctx->unit, tree_expr->span, "construction subject must be a type name");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_CONSTRUCT_SUBJECT_NOT_NAME,
+ .span = tree_expr->span,
+ });
return nil;
}
Type_Id type_id;
if (!lower_type_lookup_by_name(ctx->unit, subject->value.name.name, &type_id)) {
- lower_emit_error(
- ctx->unit, subject->span,
- string_concatenate(
- ARG_ASCII, "undefined type '", ARG_STRING, subject->value.name.name, ARG_ASCII, "'",
- ARG_END));
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNDEFINED_TYPE,
+ .span = subject->span,
+ .name = subject->value.name.name,
+ });
return nil;
}
@@ -849,13 +962,15 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
return ir_make_construct(type_id, fields, tree_expr->span);
}
-// type-as-expression is only valid as the right-side of a top-level binding;
+// type-as-expression is only valid as the right-side of a top-level binding,
// anywhere else it is an error.
struct Expression*
lower_expression_type(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
{
- lower_emit_error_c(
- ctx->unit, tree_expr->span, "type expression not allowed inside a function body");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_TYPE_EXPRESSION_IN_BODY, .span = tree_expr->span });
return nil;
}
@@ -918,7 +1033,13 @@ lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
case TREE_EXPRESSION_MUST:
// TODO: implement these
default:
- lower_emit_error_c(ctx->unit, tree_expr->span, "unimplemented: this expression kind");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_expr->span,
+ .detail = string_from_static_c_string("this expression kind"),
+ });
return nil;
}
}
@@ -997,17 +1118,25 @@ lower_statement_declaration(struct Lower_Context* ctx, struct Tree_Statement* tr
// TODO: multi-name decls like `var a, b int = ...` need to split into
// N sibling declarations or introduce a temporary for the initializer.
if (array_length(&decl->names) != 1) {
- lower_emit_error_c(ctx->unit, tree_stmt->span, "unimplemented: multi-name declarations");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_stmt->span,
+ .detail = string_from_static_c_string("multi-name declarations"),
+ });
return nil;
}
struct String name = *array_at(struct String, &decl->names, 0);
if (lower_is_shadowing(ctx, name)) {
- lower_emit_error(
- ctx->unit, tree_stmt->span,
- string_concatenate(
- ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII, "' shadows an existing binding",
- ARG_END));
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_NAME_SHADOWS,
+ .span = tree_stmt->span,
+ .name = name,
+ });
return nil;
}
struct Type_Ref type = lower_intern_type_ref(ctx, decl->type);
@@ -1039,16 +1168,26 @@ lower_statement_expression(struct Lower_Context* ctx, struct Tree_Statement* tre
if (!lhs_tree || lhs_tree->kind != TREE_EXPRESSION_NAME) {
// TODO: implement non-trivial lhs assignments like function
// calls for example. not too common but sometimes necessary.
- lower_emit_error_c(
- ctx->unit, tree_stmt->span,
- "unimplemented: compound assignment with non-trivial lvalue");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_stmt->span,
+ .detail = string_from_static_c_string(
+ "compound assignment with "
+ "non-trivial lvalue"),
+ });
return nil;
}
enum Binary_Operation simple_op = binary_operation_strip_assign(op);
if (simple_op == BINARY_NONE) {
- lower_emit_error_c(
- ctx->unit, tree_stmt->span, "unknown compound assignment operator");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN,
+ .span = tree_stmt->span,
+ });
return nil;
}
@@ -1064,9 +1203,15 @@ lower_statement_expression(struct Lower_Context* ctx, struct Tree_Statement* tre
struct Tree_Expression* subject = incdec->subject;
if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
// TODO: handle this too, like compound assignments
- lower_emit_error_c(
- ctx->unit, tree_stmt->span,
- "unimplemented: increment/decrement on non-trivial lvalue");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_stmt->span,
+ .detail = string_from_static_c_string(
+ "increment/decrement on non-trivial "
+ "lvalue"),
+ });
return nil;
}
@@ -1140,9 +1285,15 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
if (loop->style == TREE_STATEMENT_LOOP_STYLE_FOR_EACH) {
if (!decl->initializer || decl->initializer->kind != TREE_EXPRESSION_BINARY_OPERATION
|| decl->initializer->value.binary_operator.operation != BINARY_RANGE) {
- lower_emit_error_c(
- ctx->unit, tree_stmt->span,
- "unimplemented: for-each over non-range collections");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_stmt->span,
+ .detail = string_from_static_c_string(
+ "for-each over non-range "
+ "collections"),
+ });
return nil;
}
init = decl->initializer->value.binary_operator.left_operand;
@@ -1152,8 +1303,13 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
}
if (array_length(&decl->names) != 1) {
- lower_emit_error_c(
- ctx->unit, tree_stmt->span, "unimplemented: for-loop with multi-name declaration");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_stmt->span,
+ .detail = string_from_static_c_string("for-loop with multi-name declaration"),
+ });
return nil;
}
struct String name = *array_at(struct String, &decl->names, 0);
@@ -1163,11 +1319,13 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
lower_push_scope(ctx, SCOPE_TYPE_BLOCK);
if (lower_is_shadowing(ctx, name)) {
- lower_emit_error(
- ctx->unit, tree_stmt->span,
- string_concatenate(
- ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII,
- "' shadows an existing binding", ARG_END));
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_NAME_SHADOWS,
+ .span = tree_stmt->span,
+ .name = name,
+ });
lower_pop_scope(ctx, SCOPE_TYPE_BLOCK);
return nil;
}
@@ -1226,7 +1384,10 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
return ir_make_block_statement(outer, tree_stmt->span);
}
default:
- lower_emit_error_c(ctx->unit, tree_stmt->span, "unknown loop style");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNKNOWN_LOOP_STYLE, .span = tree_stmt->span });
return nil;
}
}
@@ -1267,7 +1428,13 @@ lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
case TREE_STATEMENT_CONTINUE:
return lower_statement_continue(ctx, tree_stmt);
default:
- lower_emit_error_c(ctx->unit, tree_stmt->span, "unimplemented: this statement kind");
+ lower_push_error(
+ ctx->unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_UNIMPLEMENTED,
+ .span = tree_stmt->span,
+ .detail = string_from_static_c_string("this statement kind"),
+ });
return nil;
}
}
@@ -1317,50 +1484,32 @@ lower_pass_2(struct Lower_Context* ctx)
uint
lower_span_to_line(struct String source, struct Span span)
{
- uint line = 1;
- uint upto = span.start;
- if (upto > string_length(source)) upto = string_length(source);
- for (uint i = 0; i < upto; ++i) {
- if (string_at(source, i) == '\n') line++;
- }
+ uint line, column;
+ source_position_from_span(source, span, &line, &column);
return line;
}
-// builds a nice & friendly cycle detection message and emits it as a diagnostic.
-// `chain` is the path we walked while looking for the cycle, and
-// `cycle_start` is the chain index at which the cycle closes.
+// `chain` is the walk we took while looking for the cycle, `cycle_start` is
+// the index in it at which the cycle closes back on itself.
void
-lower_report_type_cycle(
- struct Unit* unit, struct Source_File source, struct _Array* chain, uint cycle_start)
+lower_report_type_cycle(struct Unit* unit, struct _Array* chain, uint cycle_start)
{
- struct String_Buffer buf = string_buffer_new(512);
- string_buffer_append_c_str(&buf, "type cycle detected:\n");
-
uint chain_len = array_length(chain);
+ Array(Type_Id) cycle_copy = array_new(Type_Id, chain_len - cycle_start);
for (uint k = cycle_start; k < chain_len; ++k) {
- Type_Id current_id = *array_at(Type_Id, chain, k);
- Type_Id next_id;
- if (k + 1 < chain_len)
- next_id = *array_at(Type_Id, chain, k + 1);
- else
- next_id = *array_at(Type_Id, chain, cycle_start);
-
- struct Type* current = *array_at(struct Type*, &unit->types.entries, current_id);
- struct Type* next = *array_at(struct Type*, &unit->types.entries, next_id);
- uint line = lower_span_to_line(source.source, current->span);
-
- ascii line_buf[256];
- snprintf(
- line_buf, sizeof line_buf, " %s (line %lu) depends on %s\n",
- string_c_str(current->name), line, string_c_str(next->name));
- string_buffer_append_c_str(&buf, line_buf);
+ Type_Id id = *array_at(Type_Id, chain, k);
+ array_push(&cycle_copy, &id);
}
- string_buffer_append_c_str(
- &buf, "hint: break the cycle with a reference (use `&T` instead of `T`). :)");
Type_Id origin_id = *array_at(Type_Id, chain, cycle_start);
struct Type* origin = *array_at(struct Type*, &unit->types.entries, origin_id);
- lower_emit_error(unit, origin->span, string_buffer_to_string(&buf));
+ lower_push_error(
+ unit,
+ (struct Lower_Error){
+ .kind = LOWER_ERROR_TYPE_CYCLE,
+ .span = origin->span,
+ .cycle_chain = cycle_copy,
+ });
}
// topologically sorting the unit's type dependency graph.
@@ -1370,7 +1519,7 @@ lower_report_type_cycle(
// are always first to emit when their vertex in-degree is 0.
// cycles reported as diagnostic, partial emission order will still be completed.
void
-lower_topological_sort_dependency_graph(struct Unit* unit, struct Source_File source)
+lower_topological_sort_dependency_graph(struct Unit* unit)
{
const uint done_sentinel = (uint)-1; // marks vertecies that have been processed
@@ -1461,7 +1610,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit, struct Source_File so
}
}
if (found_at < chain_len) {
- lower_report_type_cycle(unit, source, &chain, found_at);
+ lower_report_type_cycle(unit, &chain, found_at);
return;
}
@@ -1471,7 +1620,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit, struct Source_File so
}
void
-lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
+lower_tree(struct Tree* tree, struct Unit* unit)
{
unit->types.entries = array_new(struct Type*, 256);
unit->types.by_hash = array_new(struct Type_Hash_To_Id, 256);
@@ -1483,7 +1632,7 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
unit->imports = array_new(struct Import, 32);
unit->type_emission_order = array_new(Type_Id, 256);
unit->had_error = false;
- unit->diagnostics = array_new(struct Diagnostic, 64);
+ unit->lower_errors = array_new(struct Lower_Error, 64);
unit->types.primitive_int_id = lower_seed_primitive(unit, "int");
unit->types.primitive_uint_id = lower_seed_primitive(unit, "uint");
@@ -1496,7 +1645,6 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
struct Lower_Context ctx = {
.unit = unit,
- .source = source,
.synthetic_type_counter = 0,
.synthetic_counter = 0,
.scope_stack = array_new(struct Scope, 32),
@@ -1510,5 +1658,5 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
lower_pass_1(&ctx, tree);
lower_pass_2(&ctx);
}
- lower_topological_sort_dependency_graph(unit, source);
+ lower_topological_sort_dependency_graph(unit);
}
diff --git a/boot/parse.c b/boot/parse.c
index db5e029..2b5d859 100644
--- a/boot/parse.c
+++ b/boot/parse.c
@@ -103,56 +103,22 @@ parser_error_is_none(const struct Parser_Error* error)
return error->kind == PARSER_ERROR_NONE;
}
-struct Span
-token_span_to_line_span(struct Span span, struct String source)
-{
- Pos line_start = span.start, line_end = span.start;
-
- // go backwards from the start of the token's span to find line start.
- while (line_start > 0 && string_at(source, line_start - 1) != '\n') line_start--;
- // go forwards from the end of the token's span to find line end.
- while (line_end < string_length(source) && string_at(source, line_end) != '\n') line_end++;
-
- return span_new(line_start, line_end);
-}
-
-// print out nice, human-readable error message
-// pointing to the location of the error in the source file.
-// TODO: bring out the infrastructure for displaying errors in the same format
-// outside of the parser, so that it can be used in other places.
void
parser_error_display(const struct Parser_Error* error, struct Source_File source_file)
{
if (parser_error_is_none(error)) return;
- uint line = error->cause.location.line;
- uint column = error->cause.location.column;
-
- struct Token cause = error->cause;
-
- STRING_FORMAT_TO(source_file.path, stderr, ANSI_WHITE "%s:%lu:%lu:\n", line, column);
- fprintf(stderr, ANSI_BOLD ANSI_RED "error: " ANSI_WHITE);
+ struct String_View lexeme = token_lexeme(&error->cause, source_file.source);
if (error->subkind != PARSER_ERROR_NONE) {
- fprintf(stderr, "%s: %s", parser_error_kind_to_string(error->kind),
- parser_error_kind_to_string(error->subkind));
+ diagnostic_render(
+ source_file, stderr, DIAGNOSTIC_ERROR, error->cause.span, "%s: %s, got '%S'",
+ parser_error_kind_to_string(error->kind), parser_error_kind_to_string(error->subkind),
+ STR(lexeme));
} else {
- fprintf(stderr, "%s", parser_error_kind_to_string(error->kind));
+ diagnostic_render(
+ source_file, stderr, DIAGNOSTIC_ERROR, error->cause.span, "%s, got '%S'",
+ parser_error_kind_to_string(error->kind), STR(lexeme));
}
- struct String_View cause_lexeme = token_lexeme(&cause, source_file.source);
- STRING_FORMAT_TO(cause_lexeme, stderr, ", got '%S' :(\n");
-
- struct Span line_span = token_span_to_line_span(cause.span, source_file.source);
-
- struct String_View source_line =
- string_substring(source_file.source, line_span.start, line_span.end);
-
- fprintf(stderr, ANSI_WHITE ANSI_NO_BOLD "%lu| ", line);
- STRING_FORMAT_TO(source_line, stderr, "%S\n");
-
- uint line_number_length = ceil(log10(line + 1));
- fprintf(stderr, ANSI_RED "%*s", (int)(column + 1 + line_number_length), " ");
- for (uint w = 0; w < span_length(cause.span); w++) { fprintf(stderr, "^"); }
- fprintf(stderr, "\n" ANSI_RESET);
}
// a parser context descibes the current surrounding structure
@@ -405,7 +371,8 @@ parser_block_node(struct Parser* p, struct Parser_Error* error)
struct Tree_Statement* current = nil;
while (!parser_probe(p, TOKEN_CURLY_CLOSE)) {
- struct Tree_Statement* statement = CHECK_RETURN(parser_statement(p, error), struct Tree_Block);
+ struct Tree_Statement* statement =
+ CHECK_RETURN(parser_statement(p, error), struct Tree_Block);
// statement ending token isn't required when the block ends on the same line,
// as in e.g.: `if (true) { print("yes") }`
@@ -956,7 +923,8 @@ parser_expression_primary_boolean(struct Parser* p, struct Parser_Error* error)
"expected boolean literal");
bool literal = token.kind == TOKEN_WORD_TRUE;
union Tree_Expression_Value expr_value = { .bool_literal = { literal } };
- return tree_expression_new(TREE_EXPRESSION_BOOLEAN_LITERAL, expr_value, token.span, token.location);
+ return tree_expression_new(
+ TREE_EXPRESSION_BOOLEAN_LITERAL, expr_value, token.span, token.location);
}
struct Tree_Expression*
@@ -1008,7 +976,8 @@ parser_expression_type(struct Parser* p, struct Parser_Error* error)
struct Span span = span_merge(start_token.span, type->span);
return tree_expression_new(
- TREE_EXPRESSION_TYPE, (union Tree_Expression_Value){ .type = { type } }, span, start_token.location);
+ TREE_EXPRESSION_TYPE, (union Tree_Expression_Value){ .type = { type } }, span,
+ start_token.location);
}
struct Tree_Expression*
@@ -1198,8 +1167,8 @@ parser_expression_postfix(struct Parser* p, struct Parser_Error* error)
struct Span span = span_merge(expression->span, token.span);
union Tree_Expression_Value value = { .increment_decrement = inc_dec };
- expression =
- tree_expression_new(TREE_EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
+ expression = tree_expression_new(
+ TREE_EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
continue;
}
@@ -1234,7 +1203,8 @@ parser_expression_unary_operation(struct Parser* p, struct Parser_Error* error)
struct Span span = span_merge(token.span, inc_dec.subject->span);
union Tree_Expression_Value value = { .increment_decrement = inc_dec };
- return tree_expression_new(TREE_EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
+ return tree_expression_new(
+ TREE_EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
}
return parser_expression_postfix(p, error);
@@ -1357,7 +1327,8 @@ parser_statement_conditional(struct Parser* p, struct Parser_Error* error)
CONTEXT_END(in_statement_clause);
struct Tree_Block then_block = CHECK(parser_block_node(p, error));
- conditional.conditions[conditional.condition_count++] = (struct Tree_Statement_Conditional_Branch){
+ conditional
+ .conditions[conditional.condition_count++] = (struct Tree_Statement_Conditional_Branch){
.when = if_condition,
.then = then_block,
};
@@ -1397,8 +1368,8 @@ parser_statement_conditional(struct Parser* p, struct Parser_Error* error)
}
return tree_statement_new(
- TREE_STATEMENT_CONDITIONAL, (union Tree_Statement_Value){ .conditional = conditional }, span,
- if_token.location);
+ TREE_STATEMENT_CONDITIONAL, (union Tree_Statement_Value){ .conditional = conditional },
+ span, if_token.location);
}
struct Tree_Statement*
@@ -1475,7 +1446,8 @@ parser_statement_block(struct Parser* p, struct Parser_Error* error)
{
struct Tree_Block block = CHECK(parser_block_node(p, error));
return tree_statement_new(
- TREE_STATEMENT_BLOCK, (union Tree_Statement_Value){ .block = { block } }, block.span, block.location);
+ TREE_STATEMENT_BLOCK, (union Tree_Statement_Value){ .block = { block } }, block.span,
+ block.location);
}
struct Tree_Statement*
@@ -1496,7 +1468,8 @@ parser_statement_break(struct Parser* p, struct Parser_Error* error)
{
struct Token break_token = CHECK(parser_need(p, TOKEN_WORD_BREAK, error));
struct Span span = break_token.span;
- return tree_statement_new(TREE_STATEMENT_BREAK, (union Tree_Statement_Value){ 0 }, span, break_token.location);
+ return tree_statement_new(
+ TREE_STATEMENT_BREAK, (union Tree_Statement_Value){ 0 }, span, break_token.location);
}
struct Tree_Statement*
diff --git a/boot/tests/lower/shadowing_error.cskt b/boot/tests/lower/shadowing_error.cskt
index b22dddf..34eacc4 100644
--- a/boot/tests/lower/shadowing_error.cskt
+++ b/boot/tests/lower/shadowing_error.cskt
@@ -25,5 +25,5 @@ main = fun () {
(declaration x (ref int) (initializer (expr 1)))
)))
(emission_order 0 1 2 3 4 5 6 7)
- (diagnostics
- (error "name 'x' shadows an existing binding")))
+ (lower_errors
+ (name-shadows x))) \ No newline at end of file
diff --git a/boot/tests/lower/type_cycle_error.cskt b/boot/tests/lower/type_cycle_error.cskt
index 29b791e..c6bfb6c 100644
--- a/boot/tests/lower/type_cycle_error.cskt
+++ b/boot/tests/lower/type_cycle_error.cskt
@@ -22,8 +22,5 @@ B = type { a A }
(type 8 A structure (field b (ref B)) (depends_on 9))
(type 9 B structure (field a (ref A)) (depends_on 8)))
(emission_order 0 1 2 3 4 5 6 7)
- (diagnostics
- (error "type cycle detected:
- A (line 1) depends on B
- B (line 2) depends on A
-hint: break the cycle with a reference (use `&T` instead of `T`). :)")))
+ (lower_errors
+ (type-cycle (chain A B)))) \ No newline at end of file
diff --git a/boot/visit/ir.c b/boot/visit/ir.c
index 1920f3b..5317276 100644
--- a/boot/visit/ir.c
+++ b/boot/visit/ir.c
@@ -66,7 +66,7 @@ struct Visit_Table
void (*visit_block)(struct Visit* visitor, struct Block* block);
void (*visit_type_ref)(struct Visit* visitor, struct Type_Ref* ref);
void (*visit_import)(struct Visit* visitor, struct Import* import);
- void (*visit_diagnostic)(struct Visit* visitor, struct Diagnostic* diagnostic);
+ void (*visit_lower_error)(struct Visit* visitor, struct Lower_Error* error);
};
void
@@ -82,8 +82,7 @@ walk_unit(struct Visit* visit, struct Unit* unit)
FOR_EACH_ARRAY (struct Function*, function, &unit->functions.entries)
VISIT(visit_function, *function);
FOR_EACH_ARRAY (struct Import, import, &unit->imports) VISIT(visit_import, import);
- FOR_EACH_ARRAY (struct Diagnostic, diagnostic, &unit->diagnostics)
- VISIT(visit_diagnostic, diagnostic);
+ FOR_EACH_ARRAY (struct Lower_Error, error, &unit->lower_errors) VISIT(visit_lower_error, error);
}
void
@@ -362,7 +361,7 @@ walk_block(struct Visit* visit, struct Block* block)
WALK_LEAF_FUNCTION(walk_type_ref, struct Type_Ref*);
WALK_LEAF_FUNCTION(walk_import, struct Import*);
-WALK_LEAF_FUNCTION(walk_diagnostic, struct Diagnostic*);
+WALK_LEAF_FUNCTION(walk_lower_error, struct Lower_Error*);
struct Visit_Table walk_functions = {
.visit_unit = walk_unit,
@@ -407,7 +406,7 @@ struct Visit_Table walk_functions = {
.visit_block = walk_block,
.visit_type_ref = walk_type_ref,
.visit_import = walk_import,
- .visit_diagnostic = walk_diagnostic,
+ .visit_lower_error = walk_lower_error,
};
void
@@ -515,15 +514,15 @@ printer_visit_unit(struct Visit* visit, struct Unit* unit)
PRINT(")");
}
- if (array_length(&unit->diagnostics) > 0) {
+ if (array_length(&unit->lower_errors) > 0) {
PRINT("\n");
printer_indent(p);
- PRINT("(diagnostics");
+ PRINT("(lower_errors");
p->indentation_level++;
- FOR_EACH_ARRAY (struct Diagnostic, diagnostic, &unit->diagnostics) {
+ FOR_EACH_ARRAY (struct Lower_Error, error, &unit->lower_errors) {
PRINT("\n");
printer_indent(p);
- VISIT(visit_diagnostic, diagnostic);
+ VISIT(visit_lower_error, error);
}
p->indentation_level--;
PRINT(")");
@@ -947,15 +946,63 @@ printer_visit_import(struct Visit* visit, struct Import* import)
}
void
-printer_visit_diagnostic(struct Visit* visit, struct Diagnostic* diagnostic)
+printer_visit_lower_error(struct Visit* visit, struct Lower_Error* error)
{
PRINTER_PREAMBLE
- const ascii* sev = "note";
- if (diagnostic->severity == DIAGNOSTIC_WARNING)
- sev = "warning";
- else if (diagnostic->severity == DIAGNOSTIC_ERROR)
- sev = "error";
- PRINT("(%s \"%s\")", sev, string_c_str(diagnostic->message));
+
+ switch (error->kind) {
+ case LOWER_ERROR_UNDEFINED_TYPE:
+ PRINT("(undefined-type %s)", string_c_str(error->name));
+ return;
+ case LOWER_ERROR_DUPLICATE_TYPE:
+ PRINT("(duplicate-type %s)", string_c_str(error->name));
+ return;
+ case LOWER_ERROR_DUPLICATE_FUNCTION:
+ PRINT("(duplicate-function %s)", string_c_str(error->name));
+ return;
+ case LOWER_ERROR_NAME_SHADOWS:
+ PRINT("(name-shadows %s)", string_c_str(error->name));
+ return;
+ case LOWER_ERROR_TYPE_CYCLE: {
+ PRINT("(type-cycle (chain");
+ FOR_EACH_ARRAY (Type_Id, id, &error->cycle_chain) {
+ struct Type* type = printer_type_at(p, *id);
+ if (type)
+ PRINT(" %s", string_c_str(type->name));
+ else
+ PRINT(" #%lu", *id);
+ }
+ PRINT("))");
+ return;
+ }
+ case LOWER_ERROR_ASSIGNMENT_AS_EXPRESSION:
+ PRINT("(assignment-as-expression)");
+ return;
+ case LOWER_ERROR_RANGE_OUTSIDE_LOOP:
+ PRINT("(range-outside-loop)");
+ return;
+ case LOWER_ERROR_CONSTRUCT_SUBJECT_NOT_NAME:
+ PRINT("(construct-subject-not-name)");
+ return;
+ case LOWER_ERROR_TYPE_EXPRESSION_IN_BODY:
+ PRINT("(type-expression-in-body)");
+ return;
+ case LOWER_ERROR_UNSUPPORTED_TOP_LEVEL:
+ PRINT("(unsupported-top-level)");
+ return;
+ case LOWER_ERROR_UNKNOWN_LOOP_STYLE:
+ PRINT("(unknown-loop-style)");
+ return;
+ case LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN:
+ PRINT("(unknown-compound-assign)");
+ return;
+ case LOWER_ERROR_UNIMPLEMENTED:
+ PRINT("(unimplemented \"%s\")", string_c_str(error->detail));
+ return;
+ default:
+ PRINT("(unknown-error)");
+ return;
+ }
}
struct Visit_Table printer_visit_functions = {
@@ -1001,7 +1048,7 @@ struct Visit_Table printer_visit_functions = {
.visit_block = printer_visit_block,
.visit_type_ref = printer_visit_type_ref,
.visit_import = printer_visit_import,
- .visit_diagnostic = printer_visit_diagnostic,
+ .visit_lower_error = printer_visit_lower_error,
};
void