prism 2.0.0.rc1-x86_64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +870 -0
  4. data/CODE_OF_CONDUCT.md +76 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/LICENSE.md +7 -0
  7. data/Makefile +145 -0
  8. data/README.md +81 -0
  9. data/config.yml +4752 -0
  10. data/docs/build_system.md +119 -0
  11. data/docs/configuration.md +69 -0
  12. data/docs/cruby_compilation.md +27 -0
  13. data/docs/design.md +53 -0
  14. data/docs/encoding.md +121 -0
  15. data/docs/fuzzing.md +81 -0
  16. data/docs/heredocs.md +36 -0
  17. data/docs/javascript.md +133 -0
  18. data/docs/local_variable_depth.md +229 -0
  19. data/docs/mapping.md +117 -0
  20. data/docs/parser_translation.md +24 -0
  21. data/docs/parsing_rules.md +22 -0
  22. data/docs/releasing.md +147 -0
  23. data/docs/relocation.md +32 -0
  24. data/docs/ripper_translation.md +63 -0
  25. data/docs/ruby_api.md +45 -0
  26. data/docs/ruby_parser_translation.md +19 -0
  27. data/docs/serialization.md +230 -0
  28. data/docs/testing.md +72 -0
  29. data/ext/prism/api_node.c +7005 -0
  30. data/ext/prism/extconf.rb +126 -0
  31. data/ext/prism/extension.c +1783 -0
  32. data/ext/prism/extension.h +19 -0
  33. data/include/prism/arena.h +37 -0
  34. data/include/prism/ast.h +10371 -0
  35. data/include/prism/buffer.h +52 -0
  36. data/include/prism/comments.h +55 -0
  37. data/include/prism/compiler/accel.h +24 -0
  38. data/include/prism/compiler/align.h +36 -0
  39. data/include/prism/compiler/assume.h +25 -0
  40. data/include/prism/compiler/exported.h +24 -0
  41. data/include/prism/compiler/fallthrough.h +22 -0
  42. data/include/prism/compiler/filesystem.h +32 -0
  43. data/include/prism/compiler/flex_array.h +19 -0
  44. data/include/prism/compiler/force_inline.h +21 -0
  45. data/include/prism/compiler/format.h +25 -0
  46. data/include/prism/compiler/inline.h +17 -0
  47. data/include/prism/compiler/nodiscard.h +22 -0
  48. data/include/prism/compiler/nonnull.h +18 -0
  49. data/include/prism/compiler/unused.h +18 -0
  50. data/include/prism/constant_pool.h +81 -0
  51. data/include/prism/diagnostic.h +93 -0
  52. data/include/prism/errors_format.h +53 -0
  53. data/include/prism/excludes.h +29 -0
  54. data/include/prism/integer.h +41 -0
  55. data/include/prism/internal/allocator.h +68 -0
  56. data/include/prism/internal/allocator_debug.h +88 -0
  57. data/include/prism/internal/arena.h +108 -0
  58. data/include/prism/internal/bit.h +42 -0
  59. data/include/prism/internal/buffer.h +91 -0
  60. data/include/prism/internal/char.h +145 -0
  61. data/include/prism/internal/comments.h +53 -0
  62. data/include/prism/internal/constant_pool.h +152 -0
  63. data/include/prism/internal/diagnostic.h +389 -0
  64. data/include/prism/internal/encoding.h +242 -0
  65. data/include/prism/internal/integer.h +68 -0
  66. data/include/prism/internal/isinf.h +16 -0
  67. data/include/prism/internal/line_offset_list.h +34 -0
  68. data/include/prism/internal/list.h +62 -0
  69. data/include/prism/internal/magic_comments.h +23 -0
  70. data/include/prism/internal/memchr.h +15 -0
  71. data/include/prism/internal/node.h +38 -0
  72. data/include/prism/internal/options.h +232 -0
  73. data/include/prism/internal/parser.h +984 -0
  74. data/include/prism/internal/regexp.h +26 -0
  75. data/include/prism/internal/serialize.h +39 -0
  76. data/include/prism/internal/source.h +72 -0
  77. data/include/prism/internal/static_literals.h +100 -0
  78. data/include/prism/internal/stringy.h +35 -0
  79. data/include/prism/internal/strncasecmp.h +18 -0
  80. data/include/prism/internal/strpbrk.h +33 -0
  81. data/include/prism/internal/tokens.h +11 -0
  82. data/include/prism/json.h +32 -0
  83. data/include/prism/line_offset_list.h +61 -0
  84. data/include/prism/magic_comments.h +35 -0
  85. data/include/prism/node.h +94 -0
  86. data/include/prism/options.h +354 -0
  87. data/include/prism/parser.h +404 -0
  88. data/include/prism/prettyprint.h +31 -0
  89. data/include/prism/serialize.h +115 -0
  90. data/include/prism/source.h +155 -0
  91. data/include/prism/stream.h +28 -0
  92. data/include/prism/string_query.h +63 -0
  93. data/include/prism/stringy.h +72 -0
  94. data/include/prism/version.h +38 -0
  95. data/include/prism.h +131 -0
  96. data/lib/prism/3.3/prism.so +0 -0
  97. data/lib/prism/3.4/prism.so +0 -0
  98. data/lib/prism/4.0/prism.so +0 -0
  99. data/lib/prism/compiler.rb +815 -0
  100. data/lib/prism/desugar_compiler.rb +463 -0
  101. data/lib/prism/dispatcher.rb +2383 -0
  102. data/lib/prism/dot_visitor.rb +4848 -0
  103. data/lib/prism/dsl.rb +1369 -0
  104. data/lib/prism/ffi.rb +740 -0
  105. data/lib/prism/inspect_visitor.rb +2425 -0
  106. data/lib/prism/lex_compat.rb +953 -0
  107. data/lib/prism/mutation_compiler.rb +780 -0
  108. data/lib/prism/node.rb +33204 -0
  109. data/lib/prism/node_ext.rb +504 -0
  110. data/lib/prism/node_find.rb +215 -0
  111. data/lib/prism/parse_result/errors.rb +72 -0
  112. data/lib/prism/parse_result/newlines.rb +423 -0
  113. data/lib/prism/parse_result.rb +1221 -0
  114. data/lib/prism/pattern.rb +314 -0
  115. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  116. data/lib/prism/polyfill/byteindex.rb +13 -0
  117. data/lib/prism/polyfill/scan_byte.rb +14 -0
  118. data/lib/prism/polyfill/unpack1.rb +14 -0
  119. data/lib/prism/polyfill/warn.rb +36 -0
  120. data/lib/prism/reflection.rb +427 -0
  121. data/lib/prism/relocation.rb +567 -0
  122. data/lib/prism/serialize.rb +5308 -0
  123. data/lib/prism/string_query.rb +46 -0
  124. data/lib/prism/translation/parser/builder.rb +70 -0
  125. data/lib/prism/translation/parser/compiler.rb +2226 -0
  126. data/lib/prism/translation/parser/lexer.rb +769 -0
  127. data/lib/prism/translation/parser.rb +376 -0
  128. data/lib/prism/translation/parser_current.rb +26 -0
  129. data/lib/prism/translation/parser_versions.rb +36 -0
  130. data/lib/prism/translation/ripper/filter.rb +53 -0
  131. data/lib/prism/translation/ripper/lexer.rb +133 -0
  132. data/lib/prism/translation/ripper/sexp.rb +118 -0
  133. data/lib/prism/translation/ripper/shim.rb +7 -0
  134. data/lib/prism/translation/ripper.rb +4278 -0
  135. data/lib/prism/translation/ruby_parser.rb +1721 -0
  136. data/lib/prism/translation.rb +20 -0
  137. data/lib/prism/visitor.rb +1440 -0
  138. data/lib/prism.rb +168 -0
  139. data/prism.gemspec +234 -0
  140. data/rbi/generated/prism/compiler.rbi +489 -0
  141. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  142. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  143. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  144. data/rbi/generated/prism/dsl.rbi +751 -0
  145. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  146. data/rbi/generated/prism/lex_compat.rbi +162 -0
  147. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  148. data/rbi/generated/prism/node.rbi +13681 -0
  149. data/rbi/generated/prism/node_ext.rbi +259 -0
  150. data/rbi/generated/prism/node_find.rbi +79 -0
  151. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  152. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  153. data/rbi/generated/prism/parse_result.rbi +764 -0
  154. data/rbi/generated/prism/pattern.rbi +134 -0
  155. data/rbi/generated/prism/reflection.rbi +89 -0
  156. data/rbi/generated/prism/relocation.rbi +329 -0
  157. data/rbi/generated/prism/serialize.rbi +181 -0
  158. data/rbi/generated/prism/string_query.rbi +36 -0
  159. data/rbi/generated/prism/translation.rbi +7 -0
  160. data/rbi/generated/prism/visitor.rbi +650 -0
  161. data/rbi/generated/prism.rbi +86 -0
  162. data/rbi/prism/translation/parser.rbi +11 -0
  163. data/rbi/prism/translation/parser_versions.rbi +28 -0
  164. data/rbi/prism/translation/ripper.rbi +15 -0
  165. data/rbi/rubyvm/node_find.rbi +14 -0
  166. data/sig/generated/prism/compiler.rbs +492 -0
  167. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  168. data/sig/generated/prism/dispatcher.rbs +1292 -0
  169. data/sig/generated/prism/dot_visitor.rbs +635 -0
  170. data/sig/generated/prism/dsl.rbs +928 -0
  171. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  172. data/sig/generated/prism/lex_compat.rbs +166 -0
  173. data/sig/generated/prism/mutation_compiler.rbs +464 -0
  174. data/sig/generated/prism/node.rbs +20160 -0
  175. data/sig/generated/prism/node_ext.rbs +290 -0
  176. data/sig/generated/prism/node_find.rbs +90 -0
  177. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  178. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  179. data/sig/generated/prism/parse_result.rbs +859 -0
  180. data/sig/generated/prism/pattern.rbs +156 -0
  181. data/sig/generated/prism/reflection.rbs +101 -0
  182. data/sig/generated/prism/relocation.rbs +428 -0
  183. data/sig/generated/prism/serialize.rbs +195 -0
  184. data/sig/generated/prism/string_query.rbs +36 -0
  185. data/sig/generated/prism/translation.rbs +8 -0
  186. data/sig/generated/prism/visitor.rbs +1111 -0
  187. data/sig/generated/prism.rbs +84 -0
  188. data/sig/manifest.yaml +2 -0
  189. data/src/arena.c +117 -0
  190. data/src/buffer.c +380 -0
  191. data/src/char.c +289 -0
  192. data/src/comments.c +454 -0
  193. data/src/constant_pool.c +406 -0
  194. data/src/diagnostic.c +886 -0
  195. data/src/encoding.c +5345 -0
  196. data/src/errors_format.c +519 -0
  197. data/src/integer.c +681 -0
  198. data/src/json.c +5733 -0
  199. data/src/line_offset_list.c +100 -0
  200. data/src/list.c +24 -0
  201. data/src/memchr.c +37 -0
  202. data/src/node.c +4515 -0
  203. data/src/options.c +451 -0
  204. data/src/parser.c +349 -0
  205. data/src/prettyprint.c +9024 -0
  206. data/src/prism.c +23739 -0
  207. data/src/regexp.c +1706 -0
  208. data/src/serialize.c +2309 -0
  209. data/src/source.c +518 -0
  210. data/src/static_literals.c +685 -0
  211. data/src/string_query.c +166 -0
  212. data/src/stringy.c +91 -0
  213. data/src/strncasecmp.c +37 -0
  214. data/src/strpbrk.c +447 -0
  215. data/src/tokens.c +724 -0
  216. metadata +259 -0
@@ -0,0 +1,1783 @@
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_constant_init(scope_local, 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
+ typedef struct {
637
+ VALUE comments;
638
+ VALUE source;
639
+ bool freeze;
640
+ long index;
641
+ } parser_comments_each_data_t;
642
+
643
+ static void
644
+ parser_comments_each(const pm_comment_t *comment, void *data) {
645
+ parser_comments_each_data_t *each_data = (parser_comments_each_data_t *) data;
646
+ VALUE value = parser_comment(each_data->source, each_data->freeze, comment);
647
+ rb_ary_store(each_data->comments, each_data->index++, value);
648
+ }
649
+
650
+ /**
651
+ * Extract the comments out of the parser into the given array, which was
652
+ * created before the source so that the source could hold it.
653
+ */
654
+ static void
655
+ parser_comments(const pm_parser_t *parser, VALUE comments, VALUE source, bool freeze) {
656
+ parser_comments_each_data_t each_data = { .comments = comments, .source = source, .freeze = freeze };
657
+ pm_parser_comments_each(parser, parser_comments_each, &each_data);
658
+ if (freeze) rb_obj_freeze(comments);
659
+ }
660
+
661
+ /**
662
+ * Build a new MagicComment instance from the given parser and magic comment.
663
+ */
664
+ static inline VALUE
665
+ parser_magic_comment(VALUE source, bool freeze, const pm_magic_comment_t *magic_comment) {
666
+ pm_location_t key = pm_magic_comment_key(magic_comment);
667
+ pm_location_t value = pm_magic_comment_value(magic_comment);
668
+
669
+ VALUE key_loc = parser_location(source, freeze, key.start, key.length);
670
+ VALUE value_loc = parser_location(source, freeze, value.start, value.length);
671
+
672
+ VALUE argv[] = { key_loc, value_loc };
673
+ return rb_class_new_instance_freeze(2, argv, rb_cPrismMagicComment, freeze);
674
+ }
675
+
676
+ typedef struct {
677
+ VALUE magic_comments;
678
+ VALUE source;
679
+ bool freeze;
680
+ } parser_magic_comments_each_data_t;
681
+
682
+ static void
683
+ parser_magic_comments_each(const pm_magic_comment_t *magic_comment, void *data) {
684
+ parser_magic_comments_each_data_t *each_data = (parser_magic_comments_each_data_t *) data;
685
+ VALUE value = parser_magic_comment(each_data->source, each_data->freeze, magic_comment);
686
+ rb_ary_push(each_data->magic_comments, value);
687
+ }
688
+
689
+ /**
690
+ * Extract the magic comments out of the parser into an array.
691
+ */
692
+ static VALUE
693
+ parser_magic_comments(const pm_parser_t *parser, VALUE source, bool freeze) {
694
+ VALUE magic_comments = rb_ary_new_capa(pm_parser_magic_comments_size(parser));
695
+
696
+ parser_magic_comments_each_data_t each_data = { magic_comments, source, freeze };
697
+ pm_parser_magic_comments_each(parser, parser_magic_comments_each, &each_data);
698
+
699
+ if (freeze) rb_obj_freeze(magic_comments);
700
+ return magic_comments;
701
+ }
702
+
703
+ /**
704
+ * Extract out the data location from the parser into a Location instance if one
705
+ * exists.
706
+ */
707
+ static VALUE
708
+ parser_data_loc(const pm_parser_t *parser, VALUE source, bool freeze) {
709
+ const pm_location_t *data_loc = pm_parser_data_loc(parser);
710
+
711
+ if (data_loc->length == 0) {
712
+ return Qnil;
713
+ } else {
714
+ return parser_location(source, freeze, data_loc->start, data_loc->length);
715
+ }
716
+ }
717
+
718
+ typedef struct {
719
+ VALUE errors;
720
+ rb_encoding *encoding;
721
+ VALUE source;
722
+ bool freeze;
723
+ } parser_errors_each_data_t;
724
+
725
+ static void
726
+ parser_errors_each(const pm_diagnostic_t *diagnostic, void *data) {
727
+ parser_errors_each_data_t *each_data = (parser_errors_each_data_t *) data;
728
+
729
+ VALUE type = ID2SYM(rb_intern(pm_diagnostic_type(diagnostic)));
730
+ VALUE message = rb_obj_freeze(rb_enc_str_new_cstr(pm_diagnostic_message(diagnostic), each_data->encoding));
731
+ VALUE location = PARSER_LOCATION(each_data->source, each_data->freeze, pm_diagnostic_location(diagnostic));
732
+
733
+ pm_error_level_t error_level = pm_diagnostic_error_level(diagnostic);
734
+ VALUE level = Qnil;
735
+
736
+ switch (error_level) {
737
+ case PM_ERROR_LEVEL_SYNTAX:
738
+ level = ID2SYM(rb_intern("syntax"));
739
+ break;
740
+ case PM_ERROR_LEVEL_ARGUMENT:
741
+ level = ID2SYM(rb_intern("argument"));
742
+ break;
743
+ case PM_ERROR_LEVEL_LOAD:
744
+ level = ID2SYM(rb_intern("load"));
745
+ break;
746
+ default:
747
+ rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, error_level);
748
+ }
749
+
750
+ VALUE argv[] = { type, message, location, level };
751
+ VALUE value = rb_class_new_instance_freeze(4, argv, rb_cPrismParseError, each_data->freeze);
752
+ rb_ary_push(each_data->errors, value);
753
+ }
754
+
755
+ /**
756
+ * Extract the errors out of the parser into an array.
757
+ */
758
+ static VALUE
759
+ parser_errors(const pm_parser_t *parser, rb_encoding *encoding, VALUE source, bool freeze) {
760
+ VALUE errors = rb_ary_new_capa(pm_parser_errors_size(parser));
761
+
762
+ parser_errors_each_data_t each_data = { errors, encoding, source, freeze };
763
+ pm_parser_errors_each(parser, parser_errors_each, &each_data);
764
+
765
+ if (freeze) rb_obj_freeze(errors);
766
+ return errors;
767
+ }
768
+
769
+ typedef struct {
770
+ VALUE warnings;
771
+ rb_encoding *encoding;
772
+ VALUE source;
773
+ bool freeze;
774
+ } parser_warnings_each_data_t;
775
+
776
+ static void
777
+ parser_warnings_each(const pm_diagnostic_t *diagnostic, void *data) {
778
+ parser_warnings_each_data_t *each_data = (parser_warnings_each_data_t *) data;
779
+
780
+ VALUE type = ID2SYM(rb_intern(pm_diagnostic_type(diagnostic)));
781
+ VALUE message = rb_obj_freeze(rb_enc_str_new_cstr(pm_diagnostic_message(diagnostic), each_data->encoding));
782
+ VALUE location = PARSER_LOCATION(each_data->source, each_data->freeze, pm_diagnostic_location(diagnostic));
783
+
784
+ pm_warning_level_t warning_level = pm_diagnostic_warning_level(diagnostic);
785
+ VALUE level = Qnil;
786
+
787
+ switch (warning_level) {
788
+ case PM_WARNING_LEVEL_DEFAULT:
789
+ level = ID2SYM(rb_intern("default"));
790
+ break;
791
+ case PM_WARNING_LEVEL_VERBOSE:
792
+ level = ID2SYM(rb_intern("verbose"));
793
+ break;
794
+ default:
795
+ rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, warning_level);
796
+ }
797
+
798
+ VALUE argv[] = { type, message, location, level };
799
+ VALUE value = rb_class_new_instance_freeze(4, argv, rb_cPrismParseWarning, each_data->freeze);
800
+ rb_ary_push(each_data->warnings, value);
801
+ }
802
+
803
+ /**
804
+ * Extract the warnings out of the parser into an array.
805
+ */
806
+ static VALUE
807
+ parser_warnings(const pm_parser_t *parser, rb_encoding *encoding, VALUE source, bool freeze) {
808
+ VALUE warnings = rb_ary_new_capa(pm_parser_warnings_size(parser));
809
+
810
+ parser_warnings_each_data_t each_data = { warnings, encoding, source, freeze };
811
+ pm_parser_warnings_each(parser, parser_warnings_each, &each_data);
812
+
813
+ if (freeze) rb_obj_freeze(warnings);
814
+ return warnings;
815
+ }
816
+
817
+ /**
818
+ * Create the arrays that hold the comments attached to each node, sized for the
819
+ * parser's comment attachments. They stay empty unless the attach_comments
820
+ * option was set.
821
+ */
822
+ static void
823
+ parser_attachments_new(const pm_parser_t *parser, VALUE *attachments, VALUE *ranges) {
824
+ uint32_t ranges_size = pm_parser_comment_ranges_size(parser);
825
+ *attachments = rb_ary_new_capa(ranges_size == 0 ? 0 : (long) pm_parser_comments_size(parser));
826
+ *ranges = rb_ary_new_capa(ranges_size == 0 ? 0 : (long) ranges_size + 1);
827
+ }
828
+
829
+ /**
830
+ * Fill in the comments attached to each node. The attachments are the Comment
831
+ * objects in the order of the parser's comment attachments, which groups them
832
+ * by node in order of node id. The ranges are indexed by node id: for each node
833
+ * that owns comments, the index at which its comments begin is stored at its
834
+ * node id and the index at which they end is stored at the following node id.
835
+ */
836
+ static void
837
+ parser_attachments(const pm_parser_t *parser, VALUE comments, VALUE attachments, VALUE ranges, bool freeze) {
838
+ uint32_t ranges_size = pm_parser_comment_ranges_size(parser);
839
+
840
+ if (ranges_size > 0) {
841
+ long size = RARRAY_LEN(comments);
842
+
843
+ for (long index = 0; index < size; index++) {
844
+ rb_ary_store(attachments, index, RARRAY_AREF(comments, pm_parser_comment_attachment(parser, (uint32_t) index)));
845
+ }
846
+
847
+ for (uint32_t node_id = 0; node_id < ranges_size; node_id++) {
848
+ pm_comment_range_t range = pm_parser_comment_range(parser, node_id);
849
+
850
+ if (range.length > 0) {
851
+ rb_ary_store(ranges, (long) node_id, LONG2FIX(range.index));
852
+ rb_ary_store(ranges, (long) node_id + 1, LONG2FIX(range.index + range.length));
853
+ }
854
+ }
855
+ }
856
+
857
+ if (freeze) {
858
+ rb_obj_freeze(attachments);
859
+ rb_obj_freeze(ranges);
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Create a new parse result from the given parser, value, encoding, and source.
865
+ */
866
+ static VALUE
867
+ parse_result_create(VALUE class, const pm_parser_t *parser, VALUE value, rb_encoding *encoding, VALUE source, bool freeze) {
868
+ VALUE result_argv[] = {
869
+ value,
870
+ parser_magic_comments(parser, source, freeze),
871
+ parser_data_loc(parser, source, freeze),
872
+ parser_errors(parser, encoding, source, freeze),
873
+ parser_warnings(parser, encoding, source, freeze),
874
+ pm_parser_continuable(parser) ? Qtrue : Qfalse,
875
+ source
876
+ };
877
+
878
+ return rb_class_new_instance_freeze(7, result_argv, class, freeze);
879
+ }
880
+
881
+ /******************************************************************************/
882
+ /* Lexing Ruby code */
883
+ /******************************************************************************/
884
+
885
+ /**
886
+ * This struct gets stored in the parser and passed in to the lex callback any
887
+ * time a new token is found. We use it to store the necessary information to
888
+ * initialize a Token instance.
889
+ */
890
+ typedef struct {
891
+ VALUE source;
892
+ VALUE tokens;
893
+ rb_encoding *encoding;
894
+ bool freeze;
895
+ } parse_lex_data_t;
896
+
897
+ /**
898
+ * This is passed as a callback to the parser. It gets called every time a new
899
+ * token is found. Once found, we initialize a new instance of Token and push it
900
+ * onto the tokens array.
901
+ */
902
+ static void
903
+ parse_lex_token(pm_parser_t *parser, pm_token_t *token, void *data) {
904
+ parse_lex_data_t *parse_lex_data = (parse_lex_data_t *) data;
905
+
906
+ VALUE value = pm_token_new(parser, token, parse_lex_data->encoding, parse_lex_data->source, parse_lex_data->freeze);
907
+ rb_ary_push(parse_lex_data->tokens, value);
908
+ }
909
+
910
+ /**
911
+ * This is called whenever the encoding changes based on the magic comment at
912
+ * the top of the file. We use it to update the encoding that we are using to
913
+ * create tokens.
914
+ */
915
+ static void
916
+ parse_lex_encoding_changed_callback(pm_parser_t *parser) {
917
+ parse_lex_data_t *parse_lex_data = (parse_lex_data_t *) pm_parser_lex_callback_data(parser);
918
+ parse_lex_data->encoding = rb_enc_find(pm_parser_encoding_name(parser));
919
+
920
+ // Since the encoding changed, we need to go back and change the encoding of
921
+ // the tokens that were already lexed. This is only going to end up being
922
+ // one or two tokens, since the encoding can only change at the top of the
923
+ // file.
924
+ VALUE tokens = parse_lex_data->tokens;
925
+ VALUE next_tokens = rb_ary_new();
926
+
927
+ for (long index = 0; index < RARRAY_LEN(tokens); index++) {
928
+ VALUE token = rb_ary_entry(tokens, index);
929
+ VALUE value = rb_ivar_get(token, rb_intern("@value"));
930
+ VALUE next_value = rb_str_dup(value);
931
+
932
+ rb_enc_associate(next_value, parse_lex_data->encoding);
933
+ if (parse_lex_data->freeze) rb_obj_freeze(next_value);
934
+
935
+ VALUE next_token_argv[] = {
936
+ parse_lex_data->source,
937
+ rb_ivar_get(token, rb_intern("@type")),
938
+ next_value,
939
+ rb_ivar_get(token, rb_intern("@location")),
940
+ rb_ivar_get(token, rb_intern("@state")),
941
+ };
942
+
943
+ VALUE next_token = rb_class_new_instance(5, next_token_argv, rb_cPrismToken);
944
+
945
+ if (parse_lex_data->freeze) {
946
+ rb_obj_freeze(next_token);
947
+ }
948
+
949
+ rb_ary_push(next_tokens, next_token);
950
+ }
951
+
952
+ rb_ary_replace(parse_lex_data->tokens, next_tokens);
953
+ }
954
+
955
+ /**
956
+ * Parse the given input and return a ParseResult containing just the tokens or
957
+ * the nodes and tokens.
958
+ */
959
+ static result_t
960
+ parse_lex_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding, bool return_nodes) {
961
+ pm_arena_t *arena = pm_arena_new();
962
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
963
+ pm_parser_encoding_changed_callback_set(parser, parse_lex_encoding_changed_callback);
964
+
965
+ VALUE source_string = rb_str_new((const char *) input, input_length);
966
+ VALUE offsets = rb_ary_new_capa(pm_parser_line_offsets(parser)->size);
967
+ VALUE comments = rb_ary_new();
968
+ VALUE attachments = rb_ary_new();
969
+ VALUE ranges = rb_ary_new();
970
+ VALUE source = rb_funcall(rb_cPrismSource, rb_id_source_for, 6, source_string, LONG2NUM(pm_parser_start_line(parser)), offsets, comments, attachments, ranges);
971
+
972
+ parse_lex_data_t parse_lex_data = {
973
+ .source = source,
974
+ .tokens = rb_ary_new(),
975
+ .encoding = rb_enc_find(pm_parser_encoding_name(parser)),
976
+ .freeze = pm_options_freeze(options),
977
+ };
978
+
979
+ parse_lex_data_t *data = &parse_lex_data;
980
+ pm_parser_lex_callback_set(parser, parse_lex_token, data);
981
+
982
+ pm_node_t *node = pm_parse(parser);
983
+
984
+ result_t result = check_raise_error_option(parser, options, path_encoding);
985
+ if (result.type == RESULT_OK) {
986
+ /* Update the Source object with the correct encoding and line offsets,
987
+ * which are only available after pm_parse() completes. */
988
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
989
+ rb_enc_associate(source_string, encoding);
990
+
991
+ const pm_line_offset_list_t *line_offsets = pm_parser_line_offsets(parser);
992
+ for (size_t index = 0; index < line_offsets->size; index++) {
993
+ rb_ary_store(offsets, (long) index, ULONG2NUM(line_offsets->offsets[index]));
994
+ }
995
+
996
+ if (pm_options_freeze(options)) {
997
+ rb_obj_freeze(source_string);
998
+ rb_obj_freeze(offsets);
999
+ rb_obj_freeze(source);
1000
+ rb_obj_freeze(parse_lex_data.tokens);
1001
+ }
1002
+
1003
+ /* The comment lists were created along with the source before parsing,
1004
+ * so they are grown to their final sizes now that those are known. */
1005
+ uint32_t ranges_size = pm_parser_comment_ranges_size(parser);
1006
+ rb_ary_resize(comments, (long) pm_parser_comments_size(parser));
1007
+
1008
+ if (ranges_size > 0) {
1009
+ rb_ary_resize(attachments, (long) pm_parser_comments_size(parser));
1010
+ rb_ary_resize(ranges, (long) ranges_size + 1);
1011
+ }
1012
+
1013
+ parser_comments(parser, comments, source, pm_options_freeze(options));
1014
+ parser_attachments(parser, comments, attachments, ranges, pm_options_freeze(options));
1015
+
1016
+ if (return_nodes) {
1017
+ VALUE value = rb_ary_new_capa(2);
1018
+ rb_ary_push(value, pm_ast_new(parser, node, parse_lex_data.encoding, source, pm_options_freeze(options)));
1019
+ rb_ary_push(value, parse_lex_data.tokens);
1020
+ if (pm_options_freeze(options)) rb_obj_freeze(value);
1021
+ result = result_ok(parse_result_create(rb_cPrismParseLexResult, parser, value, parse_lex_data.encoding, source, pm_options_freeze(options)));
1022
+ } else {
1023
+ result = result_ok(parse_result_create(rb_cPrismLexResult, parser, parse_lex_data.tokens, parse_lex_data.encoding, source, pm_options_freeze(options)));
1024
+ }
1025
+ }
1026
+
1027
+ pm_parser_free(parser);
1028
+ pm_arena_free(arena);
1029
+
1030
+ return result;
1031
+ }
1032
+
1033
+ /**
1034
+ * :markup: markdown
1035
+ * call-seq:
1036
+ * lex(source, **options) -> LexResult
1037
+ *
1038
+ * Return a LexResult instance that contains an array of Token instances
1039
+ * corresponding to the given string. For supported options, see Prism.parse.
1040
+ */
1041
+ static VALUE
1042
+ lex(int argc, VALUE *argv, VALUE self) {
1043
+ pm_options_t *options = pm_options_new();
1044
+ VALUE string = string_options(argc, argv, options);
1045
+
1046
+ result_t result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL, false);
1047
+ pm_options_free(options);
1048
+
1049
+ return result_get(result);
1050
+ }
1051
+
1052
+ /**
1053
+ * :markup: markdown
1054
+ * call-seq:
1055
+ * lex_file(filepath, **options) -> LexResult
1056
+ *
1057
+ * Return a LexResult instance that contains an array of Token instances
1058
+ * corresponding to the given file. For supported options, see Prism.parse.
1059
+ */
1060
+ static VALUE
1061
+ lex_file(int argc, VALUE *argv, VALUE self) {
1062
+ pm_options_t *options = pm_options_new();
1063
+
1064
+ VALUE encoded_filepath;
1065
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1066
+
1067
+ result_t result = parse_lex_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath), false);
1068
+ pm_source_free(src);
1069
+ pm_options_free(options);
1070
+
1071
+ return result_get(result);
1072
+ }
1073
+
1074
+ /******************************************************************************/
1075
+ /* Parsing Ruby code */
1076
+ /******************************************************************************/
1077
+
1078
+ /**
1079
+ * Parse the given input and return a ParseResult instance.
1080
+ */
1081
+ static result_t
1082
+ parse_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1083
+ pm_arena_t *arena = pm_arena_new();
1084
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1085
+
1086
+ pm_node_t *node = pm_parse(parser);
1087
+
1088
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1089
+ if (result.type == RESULT_OK) {
1090
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
1091
+
1092
+ bool freeze = pm_options_freeze(options);
1093
+ VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser));
1094
+ VALUE attachments, ranges;
1095
+ parser_attachments_new(parser, &attachments, &ranges);
1096
+
1097
+ VALUE source = pm_source_new(parser, encoding, comments, attachments, ranges, freeze);
1098
+ parser_comments(parser, comments, source, freeze);
1099
+ parser_attachments(parser, comments, attachments, ranges, freeze);
1100
+
1101
+ VALUE value = pm_ast_new(parser, node, encoding, source, freeze);
1102
+ result = result_ok(parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, freeze));
1103
+ }
1104
+
1105
+ pm_parser_free(parser);
1106
+ pm_arena_free(arena);
1107
+
1108
+ return result;
1109
+ }
1110
+
1111
+ /**
1112
+ * :markup: markdown
1113
+ * call-seq:
1114
+ * parse(source, **options) -> ParseResult
1115
+ *
1116
+ * Parse the given string and return a ParseResult instance. The options that
1117
+ * are supported are:
1118
+ *
1119
+ * * `attach_comments` - whether or not to attach each comment to the node that
1120
+ * owns it. When set, the comments on the result are sorted by start
1121
+ * offset. This should be a boolean or nil.
1122
+ * * `command_line` - either nil or a string of the various options that were
1123
+ * set on the command line. Valid values are combinations of "a", "l",
1124
+ * "n", "p", and "x".
1125
+ * * `encoding` - the encoding of the source being parsed. This should be an
1126
+ * encoding or nil.
1127
+ * * `filepath` - the filepath of the source being parsed. This should be a
1128
+ * string or nil.
1129
+ * * `freeze` - whether or not to deeply freeze the AST. This should be a
1130
+ * boolean or nil.
1131
+ * * `frozen_string_literal` - whether or not the frozen string literal pragma
1132
+ * has been set. This should be a boolean or nil.
1133
+ * * `line` - the line number that the parse starts on. This should be an
1134
+ * integer or nil. Note that this is 1-indexed.
1135
+ * * `main_script` - a boolean indicating whether or not the source being parsed
1136
+ * is the main script being run by the interpreter. This controls whether
1137
+ * or not shebangs are parsed for additional flags and whether or not the
1138
+ * parser will attempt to find a matching shebang if the first one does
1139
+ * not contain the word "ruby".
1140
+ * * `partial_script` - when the file being parsed is considered a "partial"
1141
+ * script, jumps will not be marked as errors if they are not contained
1142
+ * within loops/blocks. This is used in the case that you're parsing a
1143
+ * script that you know will be embedded inside another script later, but
1144
+ * you do not have that context yet. For example, when parsing an ERB
1145
+ * template that will be evaluated inside another script.
1146
+ * * `raise_error` - either nil, true, or a symbol indicating that an error
1147
+ * should be raised if the source contains any errors. The message of
1148
+ * the error will include the source of the lines that contain errors
1149
+ * when possible, and the value of this option controls how those lines
1150
+ * are formatted. Valid values are `:plain` (no formatting), `:style`
1151
+ * (bold formatting), and `:color` (bold and color formatting). When
1152
+ * `true` is given, the formatting is determined by whether or not
1153
+ * `$stderr` is a terminal and whether or not the `NO_COLOR` environment
1154
+ * variable is set, mirroring the behavior of CRuby itself. Syntax-level
1155
+ * errors raise SyntaxError, argument-level errors raise ArgumentError,
1156
+ * and load-level errors raise LoadError. Note that this option is
1157
+ * honored by every API that accepts these options, including predicates
1158
+ * like Prism.parse_success?, which will raise instead of returning a
1159
+ * boolean when the source contains errors.
1160
+ * * `scopes` - the locals that are in scope surrounding the code that is being
1161
+ * parsed. This should be an array of arrays of symbols or nil. Scopes are
1162
+ * ordered from the outermost scope to the innermost one.
1163
+ * * `version` - the version of Ruby syntax that prism should used to parse Ruby
1164
+ * code. By default prism parses with the syntax version nearest to the
1165
+ * current Ruby that is running (which you can trigger with `nil` or
1166
+ * `"nearest"`): the running version if prism supports it, otherwise the
1167
+ * closest version that it does support. You may also restrict the syntax
1168
+ * to a specific version of Ruby, e.g., with `"3.3.0"`. To parse with
1169
+ * exactly the same syntax version that the current Ruby is running use
1170
+ * `version: "current"`, which raises Prism::CurrentVersionError if prism
1171
+ * does not support it. To parse with the latest version of Ruby syntax
1172
+ * that prism supports, use `version: "latest"`. Raises ArgumentError if
1173
+ * the version is not currently supported by Prism.
1174
+ */
1175
+ static VALUE
1176
+ parse(int argc, VALUE *argv, VALUE self) {
1177
+ pm_options_t *options = pm_options_new();
1178
+ VALUE string = string_options(argc, argv, options);
1179
+
1180
+ const uint8_t *source = (const uint8_t *) RSTRING_PTR(string);
1181
+ size_t length = RSTRING_LEN(string);
1182
+
1183
+ #ifdef PRISM_BUILD_DEBUG
1184
+ char* dup = xmalloc(length);
1185
+ memcpy(dup, source, length);
1186
+ source = (const uint8_t *) dup;
1187
+ #endif
1188
+
1189
+ result_t result = parse_input(source, length, options, NULL);
1190
+
1191
+ #ifdef PRISM_BUILD_DEBUG
1192
+ #ifdef xfree_sized
1193
+ xfree_sized(dup, length);
1194
+ #else
1195
+ xfree(dup);
1196
+ #endif
1197
+ #endif
1198
+
1199
+ pm_options_free(options);
1200
+ return result_get(result);
1201
+ }
1202
+
1203
+ /**
1204
+ * :markup: markdown
1205
+ * call-seq:
1206
+ * parse_file(filepath, **options) -> ParseResult
1207
+ *
1208
+ * Parse the given file and return a ParseResult instance. For supported
1209
+ * options, see Prism.parse.
1210
+ */
1211
+ static VALUE
1212
+ parse_file(int argc, VALUE *argv, VALUE self) {
1213
+ pm_options_t *options = pm_options_new();
1214
+
1215
+ VALUE encoded_filepath;
1216
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1217
+
1218
+ result_t result = parse_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1219
+ pm_source_free(src);
1220
+ pm_options_free(options);
1221
+
1222
+ return result_get(result);
1223
+ }
1224
+
1225
+ /**
1226
+ * Parse the given input and return nothing.
1227
+ */
1228
+ static result_t
1229
+ profile_input(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1230
+ pm_arena_t *arena = pm_arena_new();
1231
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1232
+
1233
+ pm_parse(parser);
1234
+
1235
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1236
+ pm_parser_free(parser);
1237
+ pm_arena_free(arena);
1238
+
1239
+ return result;
1240
+ }
1241
+
1242
+ /**
1243
+ * :markup: markdown
1244
+ * call-seq:
1245
+ * profile(source, **options) -> nil
1246
+ *
1247
+ * Parse the given string and return nothing. This method is meant to allow
1248
+ * profilers to avoid the overhead of reifying the AST to Ruby. For supported
1249
+ * options, see Prism.parse.
1250
+ */
1251
+ static VALUE
1252
+ profile(int argc, VALUE *argv, VALUE self) {
1253
+ pm_options_t *options = pm_options_new();
1254
+ VALUE string = string_options(argc, argv, options);
1255
+
1256
+ result_t result = profile_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
1257
+ pm_options_free(options);
1258
+ result_get(result);
1259
+
1260
+ return Qnil;
1261
+ }
1262
+
1263
+ /**
1264
+ * :markup: markdown
1265
+ * call-seq:
1266
+ * profile_file(filepath, **options) -> nil
1267
+ *
1268
+ * Parse the given file and return nothing. This method is meant to allow
1269
+ * profilers to avoid the overhead of reifying the AST to Ruby. For supported
1270
+ * options, see Prism.parse.
1271
+ */
1272
+ static VALUE
1273
+ profile_file(int argc, VALUE *argv, VALUE self) {
1274
+ pm_options_t *options = pm_options_new();
1275
+
1276
+ VALUE encoded_filepath;
1277
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1278
+
1279
+ result_t result = profile_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1280
+ pm_source_free(src);
1281
+ pm_options_free(options);
1282
+ result_get(result);
1283
+
1284
+ return Qnil;
1285
+ }
1286
+
1287
+ static int
1288
+ parse_stream_eof(void *stream) {
1289
+ if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) {
1290
+ return 1;
1291
+ }
1292
+ return 0;
1293
+ }
1294
+
1295
+ /**
1296
+ * The largest number of bytes a single character can occupy in any encoding
1297
+ * that Ruby supports (CESU-8). `IO#gets(limit)` treats its argument as a soft
1298
+ * limit: to avoid splitting a multi-byte character it may return up to
1299
+ * `MAX_ENC_LEN - 1` more bytes than requested. Reserving `MAX_ENC_LEN` bytes of
1300
+ * headroom therefore leaves room for both that overshoot and the terminating
1301
+ * NUL byte.
1302
+ *
1303
+ * The value can be re-derived from the Ruby source tree with:
1304
+ *
1305
+ * grep -Erh "max (enc|byte) length" enc | grep -Eo '[0-9]+' | sort | tail -n1
1306
+ */
1307
+ #define MAX_ENC_LEN 6
1308
+
1309
+ /**
1310
+ * An implementation of fgets that is suitable for use with Ruby IO objects. As
1311
+ * required by pm_source_stream_fgets_t, this never writes more than `size`
1312
+ * bytes into `string` (including the terminating NUL byte).
1313
+ */
1314
+ static char *
1315
+ parse_stream_fgets(char *string, int size, void *stream) {
1316
+ RUBY_ASSERT(size > MAX_ENC_LEN);
1317
+
1318
+ /*
1319
+ * Request fewer bytes than the buffer can hold. `gets` may return more
1320
+ * bytes than requested when the limit falls in the middle of a multi-byte
1321
+ * character, and the reserved headroom guarantees the result still fits.
1322
+ */
1323
+ VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - MAX_ENC_LEN));
1324
+
1325
+ /*
1326
+ * A well-behaved stream returns a String, or nil at EOF. Coerce anything
1327
+ * else to nil so that we never treat a non-String as a byte buffer.
1328
+ */
1329
+ line = rb_check_string_type(line);
1330
+ if (NIL_P(line)) {
1331
+ return NULL;
1332
+ }
1333
+
1334
+ const char *cstr = RSTRING_PTR(line);
1335
+ long length = RSTRING_LEN(line);
1336
+
1337
+ /*
1338
+ * Defensively clamp the copy. A misbehaving `gets` may ignore the limit
1339
+ * entirely and return an arbitrarily long string; we must never write past
1340
+ * the caller's buffer. One byte is reserved for the NUL terminator.
1341
+ */
1342
+ if (length > (long) (size - 1)) {
1343
+ length = (long) (size - 1);
1344
+ }
1345
+
1346
+ memcpy(string, cstr, (size_t) length);
1347
+ string[length] = '\0';
1348
+
1349
+ return string;
1350
+ }
1351
+
1352
+ #undef MAX_ENC_LEN
1353
+
1354
+ /**
1355
+ * :markup: markdown
1356
+ * call-seq:
1357
+ * parse_stream(stream, **options) -> ParseResult
1358
+ *
1359
+ * Parse the given object that responds to `gets` and return a ParseResult
1360
+ * instance. The options that are supported are the same as Prism.parse.
1361
+ */
1362
+ static VALUE
1363
+ parse_stream(int argc, VALUE *argv, VALUE self) {
1364
+ VALUE stream;
1365
+ VALUE keywords;
1366
+ rb_scan_args(argc, argv, "1:", &stream, &keywords);
1367
+
1368
+ pm_options_t *options = pm_options_new();
1369
+ extract_options(options, Qnil, keywords);
1370
+
1371
+ pm_source_t *src = pm_source_stream_new((void *) stream, parse_stream_fgets, parse_stream_eof);
1372
+ pm_arena_t *arena = pm_arena_new();
1373
+ pm_parser_t *parser;
1374
+
1375
+ pm_node_t *node = pm_parse_stream(&parser, arena, src, options);
1376
+
1377
+ result_t result = check_raise_error_option(parser, options, NULL);
1378
+ if (result.type == RESULT_OK) {
1379
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
1380
+
1381
+ VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser));
1382
+ VALUE attachments, ranges;
1383
+ parser_attachments_new(parser, &attachments, &ranges);
1384
+
1385
+ VALUE source = pm_source_new(parser, encoding, comments, attachments, ranges, pm_options_freeze(options));
1386
+ parser_comments(parser, comments, source, pm_options_freeze(options));
1387
+ parser_attachments(parser, comments, attachments, ranges, pm_options_freeze(options));
1388
+
1389
+ VALUE value = pm_ast_new(parser, node, encoding, source, pm_options_freeze(options));
1390
+
1391
+ result = result_ok(parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, pm_options_freeze(options)));
1392
+ }
1393
+
1394
+ pm_source_free(src);
1395
+ pm_parser_free(parser);
1396
+ pm_arena_free(arena);
1397
+ pm_options_free(options);
1398
+
1399
+ return result_get(result);
1400
+ }
1401
+
1402
+ /**
1403
+ * Parse the given input and return an array of Comment objects.
1404
+ */
1405
+ static result_t
1406
+ parse_input_comments(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1407
+ pm_arena_t *arena = pm_arena_new();
1408
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1409
+
1410
+ pm_parse(parser);
1411
+
1412
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1413
+ if (result.type == RESULT_OK) {
1414
+ rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser));
1415
+
1416
+ VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser));
1417
+ VALUE attachments, ranges;
1418
+ parser_attachments_new(parser, &attachments, &ranges);
1419
+
1420
+ VALUE source = pm_source_new(parser, encoding, comments, attachments, ranges, pm_options_freeze(options));
1421
+ parser_comments(parser, comments, source, pm_options_freeze(options));
1422
+ parser_attachments(parser, comments, attachments, ranges, pm_options_freeze(options));
1423
+
1424
+ result = result_ok(comments);
1425
+ }
1426
+
1427
+ pm_parser_free(parser);
1428
+ pm_arena_free(arena);
1429
+
1430
+ return result;
1431
+ }
1432
+
1433
+ /**
1434
+ * :markup: markdown
1435
+ * call-seq:
1436
+ * parse_comments(source, **options) -> Array
1437
+ *
1438
+ * Parse the given string and return an array of Comment objects. For supported
1439
+ * options, see Prism.parse.
1440
+ */
1441
+ static VALUE
1442
+ parse_comments(int argc, VALUE *argv, VALUE self) {
1443
+ pm_options_t *options = pm_options_new();
1444
+ VALUE string = string_options(argc, argv, options);
1445
+
1446
+ result_t result = parse_input_comments((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
1447
+ pm_options_free(options);
1448
+
1449
+ return result_get(result);
1450
+ }
1451
+
1452
+ /**
1453
+ * :markup: markdown
1454
+ * call-seq:
1455
+ * parse_file_comments(filepath, **options) -> Array
1456
+ *
1457
+ * Parse the given file and return an array of Comment objects. For supported
1458
+ * options, see Prism.parse.
1459
+ */
1460
+ static VALUE
1461
+ parse_file_comments(int argc, VALUE *argv, VALUE self) {
1462
+ pm_options_t *options = pm_options_new();
1463
+
1464
+ VALUE encoded_filepath;
1465
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1466
+
1467
+ result_t result = parse_input_comments(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1468
+ pm_source_free(src);
1469
+ pm_options_free(options);
1470
+
1471
+ return result_get(result);
1472
+ }
1473
+
1474
+ /**
1475
+ * :markup: markdown
1476
+ * call-seq:
1477
+ * parse_lex(source, **options) -> ParseLexResult
1478
+ *
1479
+ * Parse the given string and return a ParseLexResult instance that contains a
1480
+ * 2-element array, where the first element is the AST and the second element is
1481
+ * an array of Token instances.
1482
+ *
1483
+ * This API is only meant to be used in the case where you need both the AST and
1484
+ * the tokens. If you only need one or the other, use either Prism.parse or
1485
+ * Prism.lex.
1486
+ *
1487
+ * For supported options, see Prism.parse.
1488
+ */
1489
+ static VALUE
1490
+ parse_lex(int argc, VALUE *argv, VALUE self) {
1491
+ pm_options_t *options = pm_options_new();
1492
+ VALUE string = string_options(argc, argv, options);
1493
+
1494
+ result_t result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL, true);
1495
+ pm_options_free(options);
1496
+
1497
+ return result_get(result);
1498
+ }
1499
+
1500
+ /**
1501
+ * :markup: markdown
1502
+ * call-seq:
1503
+ * parse_lex_file(filepath, **options) -> ParseLexResult
1504
+ *
1505
+ * Parse the given file and return a ParseLexResult instance that contains a
1506
+ * 2-element array, where the first element is the AST and the second element is
1507
+ * an array of Token instances.
1508
+ *
1509
+ * This API is only meant to be used in the case where you need both the AST and
1510
+ * the tokens. If you only need one or the other, use either Prism.parse_file
1511
+ * or Prism.lex_file.
1512
+ *
1513
+ * For supported options, see Prism.parse.
1514
+ */
1515
+ static VALUE
1516
+ parse_lex_file(int argc, VALUE *argv, VALUE self) {
1517
+ pm_options_t *options = pm_options_new();
1518
+
1519
+ VALUE encoded_filepath;
1520
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1521
+
1522
+ result_t result = parse_lex_input(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath), true);
1523
+ pm_source_free(src);
1524
+ pm_options_free(options);
1525
+
1526
+ return result_get(result);
1527
+ }
1528
+
1529
+ /**
1530
+ * Parse the given input and return true if it parses without errors.
1531
+ */
1532
+ static result_t
1533
+ parse_input_success_p(const uint8_t *input, size_t input_length, const pm_options_t *options, rb_encoding *path_encoding) {
1534
+ pm_arena_t *arena = pm_arena_new();
1535
+ pm_parser_t *parser = pm_parser_new(arena, input, input_length, options);
1536
+ pm_parse(parser);
1537
+
1538
+ result_t result = check_raise_error_option(parser, options, path_encoding);
1539
+ if (result.type == RESULT_OK) {
1540
+ result = result_ok(pm_parser_errors_size(parser) == 0 ? Qtrue : Qfalse);
1541
+ }
1542
+
1543
+ pm_parser_free(parser);
1544
+ pm_arena_free(arena);
1545
+
1546
+ return result;
1547
+ }
1548
+
1549
+ /**
1550
+ * :markup: markdown
1551
+ * call-seq:
1552
+ * parse_success?(source, **options) -> bool
1553
+ *
1554
+ * Parse the given string and return true if it parses without errors. For
1555
+ * supported options, see Prism.parse.
1556
+ */
1557
+ static VALUE
1558
+ parse_success_p(int argc, VALUE *argv, VALUE self) {
1559
+ pm_options_t *options = pm_options_new();
1560
+ VALUE string = string_options(argc, argv, options);
1561
+
1562
+ result_t result = parse_input_success_p((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, NULL);
1563
+ pm_options_free(options);
1564
+
1565
+ return result_get(result);
1566
+ }
1567
+
1568
+ /**
1569
+ * :markup: markdown
1570
+ * call-seq:
1571
+ * parse_failure?(source, **options) -> bool
1572
+ *
1573
+ * Parse the given string and return true if it parses with errors. For
1574
+ * supported options, see Prism.parse.
1575
+ */
1576
+ static VALUE
1577
+ parse_failure_p(int argc, VALUE *argv, VALUE self) {
1578
+ return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue;
1579
+ }
1580
+
1581
+ /**
1582
+ * :markup: markdown
1583
+ * call-seq:
1584
+ * parse_file_success?(filepath, **options) -> bool
1585
+ *
1586
+ * Parse the given file and return true if it parses without errors. For
1587
+ * supported options, see Prism.parse.
1588
+ */
1589
+ static VALUE
1590
+ parse_file_success_p(int argc, VALUE *argv, VALUE self) {
1591
+ pm_options_t *options = pm_options_new();
1592
+
1593
+ VALUE encoded_filepath;
1594
+ pm_source_t *src = file_options(argc, argv, options, &encoded_filepath);
1595
+
1596
+ result_t result = parse_input_success_p(pm_source_source(src), pm_source_length(src), options, rb_enc_get(encoded_filepath));
1597
+ pm_source_free(src);
1598
+ pm_options_free(options);
1599
+
1600
+ return result_get(result);
1601
+ }
1602
+
1603
+ /**
1604
+ * :markup: markdown
1605
+ * call-seq:
1606
+ * parse_file_failure?(filepath, **options) -> bool
1607
+ *
1608
+ * Parse the given file and return true if it parses with errors. For
1609
+ * supported options, see Prism.parse.
1610
+ */
1611
+ static VALUE
1612
+ parse_file_failure_p(int argc, VALUE *argv, VALUE self) {
1613
+ return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue;
1614
+ }
1615
+
1616
+ /******************************************************************************/
1617
+ /* String query methods */
1618
+ /******************************************************************************/
1619
+
1620
+ /**
1621
+ * Process the result of a call to a string query method and return an
1622
+ * appropriate value.
1623
+ */
1624
+ static VALUE
1625
+ string_query(pm_string_query_t result) {
1626
+ switch (result) {
1627
+ case PM_STRING_QUERY_ERROR:
1628
+ rb_raise(rb_eArgError, "Invalid or non ascii-compatible encoding");
1629
+ return Qfalse;
1630
+ case PM_STRING_QUERY_FALSE:
1631
+ return Qfalse;
1632
+ case PM_STRING_QUERY_TRUE:
1633
+ return Qtrue;
1634
+ }
1635
+ return Qfalse;
1636
+ }
1637
+
1638
+ /**
1639
+ * :markup: markdown
1640
+ * call-seq:
1641
+ * local?(string) -> bool
1642
+ *
1643
+ * Returns true if the string constitutes a valid local variable name. Note that
1644
+ * this means the names that can be set through Binding#local_variable_set, not
1645
+ * necessarily the ones that can be set through a local variable assignment.
1646
+ */
1647
+ static VALUE
1648
+ string_query_local_p(VALUE self, VALUE string) {
1649
+ const uint8_t *source = (const uint8_t *) check_string(string);
1650
+ return string_query(pm_string_query_local(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1651
+ }
1652
+
1653
+ /**
1654
+ * :markup: markdown
1655
+ * call-seq:
1656
+ * constant?(string) -> bool
1657
+ *
1658
+ * Returns true if the string constitutes a valid constant name. Note that this
1659
+ * means the names that can be set through Module#const_set, not necessarily the
1660
+ * ones that can be set through a constant assignment.
1661
+ */
1662
+ static VALUE
1663
+ string_query_constant_p(VALUE self, VALUE string) {
1664
+ const uint8_t *source = (const uint8_t *) check_string(string);
1665
+ return string_query(pm_string_query_constant(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1666
+ }
1667
+
1668
+ /**
1669
+ * :markup: markdown
1670
+ * call-seq:
1671
+ * method_name?(string) -> bool
1672
+ *
1673
+ * Returns true if the string constitutes a valid method name.
1674
+ */
1675
+ static VALUE
1676
+ string_query_method_name_p(VALUE self, VALUE string) {
1677
+ const uint8_t *source = (const uint8_t *) check_string(string);
1678
+ return string_query(pm_string_query_method_name(source, RSTRING_LEN(string), rb_enc_get(string)->name));
1679
+ }
1680
+
1681
+ /******************************************************************************/
1682
+ /* Initialization of the extension */
1683
+ /******************************************************************************/
1684
+
1685
+ /**
1686
+ * The init function that Ruby calls when loading this extension.
1687
+ */
1688
+ RUBY_FUNC_EXPORTED void
1689
+ Init_prism(void) {
1690
+ /* Make sure that the prism library version matches the expected version.
1691
+ * Otherwise something was compiled incorrectly. */
1692
+ if (strcmp(pm_version(), EXPECTED_PRISM_VERSION) != 0) {
1693
+ rb_raise(
1694
+ rb_eRuntimeError,
1695
+ "The prism library version (%s) does not match the expected version (%s)",
1696
+ pm_version(),
1697
+ EXPECTED_PRISM_VERSION
1698
+ );
1699
+ }
1700
+
1701
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
1702
+ /* Mark this extension as Ractor-safe. */
1703
+ rb_ext_ractor_safe(true);
1704
+ #endif
1705
+
1706
+ rb_cPrism = rb_define_module("Prism");
1707
+ rb_cPrismNode = rb_define_class_under(rb_cPrism, "Node", rb_cObject);
1708
+ rb_cPrismSource = rb_define_class_under(rb_cPrism, "Source", rb_cObject);
1709
+ rb_cPrismToken = rb_define_class_under(rb_cPrism, "Token", rb_cObject);
1710
+ rb_cPrismLocation = rb_define_class_under(rb_cPrism, "Location", rb_cObject);
1711
+ rb_cPrismComment = rb_define_class_under(rb_cPrism, "Comment", rb_cObject);
1712
+ rb_cPrismInlineComment = rb_define_class_under(rb_cPrism, "InlineComment", rb_cPrismComment);
1713
+ rb_cPrismEmbDocComment = rb_define_class_under(rb_cPrism, "EmbDocComment", rb_cPrismComment);
1714
+ rb_cPrismMagicComment = rb_define_class_under(rb_cPrism, "MagicComment", rb_cObject);
1715
+ rb_cPrismParseError = rb_define_class_under(rb_cPrism, "ParseError", rb_cObject);
1716
+ rb_cPrismParseWarning = rb_define_class_under(rb_cPrism, "ParseWarning", rb_cObject);
1717
+ rb_cPrismResult = rb_define_class_under(rb_cPrism, "Result", rb_cObject);
1718
+ rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cPrismResult);
1719
+ rb_cPrismLexResult = rb_define_class_under(rb_cPrism, "LexResult", rb_cPrismResult);
1720
+ rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult);
1721
+ rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject);
1722
+ rb_cPrismScope = rb_define_class_under(rb_cPrism, "Scope", rb_cObject);
1723
+
1724
+ rb_cPrismCurrentVersionError = rb_const_get(rb_cPrism, rb_intern("CurrentVersionError"));
1725
+
1726
+ /* Intern all of the IDs eagerly that we support so that we do not have to
1727
+ * do it every time we parse. */
1728
+ rb_id_option_attach_comments = rb_intern_const("attach_comments");
1729
+ rb_id_option_command_line = rb_intern_const("command_line");
1730
+ rb_id_option_encoding = rb_intern_const("encoding");
1731
+ rb_id_option_filepath = rb_intern_const("filepath");
1732
+ rb_id_option_freeze = rb_intern_const("freeze");
1733
+ rb_id_option_frozen_string_literal = rb_intern_const("frozen_string_literal");
1734
+ rb_id_option_line = rb_intern_const("line");
1735
+ rb_id_option_main_script = rb_intern_const("main_script");
1736
+ rb_id_option_partial_script = rb_intern_const("partial_script");
1737
+ rb_id_option_raise_error = rb_intern_const("raise_error");
1738
+ rb_id_option_scopes = rb_intern_const("scopes");
1739
+ rb_id_option_version = rb_intern_const("version");
1740
+
1741
+ rb_id_source_for = rb_intern("for");
1742
+
1743
+ rb_id_forwarding_positionals = rb_intern("*");
1744
+ rb_id_forwarding_keywords = rb_intern("**");
1745
+ rb_id_forwarding_block = rb_intern("&");
1746
+ rb_id_forwarding_all = rb_intern("...");
1747
+
1748
+ rb_id_raise_error_plain = rb_intern_const("plain");
1749
+ rb_id_raise_error_style = rb_intern_const("style");
1750
+ rb_id_raise_error_color = rb_intern_const("color");
1751
+
1752
+ /**
1753
+ * The version of the prism library.
1754
+ */
1755
+ rb_define_const(rb_cPrism, "VERSION", rb_str_freeze(rb_str_new_cstr(EXPECTED_PRISM_VERSION)));
1756
+
1757
+ rb_define_singleton_method(rb_cPrism, "lex", lex, -1);
1758
+ rb_define_singleton_method(rb_cPrism, "lex_file", lex_file, -1);
1759
+ rb_define_singleton_method(rb_cPrism, "parse", parse, -1);
1760
+ rb_define_singleton_method(rb_cPrism, "parse_file", parse_file, -1);
1761
+ rb_define_singleton_method(rb_cPrism, "profile", profile, -1);
1762
+ rb_define_singleton_method(rb_cPrism, "profile_file", profile_file, -1);
1763
+ rb_define_singleton_method(rb_cPrism, "parse_stream", parse_stream, -1);
1764
+ rb_define_singleton_method(rb_cPrism, "parse_comments", parse_comments, -1);
1765
+ rb_define_singleton_method(rb_cPrism, "parse_file_comments", parse_file_comments, -1);
1766
+ rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1);
1767
+ rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1);
1768
+ rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1);
1769
+ rb_define_singleton_method(rb_cPrism, "parse_failure?", parse_failure_p, -1);
1770
+ rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1);
1771
+ rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1);
1772
+
1773
+ #ifndef PRISM_EXCLUDE_SERIALIZATION
1774
+ rb_define_singleton_method(rb_cPrism, "dump", dump, -1);
1775
+ rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1);
1776
+ #endif
1777
+
1778
+ rb_define_singleton_method(rb_cPrismStringQuery, "local?", string_query_local_p, 1);
1779
+ rb_define_singleton_method(rb_cPrismStringQuery, "constant?", string_query_constant_p, 1);
1780
+ rb_define_singleton_method(rb_cPrismStringQuery, "method_name?", string_query_method_name_p, 1);
1781
+
1782
+ Init_prism_api_node();
1783
+ }