3#include "prism/internal/allocator.h"
4#include "prism/internal/assert.h"
5#include "prism/internal/buffer.h"
6#include "prism/internal/diagnostic.h"
7#include "prism/internal/encoding.h"
8#include "prism/internal/line_offset_list.h"
9#include "prism/internal/oom.h"
10#include "prism/internal/parser.h"
23lines_are_utf8(
const pm_parser_t *parser,
size_t start_index,
size_t end_index) {
25 const uint8_t *cursor = parser->start + line_offsets->
offsets[start_index];
26 const uint8_t *end = (end_index + 1 == line_offsets->
size) ? parser->end : (parser->start + line_offsets->offsets[end_index + 1]);
28 while (cursor < end) {
29 size_t width = pm_encoding_utf_8_char_width(cursor, end - cursor);
30 if (width == 0)
return false;
41 uint32_t column_start;
47pm_rich_error_compare(
const void *a,
const void *b) {
51 if (left->line_start != right->line_start) {
52 return (left->line_start < right->line_start) ? -1 : 1;
55 if (left->column_start != right->column_start) {
56 return (left->column_start < right->column_start) ? -1 : 1;
59 if (left->idx != right->idx) {
60 return (left->idx < right->idx) ? 1 : -1;
71pm_rich_errors_final_line(
const pm_parser_t *parser) {
73 int32_t final_line = parser->start_line + ((int32_t) line_offsets->
size) - 1;
75 if ((parser->start + line_offsets->
offsets[line_offsets->
size - 1]) == parser->end) {
89pm_rich_error_context(int32_t line, int32_t start_line, int32_t final_line, int32_t *first, int32_t *last) {
90 *first = (line - start_line > 2) ? (line - 2) : start_line;
91 *last = (final_line - line > 2) ? (line + 2) : final_line;
92 if (*last < line) *last = line;
104 int32_t final_line = pm_rich_errors_final_line(parser);
111 size_t checked_index = 0;
113 for (
size_t idx = 0; idx < nerrors; idx++) {
116 pm_rich_error_context(rich_errors[idx].line_start, parser->start_line, final_line, &first_line, &last_line);
118 size_t start_index = (size_t) (first_line - parser->start_line);
119 size_t end_index = (size_t) (last_line - parser->start_line);
121 if (start_index < checked_index) start_index = checked_index;
122 if (start_index > end_index)
continue;
124 if (!lines_are_utf8(parser, start_index, end_index))
return false;
125 checked_index = end_index + 1;
131#define COLOR_BOLD "\033[1m"
132#define COLOR_GRAY "\033[2m"
133#define COLOR_RED "\033[1;31m"
134#define COLOR_RESET "\033[m"
162pm_error_line_digits(int32_t line,
char digits[11]) {
165 uint32_t magnitude = (line < 0) ? (0
U - (uint32_t) line) : (uint32_t) line;
169 digits[10 - length++] = (char) (
'0' + (magnitude % 10));
171 }
while (magnitude > 0);
173 if (line < 0) digits[10 - length++] =
'-';
182pm_error_append_line_number(
pm_buffer_t *buffer, int32_t line, uint8_t width) {
184 uint8_t length = pm_error_line_digits(line, digits);
186 if (width > length) pm_buffer_append_repeated(buffer,
' ', (
size_t) (width - length));
187 pm_buffer_append_string(buffer, digits +
sizeof(digits) - length, length);
198 uint8_t first_width = pm_error_line_digits(first_line, digits);
199 uint8_t last_width = pm_error_line_digits(last_line, digits);
203 .width = (first_width > last_width) ? first_width : last_width
214 pm_error_append_line_number(buffer, line, format->
width);
215 PM_BUFFER_APPEND_LITERAL(buffer,
" | ");
226 pm_buffer_append_repeated(buffer,
' ', (
size_t) format->
width + 1);
227 PM_BUFFER_APPEND_LITERAL(buffer,
"| ");
238 PM_BUFFER_APPEND_LITERAL(buffer,
" ");
239 pm_buffer_append_repeated(buffer,
'~', (
size_t) format->
width + 4);
241 pm_buffer_append_byte(buffer,
'\n');
252pm_error_advance_columns(
const pm_parser_t *parser,
const uint8_t *start,
size_t *column,
size_t limit) {
255 while (*column < limit) {
256 const uint8_t *cursor = start + *column;
257 size_t char_width = 1;
261 if ((cursor < parser->end) && (*cursor >= 0x80)) {
262 char_width = pm_parser_encoding_char_width(parser, cursor, parser->end - cursor);
263 if (char_width == 0) char_width = 1;
266 *column += char_width;
275 int32_t line_delta = line - parser->start_line;
276 PRISM_ASSERT(line_delta >= 0);
278 size_t index = (size_t) line_delta;
279 PRISM_ASSERT(index < parser->line_offsets.
size);
281 const uint8_t *start = parser->start + parser->line_offsets.
offsets[index];
284 if (index >= parser->line_offsets.
size - 1) {
287 end = parser->start + parser->line_offsets.
offsets[index + 1];
290 pm_error_line_format_number(format, buffer, line);
294 bool truncate_start = column_start >= TRUNCATE;
295 const uint8_t *display_start = truncate_start ? (start + column_start) : start;
302 bool truncate_end =
false;
303 if ((column_end != 0) && ((end - start) - ((ptrdiff_t) column_end) >= TRUNCATE)) {
304 const uint8_t *end_candidate = start + column_end + TRUNCATE;
306 for (
const uint8_t *cursor = display_start; cursor < end_candidate;) {
307 size_t char_width = pm_parser_encoding_char_width(parser, cursor, parser->end - cursor);
311 if (char_width == 0)
break;
315 if (cursor + char_width > end_candidate) {
316 end_candidate = cursor;
320 cursor += char_width;
327 if (truncate_start) PM_BUFFER_APPEND_LITERAL(buffer,
"... ");
328 pm_buffer_append_string(buffer, (
const char *) display_start, (
size_t) (end - display_start));
331 PM_BUFFER_APPEND_LITERAL(buffer,
" ...\n");
332 }
else if (end == parser->end && end > parser->start && end[-1] !=
'\n') {
333 pm_buffer_append_byte(buffer,
'\n');
343 PM_BUFFER_APPEND_LITERAL(buffer,
" ");
344 pm_error_format_line(parser, buffer, format, line, 0, WINDOW);
349 int32_t final_line = pm_rich_errors_final_line(parser);
353 int32_t first_displayed_line;
354 int32_t last_displayed_line;
356 pm_rich_error_context(rich_errors[0].line_start, parser->start_line, final_line, &first_displayed_line, &unused_line);
357 pm_rich_error_context(rich_errors[nerrors - 1].line_start, parser->start_line, final_line, &unused_line, &last_displayed_line);
360 pm_error_line_format_init(&format, format_type, first_displayed_line, last_displayed_line);
373 int32_t last_line = parser->start_line - 1;
374 uint32_t window_column_start = 0;
375 uint32_t window_column_end = 0;
376 size_t window_end_idx = 0;
378 for (
size_t idx = 0; idx < nerrors; idx++) {
383 if (rich_error->line_start - last_line > 1) {
384 if (rich_error->line_start - last_line > 2) {
385 if ((idx != 0) && (rich_error->line_start - last_line > 3)) {
386 pm_error_line_format_divider(&format, buffer);
389 pm_error_format_context_line(parser, buffer, &format, rich_error->line_start - 2);
392 pm_error_format_context_line(parser, buffer, &format, rich_error->line_start - 1);
398 if (idx == window_end_idx) {
399 switch (format_type) {
401 PM_BUFFER_APPEND_LITERAL(buffer,
"> ");
404 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_BOLD
"> " COLOR_RESET);
407 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RED
"> " COLOR_RESET);
413 window_column_start = (rich_error->column_start >= TRUNCATE) ? rich_error->column_start : 0;
414 uint32_t window_limit = window_column_start + WINDOW;
418 window_column_end = rich_error->column_end;
419 for (window_end_idx = idx + 1; window_end_idx < nerrors; window_end_idx++) {
421 if ((next_error->line_start != rich_error->line_start) || (next_error->column_start >= window_limit))
break;
422 if (next_error->column_end > window_column_end) window_column_end = next_error->column_end;
425 if (window_column_end > window_limit) window_column_end = window_limit;
426 pm_error_format_line(parser, buffer, &format, rich_error->line_start, rich_error->column_start, window_column_end);
429 const uint8_t *start = parser->start + parser->line_offsets.
offsets[rich_error->line_start - parser->start_line];
430 if (start == parser->end) pm_buffer_append_byte(buffer,
'\n');
441 PM_BUFFER_APPEND_LITERAL(buffer,
" ");
442 pm_error_line_format_blank(&format, buffer);
445 if (window_column_start > 0) {
446 PM_BUFFER_APPEND_LITERAL(buffer,
" ");
447 column = window_column_start;
450 pm_buffer_append_repeated(buffer,
' ', pm_error_advance_columns(parser, start, &column, rich_error->column_start));
452 switch (format_type) {
454 pm_buffer_append_byte(buffer,
'^');
457 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_BOLD
"^");
460 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RED
"^");
466 pm_error_advance_columns(parser, start, &column, column + 1);
467 size_t tilde_limit = (rich_error->column_end < window_column_end) ? rich_error->column_end : window_column_end;
468 pm_buffer_append_repeated(buffer,
'~', pm_error_advance_columns(parser, start, &column, tilde_limit));
471 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RESET);
474 if (inline_messages) {
475 const char *message = pm_diagnostic_message(rich_error->diagnostic);
477 pm_buffer_append_byte(buffer,
' ');
478 pm_buffer_append_string(buffer, message, strlen(message));
481 pm_buffer_append_byte(buffer,
'\n');
487 last_line = rich_error->line_start;
488 int32_t next_line = (idx == nerrors - 1) ? (final_line + 1) : rich_errors[idx + 1].line_start;
490 if (next_line - last_line > 1) {
491 pm_error_format_context_line(parser, buffer, &format, ++last_line);
494 if (next_line - last_line > 1) {
495 pm_error_format_context_line(parser, buffer, &format, ++last_line);
503 pm_line_column_t start_line_column = pm_line_offset_list_line_column(line_offsets, location->
start, parser->start_line);
504 pm_line_column_t end_line_column = pm_line_offset_list_line_column(line_offsets, location->
start + location->
length, parser->start_line);
507 if (start_line_column.
line == end_line_column.
line) {
508 column_end = end_line_column.
column;
510 column_end = (uint32_t) (line_offsets->
offsets[start_line_column.
line - parser->start_line + 1] - line_offsets->
offsets[start_line_column.
line - parser->start_line] - 1);
514 if (start_line_column.
column == column_end) column_end++;
517 .diagnostic = diagnostic,
518 .line_start = start_line_column.
line,
519 .column_start = start_line_column.
column,
520 .column_end = column_end
529pm_error_append_summary(
const pm_parser_t *parser,
pm_buffer_t *buffer, int32_t line,
const char *message) {
530 pm_buffer_append_string(buffer, (
const char *) pm_string_source(&parser->filepath), pm_string_length(&parser->filepath));
531 pm_buffer_append_byte(buffer,
':');
532 pm_error_append_line_number(buffer, line, 0);
533 PM_BUFFER_APPEND_LITERAL(buffer,
": ");
534 pm_buffer_append_string(buffer, message, strlen(message));
539 size_t nerrors = parser->error_list.size;
540 PRISM_ASSERT(nerrors > 0);
543 if (rich_errors_size /
sizeof(
pm_rich_error_t) != nerrors) PRISM_OOM();
546 if (rich_errors == NULL) PRISM_OOM();
548 size_t rich_errors_idx = 0;
553 switch (pm_diagnostic_error_level(diagnostic)) {
555 pm_rich_error_init(parser, &rich_errors[rich_errors_idx], diagnostic, &location);
556 rich_errors[rich_errors_idx].idx = rich_errors_idx;
564 pm_rich_error_init(parser, rich_errors, diagnostic, &location);
565 pm_error_append_summary(parser, buffer, rich_errors[0].line_start, pm_diagnostic_message(diagnostic));
567 if (rich_errors_are_utf8(parser, 1, rich_errors)) {
568 pm_buffer_append_byte(buffer,
'\n');
569 pm_rich_errors_format(parser, buffer, format_type, 1, rich_errors,
false);
572 xfree_sized(rich_errors, rich_errors_size);
578 const char *message = pm_diagnostic_message(diagnostic);
579 pm_buffer_append_string(buffer, message, strlen(message));
581 xfree_sized(rich_errors, rich_errors_size);
590 pm_rich_error_init(parser, rich_errors, diagnostic, &location);
591 pm_error_append_summary(parser, buffer, rich_errors[0].line_start, pm_diagnostic_message(diagnostic));
593 if (rich_errors_are_utf8(parser, 1, rich_errors)) {
594 pm_buffer_append_byte(buffer,
'\n');
595 pm_rich_errors_format(parser, buffer, format_type, 1, rich_errors,
false);
598 xfree_sized(rich_errors, rich_errors_size);
604 PRISM_ASSERT(rich_errors_idx == nerrors);
608 pm_error_append_summary(parser, buffer, rich_errors[0].line_start, (nerrors > 1) ?
"syntax errors found\n" :
"syntax error found\n");
613 qsort(rich_errors, nerrors,
sizeof(
pm_rich_error_t), pm_rich_error_compare);
615 if (rich_errors_are_utf8(parser, nerrors, rich_errors)) {
619 pm_rich_errors_format(parser, buffer, format_type, nerrors, rich_errors,
true);
626 for (
size_t idx = 0; idx < nerrors; idx++) {
627 if (idx > 0) pm_buffer_append_byte(buffer,
'\n');
628 pm_error_append_summary(parser, buffer, rich_errors[idx].line_start, pm_diagnostic_message(rich_errors[idx].diagnostic));
632 xfree_sized(rich_errors, rich_errors_size);
pm_error_level_t
The levels of errors generated during parsing.
@ PM_ERROR_LEVEL_ARGUMENT
For errors that should raise an argument error.
@ PM_ERROR_LEVEL_LOAD
For errors that should raise a load error.
@ PM_ERROR_LEVEL_STACK
For errors that should raise a system stack error.
@ PM_ERROR_LEVEL_SYNTAX
For errors that should raise a syntax error.
#define xmalloc
Old name of ruby_xmalloc.
A line and column in a string.
uint32_t column
The column in bytes.
int32_t line
The line number.
A list of offsets of the start of lines in a string.
uint32_t * offsets
The list of offsets.
size_t size
The number of offsets in the list.
This struct represents a slice in the source code, defined by an offset and a length.
uint32_t start
The offset of the location from the start of the source.
uint32_t length
The length of the location.