prism 2.0.0.rc1-x86_64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +870 -0
  4. data/CODE_OF_CONDUCT.md +76 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/LICENSE.md +7 -0
  7. data/Makefile +145 -0
  8. data/README.md +81 -0
  9. data/config.yml +4752 -0
  10. data/docs/build_system.md +119 -0
  11. data/docs/configuration.md +69 -0
  12. data/docs/cruby_compilation.md +27 -0
  13. data/docs/design.md +53 -0
  14. data/docs/encoding.md +121 -0
  15. data/docs/fuzzing.md +81 -0
  16. data/docs/heredocs.md +36 -0
  17. data/docs/javascript.md +133 -0
  18. data/docs/local_variable_depth.md +229 -0
  19. data/docs/mapping.md +117 -0
  20. data/docs/parser_translation.md +24 -0
  21. data/docs/parsing_rules.md +22 -0
  22. data/docs/releasing.md +147 -0
  23. data/docs/relocation.md +32 -0
  24. data/docs/ripper_translation.md +63 -0
  25. data/docs/ruby_api.md +45 -0
  26. data/docs/ruby_parser_translation.md +19 -0
  27. data/docs/serialization.md +230 -0
  28. data/docs/testing.md +72 -0
  29. data/ext/prism/api_node.c +7005 -0
  30. data/ext/prism/extconf.rb +126 -0
  31. data/ext/prism/extension.c +1783 -0
  32. data/ext/prism/extension.h +19 -0
  33. data/include/prism/arena.h +37 -0
  34. data/include/prism/ast.h +10371 -0
  35. data/include/prism/buffer.h +52 -0
  36. data/include/prism/comments.h +55 -0
  37. data/include/prism/compiler/accel.h +24 -0
  38. data/include/prism/compiler/align.h +36 -0
  39. data/include/prism/compiler/assume.h +25 -0
  40. data/include/prism/compiler/exported.h +24 -0
  41. data/include/prism/compiler/fallthrough.h +22 -0
  42. data/include/prism/compiler/filesystem.h +32 -0
  43. data/include/prism/compiler/flex_array.h +19 -0
  44. data/include/prism/compiler/force_inline.h +21 -0
  45. data/include/prism/compiler/format.h +25 -0
  46. data/include/prism/compiler/inline.h +17 -0
  47. data/include/prism/compiler/nodiscard.h +22 -0
  48. data/include/prism/compiler/nonnull.h +18 -0
  49. data/include/prism/compiler/unused.h +18 -0
  50. data/include/prism/constant_pool.h +81 -0
  51. data/include/prism/diagnostic.h +93 -0
  52. data/include/prism/errors_format.h +53 -0
  53. data/include/prism/excludes.h +29 -0
  54. data/include/prism/integer.h +41 -0
  55. data/include/prism/internal/allocator.h +68 -0
  56. data/include/prism/internal/allocator_debug.h +88 -0
  57. data/include/prism/internal/arena.h +108 -0
  58. data/include/prism/internal/bit.h +42 -0
  59. data/include/prism/internal/buffer.h +91 -0
  60. data/include/prism/internal/char.h +145 -0
  61. data/include/prism/internal/comments.h +53 -0
  62. data/include/prism/internal/constant_pool.h +152 -0
  63. data/include/prism/internal/diagnostic.h +389 -0
  64. data/include/prism/internal/encoding.h +242 -0
  65. data/include/prism/internal/integer.h +68 -0
  66. data/include/prism/internal/isinf.h +16 -0
  67. data/include/prism/internal/line_offset_list.h +34 -0
  68. data/include/prism/internal/list.h +62 -0
  69. data/include/prism/internal/magic_comments.h +23 -0
  70. data/include/prism/internal/memchr.h +15 -0
  71. data/include/prism/internal/node.h +38 -0
  72. data/include/prism/internal/options.h +232 -0
  73. data/include/prism/internal/parser.h +984 -0
  74. data/include/prism/internal/regexp.h +26 -0
  75. data/include/prism/internal/serialize.h +39 -0
  76. data/include/prism/internal/source.h +72 -0
  77. data/include/prism/internal/static_literals.h +100 -0
  78. data/include/prism/internal/stringy.h +35 -0
  79. data/include/prism/internal/strncasecmp.h +18 -0
  80. data/include/prism/internal/strpbrk.h +33 -0
  81. data/include/prism/internal/tokens.h +11 -0
  82. data/include/prism/json.h +32 -0
  83. data/include/prism/line_offset_list.h +61 -0
  84. data/include/prism/magic_comments.h +35 -0
  85. data/include/prism/node.h +94 -0
  86. data/include/prism/options.h +354 -0
  87. data/include/prism/parser.h +404 -0
  88. data/include/prism/prettyprint.h +31 -0
  89. data/include/prism/serialize.h +115 -0
  90. data/include/prism/source.h +155 -0
  91. data/include/prism/stream.h +28 -0
  92. data/include/prism/string_query.h +63 -0
  93. data/include/prism/stringy.h +72 -0
  94. data/include/prism/version.h +38 -0
  95. data/include/prism.h +131 -0
  96. data/lib/prism/3.3/prism.so +0 -0
  97. data/lib/prism/3.4/prism.so +0 -0
  98. data/lib/prism/4.0/prism.so +0 -0
  99. data/lib/prism/compiler.rb +815 -0
  100. data/lib/prism/desugar_compiler.rb +463 -0
  101. data/lib/prism/dispatcher.rb +2383 -0
  102. data/lib/prism/dot_visitor.rb +4848 -0
  103. data/lib/prism/dsl.rb +1369 -0
  104. data/lib/prism/ffi.rb +740 -0
  105. data/lib/prism/inspect_visitor.rb +2425 -0
  106. data/lib/prism/lex_compat.rb +953 -0
  107. data/lib/prism/mutation_compiler.rb +780 -0
  108. data/lib/prism/node.rb +33204 -0
  109. data/lib/prism/node_ext.rb +504 -0
  110. data/lib/prism/node_find.rb +215 -0
  111. data/lib/prism/parse_result/errors.rb +72 -0
  112. data/lib/prism/parse_result/newlines.rb +423 -0
  113. data/lib/prism/parse_result.rb +1221 -0
  114. data/lib/prism/pattern.rb +314 -0
  115. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  116. data/lib/prism/polyfill/byteindex.rb +13 -0
  117. data/lib/prism/polyfill/scan_byte.rb +14 -0
  118. data/lib/prism/polyfill/unpack1.rb +14 -0
  119. data/lib/prism/polyfill/warn.rb +36 -0
  120. data/lib/prism/reflection.rb +427 -0
  121. data/lib/prism/relocation.rb +567 -0
  122. data/lib/prism/serialize.rb +5308 -0
  123. data/lib/prism/string_query.rb +46 -0
  124. data/lib/prism/translation/parser/builder.rb +70 -0
  125. data/lib/prism/translation/parser/compiler.rb +2226 -0
  126. data/lib/prism/translation/parser/lexer.rb +769 -0
  127. data/lib/prism/translation/parser.rb +376 -0
  128. data/lib/prism/translation/parser_current.rb +26 -0
  129. data/lib/prism/translation/parser_versions.rb +36 -0
  130. data/lib/prism/translation/ripper/filter.rb +53 -0
  131. data/lib/prism/translation/ripper/lexer.rb +133 -0
  132. data/lib/prism/translation/ripper/sexp.rb +118 -0
  133. data/lib/prism/translation/ripper/shim.rb +7 -0
  134. data/lib/prism/translation/ripper.rb +4278 -0
  135. data/lib/prism/translation/ruby_parser.rb +1721 -0
  136. data/lib/prism/translation.rb +20 -0
  137. data/lib/prism/visitor.rb +1440 -0
  138. data/lib/prism.rb +168 -0
  139. data/prism.gemspec +234 -0
  140. data/rbi/generated/prism/compiler.rbi +489 -0
  141. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  142. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  143. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  144. data/rbi/generated/prism/dsl.rbi +751 -0
  145. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  146. data/rbi/generated/prism/lex_compat.rbi +162 -0
  147. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  148. data/rbi/generated/prism/node.rbi +13681 -0
  149. data/rbi/generated/prism/node_ext.rbi +259 -0
  150. data/rbi/generated/prism/node_find.rbi +79 -0
  151. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  152. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  153. data/rbi/generated/prism/parse_result.rbi +764 -0
  154. data/rbi/generated/prism/pattern.rbi +134 -0
  155. data/rbi/generated/prism/reflection.rbi +89 -0
  156. data/rbi/generated/prism/relocation.rbi +329 -0
  157. data/rbi/generated/prism/serialize.rbi +181 -0
  158. data/rbi/generated/prism/string_query.rbi +36 -0
  159. data/rbi/generated/prism/translation.rbi +7 -0
  160. data/rbi/generated/prism/visitor.rbi +650 -0
  161. data/rbi/generated/prism.rbi +86 -0
  162. data/rbi/prism/translation/parser.rbi +11 -0
  163. data/rbi/prism/translation/parser_versions.rbi +28 -0
  164. data/rbi/prism/translation/ripper.rbi +15 -0
  165. data/rbi/rubyvm/node_find.rbi +14 -0
  166. data/sig/generated/prism/compiler.rbs +492 -0
  167. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  168. data/sig/generated/prism/dispatcher.rbs +1292 -0
  169. data/sig/generated/prism/dot_visitor.rbs +635 -0
  170. data/sig/generated/prism/dsl.rbs +928 -0
  171. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  172. data/sig/generated/prism/lex_compat.rbs +166 -0
  173. data/sig/generated/prism/mutation_compiler.rbs +464 -0
  174. data/sig/generated/prism/node.rbs +20160 -0
  175. data/sig/generated/prism/node_ext.rbs +290 -0
  176. data/sig/generated/prism/node_find.rbs +90 -0
  177. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  178. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  179. data/sig/generated/prism/parse_result.rbs +859 -0
  180. data/sig/generated/prism/pattern.rbs +156 -0
  181. data/sig/generated/prism/reflection.rbs +101 -0
  182. data/sig/generated/prism/relocation.rbs +428 -0
  183. data/sig/generated/prism/serialize.rbs +195 -0
  184. data/sig/generated/prism/string_query.rbs +36 -0
  185. data/sig/generated/prism/translation.rbs +8 -0
  186. data/sig/generated/prism/visitor.rbs +1111 -0
  187. data/sig/generated/prism.rbs +84 -0
  188. data/sig/manifest.yaml +2 -0
  189. data/src/arena.c +117 -0
  190. data/src/buffer.c +380 -0
  191. data/src/char.c +289 -0
  192. data/src/comments.c +454 -0
  193. data/src/constant_pool.c +406 -0
  194. data/src/diagnostic.c +886 -0
  195. data/src/encoding.c +5345 -0
  196. data/src/errors_format.c +519 -0
  197. data/src/integer.c +681 -0
  198. data/src/json.c +5733 -0
  199. data/src/line_offset_list.c +100 -0
  200. data/src/list.c +24 -0
  201. data/src/memchr.c +37 -0
  202. data/src/node.c +4515 -0
  203. data/src/options.c +451 -0
  204. data/src/parser.c +349 -0
  205. data/src/prettyprint.c +9024 -0
  206. data/src/prism.c +23739 -0
  207. data/src/regexp.c +1706 -0
  208. data/src/serialize.c +2309 -0
  209. data/src/source.c +518 -0
  210. data/src/static_literals.c +685 -0
  211. data/src/string_query.c +166 -0
  212. data/src/stringy.c +91 -0
  213. data/src/strncasecmp.c +37 -0
  214. data/src/strpbrk.c +447 -0
  215. data/src/tokens.c +724 -0
  216. metadata +259 -0
@@ -0,0 +1,519 @@
1
+ #include "prism/errors_format.h"
2
+
3
+ #include "prism/internal/allocator.h"
4
+ #include "prism/internal/buffer.h"
5
+ #include "prism/internal/diagnostic.h"
6
+ #include "prism/internal/encoding.h"
7
+ #include "prism/internal/line_offset_list.h"
8
+ #include "prism/internal/parser.h"
9
+
10
+ #include <assert.h>
11
+ #include <inttypes.h>
12
+ #include <stdbool.h>
13
+ #include <stdlib.h>
14
+ #include <string.h>
15
+
16
+ /**
17
+ * Check if the source touched by the given location is valid UTF-8. The
18
+ * location represents the location of the error, but the slice of the source
19
+ * that will be displayed includes the content of all of the lines that the
20
+ * error touches, so we need to check those parts as well.
21
+ */
22
+ static bool
23
+ location_is_utf8(const pm_parser_t *parser, const pm_location_t *location) {
24
+ const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
25
+ const size_t start_line = (size_t) pm_line_offset_list_line_column(line_offsets, location->start, 1).line;
26
+ const size_t end_line = (size_t) pm_line_offset_list_line_column(line_offsets, location->start + location->length, 1).line;
27
+
28
+ const uint8_t *cursor = parser->start + line_offsets->offsets[start_line - 1];
29
+ const uint8_t *end = (end_line == line_offsets->size) ? parser->end : (parser->start + line_offsets->offsets[end_line]);
30
+
31
+ while (cursor < end) {
32
+ size_t width = pm_encoding_utf_8_char_width(cursor, end - cursor);
33
+ if (width == 0) return false;
34
+
35
+ cursor += width;
36
+ }
37
+
38
+ return true;
39
+ }
40
+
41
+ typedef struct {
42
+ pm_diagnostic_t *diagnostic;
43
+ int32_t line;
44
+ uint32_t column_start;
45
+ uint32_t column_end;
46
+ size_t idx;
47
+ } pm_rich_error_t;
48
+
49
+ static int
50
+ pm_rich_error_compare(const void *a, const void *b) {
51
+ const pm_rich_error_t *left = (const pm_rich_error_t *) a;
52
+ const pm_rich_error_t *right = (const pm_rich_error_t *) b;
53
+
54
+ if (left->line != right->line) {
55
+ return (left->line < right->line) ? -1 : 1;
56
+ }
57
+
58
+ if (left->column_start != right->column_start) {
59
+ return (left->column_start < right->column_start) ? -1 : 1;
60
+ }
61
+
62
+ if (left->idx != right->idx) {
63
+ return (left->idx < right->idx) ? 1 : -1;
64
+ }
65
+
66
+ return 0;
67
+ }
68
+
69
+ #define COLOR_BOLD "\033[1m"
70
+ #define COLOR_GRAY "\033[2m"
71
+ #define COLOR_RED "\033[1;31m"
72
+ #define COLOR_RESET "\033[m"
73
+ #define TRUNCATE 30
74
+
75
+ typedef struct {
76
+ const char *number_prefix;
77
+ const char *blank_prefix;
78
+ const char *divider;
79
+ size_t blank_prefix_length;
80
+ size_t divider_length;
81
+ } pm_error_line_format_t;
82
+
83
+ /**
84
+ * Initialize the given line format struct based on whether or not to highlight
85
+ * the line and the first and last line numbers.
86
+ */
87
+ static void
88
+ pm_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) {
89
+ /* If we have a maximum line number that is negative, then we are going to
90
+ * use the absolute value for comparison but multiply by 10 to additionally
91
+ * have a column for the negative sign. */
92
+ if (first_line < 0) first_line = (-first_line) * 10;
93
+ if (last_line < 0) last_line = (-last_line) * 10;
94
+ int32_t max_line = first_line > last_line ? first_line : last_line;
95
+
96
+ if (max_line < 10) {
97
+ if (format_type > PM_ERRORS_FORMAT_PLAIN) {
98
+ *format = (pm_error_line_format_t) {
99
+ .number_prefix = COLOR_GRAY "%1" PRIi32 " | " COLOR_RESET,
100
+ .blank_prefix = COLOR_GRAY " | " COLOR_RESET,
101
+ .divider = COLOR_GRAY " ~~~~~" COLOR_RESET "\n"
102
+ };
103
+ } else {
104
+ *format = (pm_error_line_format_t) {
105
+ .number_prefix = "%1" PRIi32 " | ",
106
+ .blank_prefix = " | ",
107
+ .divider = " ~~~~~\n"
108
+ };
109
+ }
110
+ } else if (max_line < 100) {
111
+ if (format_type > PM_ERRORS_FORMAT_PLAIN) {
112
+ *format = (pm_error_line_format_t) {
113
+ .number_prefix = COLOR_GRAY "%2" PRIi32 " | " COLOR_RESET,
114
+ .blank_prefix = COLOR_GRAY " | " COLOR_RESET,
115
+ .divider = COLOR_GRAY " ~~~~~~" COLOR_RESET "\n"
116
+ };
117
+ } else {
118
+ *format = (pm_error_line_format_t) {
119
+ .number_prefix = "%2" PRIi32 " | ",
120
+ .blank_prefix = " | ",
121
+ .divider = " ~~~~~~\n"
122
+ };
123
+ }
124
+ } else if (max_line < 1000) {
125
+ if (format_type > PM_ERRORS_FORMAT_PLAIN) {
126
+ *format = (pm_error_line_format_t) {
127
+ .number_prefix = COLOR_GRAY "%3" PRIi32 " | " COLOR_RESET,
128
+ .blank_prefix = COLOR_GRAY " | " COLOR_RESET,
129
+ .divider = COLOR_GRAY " ~~~~~~~" COLOR_RESET "\n"
130
+ };
131
+ } else {
132
+ *format = (pm_error_line_format_t) {
133
+ .number_prefix = "%3" PRIi32 " | ",
134
+ .blank_prefix = " | ",
135
+ .divider = " ~~~~~~~\n"
136
+ };
137
+ }
138
+ } else if (max_line < 10000) {
139
+ if (format_type > PM_ERRORS_FORMAT_PLAIN) {
140
+ *format = (pm_error_line_format_t) {
141
+ .number_prefix = COLOR_GRAY "%4" PRIi32 " | " COLOR_RESET,
142
+ .blank_prefix = COLOR_GRAY " | " COLOR_RESET,
143
+ .divider = COLOR_GRAY " ~~~~~~~~" COLOR_RESET "\n"
144
+ };
145
+ } else {
146
+ *format = (pm_error_line_format_t) {
147
+ .number_prefix = "%4" PRIi32 " | ",
148
+ .blank_prefix = " | ",
149
+ .divider = " ~~~~~~~~\n"
150
+ };
151
+ }
152
+ } else {
153
+ if (format_type > PM_ERRORS_FORMAT_PLAIN) {
154
+ *format = (pm_error_line_format_t) {
155
+ .number_prefix = COLOR_GRAY "%5" PRIi32 " | " COLOR_RESET,
156
+ .blank_prefix = COLOR_GRAY " | " COLOR_RESET,
157
+ .divider = COLOR_GRAY " ~~~~~~~~" COLOR_RESET "\n"
158
+ };
159
+ } else {
160
+ *format = (pm_error_line_format_t) {
161
+ .number_prefix = "%5" PRIi32 " | ",
162
+ .blank_prefix = " | ",
163
+ .divider = " ~~~~~~~~\n"
164
+ };
165
+ }
166
+ }
167
+
168
+ format->blank_prefix_length = strlen(format->blank_prefix);
169
+ format->divider_length = strlen(format->divider);
170
+ }
171
+
172
+ static void
173
+ pm_error_format_line(const pm_parser_t *parser, pm_buffer_t *buffer, const char *number_prefix, int32_t line, uint32_t column_start, uint32_t column_end) {
174
+ int32_t line_delta = line - parser->start_line;
175
+ assert(line_delta >= 0);
176
+
177
+ size_t index = (size_t) line_delta;
178
+ assert(index < parser->line_offsets.size);
179
+
180
+ const uint8_t *start = parser->start + parser->line_offsets.offsets[index];
181
+ const uint8_t *end;
182
+
183
+ if (index >= parser->line_offsets.size - 1) {
184
+ end = parser->end;
185
+ } else {
186
+ end = parser->start + parser->line_offsets.offsets[index + 1];
187
+ }
188
+
189
+ pm_buffer_append_format(buffer, number_prefix, line);
190
+
191
+ /* Here we determine if we should truncate the end of the line. Note that
192
+ * this is written to avoid computing start + column_end, which could be
193
+ * more than one past the end of the source when the error is at the end
194
+ * of the input. */
195
+ bool truncate_end = false;
196
+ if ((column_end != 0) && ((end - start) - ((ptrdiff_t) column_end) >= TRUNCATE)) {
197
+ const uint8_t *end_candidate = start + column_end + TRUNCATE;
198
+
199
+ for (const uint8_t *cursor = start; cursor < end_candidate;) {
200
+ size_t char_width = pm_parser_encoding_char_width(parser, cursor, parser->end - cursor);
201
+
202
+ /* If we failed to decode a character, then just bail out and
203
+ * truncate at the fixed width. */
204
+ if (char_width == 0) break;
205
+
206
+ /* If this next character would go past the end candidate,
207
+ * then we need to truncate before it. */
208
+ if (cursor + char_width > end_candidate) {
209
+ end_candidate = cursor;
210
+ break;
211
+ }
212
+
213
+ cursor += char_width;
214
+ }
215
+
216
+ end = end_candidate;
217
+ truncate_end = true;
218
+ }
219
+
220
+ /* Here we determine if we should truncate the start of the line. */
221
+ if (column_start >= TRUNCATE) {
222
+ pm_buffer_append_string(buffer, "... ", 4);
223
+ start += column_start;
224
+ }
225
+
226
+ pm_buffer_append_string(buffer, (const char *) start, (size_t) (end - start));
227
+
228
+ if (truncate_end) {
229
+ pm_buffer_append_string(buffer, " ...\n", 5);
230
+ } else if (end == parser->end && end > parser->start && end[-1] != '\n') {
231
+ pm_buffer_append_byte(buffer, '\n');
232
+ }
233
+ }
234
+
235
+ static void
236
+ pm_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) {
237
+ pm_error_line_format_t format;
238
+ pm_error_line_format_init(&format, format_type, rich_errors[0].line, rich_errors[nerrors - 1].line);
239
+
240
+ /* We are going to iterate through every error in our error list and display
241
+ * it. While we are iterating, we will display some padding lines of the
242
+ * source before the error to give some context. We will be careful not to
243
+ * display the same line twice in case the errors are close enough in the
244
+ * source. */
245
+ int32_t last_line = parser->start_line - 1;
246
+ uint32_t last_column_start = 0;
247
+
248
+ for (size_t idx = 0; idx < nerrors; idx++) {
249
+ pm_rich_error_t *rich_error = rich_errors + idx;
250
+
251
+ /* Here we determine how many lines of padding of the source to display,
252
+ * based on the difference from the last line that was displayed. */
253
+ if (rich_error->line - last_line > 1) {
254
+ if (rich_error->line - last_line > 2) {
255
+ if ((idx != 0) && (rich_error->line - last_line > 3)) {
256
+ pm_buffer_append_string(buffer, format.divider, format.divider_length);
257
+ }
258
+
259
+ pm_buffer_append_string(buffer, " ", 2);
260
+ pm_error_format_line(parser, buffer, format.number_prefix, rich_error->line - 2, 0, 0);
261
+ }
262
+
263
+ pm_buffer_append_string(buffer, " ", 2);
264
+ pm_error_format_line(parser, buffer, format.number_prefix, rich_error->line - 1, 0, 0);
265
+ }
266
+
267
+ /* If this is the first error or we are on a new line, then we will
268
+ * display the line that has the error in it. */
269
+ if ((idx == 0) || (rich_error->line != last_line)) {
270
+ switch (format_type) {
271
+ case PM_ERRORS_FORMAT_PLAIN:
272
+ pm_buffer_append_string(buffer, "> ", 2);
273
+ break;
274
+ case PM_ERRORS_FORMAT_STYLE:
275
+ pm_buffer_append_format(buffer, "%s", COLOR_BOLD "> " COLOR_RESET);
276
+ break;
277
+ case PM_ERRORS_FORMAT_COLOR:
278
+ pm_buffer_append_format(buffer, "%s", COLOR_RED "> " COLOR_RESET);
279
+ break;
280
+ }
281
+
282
+ last_column_start = rich_error->column_start;
283
+
284
+ /* Find the maximum column end of all the errors on this line. */
285
+ uint32_t column_end = rich_error->column_end;
286
+ for (size_t next_idx = idx + 1; next_idx < nerrors; next_idx++) {
287
+ if (rich_errors[next_idx].line != rich_error->line) break;
288
+ if (rich_errors[next_idx].column_end > column_end) column_end = rich_errors[next_idx].column_end;
289
+ }
290
+
291
+ pm_error_format_line(parser, buffer, format.number_prefix, rich_error->line, rich_error->column_start, column_end);
292
+ }
293
+
294
+ const uint8_t *start = parser->start + parser->line_offsets.offsets[rich_error->line - parser->start_line];
295
+ if (start == parser->end) pm_buffer_append_byte(buffer, '\n');
296
+
297
+ /* Now we will display the actual error message. We will do this by
298
+ * first putting the prefix to the line, then a bunch of blank spaces
299
+ * depending on the column, then as many tildes as we need to display
300
+ * the width of the error, then the error message itself.
301
+ *
302
+ * Note that this doesn't take into account the width of the actual
303
+ * character when displayed in the terminal. For some east-asian
304
+ * languages or emoji, this means it can be thrown off pretty badly. We
305
+ * will need to solve this eventually. */
306
+ pm_buffer_append_string(buffer, " ", 2);
307
+ pm_buffer_append_string(buffer, format.blank_prefix, format.blank_prefix_length);
308
+
309
+ size_t column = 0;
310
+ if (last_column_start >= TRUNCATE) {
311
+ pm_buffer_append_string(buffer, " ", 4);
312
+ column = last_column_start;
313
+ }
314
+
315
+ while (column < rich_error->column_start) {
316
+ pm_buffer_append_byte(buffer, ' ');
317
+
318
+ size_t char_width = pm_parser_encoding_char_width(parser, start + column, parser->end - (start + column));
319
+ column += (char_width == 0 ? 1 : char_width);
320
+ }
321
+
322
+ switch (format_type) {
323
+ case PM_ERRORS_FORMAT_PLAIN:
324
+ pm_buffer_append_byte(buffer, '^');
325
+ break;
326
+ case PM_ERRORS_FORMAT_STYLE:
327
+ pm_buffer_append_format(buffer, "%s^", COLOR_BOLD);
328
+ break;
329
+ case PM_ERRORS_FORMAT_COLOR:
330
+ pm_buffer_append_format(buffer, "%s^", COLOR_RED);
331
+ break;
332
+ }
333
+
334
+ size_t char_width = pm_parser_encoding_char_width(parser, start + column, parser->end - (start + column));
335
+ column += (char_width == 0 ? 1 : char_width);
336
+
337
+ while (column < rich_error->column_end) {
338
+ pm_buffer_append_byte(buffer, '~');
339
+
340
+ size_t char_width = pm_parser_encoding_char_width(parser, start + column, parser->end - (start + column));
341
+ column += (char_width == 0 ? 1 : char_width);
342
+ }
343
+
344
+ if (format_type != PM_ERRORS_FORMAT_PLAIN) {
345
+ pm_buffer_append_format(buffer, "%s", COLOR_RESET);
346
+ }
347
+
348
+ if (inline_messages) {
349
+ const char *message = pm_diagnostic_message(rich_error->diagnostic);
350
+
351
+ pm_buffer_append_byte(buffer, ' ');
352
+ pm_buffer_append_string(buffer, message, strlen(message));
353
+ }
354
+
355
+ pm_buffer_append_byte(buffer, '\n');
356
+
357
+ /* Here we determine how many lines of padding to display after the
358
+ * diagnostic, depending on where the next diagnostic is in source. */
359
+ last_line = rich_error->line;
360
+ int32_t next_line;
361
+
362
+ if (idx == nerrors - 1) {
363
+ next_line = ((int32_t) parser->line_offsets.size) + parser->start_line;
364
+
365
+ /* If the file ends with a newline, subtract one from our
366
+ * "next_line" so that we do not output an extra line at the end of
367
+ * the file. */
368
+ if ((parser->start + parser->line_offsets.offsets[parser->line_offsets.size - 1]) == parser->end) {
369
+ next_line--;
370
+ }
371
+ } else {
372
+ next_line = rich_errors[idx + 1].line;
373
+ }
374
+
375
+ if (next_line - last_line > 1) {
376
+ pm_buffer_append_string(buffer, " ", 2);
377
+ pm_error_format_line(parser, buffer, format.number_prefix, ++last_line, 0, 0);
378
+ }
379
+
380
+ if (next_line - last_line > 1) {
381
+ pm_buffer_append_string(buffer, " ", 2);
382
+ pm_error_format_line(parser, buffer, format.number_prefix, ++last_line, 0, 0);
383
+ }
384
+ }
385
+ }
386
+
387
+ static void
388
+ pm_rich_error_init(const pm_parser_t *parser, pm_rich_error_t *rich_error, pm_diagnostic_t *diagnostic, pm_location_t *location) {
389
+ const pm_line_offset_list_t *line_offsets = &parser->line_offsets;
390
+ pm_line_column_t start_line_column = pm_line_offset_list_line_column(line_offsets, location->start, parser->start_line);
391
+ pm_line_column_t end_line_column = pm_line_offset_list_line_column(line_offsets, location->start + location->length, parser->start_line);
392
+
393
+ uint32_t column_end;
394
+ if (start_line_column.line == end_line_column.line) {
395
+ column_end = end_line_column.column;
396
+ } else {
397
+ 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);
398
+ }
399
+
400
+ /* Ensure we have at least one column of error. */
401
+ if (start_line_column.column == column_end) column_end++;
402
+
403
+ *rich_error = (pm_rich_error_t) {
404
+ .diagnostic = diagnostic,
405
+ .line = start_line_column.line,
406
+ .column_start = start_line_column.column,
407
+ .column_end = column_end
408
+ };
409
+ }
410
+
411
+ pm_error_level_t
412
+ pm_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, pm_errors_format_type_t format_type) {
413
+ size_t nerrors = parser->error_list.size;
414
+ assert(nerrors > 0);
415
+
416
+ int filepath_length = (int) pm_string_length(&parser->filepath);
417
+ const char *filepath = (const char *) pm_string_source(&parser->filepath);
418
+
419
+ size_t rich_errors_size = nerrors * sizeof(pm_rich_error_t);
420
+ if (rich_errors_size / sizeof(pm_rich_error_t) != nerrors) abort();
421
+
422
+ pm_rich_error_t *rich_errors = xmalloc(rich_errors_size);
423
+ if (rich_errors == NULL) abort();
424
+
425
+ bool is_utf8 = true;
426
+ size_t rich_errors_idx = 0;
427
+
428
+ for (pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) parser->error_list.head; diagnostic != NULL; diagnostic = (pm_diagnostic_t *) diagnostic->node.next) {
429
+ pm_location_t location = pm_diagnostic_location(diagnostic);
430
+
431
+ switch (pm_diagnostic_error_level(diagnostic)) {
432
+ case PM_ERROR_LEVEL_SYNTAX: {
433
+ if (is_utf8 && !location_is_utf8(parser, &location)) {
434
+ is_utf8 = false;
435
+ }
436
+
437
+ pm_rich_error_init(parser, &rich_errors[rich_errors_idx], diagnostic, &location);
438
+ rich_errors[rich_errors_idx].idx = rich_errors_idx;
439
+ rich_errors_idx++;
440
+ break;
441
+ }
442
+ case PM_ERROR_LEVEL_ARGUMENT: {
443
+ /* If we get an argument error, we want to format it and return it
444
+ * immediately. They should include a snippet of the source if
445
+ * possible. */
446
+ pm_buffer_append_format(
447
+ buffer,
448
+ "%.*s:%" PRIi32 ": %s",
449
+ filepath_length,
450
+ filepath,
451
+ pm_line_offset_list_line(&parser->line_offsets, location.start, parser->start_line),
452
+ pm_diagnostic_message(diagnostic)
453
+ );
454
+
455
+ if (location_is_utf8(parser, &location)) {
456
+ pm_buffer_append_byte(buffer, '\n');
457
+ pm_rich_error_init(parser, rich_errors, diagnostic, &location);
458
+ pm_rich_errors_format(parser, buffer, format_type, 1, rich_errors, false);
459
+ }
460
+
461
+ xfree_sized(rich_errors, rich_errors_size);
462
+ return PM_ERROR_LEVEL_ARGUMENT;
463
+ }
464
+ case PM_ERROR_LEVEL_LOAD: {
465
+ /* If we get a load error, we want to format it and return it
466
+ * immediately. It should only include the diagnostic message. */
467
+ const char *message = pm_diagnostic_message(diagnostic);
468
+ pm_buffer_append_string(buffer, message, strlen(message));
469
+
470
+ xfree_sized(rich_errors, rich_errors_size);
471
+ return PM_ERROR_LEVEL_LOAD;
472
+ }
473
+ }
474
+ }
475
+
476
+ assert(rich_errors_idx == nerrors);
477
+
478
+ /* The header displays the line of the first error in parse order, which
479
+ * is the first entry in the array because it has not been sorted yet. */
480
+ pm_buffer_append_format(
481
+ buffer,
482
+ "%.*s:%" PRIi32 ": syntax error%s found\n",
483
+ filepath_length,
484
+ filepath,
485
+ rich_errors[0].line,
486
+ (nerrors > 1) ? "s" : ""
487
+ );
488
+
489
+ if (is_utf8) {
490
+ /* In here, we have all of the rich diagnostic information and we know
491
+ * the snippets are valid UTF-8. We will sort the errors so that they
492
+ * are displayed in the order that they appear in the source, and then
493
+ * format them all together with the source code snippets. */
494
+ qsort(rich_errors, nerrors, sizeof(pm_rich_error_t), pm_rich_error_compare);
495
+ pm_rich_errors_format(parser, buffer, format_type, nerrors, rich_errors, true);
496
+ } else {
497
+ /* In here, we have content in the source that is not valid UTF-8. In
498
+ * that case, we do not want to attempt to format the source code
499
+ * snippets because we do not know how the terminal will handle it. The
500
+ * diagnostics are displayed in the order that they were generated,
501
+ * which is the order of the array before it has been sorted. */
502
+ for (size_t idx = 0; idx < nerrors; idx++) {
503
+ pm_rich_error_t *rich_error = &rich_errors[idx];
504
+
505
+ if (idx > 0) pm_buffer_append_byte(buffer, '\n');
506
+ pm_buffer_append_format(
507
+ buffer,
508
+ "%.*s:%" PRIi32 ": %s",
509
+ filepath_length,
510
+ filepath,
511
+ rich_error->line,
512
+ pm_diagnostic_message(rich_error->diagnostic)
513
+ );
514
+ }
515
+ }
516
+
517
+ xfree_sized(rich_errors, rich_errors_size);
518
+ return PM_ERROR_LEVEL_SYNTAX;
519
+ }