Ruby 4.1.0dev (2026-10-28 revision b62abdae8b2606aaf4a38fa193f2d04ba1749fdc)
extension.c
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
12VALUE rb_cPrism;
13VALUE rb_cPrismNode;
14VALUE rb_cPrismSource;
15VALUE rb_cPrismToken;
16VALUE rb_cPrismLocation;
17
18VALUE rb_cPrismComment;
19VALUE rb_cPrismInlineComment;
20VALUE rb_cPrismEmbDocComment;
21VALUE rb_cPrismMagicComment;
22VALUE rb_cPrismParseError;
23VALUE rb_cPrismParseWarning;
24VALUE rb_cPrismResult;
25VALUE rb_cPrismParseResult;
26VALUE rb_cPrismLexResult;
27VALUE rb_cPrismParseLexResult;
28VALUE rb_cPrismStringQuery;
29VALUE rb_cPrismScope;
30VALUE rb_cPrismCurrentVersionError;
31
32VALUE rb_cPrismDebugEncoding;
33
34ID rb_id_option_attach_comments;
35ID rb_id_option_command_line;
36ID rb_id_option_encoding;
37ID rb_id_option_filepath;
38ID rb_id_option_freeze;
39ID rb_id_option_frozen_string_literal;
40ID rb_id_option_line;
41ID rb_id_option_main_script;
42ID rb_id_option_partial_script;
43ID rb_id_option_raise_error;
44ID rb_id_option_scopes;
45ID rb_id_option_version;
46
47ID rb_id_source_for;
48
49ID rb_id_forwarding_positionals;
50ID rb_id_forwarding_keywords;
51ID rb_id_forwarding_block;
52ID rb_id_forwarding_all;
53
54ID rb_id_raise_error_plain;
55ID rb_id_raise_error_style;
56ID rb_id_raise_error_color;
57
58/******************************************************************************/
59/* Result struct for working with functions that may need to raise errors. */
60/******************************************************************************/
61
62typedef struct {
63 enum {
64 RESULT_OK,
65 RESULT_ERR
66 } type;
67 VALUE value;
68} result_t;
69
70static result_t
71result_ok(VALUE value) {
72 return (result_t) { .type = RESULT_OK, .value = value };
73}
74
75static result_t
76result_err(VALUE value) {
77 return (result_t) { .type = RESULT_ERR, .value = value };
78}
79
80static VALUE
81result_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
97static const char *
98check_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
115static void
116build_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) {
163 } else if (id == rb_id_forwarding_keywords) {
165 } else if (id == rb_id_forwarding_block) {
167 } else if (id == rb_id_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
207static void
208options_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
223static int
224build_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?. */
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
336 pm_options_t *options;
337 VALUE keywords;
338};
339
344static VALUE
345build_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
354static void
355extract_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
385static VALUE
386string_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
403static pm_source_t *
404file_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));
421 pm_source_t *pm_src = pm_source_file_new(source, &result);
422
423 switch (result) {
425 break;
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 }
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
456static result_t
457check_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) {
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 }
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
510static result_t
511dump_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
544static VALUE
545dump(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
580static VALUE
581dump_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
604static inline VALUE
605rb_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
614static inline VALUE
615parser_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
623#define PARSER_LOCATION(source, freeze, location) \
624 parser_location(source, freeze, location.start, location.length)
625
629static inline VALUE
630parser_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
642static void
643parser_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
655static inline VALUE
656parser_magic_comment(VALUE source, bool freeze, const pm_magic_comment_t *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
670static VALUE
671parser_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
686static VALUE
687parser_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
700static VALUE
701parser_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) {
711 level = ID2SYM(rb_intern("syntax"));
712 break;
714 level = ID2SYM(rb_intern("argument"));
715 break;
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
730static VALUE
731parser_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
745static VALUE
746parser_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) {
756 level = ID2SYM(rb_intern("default"));
757 break;
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
772static VALUE
773parser_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
789static void
790parser_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
803static void
804parser_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
833static VALUE
834parse_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
857typedef struct {
858 VALUE source;
859 VALUE tokens;
860 rb_encoding *encoding;
861 bool freeze;
863
869static void
870parse_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
882static void
883parse_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
926static result_t
927parse_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);
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
1011static VALUE
1012lex(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
1030static VALUE
1031lex_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
1051static result_t
1052parse_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
1145static VALUE
1146parse(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
1181static VALUE
1182parse_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
1198static result_t
1199profile_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
1221static VALUE
1222profile(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
1242static VALUE
1243profile_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
1257static int
1258parse_stream_eof(void *stream) {
1259 if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) {
1260 return 1;
1261 }
1262 return 0;
1263}
1264
1277#define MAX_ENC_LEN 6
1278
1284static char *
1285parse_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 size_t length = (size_t) 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 > (size_t) (size - 1)) {
1313 length = (size_t) (size - 1);
1314 }
1315
1316 memcpy(string, cstr, length);
1317 string[length] = '\0';
1318
1319 return string;
1320}
1321
1322#undef MAX_ENC_LEN
1323
1332static VALUE
1333parse_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
1375static result_t
1376parse_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
1411static VALUE
1412parse_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
1430static VALUE
1431parse_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
1459static VALUE
1460parse_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
1485static VALUE
1486parse_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
1502static result_t
1503parse_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
1527static VALUE
1528parse_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
1546static VALUE
1547parse_failure_p(int argc, VALUE *argv, VALUE self) {
1548 return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue;
1549}
1550
1559static VALUE
1560parse_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
1581static VALUE
1582parse_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
1594static VALUE
1595string_query(pm_string_query_t result) {
1596 switch (result) {
1598 rb_raise(rb_eArgError, "Invalid or non ascii-compatible encoding");
1599 return Qfalse;
1601 return Qfalse;
1603 return Qtrue;
1604 }
1605 return Qfalse;
1606}
1607
1617static VALUE
1618string_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
1632static VALUE
1633string_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
1645static VALUE
1646string_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
1659static VALUE
1660string_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
1672RUBY_FUNC_EXPORTED void
1673Init_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(
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 VALUE mRuby = rb_define_module("Ruby"); rb_cPrism = rb_define_module_under(mRuby, "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
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}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
PRISM_EXPORTED_FUNCTION pm_location_t pm_comment_location(const pm_comment_t *comment) PRISM_NONNULL(1)
Returns the location associated with the given comment.
Definition parser.c:190
PRISM_EXPORTED_FUNCTION pm_comment_type_t pm_comment_type(const pm_comment_t *comment) PRISM_NONNULL(1)
Returns the type associated with the given comment.
Definition parser.c:198
PRISM_EXPORTED_FUNCTION const pm_comment_t * pm_comment_next(const pm_comment_t *comment) PRISM_NONNULL(1)
Returns the comment that follows the given comment among the comments associated with the parser that...
Definition parser.c:206
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
pm_warning_level_t
The levels of warnings generated during parsing.
Definition diagnostic.h:41
@ PM_WARNING_LEVEL_DEFAULT
For warnings which should be emitted if $VERBOSE != nil.
Definition diagnostic.h:43
@ PM_WARNING_LEVEL_VERBOSE
For warnings which should be emitted if $VERBOSE == true.
Definition diagnostic.h:46
pm_error_level_t
The levels of errors generated during parsing.
Definition diagnostic.h:27
@ PM_ERROR_LEVEL_ARGUMENT
For errors that should raise an argument error.
Definition diagnostic.h:32
@ PM_ERROR_LEVEL_LOAD
For errors that should raise a load error.
Definition diagnostic.h:35
@ PM_ERROR_LEVEL_SYNTAX
For errors that should raise a syntax error.
Definition diagnostic.h:29
pm_errors_format_type_t
The type of formatting to use when formatting errors.
@ PM_ERRORS_FORMAT_PLAIN
Format errors in a plain format with no colors or styles.
@ PM_ERRORS_FORMAT_COLOR
Format errors in a color format with bold and colors.
@ PM_ERRORS_FORMAT_STYLE
Format errors in a style format with bold only.
int rb_scan_args(int argc, const VALUE *argv, const char *fmt,...)
Retrieves argument from argc and argv to given VALUE references according to the format string.
Definition class.c:3350
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define xfree
Old name of ruby_xfree.
Definition xmalloc.h:58
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define ULONG2NUM
Old name of RB_ULONG2NUM.
Definition long.h:60
#define SYM2ID
Old name of RB_SYM2ID.
Definition symbol.h:45
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
#define LONG2FIX
Old name of RB_INT2FIX.
Definition long.h:49
#define LONG2NUM
Old name of RB_LONG2NUM.
Definition long.h:50
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define Qtrue
Old name of RUBY_Qtrue.
#define NUM2INT
Old name of RB_NUM2INT.
Definition int.h:44
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#define NIL_P
Old name of RB_NIL_P.
#define T_SYMBOL
Old name of RUBY_T_SYMBOL.
Definition value_type.h:80
#define SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:678
void rb_syserr_fail(int e, const char *mesg)
Raises appropriate exception that represents a C errno.
Definition error.c:4053
VALUE rb_eNoMemError
NoMemoryError exception.
Definition error.c:1468
VALUE rb_eLoadError
LoadError exception.
Definition error.c:1475
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1457
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1455
VALUE rb_exc_new_str(VALUE etype, VALUE str)
Identical to rb_exc_new_cstr(), except it takes a Ruby's string instead of C's.
Definition error.c:1508
VALUE rb_eSyntaxError
SyntaxError exception.
Definition error.c:1474
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_class_new_instance(int argc, const VALUE *argv, VALUE klass)
Allocates, then initialises an instance of the given class.
Definition object.c:2315
VALUE rb_stderr
STDERR constant.
Definition io.c:194
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:240
VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass)
Queries if the given object is an instance (of possibly descendants) of the given class.
Definition object.c:912
VALUE rb_obj_freeze(VALUE obj)
Same as RB_OBJ_FREEZE(), but returns the given object.
Definition object.c:1315
VALUE rb_enc_str_new_cstr(const char *ptr, rb_encoding *enc)
Identical to rb_enc_str_new(), except it assumes the passed pointer is a pointer to a C string.
Definition string.c:1189
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1148
VALUE rb_ary_entry(VALUE ary, rb_len_t off)
Queries an element of an array.
VALUE rb_ary_new_capa(rb_len_t capa)
Identical to rb_ary_new(), except it additionally specifies how many rooms of objects it should alloc...
void rb_ary_store(VALUE ary, rb_len_t key, VALUE val)
Destructively stores the passed value to the passed array's passed index.
VALUE rb_ary_resize(VALUE ary, rb_len_t len)
Expands or shrinks the passed array to the passed length.
VALUE rb_ary_replace(VALUE copy, VALUE orig)
Replaces the contents of the former object with the contents of the latter.
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
void rb_ext_ractor_safe(bool flag)
Asserts that the extension library that calls this function is aware of Ractor.
Definition load.c:1335
#define rb_str_new(str, len)
Allocates an instance of rb_cString.
Definition string.h:1523
#define rb_exc_new_cstr(exc, str)
Identical to rb_exc_new(), except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1695
VALUE rb_str_dup(VALUE str)
Duplicates a string.
Definition string.c:2052
VALUE rb_str_freeze(VALUE str)
This is the implementation of String#freeze.
Definition string.c:3416
VALUE rb_check_string_type(VALUE obj)
Try converting an object to its stringised representation using its to_str method,...
Definition string.c:3072
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1539
VALUE rb_const_get(VALUE space, ID name)
Identical to rb_const_defined(), except it returns the actual defined value.
Definition variable.c:3473
VALUE rb_ivar_set(VALUE obj, ID name, VALUE val)
Identical to rb_iv_set(), except it accepts the name as an ID instead of a C string.
Definition variable.c:2150
VALUE rb_ivar_get(VALUE obj, ID name)
Identical to rb_iv_get(), except it accepts the name as an ID instead of a C string.
Definition variable.c:1645
int rb_respond_to(VALUE obj, ID mid)
Queries if the object responds to the method.
Definition vm_method.c:3715
static ID rb_intern_const(const char *str)
This is a "tiny optimisation" over rb_intern().
Definition symbol.h:285
const char ruby_version[]
Stringised version.
Definition version.c:82
PRISM_EXPORTED_FUNCTION pm_location_t pm_magic_comment_value(const pm_magic_comment_t *magic_comment) PRISM_NONNULL(1)
Returns the location of the value associated with the given magic comment.
Definition parser.c:285
PRISM_EXPORTED_FUNCTION pm_location_t pm_magic_comment_key(const pm_magic_comment_t *magic_comment) PRISM_NONNULL(1)
Returns the location of the key associated with the given magic comment.
Definition parser.c:277
PRISM_EXPORTED_FUNCTION const pm_magic_comment_t * pm_magic_comment_next(const pm_magic_comment_t *magic_comment) PRISM_NONNULL(1)
Returns the magic comment that follows the given magic comment among the magic comments associated wi...
Definition parser.c:293
VALUE type(ANYARGS)
ANYARGS-ed function type.
void rb_hash_foreach(VALUE q, int_type *w, VALUE e)
Iteration over the given hash.
static const uint8_t PM_OPTIONS_COMMAND_LINE_E
A bit representing whether or not the command line -e option was set.
Definition options.h:88
static const uint8_t PM_OPTIONS_COMMAND_LINE_L
A bit representing whether or not the command line -l option was set.
Definition options.h:94
static const uint8_t PM_OPTIONS_COMMAND_LINE_A
A bit representing whether or not the command line -a option was set.
Definition options.h:81
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_NONE
The default value for parameters.
Definition options.h:49
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_ALL
When the scope is forwarding with the ... parameter.
Definition options.h:61
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS
When the scope is forwarding with the * parameter.
Definition options.h:52
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS
When the scope is forwarding with the ** parameter.
Definition options.h:55
static const uint8_t PM_OPTIONS_COMMAND_LINE_N
A bit representing whether or not the command line -n option was set.
Definition options.h:100
static const uint8_t PM_OPTIONS_COMMAND_LINE_X
A bit representing whether or not the command line -x option was set.
Definition options.h:112
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_BLOCK
When the scope is forwarding with the & parameter.
Definition options.h:58
static const uint8_t PM_OPTIONS_COMMAND_LINE_P
A bit representing whether or not the command line -p option was set.
Definition options.h:106
PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_parser_t * pm_parser_new(pm_arena_t *arena, const uint8_t *source, size_t size, const pm_options_t *options) PRISM_NONNULL(1)
Allocate and initialize a parser with the given start and end pointers.
Definition prism.c:23528
PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser) PRISM_NONNULL(1)
Free both the memory held by the given parser and the parser itself.
Definition prism.c:23549
PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser) PRISM_NONNULL(1)
Initiate the parser with the given parser.
Definition prism.c:23720
comment_range comments(const pm_parser_t *parser PRISM_LIFETIMEBOUND) noexcept
The comments found while parsing, in source order.
Definition parser.hpp:257
magic_comment_range magic_comments(const pm_parser_t *parser PRISM_LIFETIMEBOUND) noexcept
The magic comments found while parsing.
Definition parser.hpp:268
diagnostic_range errors(const pm_parser_t *parser PRISM_LIFETIMEBOUND) noexcept
The errors found while parsing.
Definition parser.hpp:235
diagnostic_range warnings(const pm_parser_t *parser PRISM_LIFETIMEBOUND) noexcept
The warnings found while parsing.
Definition parser.hpp:246
#define RARRAY_LEN
Just another name of rb_array_len.
Definition rarray.h:50
#define RARRAY_AREF(a, i)
References an object in an array.
Definition rarray.h:421
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
pm_source_init_result_t
Represents the result of initializing a source from a file.
Definition source.h:50
@ PM_SOURCE_INIT_ERROR_GENERIC
Indicates a generic error from a source init function, where the type of error should be read from er...
Definition source.h:58
@ PM_SOURCE_INIT_SUCCESS
Indicates that the source was successfully initialized.
Definition source.h:52
@ PM_SOURCE_INIT_ERROR_DIRECTORY
Indicates that the file that was attempted to be opened was a directory.
Definition source.h:63
#define RTEST
This is an old name of RB_TEST.
pm_string_query_t
Represents the results of a slice query.
@ PM_STRING_QUERY_TRUE
Returned if the result of the slice query is true.
@ PM_STRING_QUERY_ERROR
Returned if the encoding given to a slice query was invalid.
@ PM_STRING_QUERY_FALSE
Returned if the result of the slice query is false.
We need a struct here to pass through rb_protect and it has to be a single value.
Definition extension.c:335
This struct gets stored in the parser and passed in to the lex callback any time a new token is found...
Definition extension.c:857
The comments owned by a node, as a slice of the comment attachments of the parser that produced it.
Definition comments.h:34
A list of offsets of the start of lines in a string.
uint32_t * offsets
The list of offsets.
size_t size
The number of offsets in the list.
This struct represents a slice in the source code, defined by an offset and a length.
Definition ast.h:579
uint32_t start
The offset of the location from the start of the source.
Definition ast.h:581
uint32_t length
The length of the location.
Definition ast.h:584
This is the base structure that represents a node in the syntax tree.
Definition ast.h:1090
A view of a sequence of bytes.
Definition stringy.h:23
This struct represents a token in the Ruby source.
Definition ast.h:551
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376