herb 0.11.1 → 0.11.2

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -2
  3. data/config.yml +6 -0
  4. data/ext/herb/extconf.rb +8 -31
  5. data/ext/herb/nodes.c +75 -75
  6. data/ext/herb/parser_options_helpers.c +26 -0
  7. data/lib/herb/analysis/partial_index.rb +1 -1
  8. data/lib/herb/analysis/partial_resolution.rb +27 -0
  9. data/lib/herb/ast/helpers.rb +1 -1
  10. data/lib/herb/configuration.rb +11 -1
  11. data/lib/herb/dev/classifier.rb +1 -1
  12. data/lib/herb/engine/compiler.rb +5 -8
  13. data/lib/herb/engine/slots/dynamics_compiler.rb +53 -14
  14. data/lib/herb/engine/slots/subtree_compiler.rb +1 -1
  15. data/lib/herb/engine/slots/visitor.rb +32 -4
  16. data/lib/herb/engine/visitors/instrumentation_visitor.rb +18 -6
  17. data/lib/herb/parser_options.rb +6 -3
  18. data/lib/herb/project.rb +19 -8
  19. data/lib/herb/version.rb +1 -1
  20. data/lib/herb.rb +1 -1
  21. data/sig/herb/analysis/partial_resolution.rbs +8 -0
  22. data/sig/herb/configuration.rbs +3 -0
  23. data/sig/herb/engine/slots/dynamics_compiler.rbs +24 -7
  24. data/sig/herb/engine/slots/visitor.rbs +14 -0
  25. data/sig/herb/engine/visitors/instrumentation_visitor.rbs +5 -1
  26. data/sig/herb/parser_options.rbs +6 -4
  27. data/sig/herb.rbs +2 -2
  28. data/src/analyze/action_view/attribute_extraction_helpers.c +40 -21
  29. data/src/analyze/action_view/generated_handlers.c +36 -36
  30. data/src/analyze/action_view/image_tag.c +7 -3
  31. data/src/analyze/action_view/javascript_include_tag.c +6 -3
  32. data/src/analyze/action_view/link_to.c +23 -23
  33. data/src/analyze/action_view/registry.c +7 -3
  34. data/src/analyze/action_view/tag_helper_node_builders.c +6 -13
  35. data/src/analyze/action_view/tag_helpers.c +139 -90
  36. data/src/analyze/action_view/turbo_frame_tag.c +5 -4
  37. data/src/analyze/analyze.c +35 -19
  38. data/src/analyze/analyze_helpers.c +161 -188
  39. data/src/analyze/analyzed_ruby.c +8 -7
  40. data/src/analyze/control_type.c +29 -22
  41. data/src/analyze/herb_directives.c +16 -20
  42. data/src/analyze/iteration_nodes.c +69 -24
  43. data/src/analyze/parse_errors.c +32 -22
  44. data/src/analyze/postfix_conditionals.c +23 -13
  45. data/src/analyze/prism_annotate.c +29 -28
  46. data/src/analyze/render_nodes.c +51 -41
  47. data/src/analyze/strict_locals.c +14 -15
  48. data/src/analyze/ternary_conditionals.c +14 -10
  49. data/src/herb.c +10 -8
  50. data/src/include/analyze/action_view/tag_helper_handler.h +1 -1
  51. data/src/include/analyze/action_view/tag_helper_node_builders.h +1 -0
  52. data/src/include/analyze/action_view/tag_helpers.h +1 -1
  53. data/src/include/analyze/analyze.h +3 -2
  54. data/src/include/analyze/analyzed_ruby.h +4 -2
  55. data/src/include/analyze/helpers.h +6 -6
  56. data/src/include/herb.h +3 -2
  57. data/src/include/parser/parser_options.h +5 -0
  58. data/src/include/prism/prism_context.h +12 -12
  59. data/src/include/prism/prism_helpers.h +30 -0
  60. data/src/include/prism_version.h +11 -0
  61. data/src/include/version.h +1 -1
  62. data/src/parser/parser_helpers.c +1 -2
  63. data/src/parser.c +6 -7
  64. data/src/parser_options_defaults.c +1 -0
  65. data/src/prism/prism_helpers.c +84 -50
  66. data/src/prism/ruby_parser.c +20 -20
  67. data/templates/ext/herb/nodes.c.erb +5 -5
  68. data/templates/ext/herb/parser_options_helpers.c.erb +33 -0
  69. data/templates/java/nodes.c.erb +6 -6
  70. data/templates/java/parser_options_helpers.c.erb +41 -2
  71. data/templates/java/parser_options_helpers.h.erb +2 -1
  72. data/templates/javascript/packages/node/extension/nodes.cpp.erb +5 -5
  73. data/templates/javascript/packages/node/extension/parser_options_helpers.cpp.erb +34 -2
  74. data/templates/javascript/packages/node/extension/parser_options_helpers.h.erb +1 -1
  75. data/templates/lib/herb/parser_options.rb.erb +1 -1
  76. data/templates/rust/src/prism/generated_deserialize.rs.erb +90 -44
  77. data/templates/rust/src/prism/generated_prettyprint.rs.erb +18 -27
  78. data/templates/src/analyze/action_view/generated_handlers.c.erb +23 -22
  79. data/templates/src/include/parser/parser_options.h.erb +10 -0
  80. data/templates/src/include/prism_version.h.erb +17 -0
  81. data/templates/src/parser_options_defaults.c.erb +1 -1
  82. data/templates/template.rb +17 -2
  83. data/templates/wasm/nodes.cpp.erb +5 -5
  84. data/templates/wasm/parser_options_helpers.cpp.erb +24 -0
  85. data/vendor/prism/Rakefile +76 -7
  86. data/vendor/prism/config.yml +174 -161
  87. data/vendor/prism/include/prism/arena.h +45 -0
  88. data/vendor/prism/include/prism/arena.hpp +42 -0
  89. data/vendor/prism/include/prism/ast.h +2738 -611
  90. data/vendor/prism/include/prism/ast.hpp +641 -0
  91. data/vendor/prism/include/prism/buffer.h +60 -0
  92. data/vendor/prism/include/prism/buffer.hpp +55 -0
  93. data/vendor/prism/include/prism/comments.h +72 -0
  94. data/vendor/prism/include/prism/compiler/accel.h +26 -0
  95. data/vendor/prism/include/prism/compiler/align.h +36 -0
  96. data/vendor/prism/include/prism/compiler/assume.h +25 -0
  97. data/vendor/prism/include/prism/compiler/branch.h +26 -0
  98. data/vendor/prism/include/prism/compiler/bswap.h +46 -0
  99. data/vendor/prism/include/prism/compiler/cplusplus.hpp +16 -0
  100. data/vendor/prism/include/prism/compiler/endianness.h +34 -0
  101. data/vendor/prism/include/prism/compiler/exceptions.hpp +17 -0
  102. data/vendor/prism/include/prism/compiler/exported.h +24 -0
  103. data/vendor/prism/include/prism/compiler/fallthrough.h +22 -0
  104. data/vendor/prism/include/prism/compiler/filesystem.h +32 -0
  105. data/vendor/prism/include/prism/compiler/flex_array.h +19 -0
  106. data/vendor/prism/include/prism/compiler/force_inline.h +21 -0
  107. data/vendor/prism/include/prism/compiler/format.h +27 -0
  108. data/vendor/prism/include/prism/compiler/inline.h +17 -0
  109. data/vendor/prism/include/prism/compiler/lifetimebound.hpp +25 -0
  110. data/vendor/prism/include/prism/compiler/nodiscard.h +22 -0
  111. data/vendor/prism/include/prism/compiler/nonnull.h +18 -0
  112. data/vendor/prism/include/prism/compiler/unused.h +18 -0
  113. data/vendor/prism/include/prism/constant_pool.h +106 -0
  114. data/vendor/prism/include/prism/diagnostic.h +55 -402
  115. data/vendor/prism/include/prism/errors_format.h +61 -0
  116. data/vendor/prism/include/prism/excludes.h +29 -0
  117. data/vendor/prism/include/prism/integer.h +41 -0
  118. data/vendor/prism/include/prism/internal/allocator.h +68 -0
  119. data/vendor/prism/include/prism/internal/allocator_debug.h +88 -0
  120. data/vendor/prism/include/prism/internal/arena.h +108 -0
  121. data/vendor/prism/include/prism/internal/bit.h +42 -0
  122. data/vendor/prism/include/prism/internal/buffer.h +94 -0
  123. data/vendor/prism/include/prism/internal/char.h +145 -0
  124. data/vendor/prism/include/prism/internal/comments.h +54 -0
  125. data/vendor/prism/include/prism/internal/constant_pool.h +152 -0
  126. data/vendor/prism/include/prism/internal/diagnostic.h +389 -0
  127. data/vendor/prism/include/prism/{encoding.h → internal/encoding.h} +27 -68
  128. data/vendor/prism/include/prism/internal/integer.h +68 -0
  129. data/vendor/prism/include/prism/internal/isinf.h +16 -0
  130. data/vendor/prism/include/prism/internal/line_offset_list.h +34 -0
  131. data/vendor/prism/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  132. data/vendor/prism/include/prism/internal/magic_comments.h +23 -0
  133. data/vendor/prism/include/prism/internal/memchr.h +15 -0
  134. data/vendor/prism/include/prism/internal/node.h +42 -0
  135. data/vendor/prism/include/prism/internal/options.h +232 -0
  136. data/vendor/prism/include/prism/internal/parser.h +1015 -0
  137. data/vendor/prism/include/prism/internal/regexp.h +26 -0
  138. data/vendor/prism/include/prism/internal/serialize.h +39 -0
  139. data/vendor/prism/include/prism/internal/source.h +72 -0
  140. data/vendor/prism/include/prism/internal/static_literals.h +60 -0
  141. data/vendor/prism/include/prism/internal/stringy.h +30 -0
  142. data/vendor/prism/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  143. data/vendor/prism/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  144. data/vendor/prism/include/prism/internal/tokens.h +11 -0
  145. data/vendor/prism/include/prism/internal/xxh3.h +16 -0
  146. data/vendor/prism/include/prism/json.h +40 -0
  147. data/vendor/prism/include/prism/line_offset_list.h +69 -0
  148. data/vendor/prism/include/prism/magic_comments.h +53 -0
  149. data/vendor/prism/include/prism/node.h +59 -49
  150. data/vendor/prism/include/prism/node.hpp +272 -0
  151. data/vendor/prism/include/prism/node_type.hpp +38 -0
  152. data/vendor/prism/include/prism/options.h +154 -279
  153. data/vendor/prism/include/prism/options.hpp +43 -0
  154. data/vendor/prism/include/prism/parser.h +367 -870
  155. data/vendor/prism/include/prism/parser.hpp +274 -0
  156. data/vendor/prism/include/prism/prettyprint.h +14 -9
  157. data/vendor/prism/include/prism/serialize.h +123 -0
  158. data/vendor/prism/include/prism/source.h +163 -0
  159. data/vendor/prism/include/prism/stream.h +36 -0
  160. data/vendor/prism/include/prism/string_query.h +82 -0
  161. data/vendor/prism/include/prism/stringy.h +61 -0
  162. data/vendor/prism/include/prism/stringy.hpp +31 -0
  163. data/vendor/prism/include/prism/version.h +20 -3
  164. data/vendor/prism/include/prism.h +35 -320
  165. data/vendor/prism/include/prism.hpp +24 -0
  166. data/vendor/prism/src/arena.c +122 -0
  167. data/vendor/prism/src/{util/pm_buffer.c → buffer.c} +53 -21
  168. data/vendor/prism/src/{util/pm_char.c → char.c} +28 -57
  169. data/vendor/prism/src/comments.c +455 -0
  170. data/vendor/prism/src/constant_pool.c +406 -0
  171. data/vendor/prism/src/diagnostic.c +120 -68
  172. data/vendor/prism/src/encoding.c +8 -3
  173. data/vendor/prism/src/errors_format.c +519 -0
  174. data/vendor/prism/src/{util/pm_integer.c → integer.c} +38 -22
  175. data/vendor/prism/src/json.c +5733 -0
  176. data/vendor/prism/src/line_offset_list.c +100 -0
  177. data/vendor/prism/src/list.c +24 -0
  178. data/vendor/prism/src/{util/pm_memchr.c → memchr.c} +8 -6
  179. data/vendor/prism/src/node.c +3465 -7787
  180. data/vendor/prism/src/options.c +161 -54
  181. data/vendor/prism/src/parser.c +371 -0
  182. data/vendor/prism/src/prettyprint.c +421 -354
  183. data/vendor/prism/src/prism.c +8522 -7280
  184. data/vendor/prism/src/regexp.c +960 -45
  185. data/vendor/prism/src/serialize.c +2434 -2048
  186. data/vendor/prism/src/source.c +524 -0
  187. data/vendor/prism/src/static_literals.c +303 -360
  188. data/vendor/prism/src/string_query.c +180 -0
  189. data/vendor/prism/src/stringy.c +61 -0
  190. data/vendor/prism/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  191. data/vendor/prism/src/strpbrk.c +447 -0
  192. data/vendor/prism/src/{token_type.c → tokens.c} +39 -18
  193. data/vendor/prism/src/xxh3.c +292 -0
  194. data/vendor/prism/templates/ext/prism/api_node.c.erb +58 -44
  195. data/vendor/prism/templates/include/prism/ast.h.erb +67 -17
  196. data/vendor/prism/templates/include/prism/ast.hpp.erb +31 -0
  197. data/vendor/prism/templates/include/prism/internal/diagnostic.h.erb +60 -0
  198. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/AbstractNodeVisitor.java.erb +1 -1
  199. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Loader.java.erb +232 -133
  200. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Nodes.java.erb +62 -23
  201. data/vendor/prism/templates/javascript/src/deserialize.js.erb +153 -146
  202. data/vendor/prism/templates/javascript/src/nodes.js.erb +4 -9
  203. data/vendor/prism/templates/lib/prism/compiler.rb.erb +14 -5
  204. data/vendor/prism/templates/lib/prism/dispatcher.rb.erb +20 -12
  205. data/vendor/prism/templates/lib/prism/dot_visitor.rb.erb +37 -11
  206. data/vendor/prism/templates/lib/prism/dsl.rb.erb +43 -4
  207. data/vendor/prism/templates/lib/prism/inspect_visitor.rb.erb +26 -10
  208. data/vendor/prism/templates/lib/prism/mutation_compiler.rb.erb +5 -2
  209. data/vendor/prism/templates/lib/prism/node.rb.erb +283 -105
  210. data/vendor/prism/templates/lib/prism/reflection.rb.erb +11 -2
  211. data/vendor/prism/templates/lib/prism/serialize.rb.erb +291 -134
  212. data/vendor/prism/templates/lib/prism/visitor.rb.erb +18 -0
  213. data/vendor/prism/templates/src/diagnostic.c.erb +114 -63
  214. data/vendor/prism/templates/src/json.c.erb +130 -0
  215. data/vendor/prism/templates/src/node.c.erb +79 -236
  216. data/vendor/prism/templates/src/prettyprint.c.erb +24 -13
  217. data/vendor/prism/templates/src/serialize.c.erb +369 -183
  218. data/vendor/prism/templates/src/{token_type.c.erb → tokens.c.erb} +22 -11
  219. data/vendor/prism/templates/template.rb +193 -91
  220. metadata +100 -42
  221. data/vendor/prism/include/prism/defines.h +0 -260
  222. data/vendor/prism/include/prism/pack.h +0 -163
  223. data/vendor/prism/include/prism/regexp.h +0 -43
  224. data/vendor/prism/include/prism/static_literals.h +0 -121
  225. data/vendor/prism/include/prism/util/pm_buffer.h +0 -236
  226. data/vendor/prism/include/prism/util/pm_char.h +0 -204
  227. data/vendor/prism/include/prism/util/pm_constant_pool.h +0 -218
  228. data/vendor/prism/include/prism/util/pm_integer.h +0 -130
  229. data/vendor/prism/include/prism/util/pm_memchr.h +0 -29
  230. data/vendor/prism/include/prism/util/pm_newline_list.h +0 -113
  231. data/vendor/prism/include/prism/util/pm_string.h +0 -200
  232. data/vendor/prism/src/pack.c +0 -509
  233. data/vendor/prism/src/util/pm_constant_pool.c +0 -342
  234. data/vendor/prism/src/util/pm_list.c +0 -49
  235. data/vendor/prism/src/util/pm_newline_list.c +0 -125
  236. data/vendor/prism/src/util/pm_string.c +0 -381
  237. data/vendor/prism/src/util/pm_strpbrk.c +0 -206
  238. data/vendor/prism/templates/include/prism/diagnostic.h.erb +0 -130
  239. data/vendor/prism/templates/rbi/prism/dsl.rbi.erb +0 -68
  240. data/vendor/prism/templates/rbi/prism/node.rbi.erb +0 -167
  241. data/vendor/prism/templates/rbi/prism/visitor.rbi.erb +0 -18
  242. data/vendor/prism/templates/sig/prism/_private/dot_visitor.rbs.erb +0 -45
  243. data/vendor/prism/templates/sig/prism/dsl.rbs.erb +0 -31
  244. data/vendor/prism/templates/sig/prism/mutation_compiler.rbs.erb +0 -7
  245. data/vendor/prism/templates/sig/prism/node.rbs.erb +0 -136
  246. data/vendor/prism/templates/sig/prism/visitor.rbs.erb +0 -17
  247. data/vendor/prism/templates/sig/prism.rbs.erb +0 -92
@@ -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,30 @@
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 sys/types.h before inttypes.h to work around an issue with certain
7
+ * versions of GCC and newlib which causes omission of PRIu64 and the other
8
+ * 64-bit macros. */
9
+ #include <sys/types.h>
10
+ #include <inttypes.h>
11
+
12
+ #include <assert.h>
13
+ #include <stdbool.h>
14
+ #include <stddef.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+
18
+ /**
19
+ * Free the internal memory of an integer. This memory will only be allocated if
20
+ * the integer exceeds the size of a single uint32_t.
21
+ */
22
+ static void
23
+ pm_integer_free(pm_integer_t *integer) {
24
+ if (integer->values) {
25
+ xfree(integer->values);
26
+ }
27
+ }
2
28
 
3
29
  /**
4
30
  * Pull out the length and values from the integer, regardless of the form in
@@ -374,7 +400,7 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u
374
400
  }
375
401
  }
376
402
 
377
- xfree(bigints);
403
+ xfree_sized(bigints, bigints_length * sizeof(pm_integer_t));
378
404
  bigints = next_bigints;
379
405
  bigints_length = next_length;
380
406
  }
@@ -383,7 +409,7 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u
383
409
  destination->negative = source->negative;
384
410
  pm_integer_normalize(destination);
385
411
 
386
- xfree(bigints);
412
+ xfree_sized(bigints, bigints_length * sizeof(pm_integer_t));
387
413
  pm_integer_free(&base);
388
414
  }
389
415
 
@@ -422,7 +448,7 @@ pm_integer_parse_powof2(pm_integer_t *integer, uint32_t base, const uint8_t *dig
422
448
  static void
423
449
  pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t digits_length) {
424
450
  const size_t batch = 9;
425
- size_t length = (digits_length + batch - 1) / batch;
451
+ const size_t length = (digits_length + batch - 1) / batch;
426
452
 
427
453
  uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t));
428
454
  uint32_t value = 0;
@@ -439,7 +465,7 @@ pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t di
439
465
 
440
466
  // Convert base from 10**9 to 1<<32.
441
467
  pm_integer_convert_base(integer, &((pm_integer_t) { .length = length, .values = values, .value = 0, .negative = false }), 1000000000, ((uint64_t) 1 << 32));
442
- xfree(values);
468
+ xfree_sized(values, length * sizeof(uint32_t));
443
469
  }
444
470
 
445
471
  /**
@@ -448,7 +474,8 @@ pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t di
448
474
  static void
449
475
  pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *start, const uint8_t *end) {
450
476
  // Allocate an array to store digits.
451
- uint8_t *digits = xmalloc(sizeof(uint8_t) * (size_t) (end - start));
477
+ const size_t digits_capa = sizeof(uint8_t) * (size_t) (end - start);
478
+ uint8_t *digits = xmalloc(digits_capa);
452
479
  size_t digits_length = 0;
453
480
 
454
481
  for (; start < end; start++) {
@@ -463,7 +490,7 @@ pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *
463
490
  pm_integer_parse_powof2(integer, multiplier, digits, digits_length);
464
491
  }
465
492
 
466
- xfree(digits);
493
+ xfree_sized(digits, digits_capa);
467
494
  }
468
495
 
469
496
  /**
@@ -481,7 +508,7 @@ pm_integer_parse(pm_integer_t *integer, pm_integer_base_t base, const uint8_t *s
481
508
  uint32_t multiplier = 10;
482
509
  switch (base) {
483
510
  case PM_INTEGER_BASE_DEFAULT:
484
- while (*start == '0') start++; // 01 -> 1
511
+ while (start < end && *start == '0') start++; // 01 -> 1
485
512
  break;
486
513
  case PM_INTEGER_BASE_BINARY:
487
514
  start += 2; // 0b
@@ -603,7 +630,7 @@ void pm_integers_reduce(pm_integer_t *numerator, pm_integer_t *denominator) {
603
630
  /**
604
631
  * Convert an integer to a decimal string.
605
632
  */
606
- PRISM_EXPORTED_FUNCTION void
633
+ void
607
634
  pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
608
635
  if (integer->negative) {
609
636
  pm_buffer_append_byte(buffer, '-');
@@ -635,7 +662,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
635
662
  }
636
663
 
637
664
  // Allocate a buffer that we'll copy the decimal digits into.
638
- size_t digits_length = converted.length * 9;
665
+ const size_t digits_length = converted.length * 9;
639
666
  char *digits = xcalloc(digits_length, sizeof(char));
640
667
  if (digits == NULL) return;
641
668
 
@@ -654,17 +681,6 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
654
681
 
655
682
  // Finally, append the string to the buffer and free the digits.
656
683
  pm_buffer_append_string(buffer, digits + start_offset, digits_length - start_offset);
657
- xfree(digits);
684
+ xfree_sized(digits, sizeof(char) * digits_length);
658
685
  pm_integer_free(&converted);
659
686
  }
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
- }