prism 2.0.0-aarch64-mingw-ucrt

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 (232) hide show
  1. checksums.yaml +7 -0
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +881 -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 +244 -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 +1768 -0
  32. data/ext/prism/extension.h +19 -0
  33. data/include/prism/arena.h +45 -0
  34. data/include/prism/arena.hpp +42 -0
  35. data/include/prism/ast.h +10381 -0
  36. data/include/prism/ast.hpp +641 -0
  37. data/include/prism/buffer.h +60 -0
  38. data/include/prism/buffer.hpp +55 -0
  39. data/include/prism/comments.h +72 -0
  40. data/include/prism/compiler/accel.h +26 -0
  41. data/include/prism/compiler/align.h +36 -0
  42. data/include/prism/compiler/assume.h +25 -0
  43. data/include/prism/compiler/branch.h +26 -0
  44. data/include/prism/compiler/bswap.h +46 -0
  45. data/include/prism/compiler/cplusplus.hpp +16 -0
  46. data/include/prism/compiler/endianness.h +34 -0
  47. data/include/prism/compiler/exceptions.hpp +17 -0
  48. data/include/prism/compiler/exported.h +24 -0
  49. data/include/prism/compiler/fallthrough.h +22 -0
  50. data/include/prism/compiler/filesystem.h +32 -0
  51. data/include/prism/compiler/flex_array.h +19 -0
  52. data/include/prism/compiler/force_inline.h +21 -0
  53. data/include/prism/compiler/format.h +27 -0
  54. data/include/prism/compiler/inline.h +17 -0
  55. data/include/prism/compiler/lifetimebound.hpp +25 -0
  56. data/include/prism/compiler/nodiscard.h +22 -0
  57. data/include/prism/compiler/nonnull.h +18 -0
  58. data/include/prism/compiler/unused.h +18 -0
  59. data/include/prism/constant_pool.h +106 -0
  60. data/include/prism/diagnostic.h +111 -0
  61. data/include/prism/errors_format.h +61 -0
  62. data/include/prism/excludes.h +29 -0
  63. data/include/prism/integer.h +41 -0
  64. data/include/prism/internal/allocator.h +68 -0
  65. data/include/prism/internal/allocator_debug.h +88 -0
  66. data/include/prism/internal/arena.h +108 -0
  67. data/include/prism/internal/bit.h +42 -0
  68. data/include/prism/internal/buffer.h +94 -0
  69. data/include/prism/internal/char.h +145 -0
  70. data/include/prism/internal/comments.h +53 -0
  71. data/include/prism/internal/constant_pool.h +152 -0
  72. data/include/prism/internal/diagnostic.h +389 -0
  73. data/include/prism/internal/encoding.h +242 -0
  74. data/include/prism/internal/integer.h +68 -0
  75. data/include/prism/internal/isinf.h +16 -0
  76. data/include/prism/internal/line_offset_list.h +34 -0
  77. data/include/prism/internal/list.h +62 -0
  78. data/include/prism/internal/magic_comments.h +23 -0
  79. data/include/prism/internal/memchr.h +15 -0
  80. data/include/prism/internal/node.h +42 -0
  81. data/include/prism/internal/options.h +232 -0
  82. data/include/prism/internal/parser.h +1004 -0
  83. data/include/prism/internal/regexp.h +26 -0
  84. data/include/prism/internal/serialize.h +39 -0
  85. data/include/prism/internal/source.h +72 -0
  86. data/include/prism/internal/static_literals.h +100 -0
  87. data/include/prism/internal/stringy.h +30 -0
  88. data/include/prism/internal/strncasecmp.h +18 -0
  89. data/include/prism/internal/strpbrk.h +33 -0
  90. data/include/prism/internal/tokens.h +11 -0
  91. data/include/prism/internal/xxh3.h +16 -0
  92. data/include/prism/json.h +40 -0
  93. data/include/prism/line_offset_list.h +69 -0
  94. data/include/prism/magic_comments.h +53 -0
  95. data/include/prism/node.h +139 -0
  96. data/include/prism/node.hpp +272 -0
  97. data/include/prism/node_type.hpp +38 -0
  98. data/include/prism/options.h +363 -0
  99. data/include/prism/options.hpp +43 -0
  100. data/include/prism/parser.h +433 -0
  101. data/include/prism/parser.hpp +274 -0
  102. data/include/prism/prettyprint.h +39 -0
  103. data/include/prism/serialize.h +123 -0
  104. data/include/prism/source.h +163 -0
  105. data/include/prism/stream.h +36 -0
  106. data/include/prism/string_query.h +82 -0
  107. data/include/prism/stringy.h +61 -0
  108. data/include/prism/stringy.hpp +31 -0
  109. data/include/prism/version.h +46 -0
  110. data/include/prism.h +123 -0
  111. data/include/prism.hpp +24 -0
  112. data/lib/prism/3.4/prism.so +0 -0
  113. data/lib/prism/4.0/prism.so +0 -0
  114. data/lib/prism/compiler.rb +815 -0
  115. data/lib/prism/desugar_compiler.rb +463 -0
  116. data/lib/prism/dispatcher.rb +2383 -0
  117. data/lib/prism/dot_visitor.rb +4848 -0
  118. data/lib/prism/dsl.rb +1369 -0
  119. data/lib/prism/ffi.rb +746 -0
  120. data/lib/prism/inspect_visitor.rb +2425 -0
  121. data/lib/prism/lex_compat.rb +953 -0
  122. data/lib/prism/mutation_compiler.rb +780 -0
  123. data/lib/prism/node.rb +33940 -0
  124. data/lib/prism/node_ext.rb +504 -0
  125. data/lib/prism/node_find.rb +215 -0
  126. data/lib/prism/parse_result/errors.rb +72 -0
  127. data/lib/prism/parse_result/newlines.rb +423 -0
  128. data/lib/prism/parse_result.rb +1236 -0
  129. data/lib/prism/pattern.rb +314 -0
  130. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  131. data/lib/prism/polyfill/byteindex.rb +13 -0
  132. data/lib/prism/polyfill/scan_byte.rb +14 -0
  133. data/lib/prism/polyfill/unpack1.rb +14 -0
  134. data/lib/prism/polyfill/warn.rb +36 -0
  135. data/lib/prism/reflection.rb +427 -0
  136. data/lib/prism/relocation.rb +567 -0
  137. data/lib/prism/serialize.rb +4589 -0
  138. data/lib/prism/string_query.rb +54 -0
  139. data/lib/prism/translation/parser/builder.rb +70 -0
  140. data/lib/prism/translation/parser/compiler.rb +2226 -0
  141. data/lib/prism/translation/parser/lexer.rb +769 -0
  142. data/lib/prism/translation/parser.rb +376 -0
  143. data/lib/prism/translation/parser_current.rb +26 -0
  144. data/lib/prism/translation/parser_versions.rb +36 -0
  145. data/lib/prism/translation/ripper/filter.rb +53 -0
  146. data/lib/prism/translation/ripper/lexer.rb +133 -0
  147. data/lib/prism/translation/ripper/sexp.rb +118 -0
  148. data/lib/prism/translation/ripper/shim.rb +7 -0
  149. data/lib/prism/translation/ripper.rb +4278 -0
  150. data/lib/prism/translation/ruby_parser.rb +1721 -0
  151. data/lib/prism/translation.rb +20 -0
  152. data/lib/prism/visitor.rb +1440 -0
  153. data/lib/prism.rb +168 -0
  154. data/prism.gemspec +251 -0
  155. data/rbi/generated/prism/compiler.rbi +489 -0
  156. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  157. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  158. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  159. data/rbi/generated/prism/dsl.rbi +751 -0
  160. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  161. data/rbi/generated/prism/lex_compat.rbi +162 -0
  162. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  163. data/rbi/generated/prism/node.rbi +15143 -0
  164. data/rbi/generated/prism/node_ext.rbi +259 -0
  165. data/rbi/generated/prism/node_find.rbi +79 -0
  166. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  167. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  168. data/rbi/generated/prism/parse_result.rbi +776 -0
  169. data/rbi/generated/prism/pattern.rbi +134 -0
  170. data/rbi/generated/prism/reflection.rbi +89 -0
  171. data/rbi/generated/prism/relocation.rbi +329 -0
  172. data/rbi/generated/prism/serialize.rbi +185 -0
  173. data/rbi/generated/prism/string_query.rbi +43 -0
  174. data/rbi/generated/prism/translation.rbi +7 -0
  175. data/rbi/generated/prism/visitor.rbi +650 -0
  176. data/rbi/generated/prism.rbi +86 -0
  177. data/rbi/prism/translation/parser.rbi +11 -0
  178. data/rbi/prism/translation/parser_versions.rbi +28 -0
  179. data/rbi/prism/translation/ripper.rbi +15 -0
  180. data/rbi/rubyvm/node_find.rbi +14 -0
  181. data/sig/generated/prism/compiler.rbs +492 -0
  182. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  183. data/sig/generated/prism/dispatcher.rbs +1292 -0
  184. data/sig/generated/prism/dot_visitor.rbs +635 -0
  185. data/sig/generated/prism/dsl.rbs +928 -0
  186. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  187. data/sig/generated/prism/lex_compat.rbs +166 -0
  188. data/sig/generated/prism/mutation_compiler.rbs +464 -0
  189. data/sig/generated/prism/node.rbs +21632 -0
  190. data/sig/generated/prism/node_ext.rbs +290 -0
  191. data/sig/generated/prism/node_find.rbs +90 -0
  192. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  193. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  194. data/sig/generated/prism/parse_result.rbs +871 -0
  195. data/sig/generated/prism/pattern.rbs +156 -0
  196. data/sig/generated/prism/reflection.rbs +101 -0
  197. data/sig/generated/prism/relocation.rbs +428 -0
  198. data/sig/generated/prism/serialize.rbs +200 -0
  199. data/sig/generated/prism/string_query.rbs +43 -0
  200. data/sig/generated/prism/translation.rbs +8 -0
  201. data/sig/generated/prism/visitor.rbs +1111 -0
  202. data/sig/generated/prism.rbs +84 -0
  203. data/sig/manifest.yaml +2 -0
  204. data/src/arena.c +117 -0
  205. data/src/buffer.c +389 -0
  206. data/src/char.c +289 -0
  207. data/src/comments.c +454 -0
  208. data/src/constant_pool.c +406 -0
  209. data/src/diagnostic.c +894 -0
  210. data/src/encoding.c +5345 -0
  211. data/src/errors_format.c +519 -0
  212. data/src/integer.c +681 -0
  213. data/src/json.c +5733 -0
  214. data/src/line_offset_list.c +100 -0
  215. data/src/list.c +24 -0
  216. data/src/memchr.c +37 -0
  217. data/src/node.c +4363 -0
  218. data/src/options.c +445 -0
  219. data/src/parser.c +371 -0
  220. data/src/prettyprint.c +9024 -0
  221. data/src/prism.c +23901 -0
  222. data/src/regexp.c +1705 -0
  223. data/src/serialize.c +2660 -0
  224. data/src/source.c +524 -0
  225. data/src/static_literals.c +685 -0
  226. data/src/string_query.c +180 -0
  227. data/src/stringy.c +61 -0
  228. data/src/strncasecmp.c +37 -0
  229. data/src/strpbrk.c +447 -0
  230. data/src/tokens.c +724 -0
  231. data/src/xxh3.c +292 -0
  232. metadata +275 -0
@@ -0,0 +1,1768 @@
1
+ #include "prism/extension.h"
2
+
3
+ #ifdef _WIN32
4
+ #include <ruby/win32.h>
5
+ #endif
6
+
7
+ #include <errno.h>
8
+
9
+ // NOTE: this file should contain only bindings. All non-trivial logic should be
10
+ // in libprism so it can be shared its the various callers.
11
+
12
+ VALUE rb_cPrism;
13
+ VALUE rb_cPrismNode;
14
+ VALUE rb_cPrismSource;
15
+ VALUE rb_cPrismToken;
16
+ VALUE rb_cPrismLocation;
17
+
18
+ VALUE rb_cPrismComment;
19
+ VALUE rb_cPrismInlineComment;
20
+ VALUE rb_cPrismEmbDocComment;
21
+ VALUE rb_cPrismMagicComment;
22
+ VALUE rb_cPrismParseError;
23
+ VALUE rb_cPrismParseWarning;
24
+ VALUE rb_cPrismResult;
25
+ VALUE rb_cPrismParseResult;
26
+ VALUE rb_cPrismLexResult;
27
+ VALUE rb_cPrismParseLexResult;
28
+ VALUE rb_cPrismStringQuery;
29
+ VALUE rb_cPrismScope;
30
+ VALUE rb_cPrismCurrentVersionError;
31
+
32
+ VALUE rb_cPrismDebugEncoding;
33
+
34
+ ID rb_id_option_attach_comments;
35
+ ID rb_id_option_command_line;
36
+ ID rb_id_option_encoding;
37
+ ID rb_id_option_filepath;
38
+ ID rb_id_option_freeze;
39
+ ID rb_id_option_frozen_string_literal;
40
+ ID rb_id_option_line;
41
+ ID rb_id_option_main_script;
42
+ ID rb_id_option_partial_script;
43
+ ID rb_id_option_raise_error;
44
+ ID rb_id_option_scopes;
45
+ ID rb_id_option_version;
46
+
47
+ ID rb_id_source_for;
48
+
49
+ ID rb_id_forwarding_positionals;
50
+ ID rb_id_forwarding_keywords;
51
+ ID rb_id_forwarding_block;
52
+ ID rb_id_forwarding_all;
53
+
54
+ ID rb_id_raise_error_plain;
55
+ ID rb_id_raise_error_style;
56
+ ID rb_id_raise_error_color;
57
+
58
+ /******************************************************************************/
59
+ /* Result struct for working with functions that may need to raise errors. */
60
+ /******************************************************************************/
61
+
62
+ typedef struct {
63
+ enum {
64
+ RESULT_OK,
65
+ RESULT_ERR
66
+ } type;
67
+ VALUE value;
68
+ } result_t;
69
+
70
+ static result_t
71
+ result_ok(VALUE value) {
72
+ return (result_t) { .type = RESULT_OK, .value = value };
73
+ }
74
+
75
+ static result_t
76
+ result_err(VALUE value) {
77
+ return (result_t) { .type = RESULT_ERR, .value = value };
78
+ }
79
+
80
+ static VALUE
81
+ result_get(result_t result) {
82
+ if (result.type == RESULT_OK) {
83
+ return result.value;
84
+ } else {
85
+ rb_exc_raise(result.value);
86
+ }
87
+ }
88
+
89
+ /******************************************************************************/
90
+ /* IO of Ruby code */
91
+ /******************************************************************************/
92
+
93
+ /**
94
+ * Check if the given VALUE is a string. If it's not a string, then raise a
95
+ * TypeError. Otherwise return the VALUE as a C string.
96
+ */
97
+ static const char *
98
+ check_string(VALUE value) {
99
+ // Check if the value is a string. If it's not, then raise a type error.
100
+ if (!RB_TYPE_P(value, T_STRING)) {
101
+ rb_raise(rb_eTypeError, "wrong argument type %" PRIsVALUE " (expected String)", rb_obj_class(value));
102
+ }
103
+
104
+ // Otherwise, return the value as a C string.
105
+ return RSTRING_PTR(value);
106
+ }
107
+
108
+ /******************************************************************************/
109
+ /* Building C options from Ruby options */
110
+ /******************************************************************************/
111
+
112
+ /**
113
+ * Build the scopes associated with the provided Ruby keyword value.
114
+ */
115
+ static void
116
+ build_options_scopes(pm_options_t *options, VALUE scopes) {
117
+ // Check if the value is an array. If it's not, then raise a type error.
118
+ if (!RB_TYPE_P(scopes, T_ARRAY)) {
119
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(scopes));
120
+ }
121
+
122
+ // Initialize the scopes array.
123
+ size_t scopes_count = RARRAY_LEN(scopes);
124
+ if (!pm_options_scopes_init(options, scopes_count)) {
125
+ rb_raise(rb_eNoMemError, "failed to allocate memory");
126
+ }
127
+
128
+ // Iterate over the scopes and add them to the options.
129
+ for (size_t scope_index = 0; scope_index < scopes_count; scope_index++) {
130
+ VALUE scope = rb_ary_entry(scopes, scope_index);
131
+
132
+ // The scope can be either an array or it can be a Prism::Scope object.
133
+ // Parse out the correct values here from either.
134
+ VALUE locals;
135
+ uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE;
136
+
137
+ if (RB_TYPE_P(scope, T_ARRAY)) {
138
+ locals = scope;
139
+ } else if (rb_obj_is_kind_of(scope, rb_cPrismScope)) {
140
+ locals = rb_ivar_get(scope, rb_intern("@locals"));
141
+ if (!RB_TYPE_P(locals, T_ARRAY)) {
142
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(locals));
143
+ }
144
+
145
+ VALUE names = rb_ivar_get(scope, rb_intern("@forwarding"));
146
+ if (!RB_TYPE_P(names, T_ARRAY)) {
147
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(names));
148
+ }
149
+
150
+ size_t names_count = RARRAY_LEN(names);
151
+ for (size_t name_index = 0; name_index < names_count; name_index++) {
152
+ VALUE name = rb_ary_entry(names, name_index);
153
+
154
+ // Check that the name is a symbol. If it's not, then raise
155
+ // a type error.
156
+ if (!RB_TYPE_P(name, T_SYMBOL)) {
157
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(name));
158
+ }
159
+
160
+ ID id = SYM2ID(name);
161
+ if (id == rb_id_forwarding_positionals) {
162
+ forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS;
163
+ } else if (id == rb_id_forwarding_keywords) {
164
+ forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS;
165
+ } else if (id == rb_id_forwarding_block) {
166
+ forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK;
167
+ } else if (id == rb_id_forwarding_all) {
168
+ forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL;
169
+ } else {
170
+ rb_raise(rb_eArgError, "invalid forwarding value: %" PRIsVALUE, name);
171
+ }
172
+ }
173
+ } else {
174
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array or Prism::Scope)", rb_obj_class(scope));
175
+ }
176
+
177
+ // Initialize the scope array.
178
+ size_t locals_count = RARRAY_LEN(locals);
179
+ pm_options_scope_t *options_scope = pm_options_scope_mut(options, scope_index);
180
+ pm_options_scope_init(options_scope, locals_count);
181
+
182
+ // Iterate over the locals and add them to the scope.
183
+ for (size_t local_index = 0; local_index < locals_count; local_index++) {
184
+ VALUE local = rb_ary_entry(locals, local_index);
185
+
186
+ // Check that the local is a symbol. If it's not, then raise a
187
+ // type error.
188
+ if (!RB_TYPE_P(local, T_SYMBOL)) {
189
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(local));
190
+ }
191
+
192
+ // Add the local to the scope.
193
+ pm_string_t *scope_local = pm_options_scope_local_mut(options_scope, local_index);
194
+ const char *name = rb_id2name(SYM2ID(local));
195
+ pm_string_init(scope_local, (const uint8_t *) name, strlen(name));
196
+ }
197
+
198
+ // Now set the forwarding options.
199
+ pm_options_scope_forwarding_set(options_scope, forwarding);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Set the version option to the syntax version of the currently running Ruby,
205
+ * or to the closest version prism supports if it does not know that one.
206
+ */
207
+ static void
208
+ options_version_set_nearest(pm_options_t *options) {
209
+ if (!pm_options_version_set(options, ruby_version, 3)) {
210
+ // Prism doesn't know this specific version. Is it lower?
211
+ if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) {
212
+ pm_options_version_set_lowest(options);
213
+ } else {
214
+ // Must be higher.
215
+ pm_options_version_set_highest(options);
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * An iterator function that is called for each key-value in the keywords hash.
222
+ */
223
+ static int
224
+ build_options_i(VALUE key, VALUE value, VALUE argument) {
225
+ pm_options_t *options = (pm_options_t *) argument;
226
+ ID key_id = SYM2ID(key);
227
+
228
+ if (key_id == rb_id_option_filepath) {
229
+ if (!NIL_P(value)) pm_options_filepath_set(options, check_string(value));
230
+ } else if (key_id == rb_id_option_encoding) {
231
+ if (!NIL_P(value)) {
232
+ if (value == Qfalse) {
233
+ pm_options_encoding_locked_set(options, true);
234
+ } else {
235
+ pm_options_encoding_set(options, rb_enc_name(rb_to_encoding(value)));
236
+ }
237
+ }
238
+ } else if (key_id == rb_id_option_line) {
239
+ if (!NIL_P(value)) pm_options_line_set(options, NUM2INT(value));
240
+ } else if (key_id == rb_id_option_frozen_string_literal) {
241
+ if (!NIL_P(value)) pm_options_frozen_string_literal_set(options, RTEST(value));
242
+ } else if (key_id == rb_id_option_version) {
243
+ if (!NIL_P(value)) {
244
+ const char *version = check_string(value);
245
+
246
+ if (RSTRING_LEN(value) == 7 && strncmp(version, "current", 7) == 0) {
247
+ if (!pm_options_version_set(options, ruby_version, 3)) {
248
+ rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, ruby_version));
249
+ }
250
+ } else if (RSTRING_LEN(value) == 7 && strncmp(version, "nearest", 7) == 0) {
251
+ options_version_set_nearest(options);
252
+ } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) {
253
+ rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value);
254
+ }
255
+ }
256
+ } else if (key_id == rb_id_option_scopes) {
257
+ if (!NIL_P(value)) build_options_scopes(options, value);
258
+ } else if (key_id == rb_id_option_command_line) {
259
+ if (!NIL_P(value)) {
260
+ const char *string = check_string(value);
261
+ uint8_t command_line = 0;
262
+
263
+ for (size_t index = 0; index < strlen(string); index++) {
264
+ switch (string[index]) {
265
+ case 'a': command_line |= PM_OPTIONS_COMMAND_LINE_A; break;
266
+ case 'e': command_line |= PM_OPTIONS_COMMAND_LINE_E; break;
267
+ case 'l': command_line |= PM_OPTIONS_COMMAND_LINE_L; break;
268
+ case 'n': command_line |= PM_OPTIONS_COMMAND_LINE_N; break;
269
+ case 'p': command_line |= PM_OPTIONS_COMMAND_LINE_P; break;
270
+ case 'x': command_line |= PM_OPTIONS_COMMAND_LINE_X; break;
271
+ default: rb_raise(rb_eArgError, "invalid command line flag: '%c'", string[index]); break;
272
+ }
273
+ }
274
+
275
+ pm_options_command_line_set(options, command_line);
276
+ }
277
+ } else if (key_id == rb_id_option_main_script) {
278
+ if (!NIL_P(value)) pm_options_main_script_set(options, RTEST(value));
279
+ } else if (key_id == rb_id_option_partial_script) {
280
+ if (!NIL_P(value)) pm_options_partial_script_set(options, RTEST(value));
281
+ } else if (key_id == rb_id_option_freeze) {
282
+ if (!NIL_P(value)) pm_options_freeze_set(options, RTEST(value));
283
+ } else if (key_id == rb_id_option_attach_comments) {
284
+ if (!NIL_P(value)) pm_options_attach_comments_set(options, RTEST(value));
285
+ } else if (key_id == rb_id_option_raise_error) {
286
+ if (!NIL_P(value)) {
287
+ if (value == Qtrue) {
288
+ /* When given true, color when $stderr is a terminal (unless
289
+ * NO_COLOR is set), bold styling when NO_COLOR is set, plain
290
+ * otherwise.
291
+ *
292
+ * rb_stderr_tty_p is not available to extensions, so instead
293
+ * we call the tty? method on $stderr, guarding against it
294
+ * having been replaced by an object that does not respond to
295
+ * tty?. */
296
+ if (rb_respond_to(rb_stderr, rb_intern_const("tty?")) && RTEST(rb_funcall(rb_stderr, rb_intern_const("tty?"), 0))) {
297
+ const char *no_color = getenv("NO_COLOR");
298
+ if (no_color == NULL || no_color[0] == '\0') {
299
+ pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_COLOR);
300
+ } else {
301
+ pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_STYLE);
302
+ }
303
+ } else {
304
+ pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_PLAIN);
305
+ }
306
+ } else {
307
+ if (!SYMBOL_P(value)) {
308
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(value));
309
+ }
310
+
311
+ ID value_id = SYM2ID(value);
312
+ if (value_id == rb_id_raise_error_plain) {
313
+ pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_PLAIN);
314
+ } else if (value_id == rb_id_raise_error_style) {
315
+ pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_STYLE);
316
+ } else if (value_id == rb_id_raise_error_color) {
317
+ pm_options_raise_error_set(options, (uint8_t) PM_ERRORS_FORMAT_COLOR);
318
+ } else {
319
+ rb_raise(rb_eArgError, "invalid raise_error value: %" PRIsVALUE, value);
320
+ }
321
+ }
322
+ }
323
+ } else {
324
+ rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, key);
325
+ }
326
+
327
+ return ST_CONTINUE;
328
+ }
329
+
330
+ /**
331
+ * We need a struct here to pass through rb_protect and it has to be a single
332
+ * value. Because the sizeof(VALUE) == sizeof(void *), we're going to pass this
333
+ * through as an opaque pointer and cast it on both sides.
334
+ */
335
+ struct build_options_data {
336
+ pm_options_t *options;
337
+ VALUE keywords;
338
+ };
339
+
340
+ /**
341
+ * Build the set of options from the given keywords. Note that this can raise a
342
+ * Ruby error if the options are not valid.
343
+ */
344
+ static VALUE
345
+ build_options(VALUE argument) {
346
+ struct build_options_data *data = (struct build_options_data *) argument;
347
+ rb_hash_foreach(data->keywords, build_options_i, (VALUE) data->options);
348
+ return Qnil;
349
+ }
350
+
351
+ /**
352
+ * Extract the options from the given keyword arguments.
353
+ */
354
+ static void
355
+ extract_options(pm_options_t *options, VALUE filepath, VALUE keywords) {
356
+ pm_options_line_set(options, 1); /* default */
357
+ options_version_set_nearest(options); /* default */
358
+
359
+ if (!NIL_P(keywords)) {
360
+ struct build_options_data data = { .options = options, .keywords = keywords };
361
+ struct build_options_data *argument = &data;
362
+
363
+ int state = 0;
364
+ rb_protect(build_options, (VALUE) argument, &state);
365
+
366
+ if (state != 0) {
367
+ pm_options_free(options);
368
+ rb_jump_tag(state);
369
+ }
370
+ }
371
+
372
+ if (!NIL_P(filepath)) {
373
+ if (!RB_TYPE_P(filepath, T_STRING)) {
374
+ pm_options_free(options);
375
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected String)", rb_obj_class(filepath));
376
+ }
377
+
378
+ pm_options_filepath_set(options, RSTRING_PTR(filepath));
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Read options for methods that look like (source, **options).
384
+ */
385
+ static VALUE
386
+ string_options(int argc, VALUE *argv, pm_options_t *options) {
387
+ VALUE string;
388
+ VALUE keywords;
389
+ rb_scan_args(argc, argv, "1:", &string, &keywords);
390
+
391
+ if (!RB_TYPE_P(string, T_STRING)) {
392
+ pm_options_free(options);
393
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected String)", rb_obj_class(string));
394
+ }
395
+
396
+ extract_options(options, Qnil, keywords);
397
+ return string;
398
+ }
399
+
400
+ /**
401
+ * Read options for methods that look like (filepath, **options).
402
+ */
403
+ static pm_source_t *
404
+ file_options(int argc, VALUE *argv, pm_options_t *options, VALUE *encoded_filepath) {
405
+ VALUE filepath;
406
+ VALUE keywords;
407
+ rb_scan_args(argc, argv, "1:", &filepath, &keywords);
408
+
409
+ int state = 0;
410
+ filepath = rb_protect(rb_get_path, filepath, &state);
411
+ if (state != 0) {
412
+ pm_options_free(options);
413
+ rb_jump_tag(state);
414
+ }
415
+
416
+ *encoded_filepath = rb_str_encode_ospath(filepath);
417
+ extract_options(options, *encoded_filepath, keywords);
418
+
419
+ const char *source = (const char *) pm_string_source(pm_options_filepath(options));
420
+ pm_source_init_result_t result;
421
+ pm_source_t *pm_src = pm_source_file_new(source, &result);
422
+
423
+ switch (result) {
424
+ case PM_SOURCE_INIT_SUCCESS:
425
+ break;
426
+ case PM_SOURCE_INIT_ERROR_GENERIC: {
427
+ pm_options_free(options);
428
+
429
+ #ifdef _WIN32
430
+ int e = rb_w32_map_errno(GetLastError());
431
+ #else
432
+ int e = errno;
433
+ #endif
434
+
435
+ rb_syserr_fail(e, source);
436
+ break;
437
+ }
438
+ case PM_SOURCE_INIT_ERROR_DIRECTORY:
439
+ pm_options_free(options);
440
+ rb_syserr_fail(EISDIR, source);
441
+ break;
442
+ default:
443
+ pm_options_free(options);
444
+ rb_raise(rb_eRuntimeError, "Unknown error (%d) initializing file: %s", result, source);
445
+ break;
446
+ }
447
+
448
+ return pm_src;
449
+ }
450
+
451
+ /**
452
+ * Check if the given parser has any errors and the raise option was set. If
453
+ * both are true, format them and return the formatted error wrapped in a
454
+ * result.
455
+ */
456
+ static result_t
457
+ check_raise_error_option(pm_parser_t *parser, const pm_options_t *options, rb_encoding *path_encoding) {
458
+ if (pm_parser_errors_size(parser) == 0) return result_ok(Qnil);
459
+
460
+ uint8_t raise_error = pm_options_raise_error(options);
461
+ if (raise_error == 0) return result_ok(Qnil);
462
+
463
+ pm_buffer_t *buffer = pm_buffer_new();
464
+ if (buffer == NULL) {
465
+ return result_err(rb_exc_new_cstr(rb_eNoMemError, "failed to allocate memory"));
466
+ }
467
+
468
+ pm_error_level_t error_level = pm_errors_format(parser, buffer, (pm_errors_format_type_t) raise_error);
469
+
470
+ /* Copy the message out of the buffer and free it before building any
471
+ * other Ruby objects so that the buffer cannot be leaked if building the
472
+ * objects raises. */
473
+ rb_encoding *message_encoding = (error_level == PM_ERROR_LEVEL_LOAD) ? rb_locale_encoding() : rb_enc_find(pm_parser_encoding_name(parser));
474
+ VALUE message = rb_enc_str_new(pm_buffer_value(buffer), (long) pm_buffer_length(buffer), message_encoding);
475
+ pm_buffer_free(buffer);
476
+
477
+ VALUE error = Qnil;
478
+ switch (error_level) {
479
+ case PM_ERROR_LEVEL_SYNTAX: {
480
+ error = rb_exc_new_str(rb_eSyntaxError, message);
481
+
482
+ rb_encoding *path_encoding_normal = path_encoding == NULL ? rb_utf8_encoding() : path_encoding;
483
+ VALUE path = rb_enc_str_new((const char *) pm_string_source(pm_options_filepath(options)), pm_string_length(pm_options_filepath(options)), path_encoding_normal);
484
+ rb_ivar_set(error, rb_intern_const("@path"), path);
485
+ break;
486
+ }
487
+ case PM_ERROR_LEVEL_ARGUMENT: {
488
+ error = rb_exc_new_str(rb_eArgError, message);
489
+ break;
490
+ }
491
+ case PM_ERROR_LEVEL_LOAD: {
492
+ error = rb_exc_new_str(rb_eLoadError, message);
493
+ rb_ivar_set(error, rb_intern_const("@path"), Qnil);
494
+ break;
495
+ }
496
+ }
497
+
498
+ return result_err(error);
499
+ }
500
+
501
+ #ifndef PRISM_EXCLUDE_SERIALIZATION
502
+
503
+ /******************************************************************************/
504
+ /* Serializing the AST */
505
+ /******************************************************************************/
506
+
507
+ /**
508
+ * Dump the AST corresponding to the given input to a string.
509
+ */
510
+ static result_t
511
+ dump_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
512
+ pm_arena_t *arena = pm_arena_new();
513
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
514
+ pm_node_t *node = pm_parse(parser);
515
+
516
+ result_t result = check_raise_error_option(parser, options, path_encoding);
517
+ if (result.type == RESULT_OK) {
518
+ pm_buffer_t *buffer = pm_buffer_new();
519
+ if (buffer) {
520
+ pm_serialize(parser, node, buffer);
521
+ result = result_ok(rb_str_new(pm_buffer_value(buffer), pm_buffer_length(buffer)));
522
+ pm_buffer_free(buffer);
523
+
524
+ if (pm_options_freeze(options)) rb_obj_freeze(result.value);
525
+ } else {
526
+ result = result_err(rb_exc_new_cstr(rb_eNoMemError, "failed to allocate memory"));
527
+ }
528
+ }
529
+
530
+ pm_parser_free(parser);
531
+ pm_arena_free(arena);
532
+
533
+ return result;
534
+ }
535
+
536
+ /**
537
+ * :markup: markdown
538
+ * call-seq:
539
+ * dump(source, **options) -> String
540
+ *
541
+ * Dump the AST corresponding to the given string to a string. For supported
542
+ * options, see Prism.parse.
543
+ */
544
+ static VALUE
545
+ dump(int argc, VALUE *argv, VALUE self) {
546
+ pm_options_t *options = pm_options_new();
547
+ VALUE string = string_options(argc, argv, options);
548
+
549
+ const uint8_t *source = (const uint8_t *) RSTRING_PTR(string);
550
+ size_t length = RSTRING_LEN(string);
551
+
552
+ #ifdef PRISM_BUILD_DEBUG
553
+ char* dup = xmalloc(length);
554
+ memcpy(dup, source, length);
555
+ source = (const uint8_t *) dup;
556
+ #endif
557
+
558
+ result_t result = dump_input(source, length, options, NULL);
559
+
560
+ #ifdef PRISM_BUILD_DEBUG
561
+ #ifdef xfree_sized
562
+ xfree_sized(dup, length);
563
+ #else
564
+ xfree(dup);
565
+ #endif
566
+ #endif
567
+
568
+ pm_options_free(options);
569
+ return result_get(result);
570
+ }
571
+
572
+ /**
573
+ * :markup: markdown
574
+ * call-seq:
575
+ * dump_file(filepath, **options) -> String
576
+ *
577
+ * Dump the AST corresponding to the given file to a string. For supported
578
+ * options, see Prism.parse.
579
+ */
580
+ static VALUE
581
+ dump_file(int argc, VALUE *argv, VALUE self) {
582
+ pm_options_t *options = pm_options_new();
583
+
584
+ VALUE encoded_filepath;
585
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
586
+
587
+ result_t result = dump_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
588
+ pm_source_free(src);
589
+ pm_options_free(options);
590
+
591
+ return result_get(result);
592
+ }
593
+
594
+ #endif
595
+
596
+ /******************************************************************************/
597
+ /* Extracting values for the parse result */
598
+ /******************************************************************************/
599
+
600
+ /**
601
+ * The same as rb_class_new_instance, but accepts an additional boolean to
602
+ * indicate whether or not the resulting class instance should be frozen.
603
+ */
604
+ static inline VALUE
605
+ rb_class_new_instance_freeze(int argc, const VALUE *argv, VALUE klass, bool freeze) {
606
+ VALUE value = rb_class_new_instance(argc, argv, klass);
607
+ if (freeze) rb_obj_freeze(value);
608
+ return value;
609
+ }
610
+
611
+ /**
612
+ * Create a new Location instance from the given parser and bounds.
613
+ */
614
+ static inline VALUE
615
+ parser_location(VALUE source, bool freeze, uint32_t start, uint32_t length) {
616
+ VALUE argv[] = { source, LONG2FIX(start), LONG2FIX(length) };
617
+ return rb_class_new_instance_freeze(3, argv, rb_cPrismLocation, freeze);
618
+ }
619
+
620
+ /**
621
+ * Create a new Location instance from the given parser and location.
622
+ */
623
+ #define PARSER_LOCATION(source, freeze, location) \
624
+ parser_location(source, freeze, location.start, location.length)
625
+
626
+ /**
627
+ * Build a new Comment instance from the given parser and comment.
628
+ */
629
+ static inline VALUE
630
+ parser_comment(VALUE source, bool freeze, const pm_comment_t *comment) {
631
+ VALUE argv[] = { PARSER_LOCATION(source, freeze, pm_comment_location(comment)) };
632
+ VALUE type = (pm_comment_type(comment) == PM_COMMENT_EMBDOC) ? rb_cPrismEmbDocComment : rb_cPrismInlineComment;
633
+ return rb_class_new_instance_freeze(1, argv, type, freeze);
634
+ }
635
+
636
+ /**
637
+ * Extract the comments out of the parser into the given array, which was
638
+ * created before the source so that the source could hold it. The caller may
639
+ * have already resized it to the number of comments, so each comment is stored
640
+ * at its index rather than pushed.
641
+ */
642
+ static void
643
+ parser_comments(const pm_parser_t *parser, VALUE comments, VALUE source, bool freeze) {
644
+ long index = 0;
645
+ for (const pm_comment_t *comment = pm_parser_comments_first(parser); comment != NULL; comment = pm_comment_next(comment)) {
646
+ rb_ary_store(comments, index++, parser_comment(source, freeze, comment));
647
+ }
648
+
649
+ if (freeze) rb_obj_freeze(comments);
650
+ }
651
+
652
+ /**
653
+ * Build a new MagicComment instance from the given parser and magic comment.
654
+ */
655
+ static inline VALUE
656
+ parser_magic_comment(VALUE source, bool freeze, const pm_magic_comment_t *magic_comment) {
657
+ pm_location_t key = pm_magic_comment_key(magic_comment);
658
+ pm_location_t value = pm_magic_comment_value(magic_comment);
659
+
660
+ VALUE key_loc = parser_location(source, freeze, key.start, key.length);
661
+ VALUE value_loc = parser_location(source, freeze, value.start, value.length);
662
+
663
+ VALUE argv[] = { key_loc, value_loc };
664
+ return rb_class_new_instance_freeze(2, argv, rb_cPrismMagicComment, freeze);
665
+ }
666
+
667
+ /**
668
+ * Extract the magic comments out of the parser into an array.
669
+ */
670
+ static VALUE
671
+ parser_magic_comments(const pm_parser_t *parser, VALUE source, bool freeze) {
672
+ VALUE magic_comments = rb_ary_new_capa(pm_parser_magic_comments_size(parser));
673
+
674
+ for (const pm_magic_comment_t *magic_comment = pm_parser_magic_comments_first(parser); magic_comment != NULL; magic_comment = pm_magic_comment_next(magic_comment)) {
675
+ rb_ary_push(magic_comments, parser_magic_comment(source, freeze, magic_comment));
676
+ }
677
+
678
+ if (freeze) rb_obj_freeze(magic_comments);
679
+ return magic_comments;
680
+ }
681
+
682
+ /**
683
+ * Extract out the data location from the parser into a Location instance if one
684
+ * exists.
685
+ */
686
+ static VALUE
687
+ parser_data_loc(const pm_parser_t *parser, VALUE source, bool freeze) {
688
+ const pm_location_t *data_loc = pm_parser_data_loc(parser);
689
+
690
+ if (data_loc->length == 0) {
691
+ return Qnil;
692
+ } else {
693
+ return parser_location(source, freeze, data_loc->start, data_loc->length);
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Build a new ParseError instance from the given error.
699
+ */
700
+ static VALUE
701
+ parser_error(const pm_diagnostic_t *diagnostic, rb_encoding *encoding, VALUE source, bool freeze) {
702
+ VALUE type = ID2SYM(rb_intern(pm_diagnostic_type(diagnostic)));
703
+ VALUE message = rb_obj_freeze(rb_enc_str_new_cstr(pm_diagnostic_message(diagnostic), encoding));
704
+ VALUE location = PARSER_LOCATION(source, freeze, pm_diagnostic_location(diagnostic));
705
+
706
+ pm_error_level_t error_level = pm_diagnostic_error_level(diagnostic);
707
+ VALUE level = Qnil;
708
+
709
+ switch (error_level) {
710
+ case PM_ERROR_LEVEL_SYNTAX:
711
+ level = ID2SYM(rb_intern("syntax"));
712
+ break;
713
+ case PM_ERROR_LEVEL_ARGUMENT:
714
+ level = ID2SYM(rb_intern("argument"));
715
+ break;
716
+ case PM_ERROR_LEVEL_LOAD:
717
+ level = ID2SYM(rb_intern("load"));
718
+ break;
719
+ default:
720
+ rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, error_level);
721
+ }
722
+
723
+ VALUE argv[] = { type, message, location, level };
724
+ return rb_class_new_instance_freeze(4, argv, rb_cPrismParseError, freeze);
725
+ }
726
+
727
+ /**
728
+ * Extract the errors out of the parser into an array.
729
+ */
730
+ static VALUE
731
+ parser_errors(const pm_parser_t *parser, rb_encoding *encoding, VALUE source, bool freeze) {
732
+ VALUE errors = rb_ary_new_capa(pm_parser_errors_size(parser));
733
+
734
+ for (const pm_diagnostic_t *error = pm_parser_errors_first(parser); error != NULL; error = pm_diagnostic_next(error)) {
735
+ rb_ary_push(errors, parser_error(error, encoding, source, freeze));
736
+ }
737
+
738
+ if (freeze) rb_obj_freeze(errors);
739
+ return errors;
740
+ }
741
+
742
+ /**
743
+ * Build a new ParseWarning instance from the given warning.
744
+ */
745
+ static VALUE
746
+ parser_warning(const pm_diagnostic_t *diagnostic, rb_encoding *encoding, VALUE source, bool freeze) {
747
+ VALUE type = ID2SYM(rb_intern(pm_diagnostic_type(diagnostic)));
748
+ VALUE message = rb_obj_freeze(rb_enc_str_new_cstr(pm_diagnostic_message(diagnostic), encoding));
749
+ VALUE location = PARSER_LOCATION(source, freeze, pm_diagnostic_location(diagnostic));
750
+
751
+ pm_warning_level_t warning_level = pm_diagnostic_warning_level(diagnostic);
752
+ VALUE level = Qnil;
753
+
754
+ switch (warning_level) {
755
+ case PM_WARNING_LEVEL_DEFAULT:
756
+ level = ID2SYM(rb_intern("default"));
757
+ break;
758
+ case PM_WARNING_LEVEL_VERBOSE:
759
+ level = ID2SYM(rb_intern("verbose"));
760
+ break;
761
+ default:
762
+ rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, warning_level);
763
+ }
764
+
765
+ VALUE argv[] = { type, message, location, level };
766
+ return rb_class_new_instance_freeze(4, argv, rb_cPrismParseWarning, freeze);
767
+ }
768
+
769
+ /**
770
+ * Extract the warnings out of the parser into an array.
771
+ */
772
+ static VALUE
773
+ parser_warnings(const pm_parser_t *parser, rb_encoding *encoding, VALUE source, bool freeze) {
774
+ VALUE warnings = rb_ary_new_capa(pm_parser_warnings_size(parser));
775
+
776
+ for (const pm_diagnostic_t *warning = pm_parser_warnings_first(parser); warning != NULL; warning = pm_diagnostic_next(warning)) {
777
+ rb_ary_push(warnings, parser_warning(warning, encoding, source, freeze));
778
+ }
779
+
780
+ if (freeze) rb_obj_freeze(warnings);
781
+ return warnings;
782
+ }
783
+
784
+ /**
785
+ * Create the arrays that hold the comments attached to each node, sized for the
786
+ * parser's comment attachments. They stay empty unless the attach_comments
787
+ * option was set.
788
+ */
789
+ static void
790
+ parser_attachments_new(const pm_parser_t *parser, VALUE *attachments, VALUE *ranges) {
791
+ uint32_t ranges_size = pm_parser_comment_ranges_size(parser);
792
+ *attachments = rb_ary_new_capa(ranges_size == 0 ? 0 : (long) pm_parser_comments_size(parser));
793
+ *ranges = rb_ary_new_capa(ranges_size == 0 ? 0 : (long) ranges_size + 1);
794
+ }
795
+
796
+ /**
797
+ * Fill in the comments attached to each node. The attachments are the Comment
798
+ * objects in the order of the parser's comment attachments, which groups them
799
+ * by node in order of node id. The ranges are indexed by node id: for each node
800
+ * that owns comments, the index at which its comments begin is stored at its
801
+ * node id and the index at which they end is stored at the following node id.
802
+ */
803
+ static void
804
+ parser_attachments(const pm_parser_t *parser, VALUE comments, VALUE attachments, VALUE ranges, bool freeze) {
805
+ uint32_t ranges_size = pm_parser_comment_ranges_size(parser);
806
+
807
+ if (ranges_size > 0) {
808
+ long size = RARRAY_LEN(comments);
809
+
810
+ for (long index = 0; index < size; index++) {
811
+ rb_ary_store(attachments, index, RARRAY_AREF(comments, pm_parser_comment_attachment(parser, (uint32_t) index)));
812
+ }
813
+
814
+ for (uint32_t node_id = 0; node_id < ranges_size; node_id++) {
815
+ pm_comment_range_t range = pm_parser_comment_range(parser, node_id);
816
+
817
+ if (range.length > 0) {
818
+ rb_ary_store(ranges, (long) node_id, LONG2FIX(range.index));
819
+ rb_ary_store(ranges, (long) node_id + 1, LONG2FIX(range.index + range.length));
820
+ }
821
+ }
822
+ }
823
+
824
+ if (freeze) {
825
+ rb_obj_freeze(attachments);
826
+ rb_obj_freeze(ranges);
827
+ }
828
+ }
829
+
830
+ /**
831
+ * Create a new parse result from the given parser, value, encoding, and source.
832
+ */
833
+ static VALUE
834
+ parse_result_create(VALUE class, const pm_parser_t *parser, VALUE value, rb_encoding *encoding, VALUE source, bool freeze) {
835
+ VALUE result_argv[] = {
836
+ value,
837
+ parser_magic_comments(parser, source, freeze),
838
+ parser_data_loc(parser, source, freeze),
839
+ parser_errors(parser, encoding, source, freeze),
840
+ parser_warnings(parser, encoding, source, freeze),
841
+ pm_parser_continuable(parser) ? Qtrue : Qfalse,
842
+ source
843
+ };
844
+
845
+ return rb_class_new_instance_freeze(7, result_argv, class, freeze);
846
+ }
847
+
848
+ /******************************************************************************/
849
+ /* Lexing Ruby code */
850
+ /******************************************************************************/
851
+
852
+ /**
853
+ * This struct gets stored in the parser and passed in to the lex callback any
854
+ * time a new token is found. We use it to store the necessary information to
855
+ * initialize a Token instance.
856
+ */
857
+ typedef struct {
858
+ VALUE source;
859
+ VALUE tokens;
860
+ rb_encoding *encoding;
861
+ bool freeze;
862
+ } parse_lex_data_t;
863
+
864
+ /**
865
+ * This is passed as a callback to the parser. It gets called every time a new
866
+ * token is found. Once found, we initialize a new instance of Token and push it
867
+ * onto the tokens array.
868
+ */
869
+ static void
870
+ parse_lex_token(pm_parser_t *parser, pm_token_t *token, void *data) {
871
+ parse_lex_data_t *parse_lex_data = (parse_lex_data_t *) data;
872
+
873
+ VALUE value = pm_token_new(parser, token, parse_lex_data->encoding, parse_lex_data->source, parse_lex_data->freeze);
874
+ rb_ary_push(parse_lex_data->tokens, value);
875
+ }
876
+
877
+ /**
878
+ * This is called whenever the encoding changes based on the magic comment at
879
+ * the top of the file. We use it to update the encoding that we are using to
880
+ * create tokens.
881
+ */
882
+ static void
883
+ parse_lex_encoding_changed_callback(pm_parser_t *parser) {
884
+ parse_lex_data_t *parse_lex_data = (parse_lex_data_t *) pm_parser_lex_callback_data(parser);
885
+ parse_lex_data->encoding = rb_enc_find(pm_parser_encoding_name(parser));
886
+
887
+ // Since the encoding changed, we need to go back and change the encoding of
888
+ // the tokens that were already lexed. This is only going to end up being
889
+ // one or two tokens, since the encoding can only change at the top of the
890
+ // file.
891
+ VALUE tokens = parse_lex_data->tokens;
892
+ VALUE next_tokens = rb_ary_new();
893
+
894
+ for (long index = 0; index < RARRAY_LEN(tokens); index++) {
895
+ VALUE token = rb_ary_entry(tokens, index);
896
+ VALUE value = rb_ivar_get(token, rb_intern("@value"));
897
+ VALUE next_value = rb_str_dup(value);
898
+
899
+ rb_enc_associate(next_value, parse_lex_data->encoding);
900
+ if (parse_lex_data->freeze) rb_obj_freeze(next_value);
901
+
902
+ VALUE next_token_argv[] = {
903
+ parse_lex_data->source,
904
+ rb_ivar_get(token, rb_intern("@type")),
905
+ next_value,
906
+ rb_ivar_get(token, rb_intern("@location")),
907
+ rb_ivar_get(token, rb_intern("@state")),
908
+ };
909
+
910
+ VALUE next_token = rb_class_new_instance(5, next_token_argv, rb_cPrismToken);
911
+
912
+ if (parse_lex_data->freeze) {
913
+ rb_obj_freeze(next_token);
914
+ }
915
+
916
+ rb_ary_push(next_tokens, next_token);
917
+ }
918
+
919
+ rb_ary_replace(parse_lex_data->tokens, next_tokens);
920
+ }
921
+
922
+ /**
923
+ * Parse the given input and return a ParseResult containing just the tokens or
924
+ * the nodes and tokens.
925
+ */
926
+ static result_t
927
+ parse_lex_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding, bool return_nodes) {
928
+ pm_arena_t *arena = pm_arena_new();
929
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
930
+ pm_parser_encoding_changed_callback_set(parser, parse_lex_encoding_changed_callback);
931
+
932
+ VALUE source_string = rb_str_new((const char *) input, input_length);
933
+ VALUE offsets = rb_ary_new_capa(pm_parser_line_offsets(parser)->size);
934
+ VALUE comments = rb_ary_new();
935
+ VALUE attachments = rb_ary_new();
936
+ VALUE ranges = rb_ary_new();
937
+ VALUE source = rb_funcall(rb_cPrismSource, rb_id_source_for, 6, source_string, LONG2NUM(pm_parser_start_line(parser)), offsets, comments, attachments, ranges);
938
+
939
+ parse_lex_data_t parse_lex_data = {
940
+ .source = source,
941
+ .tokens = rb_ary_new(),
942
+ .encoding = rb_enc_find(pm_parser_encoding_name(parser)),
943
+ .freeze = pm_options_freeze(options),
944
+ };
945
+
946
+ parse_lex_data_t *data = &parse_lex_data;
947
+ pm_parser_lex_callback_set(parser, parse_lex_token, data);
948
+
949
+ pm_node_t *node = pm_parse(parser);
950
+
951
+ result_t result = check_raise_error_option(parser, options, path_encoding);
952
+ if (result.type == RESULT_OK) {
953
+ /* Update the Source object with the correct encoding, line offsets,
954
+ * and source hash, which are only available after pm_parse()
955
+ * completes. */
956
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
957
+ rb_enc_associate(source_string, encoding);
958
+
959
+ const pm_line_offset_list_t *line_offsets = pm_parser_line_offsets(parser);
960
+ for (size_t index = 0; index < line_offsets->size; index++) {
961
+ rb_ary_store(offsets, (long) index, ULONG2NUM(line_offsets->offsets[index]));
962
+ }
963
+
964
+ rb_funcall(source, rb_intern("replace_checksum"), 1, ULL2NUM(pm_parser_checksum(parser)));
965
+
966
+ if (pm_options_freeze(options)) {
967
+ rb_obj_freeze(source_string);
968
+ rb_obj_freeze(offsets);
969
+ rb_obj_freeze(source);
970
+ rb_obj_freeze(parse_lex_data.tokens);
971
+ }
972
+
973
+ /* The comment lists were created along with the source before parsing,
974
+ * so they are grown to their final sizes now that those are known. */
975
+ uint32_t ranges_size = pm_parser_comment_ranges_size(parser);
976
+ rb_ary_resize(comments, (long) pm_parser_comments_size(parser));
977
+
978
+ if (ranges_size > 0) {
979
+ rb_ary_resize(attachments, (long) pm_parser_comments_size(parser));
980
+ rb_ary_resize(ranges, (long) ranges_size + 1);
981
+ }
982
+
983
+ parser_comments(parser, comments, source, pm_options_freeze(options));
984
+ parser_attachments(parser, comments, attachments, ranges, pm_options_freeze(options));
985
+
986
+ if (return_nodes) {
987
+ VALUE value = rb_ary_new_capa(2);
988
+ rb_ary_push(value, pm_ast_new(parser, node, parse_lex_data.encoding, source, pm_options_freeze(options)));
989
+ rb_ary_push(value, parse_lex_data.tokens);
990
+ if (pm_options_freeze(options)) rb_obj_freeze(value);
991
+ result = result_ok(parse_result_create(rb_cPrismParseLexResult, parser, value, parse_lex_data.encoding, source, pm_options_freeze(options)));
992
+ } else {
993
+ result = result_ok(parse_result_create(rb_cPrismLexResult, parser, parse_lex_data.tokens, parse_lex_data.encoding, source, pm_options_freeze(options)));
994
+ }
995
+ }
996
+
997
+ pm_parser_free(parser);
998
+ pm_arena_free(arena);
999
+
1000
+ return result;
1001
+ }
1002
+
1003
+ /**
1004
+ * :markup: markdown
1005
+ * call-seq:
1006
+ * lex(source, **options) -> LexResult
1007
+ *
1008
+ * Return a LexResult instance that contains an array of Token instances
1009
+ * corresponding to the given string. For supported options, see Prism.parse.
1010
+ */
1011
+ static VALUE
1012
+ lex(int argc, VALUE *argv, VALUE self) {
1013
+ pm_options_t *options = pm_options_new();
1014
+ VALUE string = string_options(argc, argv, options);
1015
+
1016
+ result_t result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL, false);
1017
+ pm_options_free(options);
1018
+
1019
+ return result_get(result);
1020
+ }
1021
+
1022
+ /**
1023
+ * :markup: markdown
1024
+ * call-seq:
1025
+ * lex_file(filepath, **options) -> LexResult
1026
+ *
1027
+ * Return a LexResult instance that contains an array of Token instances
1028
+ * corresponding to the given file. For supported options, see Prism.parse.
1029
+ */
1030
+ static VALUE
1031
+ lex_file(int argc, VALUE *argv, VALUE self) {
1032
+ pm_options_t *options = pm_options_new();
1033
+
1034
+ VALUE encoded_filepath;
1035
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1036
+
1037
+ result_t result = parse_lex_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath), false);
1038
+ pm_source_free(src);
1039
+ pm_options_free(options);
1040
+
1041
+ return result_get(result);
1042
+ }
1043
+
1044
+ /******************************************************************************/
1045
+ /* Parsing Ruby code */
1046
+ /******************************************************************************/
1047
+
1048
+ /**
1049
+ * Parse the given input and return a ParseResult instance.
1050
+ */
1051
+ static result_t
1052
+ parse_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1053
+ pm_arena_t *arena = pm_arena_new();
1054
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1055
+
1056
+ pm_node_t *node = pm_parse(parser);
1057
+
1058
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1059
+ if (result.type == RESULT_OK) {
1060
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
1061
+
1062
+ bool freeze = pm_options_freeze(options);
1063
+ VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser));
1064
+ VALUE attachments, ranges;
1065
+ parser_attachments_new(parser, &attachments, &ranges);
1066
+
1067
+ VALUE source = pm_source_new(parser, encoding, comments, attachments, ranges, freeze);
1068
+ parser_comments(parser, comments, source, freeze);
1069
+ parser_attachments(parser, comments, attachments, ranges, freeze);
1070
+
1071
+ VALUE value = pm_ast_new(parser, node, encoding, source, freeze);
1072
+ result = result_ok(parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, freeze));
1073
+ }
1074
+
1075
+ pm_parser_free(parser);
1076
+ pm_arena_free(arena);
1077
+
1078
+ return result;
1079
+ }
1080
+
1081
+ /**
1082
+ * :markup: markdown
1083
+ * call-seq:
1084
+ * parse(source, **options) -> ParseResult
1085
+ *
1086
+ * Parse the given string and return a ParseResult instance. The options that
1087
+ * are supported are:
1088
+ *
1089
+ * * `attach_comments` - whether or not to attach each comment to the node that
1090
+ * owns it. When set, the comments on the result are sorted by start
1091
+ * offset. This should be a boolean or nil.
1092
+ * * `command_line` - either nil or a string of the various options that were
1093
+ * set on the command line. Valid values are combinations of "a", "l",
1094
+ * "n", "p", and "x".
1095
+ * * `encoding` - the encoding of the source being parsed. This should be an
1096
+ * encoding or nil.
1097
+ * * `filepath` - the filepath of the source being parsed. This should be a
1098
+ * string or nil.
1099
+ * * `freeze` - whether or not to deeply freeze the AST. This should be a
1100
+ * boolean or nil.
1101
+ * * `frozen_string_literal` - whether or not the frozen string literal pragma
1102
+ * has been set. This should be a boolean or nil.
1103
+ * * `line` - the line number that the parse starts on. This should be an
1104
+ * integer or nil. Note that this is 1-indexed.
1105
+ * * `main_script` - a boolean indicating whether or not the source being parsed
1106
+ * is the main script being run by the interpreter. This controls whether
1107
+ * or not shebangs are parsed for additional flags and whether or not the
1108
+ * parser will attempt to find a matching shebang if the first one does
1109
+ * not contain the word "ruby".
1110
+ * * `partial_script` - when the file being parsed is considered a "partial"
1111
+ * script, jumps will not be marked as errors if they are not contained
1112
+ * within loops/blocks. This is used in the case that you're parsing a
1113
+ * script that you know will be embedded inside another script later, but
1114
+ * you do not have that context yet. For example, when parsing an ERB
1115
+ * template that will be evaluated inside another script.
1116
+ * * `raise_error` - either nil, true, or a symbol indicating that an error
1117
+ * should be raised if the source contains any errors. The message of
1118
+ * the error will include the source of the lines that contain errors
1119
+ * when possible, and the value of this option controls how those lines
1120
+ * are formatted. Valid values are `:plain` (no formatting), `:style`
1121
+ * (bold formatting), and `:color` (bold and color formatting). When
1122
+ * `true` is given, the formatting is determined by whether or not
1123
+ * `$stderr` is a terminal and whether or not the `NO_COLOR` environment
1124
+ * variable is set, mirroring the behavior of CRuby itself. Syntax-level
1125
+ * errors raise SyntaxError, argument-level errors raise ArgumentError,
1126
+ * and load-level errors raise LoadError. Note that this option is
1127
+ * honored by every API that accepts these options, including predicates
1128
+ * like Prism.parse_success?, which will raise instead of returning a
1129
+ * boolean when the source contains errors.
1130
+ * * `scopes` - the locals that are in scope surrounding the code that is being
1131
+ * parsed. This should be an array of arrays of symbols or nil. Scopes are
1132
+ * ordered from the outermost scope to the innermost one.
1133
+ * * `version` - the version of Ruby syntax that prism should used to parse Ruby
1134
+ * code. By default prism parses with the syntax version nearest to the
1135
+ * current Ruby that is running (which you can trigger with `nil` or
1136
+ * `"nearest"`): the running version if prism supports it, otherwise the
1137
+ * closest version that it does support. You may also restrict the syntax
1138
+ * to a specific version of Ruby, e.g., with `"3.3.0"`. To parse with
1139
+ * exactly the same syntax version that the current Ruby is running use
1140
+ * `version: "current"`, which raises Prism::CurrentVersionError if prism
1141
+ * does not support it. To parse with the latest version of Ruby syntax
1142
+ * that prism supports, use `version: "latest"`. Raises ArgumentError if
1143
+ * the version is not currently supported by Prism.
1144
+ */
1145
+ static VALUE
1146
+ parse(int argc, VALUE *argv, VALUE self) {
1147
+ pm_options_t *options = pm_options_new();
1148
+ VALUE string = string_options(argc, argv, options);
1149
+
1150
+ const uint8_t *source = (const uint8_t *) RSTRING_PTR(string);
1151
+ size_t length = RSTRING_LEN(string);
1152
+
1153
+ #ifdef PRISM_BUILD_DEBUG
1154
+ char* dup = xmalloc(length);
1155
+ memcpy(dup, source, length);
1156
+ source = (const uint8_t *) dup;
1157
+ #endif
1158
+
1159
+ result_t result = parse_input(source, length, options, NULL);
1160
+
1161
+ #ifdef PRISM_BUILD_DEBUG
1162
+ #ifdef xfree_sized
1163
+ xfree_sized(dup, length);
1164
+ #else
1165
+ xfree(dup);
1166
+ #endif
1167
+ #endif
1168
+
1169
+ pm_options_free(options);
1170
+ return result_get(result);
1171
+ }
1172
+
1173
+ /**
1174
+ * :markup: markdown
1175
+ * call-seq:
1176
+ * parse_file(filepath, **options) -> ParseResult
1177
+ *
1178
+ * Parse the given file and return a ParseResult instance. For supported
1179
+ * options, see Prism.parse.
1180
+ */
1181
+ static VALUE
1182
+ parse_file(int argc, VALUE *argv, VALUE self) {
1183
+ pm_options_t *options = pm_options_new();
1184
+
1185
+ VALUE encoded_filepath;
1186
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1187
+
1188
+ result_t result = parse_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1189
+ pm_source_free(src);
1190
+ pm_options_free(options);
1191
+
1192
+ return result_get(result);
1193
+ }
1194
+
1195
+ /**
1196
+ * Parse the given input and return nothing.
1197
+ */
1198
+ static result_t
1199
+ profile_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1200
+ pm_arena_t *arena = pm_arena_new();
1201
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1202
+
1203
+ pm_parse(parser);
1204
+
1205
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1206
+ pm_parser_free(parser);
1207
+ pm_arena_free(arena);
1208
+
1209
+ return result;
1210
+ }
1211
+
1212
+ /**
1213
+ * :markup: markdown
1214
+ * call-seq:
1215
+ * profile(source, **options) -> nil
1216
+ *
1217
+ * Parse the given string and return nothing. This method is meant to allow
1218
+ * profilers to avoid the overhead of reifying the AST to Ruby. For supported
1219
+ * options, see Prism.parse.
1220
+ */
1221
+ static VALUE
1222
+ profile(int argc, VALUE *argv, VALUE self) {
1223
+ pm_options_t *options = pm_options_new();
1224
+ VALUE string = string_options(argc, argv, options);
1225
+
1226
+ result_t result = profile_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
1227
+ pm_options_free(options);
1228
+ result_get(result);
1229
+
1230
+ return Qnil;
1231
+ }
1232
+
1233
+ /**
1234
+ * :markup: markdown
1235
+ * call-seq:
1236
+ * profile_file(filepath, **options) -> nil
1237
+ *
1238
+ * Parse the given file and return nothing. This method is meant to allow
1239
+ * profilers to avoid the overhead of reifying the AST to Ruby. For supported
1240
+ * options, see Prism.parse.
1241
+ */
1242
+ static VALUE
1243
+ profile_file(int argc, VALUE *argv, VALUE self) {
1244
+ pm_options_t *options = pm_options_new();
1245
+
1246
+ VALUE encoded_filepath;
1247
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1248
+
1249
+ result_t result = profile_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1250
+ pm_source_free(src);
1251
+ pm_options_free(options);
1252
+ result_get(result);
1253
+
1254
+ return Qnil;
1255
+ }
1256
+
1257
+ static int
1258
+ parse_stream_eof(void *stream) {
1259
+ if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) {
1260
+ return 1;
1261
+ }
1262
+ return 0;
1263
+ }
1264
+
1265
+ /**
1266
+ * The largest number of bytes a single character can occupy in any encoding
1267
+ * that Ruby supports (CESU-8). `IO#gets(limit)` treats its argument as a soft
1268
+ * limit: to avoid splitting a multi-byte character it may return up to
1269
+ * `MAX_ENC_LEN - 1` more bytes than requested. Reserving `MAX_ENC_LEN` bytes of
1270
+ * headroom therefore leaves room for both that overshoot and the terminating
1271
+ * NUL byte.
1272
+ *
1273
+ * The value can be re-derived from the Ruby source tree with:
1274
+ *
1275
+ * grep -Erh "max (enc|byte) length" enc | grep -Eo '[0-9]+' | sort | tail -n1
1276
+ */
1277
+ #define MAX_ENC_LEN 6
1278
+
1279
+ /**
1280
+ * An implementation of fgets that is suitable for use with Ruby IO objects. As
1281
+ * required by pm_source_stream_fgets_t, this never writes more than `size`
1282
+ * bytes into `string` (including the terminating NUL byte).
1283
+ */
1284
+ static char *
1285
+ parse_stream_fgets(char *string, int size, void *stream) {
1286
+ RUBY_ASSERT(size > MAX_ENC_LEN);
1287
+
1288
+ /*
1289
+ * Request fewer bytes than the buffer can hold. `gets` may return more
1290
+ * bytes than requested when the limit falls in the middle of a multi-byte
1291
+ * character, and the reserved headroom guarantees the result still fits.
1292
+ */
1293
+ VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - MAX_ENC_LEN));
1294
+
1295
+ /*
1296
+ * A well-behaved stream returns a String, or nil at EOF. Coerce anything
1297
+ * else to nil so that we never treat a non-String as a byte buffer.
1298
+ */
1299
+ line = rb_check_string_type(line);
1300
+ if (NIL_P(line)) {
1301
+ return NULL;
1302
+ }
1303
+
1304
+ const char *cstr = RSTRING_PTR(line);
1305
+ long length = RSTRING_LEN(line);
1306
+
1307
+ /*
1308
+ * Defensively clamp the copy. A misbehaving `gets` may ignore the limit
1309
+ * entirely and return an arbitrarily long string; we must never write past
1310
+ * the caller's buffer. One byte is reserved for the NUL terminator.
1311
+ */
1312
+ if (length > (long) (size - 1)) {
1313
+ length = (long) (size - 1);
1314
+ }
1315
+
1316
+ memcpy(string, cstr, (size_t) length);
1317
+ string[length] = '\0';
1318
+
1319
+ return string;
1320
+ }
1321
+
1322
+ #undef MAX_ENC_LEN
1323
+
1324
+ /**
1325
+ * :markup: markdown
1326
+ * call-seq:
1327
+ * parse_stream(stream, **options) -> ParseResult
1328
+ *
1329
+ * Parse the given object that responds to `gets` and return a ParseResult
1330
+ * instance. The options that are supported are the same as Prism.parse.
1331
+ */
1332
+ static VALUE
1333
+ parse_stream(int argc, VALUE *argv, VALUE self) {
1334
+ VALUE stream;
1335
+ VALUE keywords;
1336
+ rb_scan_args(argc, argv, "1:", &stream, &keywords);
1337
+
1338
+ pm_options_t *options = pm_options_new();
1339
+ extract_options(options, Qnil, keywords);
1340
+
1341
+ pm_source_t *src = pm_source_stream_new((void *) stream, parse_stream_fgets, parse_stream_eof);
1342
+ pm_arena_t *arena = pm_arena_new();
1343
+ pm_parser_t *parser;
1344
+
1345
+ pm_node_t *node = pm_parse_stream(&parser, arena, src, options);
1346
+
1347
+ result_t result = check_raise_error_option(parser, options, NULL);
1348
+ if (result.type == RESULT_OK) {
1349
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
1350
+
1351
+ VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser));
1352
+ VALUE attachments, ranges;
1353
+ parser_attachments_new(parser, &attachments, &ranges);
1354
+
1355
+ VALUE source = pm_source_new(parser, encoding, comments, attachments, ranges, pm_options_freeze(options));
1356
+ parser_comments(parser, comments, source, pm_options_freeze(options));
1357
+ parser_attachments(parser, comments, attachments, ranges, pm_options_freeze(options));
1358
+
1359
+ VALUE value = pm_ast_new(parser, node, encoding, source, pm_options_freeze(options));
1360
+
1361
+ result = result_ok(parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, pm_options_freeze(options)));
1362
+ }
1363
+
1364
+ pm_source_free(src);
1365
+ pm_parser_free(parser);
1366
+ pm_arena_free(arena);
1367
+ pm_options_free(options);
1368
+
1369
+ return result_get(result);
1370
+ }
1371
+
1372
+ /**
1373
+ * Parse the given input and return an array of Comment objects.
1374
+ */
1375
+ static result_t
1376
+ parse_input_comments(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1377
+ pm_arena_t *arena = pm_arena_new();
1378
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1379
+
1380
+ pm_parse(parser);
1381
+
1382
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1383
+ if (result.type == RESULT_OK) {
1384
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
1385
+
1386
+ VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser));
1387
+ VALUE attachments, ranges;
1388
+ parser_attachments_new(parser, &attachments, &ranges);
1389
+
1390
+ VALUE source = pm_source_new(parser, encoding, comments, attachments, ranges, pm_options_freeze(options));
1391
+ parser_comments(parser, comments, source, pm_options_freeze(options));
1392
+ parser_attachments(parser, comments, attachments, ranges, pm_options_freeze(options));
1393
+
1394
+ result = result_ok(comments);
1395
+ }
1396
+
1397
+ pm_parser_free(parser);
1398
+ pm_arena_free(arena);
1399
+
1400
+ return result;
1401
+ }
1402
+
1403
+ /**
1404
+ * :markup: markdown
1405
+ * call-seq:
1406
+ * parse_comments(source, **options) -> Array
1407
+ *
1408
+ * Parse the given string and return an array of Comment objects. For supported
1409
+ * options, see Prism.parse.
1410
+ */
1411
+ static VALUE
1412
+ parse_comments(int argc, VALUE *argv, VALUE self) {
1413
+ pm_options_t *options = pm_options_new();
1414
+ VALUE string = string_options(argc, argv, options);
1415
+
1416
+ result_t result = parse_input_comments((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
1417
+ pm_options_free(options);
1418
+
1419
+ return result_get(result);
1420
+ }
1421
+
1422
+ /**
1423
+ * :markup: markdown
1424
+ * call-seq:
1425
+ * parse_file_comments(filepath, **options) -> Array
1426
+ *
1427
+ * Parse the given file and return an array of Comment objects. For supported
1428
+ * options, see Prism.parse.
1429
+ */
1430
+ static VALUE
1431
+ parse_file_comments(int argc, VALUE *argv, VALUE self) {
1432
+ pm_options_t *options = pm_options_new();
1433
+
1434
+ VALUE encoded_filepath;
1435
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1436
+
1437
+ result_t result = parse_input_comments(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1438
+ pm_source_free(src);
1439
+ pm_options_free(options);
1440
+
1441
+ return result_get(result);
1442
+ }
1443
+
1444
+ /**
1445
+ * :markup: markdown
1446
+ * call-seq:
1447
+ * parse_lex(source, **options) -> ParseLexResult
1448
+ *
1449
+ * Parse the given string and return a ParseLexResult instance that contains a
1450
+ * 2-element array, where the first element is the AST and the second element is
1451
+ * an array of Token instances.
1452
+ *
1453
+ * This API is only meant to be used in the case where you need both the AST and
1454
+ * the tokens. If you only need one or the other, use either Prism.parse or
1455
+ * Prism.lex.
1456
+ *
1457
+ * For supported options, see Prism.parse.
1458
+ */
1459
+ static VALUE
1460
+ parse_lex(int argc, VALUE *argv, VALUE self) {
1461
+ pm_options_t *options = pm_options_new();
1462
+ VALUE string = string_options(argc, argv, options);
1463
+
1464
+ result_t result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL, true);
1465
+ pm_options_free(options);
1466
+
1467
+ return result_get(result);
1468
+ }
1469
+
1470
+ /**
1471
+ * :markup: markdown
1472
+ * call-seq:
1473
+ * parse_lex_file(filepath, **options) -> ParseLexResult
1474
+ *
1475
+ * Parse the given file and return a ParseLexResult instance that contains a
1476
+ * 2-element array, where the first element is the AST and the second element is
1477
+ * an array of Token instances.
1478
+ *
1479
+ * This API is only meant to be used in the case where you need both the AST and
1480
+ * the tokens. If you only need one or the other, use either Prism.parse_file
1481
+ * or Prism.lex_file.
1482
+ *
1483
+ * For supported options, see Prism.parse.
1484
+ */
1485
+ static VALUE
1486
+ parse_lex_file(int argc, VALUE *argv, VALUE self) {
1487
+ pm_options_t *options = pm_options_new();
1488
+
1489
+ VALUE encoded_filepath;
1490
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1491
+
1492
+ result_t result = parse_lex_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath), true);
1493
+ pm_source_free(src);
1494
+ pm_options_free(options);
1495
+
1496
+ return result_get(result);
1497
+ }
1498
+
1499
+ /**
1500
+ * Parse the given input and return true if it parses without errors.
1501
+ */
1502
+ static result_t
1503
+ parse_input_success_p(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1504
+ pm_arena_t *arena = pm_arena_new();
1505
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1506
+ pm_parse(parser);
1507
+
1508
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1509
+ if (result.type == RESULT_OK) {
1510
+ result = result_ok(pm_parser_errors_size(parser) == 0 ? Qtrue : Qfalse);
1511
+ }
1512
+
1513
+ pm_parser_free(parser);
1514
+ pm_arena_free(arena);
1515
+
1516
+ return result;
1517
+ }
1518
+
1519
+ /**
1520
+ * :markup: markdown
1521
+ * call-seq:
1522
+ * parse_success?(source, **options) -> bool
1523
+ *
1524
+ * Parse the given string and return true if it parses without errors. For
1525
+ * supported options, see Prism.parse.
1526
+ */
1527
+ static VALUE
1528
+ parse_success_p(int argc, VALUE *argv, VALUE self) {
1529
+ pm_options_t *options = pm_options_new();
1530
+ VALUE string = string_options(argc, argv, options);
1531
+
1532
+ result_t result = parse_input_success_p((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
1533
+ pm_options_free(options);
1534
+
1535
+ return result_get(result);
1536
+ }
1537
+
1538
+ /**
1539
+ * :markup: markdown
1540
+ * call-seq:
1541
+ * parse_failure?(source, **options) -> bool
1542
+ *
1543
+ * Parse the given string and return true if it parses with errors. For
1544
+ * supported options, see Prism.parse.
1545
+ */
1546
+ static VALUE
1547
+ parse_failure_p(int argc, VALUE *argv, VALUE self) {
1548
+ return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue;
1549
+ }
1550
+
1551
+ /**
1552
+ * :markup: markdown
1553
+ * call-seq:
1554
+ * parse_file_success?(filepath, **options) -> bool
1555
+ *
1556
+ * Parse the given file and return true if it parses without errors. For
1557
+ * supported options, see Prism.parse.
1558
+ */
1559
+ static VALUE
1560
+ parse_file_success_p(int argc, VALUE *argv, VALUE self) {
1561
+ pm_options_t *options = pm_options_new();
1562
+
1563
+ VALUE encoded_filepath;
1564
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1565
+
1566
+ result_t result = parse_input_success_p(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1567
+ pm_source_free(src);
1568
+ pm_options_free(options);
1569
+
1570
+ return result_get(result);
1571
+ }
1572
+
1573
+ /**
1574
+ * :markup: markdown
1575
+ * call-seq:
1576
+ * parse_file_failure?(filepath, **options) -> bool
1577
+ *
1578
+ * Parse the given file and return true if it parses with errors. For
1579
+ * supported options, see Prism.parse.
1580
+ */
1581
+ static VALUE
1582
+ parse_file_failure_p(int argc, VALUE *argv, VALUE self) {
1583
+ return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue;
1584
+ }
1585
+
1586
+ /******************************************************************************/
1587
+ /* String query methods */
1588
+ /******************************************************************************/
1589
+
1590
+ /**
1591
+ * Process the result of a call to a string query method and return an
1592
+ * appropriate value.
1593
+ */
1594
+ static VALUE
1595
+ string_query(pm_string_query_t result) {
1596
+ switch (result) {
1597
+ case PM_STRING_QUERY_ERROR:
1598
+ rb_raise(rb_eArgError, "Invalid or non ascii-compatible encoding");
1599
+ return Qfalse;
1600
+ case PM_STRING_QUERY_FALSE:
1601
+ return Qfalse;
1602
+ case PM_STRING_QUERY_TRUE:
1603
+ return Qtrue;
1604
+ }
1605
+ return Qfalse;
1606
+ }
1607
+
1608
+ /**
1609
+ * :markup: markdown
1610
+ * call-seq:
1611
+ * local?(string) -> bool
1612
+ *
1613
+ * Returns true if the string constitutes a valid local variable name. Note that
1614
+ * this means the names that can be set through Binding#local_variable_set, not
1615
+ * necessarily the ones that can be set through a local variable assignment.
1616
+ */
1617
+ static VALUE
1618
+ string_query_local_p(VALUE self, VALUE string) {
1619
+ const uint8_t *source = (const uint8_t *) check_string(string);
1620
+ return string_query(pm_string_query_local(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1621
+ }
1622
+
1623
+ /**
1624
+ * :markup: markdown
1625
+ * call-seq:
1626
+ * constant?(string) -> bool
1627
+ *
1628
+ * Returns true if the string constitutes a valid constant name. Note that this
1629
+ * means the names that can be set through Module#const_set, not necessarily the
1630
+ * ones that can be set through a constant assignment.
1631
+ */
1632
+ static VALUE
1633
+ string_query_constant_p(VALUE self, VALUE string) {
1634
+ const uint8_t *source = (const uint8_t *) check_string(string);
1635
+ return string_query(pm_string_query_constant(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1636
+ }
1637
+
1638
+ /**
1639
+ * :markup: markdown
1640
+ * call-seq:
1641
+ * method_name?(string) -> bool
1642
+ *
1643
+ * Returns true if the string constitutes a valid method name.
1644
+ */
1645
+ static VALUE
1646
+ string_query_method_name_p(VALUE self, VALUE string) {
1647
+ const uint8_t *source = (const uint8_t *) check_string(string);
1648
+ return string_query(pm_string_query_method_name(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1649
+ }
1650
+
1651
+ /**
1652
+ * :markup: markdown
1653
+ * call-seq:
1654
+ * keyword?(string) -> bool
1655
+ *
1656
+ * Returns true if the string is a valid keyword, such as `if`, `defined?`, or
1657
+ * `__FILE__`.
1658
+ */
1659
+ static VALUE
1660
+ string_query_keyword_p(VALUE self, VALUE string) {
1661
+ const uint8_t *source = (const uint8_t *) check_string(string);
1662
+ return string_query(pm_string_query_keyword(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1663
+ }
1664
+
1665
+ /******************************************************************************/
1666
+ /* Initialization of the extension */
1667
+ /******************************************************************************/
1668
+
1669
+ /**
1670
+ * The init function that Ruby calls when loading this extension.
1671
+ */
1672
+ RUBY_FUNC_EXPORTED void
1673
+ Init_prism(void) {
1674
+ /* Make sure that the prism library version matches the expected version.
1675
+ * Otherwise something was compiled incorrectly. */
1676
+ if (strcmp(pm_version(), EXPECTED_PRISM_VERSION) != 0) {
1677
+ rb_raise(
1678
+ rb_eRuntimeError,
1679
+ "The prism library version (%s) does not match the expected version (%s)",
1680
+ pm_version(),
1681
+ EXPECTED_PRISM_VERSION
1682
+ );
1683
+ }
1684
+
1685
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
1686
+ /* Mark this extension as Ractor-safe. */
1687
+ rb_ext_ractor_safe(true);
1688
+ #endif
1689
+
1690
+ rb_cPrism = rb_define_module("Prism");
1691
+ rb_cPrismNode = rb_define_class_under(rb_cPrism, "Node", rb_cObject);
1692
+ rb_cPrismSource = rb_define_class_under(rb_cPrism, "Source", rb_cObject);
1693
+ rb_cPrismToken = rb_define_class_under(rb_cPrism, "Token", rb_cObject);
1694
+ rb_cPrismLocation = rb_define_class_under(rb_cPrism, "Location", rb_cObject);
1695
+ rb_cPrismComment = rb_define_class_under(rb_cPrism, "Comment", rb_cObject);
1696
+ rb_cPrismInlineComment = rb_define_class_under(rb_cPrism, "InlineComment", rb_cPrismComment);
1697
+ rb_cPrismEmbDocComment = rb_define_class_under(rb_cPrism, "EmbDocComment", rb_cPrismComment);
1698
+ rb_cPrismMagicComment = rb_define_class_under(rb_cPrism, "MagicComment", rb_cObject);
1699
+ rb_cPrismParseError = rb_define_class_under(rb_cPrism, "ParseError", rb_cObject);
1700
+ rb_cPrismParseWarning = rb_define_class_under(rb_cPrism, "ParseWarning", rb_cObject);
1701
+ rb_cPrismResult = rb_define_class_under(rb_cPrism, "Result", rb_cObject);
1702
+ rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cPrismResult);
1703
+ rb_cPrismLexResult = rb_define_class_under(rb_cPrism, "LexResult", rb_cPrismResult);
1704
+ rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult);
1705
+ rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject);
1706
+ rb_cPrismScope = rb_define_class_under(rb_cPrism, "Scope", rb_cObject);
1707
+
1708
+ rb_cPrismCurrentVersionError = rb_const_get(rb_cPrism, rb_intern("CurrentVersionError"));
1709
+
1710
+ /* Intern all of the IDs eagerly that we support so that we do not have to
1711
+ * do it every time we parse. */
1712
+ rb_id_option_attach_comments = rb_intern_const("attach_comments");
1713
+ rb_id_option_command_line = rb_intern_const("command_line");
1714
+ rb_id_option_encoding = rb_intern_const("encoding");
1715
+ rb_id_option_filepath = rb_intern_const("filepath");
1716
+ rb_id_option_freeze = rb_intern_const("freeze");
1717
+ rb_id_option_frozen_string_literal = rb_intern_const("frozen_string_literal");
1718
+ rb_id_option_line = rb_intern_const("line");
1719
+ rb_id_option_main_script = rb_intern_const("main_script");
1720
+ rb_id_option_partial_script = rb_intern_const("partial_script");
1721
+ rb_id_option_raise_error = rb_intern_const("raise_error");
1722
+ rb_id_option_scopes = rb_intern_const("scopes");
1723
+ rb_id_option_version = rb_intern_const("version");
1724
+
1725
+ rb_id_source_for = rb_intern("for");
1726
+
1727
+ rb_id_forwarding_positionals = rb_intern("*");
1728
+ rb_id_forwarding_keywords = rb_intern("**");
1729
+ rb_id_forwarding_block = rb_intern("&");
1730
+ rb_id_forwarding_all = rb_intern("...");
1731
+
1732
+ rb_id_raise_error_plain = rb_intern_const("plain");
1733
+ rb_id_raise_error_style = rb_intern_const("style");
1734
+ rb_id_raise_error_color = rb_intern_const("color");
1735
+
1736
+ /**
1737
+ * The version of the prism library.
1738
+ */
1739
+ rb_define_const(rb_cPrism, "VERSION", rb_str_freeze(rb_str_new_cstr(EXPECTED_PRISM_VERSION)));
1740
+
1741
+ rb_define_singleton_method(rb_cPrism, "lex", lex, -1);
1742
+ rb_define_singleton_method(rb_cPrism, "lex_file", lex_file, -1);
1743
+ rb_define_singleton_method(rb_cPrism, "parse", parse, -1);
1744
+ rb_define_singleton_method(rb_cPrism, "parse_file", parse_file, -1);
1745
+ rb_define_singleton_method(rb_cPrism, "profile", profile, -1);
1746
+ rb_define_singleton_method(rb_cPrism, "profile_file", profile_file, -1);
1747
+ rb_define_singleton_method(rb_cPrism, "parse_stream", parse_stream, -1);
1748
+ rb_define_singleton_method(rb_cPrism, "parse_comments", parse_comments, -1);
1749
+ rb_define_singleton_method(rb_cPrism, "parse_file_comments", parse_file_comments, -1);
1750
+ rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1);
1751
+ rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1);
1752
+ rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1);
1753
+ rb_define_singleton_method(rb_cPrism, "parse_failure?", parse_failure_p, -1);
1754
+ rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1);
1755
+ rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1);
1756
+
1757
+ #ifndef PRISM_EXCLUDE_SERIALIZATION
1758
+ rb_define_singleton_method(rb_cPrism, "dump", dump, -1);
1759
+ rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1);
1760
+ #endif
1761
+
1762
+ rb_define_singleton_method(rb_cPrismStringQuery, "local?", string_query_local_p, 1);
1763
+ rb_define_singleton_method(rb_cPrismStringQuery, "constant?", string_query_constant_p, 1);
1764
+ rb_define_singleton_method(rb_cPrismStringQuery, "method_name?", string_query_method_name_p, 1);
1765
+ rb_define_singleton_method(rb_cPrismStringQuery, "keyword?", string_query_keyword_p, 1);
1766
+
1767
+ Init_prism_api_node();
1768
+ }