prism 1.9.0 → 2.0.0.rc2

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -0
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/LICENSE.md +1 -1
  5. data/Makefile +41 -12
  6. data/README.md +35 -97
  7. data/config.yml +174 -161
  8. data/docs/build_system.md +2 -2
  9. data/docs/configuration.md +5 -4
  10. data/docs/encoding.md +2 -2
  11. data/docs/fuzzing.md +4 -11
  12. data/docs/javascript.md +19 -4
  13. data/docs/mapping.md +1 -1
  14. data/docs/releasing.md +77 -5
  15. data/docs/relocation.md +2 -4
  16. data/docs/serialization.md +22 -24
  17. data/docs/testing.md +46 -29
  18. data/ext/prism/api_node.c +1461 -1401
  19. data/ext/prism/extconf.rb +3 -4
  20. data/ext/prism/extension.c +796 -422
  21. data/ext/prism/extension.h +3 -3
  22. data/include/prism/arena.h +37 -0
  23. data/include/prism/ast.h +2727 -610
  24. data/include/prism/buffer.h +52 -0
  25. data/include/prism/comments.h +55 -0
  26. data/include/prism/compiler/accel.h +26 -0
  27. data/include/prism/compiler/align.h +36 -0
  28. data/include/prism/compiler/assume.h +25 -0
  29. data/include/prism/compiler/bswap.h +46 -0
  30. data/include/prism/compiler/endianness.h +34 -0
  31. data/include/prism/compiler/exported.h +24 -0
  32. data/include/prism/compiler/fallthrough.h +22 -0
  33. data/include/prism/compiler/filesystem.h +32 -0
  34. data/include/prism/compiler/flex_array.h +19 -0
  35. data/include/prism/compiler/force_inline.h +21 -0
  36. data/include/prism/compiler/format.h +25 -0
  37. data/include/prism/compiler/inline.h +17 -0
  38. data/include/prism/compiler/nodiscard.h +22 -0
  39. data/include/prism/compiler/nonnull.h +18 -0
  40. data/include/prism/compiler/unused.h +18 -0
  41. data/include/prism/constant_pool.h +81 -0
  42. data/include/prism/diagnostic.h +38 -403
  43. data/include/prism/errors_format.h +53 -0
  44. data/include/prism/excludes.h +29 -0
  45. data/include/prism/integer.h +41 -0
  46. data/include/prism/internal/allocator.h +68 -0
  47. data/include/prism/internal/allocator_debug.h +88 -0
  48. data/include/prism/internal/arena.h +108 -0
  49. data/include/prism/internal/bit.h +42 -0
  50. data/include/prism/internal/buffer.h +94 -0
  51. data/include/prism/internal/char.h +145 -0
  52. data/include/prism/internal/comments.h +53 -0
  53. data/include/prism/internal/constant_pool.h +152 -0
  54. data/include/prism/internal/diagnostic.h +389 -0
  55. data/include/prism/{encoding.h → internal/encoding.h} +27 -68
  56. data/include/prism/internal/integer.h +68 -0
  57. data/include/prism/internal/isinf.h +16 -0
  58. data/include/prism/internal/line_offset_list.h +34 -0
  59. data/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  60. data/include/prism/internal/magic_comments.h +23 -0
  61. data/include/prism/internal/memchr.h +15 -0
  62. data/include/prism/internal/node.h +38 -0
  63. data/include/prism/internal/options.h +232 -0
  64. data/include/prism/internal/parser.h +1004 -0
  65. data/include/prism/internal/regexp.h +26 -0
  66. data/include/prism/internal/serialize.h +39 -0
  67. data/include/prism/internal/source.h +72 -0
  68. data/include/prism/{static_literals.h → internal/static_literals.h} +26 -47
  69. data/include/prism/internal/stringy.h +35 -0
  70. data/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  71. data/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  72. data/include/prism/internal/tokens.h +11 -0
  73. data/include/prism/internal/xxh3.h +16 -0
  74. data/include/prism/json.h +32 -0
  75. data/include/prism/line_offset_list.h +61 -0
  76. data/include/prism/magic_comments.h +35 -0
  77. data/include/prism/node.h +18 -53
  78. data/include/prism/options.h +146 -280
  79. data/include/prism/parser.h +356 -867
  80. data/include/prism/prettyprint.h +6 -9
  81. data/include/prism/serialize.h +115 -0
  82. data/include/prism/source.h +155 -0
  83. data/include/prism/stream.h +28 -0
  84. data/include/prism/string_query.h +74 -0
  85. data/include/prism/stringy.h +72 -0
  86. data/include/prism/version.h +12 -3
  87. data/include/prism.h +43 -320
  88. data/lib/prism/compiler.rb +319 -305
  89. data/lib/prism/desugar_compiler.rb +105 -34
  90. data/lib/prism/dispatcher.rb +646 -473
  91. data/lib/prism/dot_visitor.rb +592 -511
  92. data/lib/prism/dsl.rb +391 -25
  93. data/lib/prism/ffi.rb +258 -90
  94. data/lib/prism/inspect_visitor.rb +357 -325
  95. data/lib/prism/lex_compat.rb +254 -212
  96. data/lib/prism/mutation_compiler.rb +313 -305
  97. data/lib/prism/node.rb +20966 -6992
  98. data/lib/prism/node_ext.rb +180 -187
  99. data/lib/prism/node_find.rb +215 -0
  100. data/lib/prism/parse_result/errors.rb +7 -1
  101. data/lib/prism/parse_result/newlines.rb +276 -8
  102. data/lib/prism/parse_result.rb +468 -139
  103. data/lib/prism/pattern.rb +65 -20
  104. data/lib/prism/reflection.rb +23 -12
  105. data/lib/prism/relocation.rb +149 -87
  106. data/lib/prism/serialize.rb +3460 -539
  107. data/lib/prism/string_query.rb +24 -1
  108. data/lib/prism/translation/parser/builder.rb +10 -2
  109. data/lib/prism/translation/parser/compiler.rb +44 -52
  110. data/lib/prism/translation/parser/lexer.rb +41 -92
  111. data/lib/prism/translation/parser.rb +1 -1
  112. data/lib/prism/translation/parser_current.rb +1 -1
  113. data/lib/prism/translation/ripper/lexer.rb +8 -10
  114. data/lib/prism/translation/ripper/sexp.rb +2 -10
  115. data/lib/prism/translation/ripper/shim.rb +2 -0
  116. data/lib/prism/translation/ripper.rb +967 -209
  117. data/lib/prism/translation/ruby_parser.rb +73 -316
  118. data/lib/prism/translation.rb +2 -0
  119. data/lib/prism/visitor.rb +632 -5
  120. data/lib/prism.rb +79 -9
  121. data/prism.gemspec +128 -62
  122. data/rbi/generated/prism/compiler.rbi +489 -0
  123. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  124. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  125. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  126. data/rbi/generated/prism/dsl.rbi +751 -0
  127. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  128. data/rbi/generated/prism/lex_compat.rbi +162 -0
  129. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  130. data/rbi/generated/prism/node.rbi +14407 -0
  131. data/rbi/generated/prism/node_ext.rbi +259 -0
  132. data/rbi/generated/prism/node_find.rbi +79 -0
  133. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  134. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  135. data/rbi/generated/prism/parse_result.rbi +776 -0
  136. data/rbi/generated/prism/pattern.rbi +134 -0
  137. data/rbi/generated/prism/reflection.rbi +89 -0
  138. data/rbi/generated/prism/relocation.rbi +329 -0
  139. data/rbi/generated/prism/serialize.rbi +184 -0
  140. data/rbi/generated/prism/string_query.rbi +43 -0
  141. data/rbi/generated/prism/translation.rbi +7 -0
  142. data/rbi/generated/prism/visitor.rbi +650 -0
  143. data/rbi/generated/prism.rbi +86 -0
  144. data/rbi/prism/translation/parser_versions.rbi +5 -0
  145. data/rbi/rubyvm/node_find.rbi +14 -0
  146. data/sig/generated/prism/compiler.rbs +492 -0
  147. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  148. data/sig/generated/prism/dispatcher.rbs +1292 -0
  149. data/sig/generated/prism/dot_visitor.rbs +635 -0
  150. data/sig/generated/prism/dsl.rbs +928 -0
  151. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  152. data/sig/generated/prism/lex_compat.rbs +166 -0
  153. data/sig/{prism → generated/prism}/mutation_compiler.rbs +309 -4
  154. data/sig/generated/prism/node.rbs +20896 -0
  155. data/sig/generated/prism/node_ext.rbs +290 -0
  156. data/sig/generated/prism/node_find.rbs +90 -0
  157. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  158. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  159. data/sig/generated/prism/parse_result.rbs +871 -0
  160. data/sig/generated/prism/pattern.rbs +156 -0
  161. data/sig/generated/prism/reflection.rbs +101 -0
  162. data/sig/generated/prism/relocation.rbs +428 -0
  163. data/sig/generated/prism/serialize.rbs +198 -0
  164. data/sig/generated/prism/string_query.rbs +43 -0
  165. data/sig/generated/prism/translation.rbs +8 -0
  166. data/sig/generated/prism/visitor.rbs +1111 -0
  167. data/sig/generated/prism.rbs +84 -0
  168. data/sig/manifest.yaml +2 -0
  169. data/src/arena.c +117 -0
  170. data/src/{util/pm_buffer.c → buffer.c} +53 -21
  171. data/src/{util/pm_char.c → char.c} +28 -57
  172. data/src/comments.c +454 -0
  173. data/src/constant_pool.c +406 -0
  174. data/src/diagnostic.c +97 -65
  175. data/src/encoding.c +8 -3
  176. data/src/errors_format.c +519 -0
  177. data/src/{util/pm_integer.c → integer.c} +33 -22
  178. data/src/json.c +5733 -0
  179. data/src/line_offset_list.c +100 -0
  180. data/src/list.c +24 -0
  181. data/src/{util/pm_memchr.c → memchr.c} +8 -6
  182. data/src/node.c +3504 -7674
  183. data/src/options.c +162 -49
  184. data/src/parser.c +367 -0
  185. data/src/prettyprint.c +421 -354
  186. data/src/prism.c +8402 -7161
  187. data/src/regexp.c +961 -45
  188. data/src/serialize.c +460 -424
  189. data/src/source.c +518 -0
  190. data/src/static_literals.c +111 -43
  191. data/src/string_query.c +180 -0
  192. data/src/stringy.c +91 -0
  193. data/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  194. data/src/strpbrk.c +447 -0
  195. data/src/{token_type.c → tokens.c} +39 -18
  196. data/src/xxh3.c +292 -0
  197. metadata +128 -63
  198. data/ext/prism/api_pack.c +0 -276
  199. data/include/prism/defines.h +0 -260
  200. data/include/prism/pack.h +0 -163
  201. data/include/prism/regexp.h +0 -43
  202. data/include/prism/util/pm_buffer.h +0 -236
  203. data/include/prism/util/pm_char.h +0 -204
  204. data/include/prism/util/pm_constant_pool.h +0 -218
  205. data/include/prism/util/pm_integer.h +0 -130
  206. data/include/prism/util/pm_memchr.h +0 -29
  207. data/include/prism/util/pm_newline_list.h +0 -113
  208. data/include/prism/util/pm_string.h +0 -200
  209. data/lib/prism/pack.rb +0 -230
  210. data/lib/prism/parse_result/comments.rb +0 -188
  211. data/rbi/prism/compiler.rbi +0 -12
  212. data/rbi/prism/dsl.rbi +0 -524
  213. data/rbi/prism/inspect_visitor.rbi +0 -12
  214. data/rbi/prism/node.rbi +0 -8750
  215. data/rbi/prism/node_ext.rbi +0 -107
  216. data/rbi/prism/parse_result.rbi +0 -404
  217. data/rbi/prism/reflection.rbi +0 -58
  218. data/rbi/prism/string_query.rbi +0 -12
  219. data/rbi/prism/visitor.rbi +0 -473
  220. data/rbi/prism.rbi +0 -63
  221. data/sig/prism/compiler.rbs +0 -9
  222. data/sig/prism/dispatcher.rbs +0 -19
  223. data/sig/prism/dot_visitor.rbs +0 -6
  224. data/sig/prism/dsl.rbs +0 -351
  225. data/sig/prism/inspect_visitor.rbs +0 -22
  226. data/sig/prism/lex_compat.rbs +0 -10
  227. data/sig/prism/node.rbs +0 -4042
  228. data/sig/prism/node_ext.rbs +0 -149
  229. data/sig/prism/pack.rbs +0 -43
  230. data/sig/prism/parse_result/comments.rbs +0 -38
  231. data/sig/prism/parse_result.rbs +0 -197
  232. data/sig/prism/pattern.rbs +0 -13
  233. data/sig/prism/reflection.rbs +0 -50
  234. data/sig/prism/relocation.rbs +0 -185
  235. data/sig/prism/serialize.rbs +0 -8
  236. data/sig/prism/string_query.rbs +0 -11
  237. data/sig/prism/visitor.rbs +0 -169
  238. data/sig/prism.rbs +0 -272
  239. data/src/pack.c +0 -509
  240. data/src/util/pm_constant_pool.c +0 -342
  241. data/src/util/pm_list.c +0 -49
  242. data/src/util/pm_newline_list.c +0 -125
  243. data/src/util/pm_string.c +0 -381
  244. data/src/util/pm_strpbrk.c +0 -206
@@ -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
+ }
@@ -1,4 +1,25 @@
1
- #include "prism/util/pm_integer.h"
1
+ #include "prism/internal/integer.h"
2
+
3
+ #include "prism/internal/allocator.h"
4
+ #include "prism/internal/buffer.h"
5
+
6
+ #include <assert.h>
7
+ #include <inttypes.h>
8
+ #include <stdbool.h>
9
+ #include <stddef.h>
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+
13
+ /**
14
+ * Free the internal memory of an integer. This memory will only be allocated if
15
+ * the integer exceeds the size of a single uint32_t.
16
+ */
17
+ static void
18
+ pm_integer_free(pm_integer_t *integer) {
19
+ if (integer->values) {
20
+ xfree(integer->values);
21
+ }
22
+ }
2
23
 
3
24
  /**
4
25
  * Pull out the length and values from the integer, regardless of the form in
@@ -374,7 +395,7 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u
374
395
  }
375
396
  }
376
397
 
377
- xfree(bigints);
398
+ xfree_sized(bigints, bigints_length * sizeof(pm_integer_t));
378
399
  bigints = next_bigints;
379
400
  bigints_length = next_length;
380
401
  }
@@ -383,7 +404,7 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u
383
404
  destination->negative = source->negative;
384
405
  pm_integer_normalize(destination);
385
406
 
386
- xfree(bigints);
407
+ xfree_sized(bigints, bigints_length * sizeof(pm_integer_t));
387
408
  pm_integer_free(&base);
388
409
  }
389
410
 
@@ -422,7 +443,7 @@ pm_integer_parse_powof2(pm_integer_t *integer, uint32_t base, const uint8_t *dig
422
443
  static void
423
444
  pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t digits_length) {
424
445
  const size_t batch = 9;
425
- size_t length = (digits_length + batch - 1) / batch;
446
+ const size_t length = (digits_length + batch - 1) / batch;
426
447
 
427
448
  uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t));
428
449
  uint32_t value = 0;
@@ -439,7 +460,7 @@ pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t di
439
460
 
440
461
  // Convert base from 10**9 to 1<<32.
441
462
  pm_integer_convert_base(integer, &((pm_integer_t) { .length = length, .values = values, .value = 0, .negative = false }), 1000000000, ((uint64_t) 1 << 32));
442
- xfree(values);
463
+ xfree_sized(values, length * sizeof(uint32_t));
443
464
  }
444
465
 
445
466
  /**
@@ -448,7 +469,8 @@ pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t di
448
469
  static void
449
470
  pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *start, const uint8_t *end) {
450
471
  // Allocate an array to store digits.
451
- uint8_t *digits = xmalloc(sizeof(uint8_t) * (size_t) (end - start));
472
+ const size_t digits_capa = sizeof(uint8_t) * (size_t) (end - start);
473
+ uint8_t *digits = xmalloc(digits_capa);
452
474
  size_t digits_length = 0;
453
475
 
454
476
  for (; start < end; start++) {
@@ -463,7 +485,7 @@ pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *
463
485
  pm_integer_parse_powof2(integer, multiplier, digits, digits_length);
464
486
  }
465
487
 
466
- xfree(digits);
488
+ xfree_sized(digits, digits_capa);
467
489
  }
468
490
 
469
491
  /**
@@ -481,7 +503,7 @@ pm_integer_parse(pm_integer_t *integer, pm_integer_base_t base, const uint8_t *s
481
503
  uint32_t multiplier = 10;
482
504
  switch (base) {
483
505
  case PM_INTEGER_BASE_DEFAULT:
484
- while (*start == '0') start++; // 01 -> 1
506
+ while (start < end && *start == '0') start++; // 01 -> 1
485
507
  break;
486
508
  case PM_INTEGER_BASE_BINARY:
487
509
  start += 2; // 0b
@@ -603,7 +625,7 @@ void pm_integers_reduce(pm_integer_t *numerator, pm_integer_t *denominator) {
603
625
  /**
604
626
  * Convert an integer to a decimal string.
605
627
  */
606
- PRISM_EXPORTED_FUNCTION void
628
+ void
607
629
  pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
608
630
  if (integer->negative) {
609
631
  pm_buffer_append_byte(buffer, '-');
@@ -635,7 +657,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
635
657
  }
636
658
 
637
659
  // Allocate a buffer that we'll copy the decimal digits into.
638
- size_t digits_length = converted.length * 9;
660
+ const size_t digits_length = converted.length * 9;
639
661
  char *digits = xcalloc(digits_length, sizeof(char));
640
662
  if (digits == NULL) return;
641
663
 
@@ -654,17 +676,6 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
654
676
 
655
677
  // Finally, append the string to the buffer and free the digits.
656
678
  pm_buffer_append_string(buffer, digits + start_offset, digits_length - start_offset);
657
- xfree(digits);
679
+ xfree_sized(digits, sizeof(char) * digits_length);
658
680
  pm_integer_free(&converted);
659
681
  }
660
-
661
- /**
662
- * Free the internal memory of an integer. This memory will only be allocated if
663
- * the integer exceeds the size of a single uint32_t.
664
- */
665
- PRISM_EXPORTED_FUNCTION void
666
- pm_integer_free(pm_integer_t *integer) {
667
- if (integer->values) {
668
- xfree(integer->values);
669
- }
670
- }