Ruby 4.1.0dev (2026-11-09 revision 0e258742221c81dddb807e48a5691ce44b46dadd)
errors_format.c
2
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"
11
12#include <inttypes.h>
13#include <stdbool.h>
14#include <stdlib.h>
15#include <string.h>
16
22static bool
23lines_are_utf8(const pm_parser_t *parser, size_t start_index, size_t end_index) {
24 const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
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]);
27
28 while (cursor < end) {
29 size_t width = pm_encoding_utf_8_char_width(cursor, end - cursor);
30 if (width == 0) return false;
31
32 cursor += width;
33 }
34
35 return true;
36}
37
38typedef struct {
39 pm_diagnostic_t *diagnostic;
40 int32_t line_start;
41 uint32_t column_start;
42 uint32_t column_end;
43 size_t idx;
45
46static int
47pm_rich_error_compare(const void *a, const void *b) {
48 const pm_rich_error_t *left = (const pm_rich_error_t *) a;
49 const pm_rich_error_t *right = (const pm_rich_error_t *) b;
50
51 if (left->line_start != right->line_start) {
52 return (left->line_start < right->line_start) ? -1 : 1;
53 }
54
55 if (left->column_start != right->column_start) {
56 return (left->column_start < right->column_start) ? -1 : 1;
57 }
58
59 if (left->idx != right->idx) {
60 return (left->idx < right->idx) ? 1 : -1;
61 }
62
63 return 0;
64}
65
70static int32_t
71pm_rich_errors_final_line(const pm_parser_t *parser) {
72 const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
73 int32_t final_line = parser->start_line + ((int32_t) line_offsets->size) - 1;
74
75 if ((parser->start + line_offsets->offsets[line_offsets->size - 1]) == parser->end) {
76 final_line--;
77 }
78
79 return final_line;
80}
81
88static void
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;
93}
94
102static bool
103rich_errors_are_utf8(const pm_parser_t *parser, size_t nerrors, const pm_rich_error_t *rich_errors) {
104 int32_t final_line = pm_rich_errors_final_line(parser);
105
106 /* Every line from the first line of context of the most recently visited
107 * error up to (but not including) this index has already been checked.
108 * Because the errors are sorted, the first line of context never
109 * decreases, so any later error only needs the lines from this index
110 * onward. */
111 size_t checked_index = 0;
112
113 for (size_t idx = 0; idx < nerrors; idx++) {
114 int32_t first_line;
115 int32_t last_line;
116 pm_rich_error_context(rich_errors[idx].line_start, parser->start_line, final_line, &first_line, &last_line);
117
118 size_t start_index = (size_t) (first_line - parser->start_line);
119 size_t end_index = (size_t) (last_line - parser->start_line);
120
121 if (start_index < checked_index) start_index = checked_index;
122 if (start_index > end_index) continue;
123
124 if (!lines_are_utf8(parser, start_index, end_index)) return false;
125 checked_index = end_index + 1;
126 }
127
128 return true;
129}
130
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"
135#define TRUNCATE 30
136
137/* The maximum number of columns of a line that errors are displayed beneath
138 * before the line is displayed again. This must be at least TRUNCATE so that
139 * every window after the first on a line starts with a truncated line. Lines
140 * of context are cut off at the same width. */
141#define WINDOW 80
142
154
161static uint8_t
162pm_error_line_digits(int32_t line, char digits[11]) {
163 /* The magnitude is computed in unsigned arithmetic so that negating the
164 * minimum value of a 32-bit integer is well-defined. */
165 uint32_t magnitude = (line < 0) ? (0U - (uint32_t) line) : (uint32_t) line;
166 uint8_t length = 0;
167
168 do {
169 digits[10 - length++] = (char) ('0' + (magnitude % 10));
170 magnitude /= 10;
171 } while (magnitude > 0);
172
173 if (line < 0) digits[10 - length++] = '-';
174 return length;
175}
176
181static void
182pm_error_append_line_number(pm_buffer_t *buffer, int32_t line, uint8_t width) {
183 char digits[11];
184 uint8_t length = pm_error_line_digits(line, digits);
185
186 if (width > length) pm_buffer_append_repeated(buffer, ' ', (size_t) (width - length));
187 pm_buffer_append_string(buffer, digits + sizeof(digits) - length, length);
188}
189
195static void
196pm_error_line_format_init(pm_error_line_format_t *format, pm_errors_format_type_t format_type, int32_t first_line, int32_t last_line) {
197 char digits[11];
198 uint8_t first_width = pm_error_line_digits(first_line, digits);
199 uint8_t last_width = pm_error_line_digits(last_line, digits);
200
201 *format = (pm_error_line_format_t) {
202 .type = format_type,
203 .width = (first_width > last_width) ? first_width : last_width
204 };
205}
206
211static void
212pm_error_line_format_number(const pm_error_line_format_t *format, pm_buffer_t *buffer, int32_t line) {
213 if (format->type > PM_ERRORS_FORMAT_PLAIN) PM_BUFFER_APPEND_LITERAL(buffer, COLOR_GRAY);
214 pm_error_append_line_number(buffer, line, format->width);
215 PM_BUFFER_APPEND_LITERAL(buffer, " | ");
216 if (format->type > PM_ERRORS_FORMAT_PLAIN) PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RESET);
217}
218
223static void
224pm_error_line_format_blank(const pm_error_line_format_t *format, pm_buffer_t *buffer) {
225 if (format->type > PM_ERRORS_FORMAT_PLAIN) PM_BUFFER_APPEND_LITERAL(buffer, COLOR_GRAY);
226 pm_buffer_append_repeated(buffer, ' ', (size_t) format->width + 1);
227 PM_BUFFER_APPEND_LITERAL(buffer, "| ");
228 if (format->type > PM_ERRORS_FORMAT_PLAIN) PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RESET);
229}
230
235static void
236pm_error_line_format_divider(const pm_error_line_format_t *format, pm_buffer_t *buffer) {
237 if (format->type > PM_ERRORS_FORMAT_PLAIN) PM_BUFFER_APPEND_LITERAL(buffer, COLOR_GRAY);
238 PM_BUFFER_APPEND_LITERAL(buffer, " ");
239 pm_buffer_append_repeated(buffer, '~', (size_t) format->width + 4);
240 if (format->type > PM_ERRORS_FORMAT_PLAIN) PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RESET);
241 pm_buffer_append_byte(buffer, '\n');
242}
243
251static size_t
252pm_error_advance_columns(const pm_parser_t *parser, const uint8_t *start, size_t *column, size_t limit) {
253 size_t count = 0;
254
255 while (*column < limit) {
256 const uint8_t *cursor = start + *column;
257 size_t char_width = 1;
258
259 /* Every supported encoding is ASCII-compatible, so a byte below 0x80
260 * at the start of a character is a character by itself. */
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;
264 }
265
266 *column += char_width;
267 count++;
268 }
269
270 return count;
271}
272
273static void
274pm_error_format_line(const pm_parser_t *parser, pm_buffer_t *buffer, const pm_error_line_format_t *format, int32_t line, uint32_t column_start, uint32_t column_end) {
275 int32_t line_delta = line - parser->start_line;
276 PRISM_ASSERT(line_delta >= 0);
277
278 size_t index = (size_t) line_delta;
279 PRISM_ASSERT(index < parser->line_offsets.size);
280
281 const uint8_t *start = parser->start + parser->line_offsets.offsets[index];
282 const uint8_t *end;
283
284 if (index >= parser->line_offsets.size - 1) {
285 end = parser->end;
286 } else {
287 end = parser->start + parser->line_offsets.offsets[index + 1];
288 }
289
290 pm_error_line_format_number(format, buffer, line);
291
292 /* Here we determine if we should truncate the start of the line. The
293 * column is the start of a diagnostic, so it is on a character boundary. */
294 bool truncate_start = column_start >= TRUNCATE;
295 const uint8_t *display_start = truncate_start ? (start + column_start) : start;
296
297 /* Here we determine if we should truncate the end of the line. Note that
298 * this is written to avoid computing start + column_end, which could be
299 * more than one past the end of the source when the error is at the end
300 * of the input. The walk to find a character boundary begins at the
301 * displayed start so that its cost is bounded by the displayed width. */
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;
305
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);
308
309 /* If we failed to decode a character, then just bail out and
310 * truncate at the fixed width. */
311 if (char_width == 0) break;
312
313 /* If this next character would go past the end candidate,
314 * then we need to truncate before it. */
315 if (cursor + char_width > end_candidate) {
316 end_candidate = cursor;
317 break;
318 }
319
320 cursor += char_width;
321 }
322
323 end = end_candidate;
324 truncate_end = true;
325 }
326
327 if (truncate_start) PM_BUFFER_APPEND_LITERAL(buffer, "... ");
328 pm_buffer_append_string(buffer, (const char *) display_start, (size_t) (end - display_start));
329
330 if (truncate_end) {
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');
334 }
335}
336
341static void
342pm_error_format_context_line(const pm_parser_t *parser, pm_buffer_t *buffer, const pm_error_line_format_t *format, int32_t line) {
343 PM_BUFFER_APPEND_LITERAL(buffer, " ");
344 pm_error_format_line(parser, buffer, format, line, 0, WINDOW);
345}
346
347static void
348pm_rich_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, pm_errors_format_type_t format_type, size_t nerrors, pm_rich_error_t *rich_errors, bool inline_messages) {
349 int32_t final_line = pm_rich_errors_final_line(parser);
350
351 /* The line numbers that are displayed range from the first line of context
352 * of the first error to the last line of context of the last error. */
353 int32_t first_displayed_line;
354 int32_t last_displayed_line;
355 int32_t unused_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);
358
360 pm_error_line_format_init(&format, format_type, first_displayed_line, last_displayed_line);
361
362 /* We are going to iterate through every error in our error list and display
363 * it. While we are iterating, we will display some padding lines of the
364 * source before the error to give some context. We will be careful not to
365 * display the same line twice in case the errors are close enough in the
366 * source.
367 *
368 * The errors on a line are split into windows of at most WINDOW columns.
369 * Each window displays its own slice of the line, and the markers beneath
370 * it are padded relative to the start of that slice and cut off at its
371 * end. This bounds the length of every marker line, so the output grows
372 * linearly with the number of errors no matter how long the line is. */
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;
377
378 for (size_t idx = 0; idx < nerrors; idx++) {
379 pm_rich_error_t *rich_error = rich_errors + idx;
380
381 /* Here we determine how many lines of padding of the source to display,
382 * based on the difference from the last line that was displayed. */
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);
387 }
388
389 pm_error_format_context_line(parser, buffer, &format, rich_error->line_start - 2);
390 }
391
392 pm_error_format_context_line(parser, buffer, &format, rich_error->line_start - 1);
393 }
394
395 /* If this error is not in the current window (because it is the first
396 * error, it is on a new line, or it is too far to the right), then we
397 * start a new window and display the line that has the error in it. */
398 if (idx == window_end_idx) {
399 switch (format_type) {
401 PM_BUFFER_APPEND_LITERAL(buffer, "> ");
402 break;
404 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_BOLD "> " COLOR_RESET);
405 break;
407 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RED "> " COLOR_RESET);
408 break;
409 }
410
411 /* The displayed slice of the line starts at this error, unless it
412 * is close enough to the start of the line to show it all. */
413 window_column_start = (rich_error->column_start >= TRUNCATE) ? rich_error->column_start : 0;
414 uint32_t window_limit = window_column_start + WINDOW;
415
416 /* Find the errors on this line that start within the window, and
417 * the maximum column end of all of them, capped at the window. */
418 window_column_end = rich_error->column_end;
419 for (window_end_idx = idx + 1; window_end_idx < nerrors; window_end_idx++) {
420 const pm_rich_error_t *next_error = &rich_errors[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;
423 }
424
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);
427 }
428
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');
431
432 /* Now we will display the actual error message. We will do this by
433 * first putting the prefix to the line, then a bunch of blank spaces
434 * depending on the column, then as many tildes as we need to display
435 * the width of the error, then the error message itself.
436 *
437 * Note that this doesn't take into account the width of the actual
438 * character when displayed in the terminal. For some east-asian
439 * languages or emoji, this means it can be thrown off pretty badly. We
440 * will need to solve this eventually. */
441 PM_BUFFER_APPEND_LITERAL(buffer, " ");
442 pm_error_line_format_blank(&format, buffer);
443
444 size_t column = 0;
445 if (window_column_start > 0) {
446 PM_BUFFER_APPEND_LITERAL(buffer, " ");
447 column = window_column_start;
448 }
449
450 pm_buffer_append_repeated(buffer, ' ', pm_error_advance_columns(parser, start, &column, rich_error->column_start));
451
452 switch (format_type) {
454 pm_buffer_append_byte(buffer, '^');
455 break;
457 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_BOLD "^");
458 break;
460 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RED "^");
461 break;
462 }
463
464 /* The caret marks the first character of the error, and the tildes
465 * mark the rest of it, up to the end of the window. */
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));
469
470 if (format_type != PM_ERRORS_FORMAT_PLAIN) {
471 PM_BUFFER_APPEND_LITERAL(buffer, COLOR_RESET);
472 }
473
474 if (inline_messages) {
475 const char *message = pm_diagnostic_message(rich_error->diagnostic);
476
477 pm_buffer_append_byte(buffer, ' ');
478 pm_buffer_append_string(buffer, message, strlen(message));
479 }
480
481 pm_buffer_append_byte(buffer, '\n');
482
483 /* Here we determine how many lines of padding to display after the
484 * diagnostic, depending on where the next diagnostic is in source. The
485 * line after the last error is one past the last line that can be
486 * displayed. */
487 last_line = rich_error->line_start;
488 int32_t next_line = (idx == nerrors - 1) ? (final_line + 1) : rich_errors[idx + 1].line_start;
489
490 if (next_line - last_line > 1) {
491 pm_error_format_context_line(parser, buffer, &format, ++last_line);
492 }
493
494 if (next_line - last_line > 1) {
495 pm_error_format_context_line(parser, buffer, &format, ++last_line);
496 }
497 }
498}
499
500static void
501pm_rich_error_init(const pm_parser_t *parser, pm_rich_error_t *rich_error, pm_diagnostic_t *diagnostic, pm_location_t *location) {
502 const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
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);
505
506 uint32_t column_end;
507 if (start_line_column.line == end_line_column.line) {
508 column_end = end_line_column.column;
509 } else {
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);
511 }
512
513 /* Ensure we have at least one column of error. */
514 if (start_line_column.column == column_end) column_end++;
515
516 *rich_error = (pm_rich_error_t) {
517 .diagnostic = diagnostic,
518 .line_start = start_line_column.line,
519 .column_start = start_line_column.column,
520 .column_end = column_end
521 };
522}
523
528static void
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));
535}
536
538pm_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, pm_errors_format_type_t format_type) {
539 size_t nerrors = parser->error_list.size;
540 PRISM_ASSERT(nerrors > 0);
541
542 size_t rich_errors_size = nerrors * sizeof(pm_rich_error_t);
543 if (rich_errors_size / sizeof(pm_rich_error_t) != nerrors) PRISM_OOM();
544
545 pm_rich_error_t *rich_errors = xmalloc(rich_errors_size);
546 if (rich_errors == NULL) PRISM_OOM();
547
548 size_t rich_errors_idx = 0;
549
550 for (pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) parser->error_list.head; diagnostic != NULL; diagnostic = (pm_diagnostic_t *) diagnostic->node.next) {
551 pm_location_t location = pm_diagnostic_location(diagnostic);
552
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;
557 rich_errors_idx++;
558 break;
559 }
561 /* If we get an argument error, we want to format it and return it
562 * immediately. They should include a snippet of the source if
563 * possible. */
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));
566
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);
570 }
571
572 xfree_sized(rich_errors, rich_errors_size);
574 }
575 case PM_ERROR_LEVEL_LOAD: {
576 /* If we get a load error, we want to format it and return it
577 * immediately. It should only include the diagnostic message. */
578 const char *message = pm_diagnostic_message(diagnostic);
579 pm_buffer_append_string(buffer, message, strlen(message));
580
581 xfree_sized(rich_errors, rich_errors_size);
582 return PM_ERROR_LEVEL_LOAD;
583 }
585 /* If we get a stack error, we want to format it and return it
586 * immediately. Once the stack is exhausted, the syntax errors
587 * around it come from recovering from that point rather than from
588 * the source, so they are left out. It should include a snippet of
589 * the source if possible. */
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));
592
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);
596 }
597
598 xfree_sized(rich_errors, rich_errors_size);
600 }
601 }
602 }
603
604 PRISM_ASSERT(rich_errors_idx == nerrors);
605
606 /* The header displays the line of the first error in parse order, which
607 * is the first entry in the array because it has not been sorted yet. */
608 pm_error_append_summary(parser, buffer, rich_errors[0].line_start, (nerrors > 1) ? "syntax errors found\n" : "syntax error found\n");
609
610 /* Sort the errors so that they are in the order that they appear in the
611 * source. This is the order they are displayed in, and it also lets the
612 * UTF-8 check visit each line only once. */
613 qsort(rich_errors, nerrors, sizeof(pm_rich_error_t), pm_rich_error_compare);
614
615 if (rich_errors_are_utf8(parser, nerrors, rich_errors)) {
616 /* In here, we have all of the rich diagnostic information and we know
617 * the snippets are valid UTF-8, so we format them all together with
618 * the source code snippets. */
619 pm_rich_errors_format(parser, buffer, format_type, nerrors, rich_errors, true);
620 } else {
621 /* In here, we have content in the source that is not valid UTF-8. In
622 * that case, we do not want to attempt to format the source code
623 * snippets because we do not know how the terminal will handle it. The
624 * diagnostics are displayed in the order that they appear in the
625 * source, as they are when the snippets are displayed. */
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));
629 }
630 }
631
632 xfree_sized(rich_errors, rich_errors_size);
634}
pm_error_level_t
The levels of errors generated during parsing.
Definition diagnostic.h:27
@ PM_ERROR_LEVEL_ARGUMENT
For errors that should raise an argument error.
Definition diagnostic.h:32
@ PM_ERROR_LEVEL_LOAD
For errors that should raise a load error.
Definition diagnostic.h:35
@ PM_ERROR_LEVEL_STACK
For errors that should raise a system stack error.
Definition diagnostic.h:42
@ PM_ERROR_LEVEL_SYNTAX
For errors that should raise a syntax error.
Definition diagnostic.h:29
A function for formatting the errors in a parser into a buffer.
pm_errors_format_type_t
The type of formatting to use when formatting errors.
@ PM_ERRORS_FORMAT_PLAIN
Format errors in a plain format with no colors or styles.
@ PM_ERRORS_FORMAT_COLOR
Format errors in a color format with bold and colors.
@ PM_ERRORS_FORMAT_STYLE
Format errors in a style format with bold only.
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
C99 shim for <stdbool.h>
pm_errors_format_type_t type
The type of formatting to use when formatting the errors.
uint8_t width
The number of columns taken up by the widest line number that will be displayed, including the sign o...
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.
Definition ast.h:579
uint32_t start
The offset of the location from the start of the source.
Definition ast.h:581
uint32_t length
The length of the location.
Definition ast.h:584
Definition dtoa.c:309