diff options
Diffstat (limited to 'prism/extension.c')
| -rw-r--r-- | prism/extension.c | 1489 |
1 files changed, 1489 insertions, 0 deletions
diff --git a/prism/extension.c b/prism/extension.c new file mode 100644 index 0000000000..27df8dac50 --- /dev/null +++ b/prism/extension.c @@ -0,0 +1,1489 @@ +#include "prism/extension.h" + +#ifdef _WIN32 +#include <ruby/win32.h> +#endif + +#include <errno.h> + +// NOTE: this file should contain only bindings. All non-trivial logic should be +// in libprism so it can be shared its the various callers. + +VALUE rb_cPrism; +VALUE rb_cPrismNode; +VALUE rb_cPrismSource; +VALUE rb_cPrismToken; +VALUE rb_cPrismLocation; + +VALUE rb_cPrismComment; +VALUE rb_cPrismInlineComment; +VALUE rb_cPrismEmbDocComment; +VALUE rb_cPrismMagicComment; +VALUE rb_cPrismParseError; +VALUE rb_cPrismParseWarning; +VALUE rb_cPrismResult; +VALUE rb_cPrismParseResult; +VALUE rb_cPrismLexResult; +VALUE rb_cPrismParseLexResult; +VALUE rb_cPrismStringQuery; +VALUE rb_cPrismScope; +VALUE rb_cPrismCurrentVersionError; + +VALUE rb_cPrismDebugEncoding; + +ID rb_id_option_command_line; +ID rb_id_option_encoding; +ID rb_id_option_filepath; +ID rb_id_option_freeze; +ID rb_id_option_frozen_string_literal; +ID rb_id_option_line; +ID rb_id_option_main_script; +ID rb_id_option_partial_script; +ID rb_id_option_scopes; +ID rb_id_option_version; +ID rb_id_source_for; +ID rb_id_forwarding_positionals; +ID rb_id_forwarding_keywords; +ID rb_id_forwarding_block; +ID rb_id_forwarding_all; + +/******************************************************************************/ +/* IO of Ruby code */ +/******************************************************************************/ + +/** + * Check if the given VALUE is a string. If it's not a string, then raise a + * TypeError. Otherwise return the VALUE as a C string. + */ +static const char * +check_string(VALUE value) { + // Check if the value is a string. If it's not, then raise a type error. + if (!RB_TYPE_P(value, T_STRING)) { + rb_raise(rb_eTypeError, "wrong argument type %" PRIsVALUE " (expected String)", rb_obj_class(value)); + } + + // Otherwise, return the value as a C string. + return RSTRING_PTR(value); +} + + +/******************************************************************************/ +/* Building C options from Ruby options */ +/******************************************************************************/ + +/** + * Build the scopes associated with the provided Ruby keyword value. + */ +static void +build_options_scopes(pm_options_t *options, VALUE scopes) { + // Check if the value is an array. If it's not, then raise a type error. + if (!RB_TYPE_P(scopes, T_ARRAY)) { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(scopes)); + } + + // Initialize the scopes array. + size_t scopes_count = RARRAY_LEN(scopes); + if (!pm_options_scopes_init(options, scopes_count)) { + rb_raise(rb_eNoMemError, "failed to allocate memory"); + } + + // Iterate over the scopes and add them to the options. + for (size_t scope_index = 0; scope_index < scopes_count; scope_index++) { + VALUE scope = rb_ary_entry(scopes, scope_index); + + // The scope can be either an array or it can be a Prism::Scope object. + // Parse out the correct values here from either. + VALUE locals; + uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; + + if (RB_TYPE_P(scope, T_ARRAY)) { + locals = scope; + } else if (rb_obj_is_kind_of(scope, rb_cPrismScope)) { + locals = rb_ivar_get(scope, rb_intern("@locals")); + if (!RB_TYPE_P(locals, T_ARRAY)) { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(locals)); + } + + VALUE names = rb_ivar_get(scope, rb_intern("@forwarding")); + if (!RB_TYPE_P(names, T_ARRAY)) { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(names)); + } + + size_t names_count = RARRAY_LEN(names); + for (size_t name_index = 0; name_index < names_count; name_index++) { + VALUE name = rb_ary_entry(names, name_index); + + // Check that the name is a symbol. If it's not, then raise + // a type error. + if (!RB_TYPE_P(name, T_SYMBOL)) { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(name)); + } + + ID id = SYM2ID(name); + if (id == rb_id_forwarding_positionals) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS; + } else if (id == rb_id_forwarding_keywords) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS; + } else if (id == rb_id_forwarding_block) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK; + } else if (id == rb_id_forwarding_all) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL; + } else { + rb_raise(rb_eArgError, "invalid forwarding value: %" PRIsVALUE, name); + } + } + } else { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array or Prism::Scope)", rb_obj_class(scope)); + } + + // Initialize the scope array. + size_t locals_count = RARRAY_LEN(locals); + pm_options_scope_t *options_scope = pm_options_scope_mut(options, scope_index); + pm_options_scope_init(options_scope, locals_count); + + // Iterate over the locals and add them to the scope. + for (size_t local_index = 0; local_index < locals_count; local_index++) { + VALUE local = rb_ary_entry(locals, local_index); + + // Check that the local is a symbol. If it's not, then raise a + // type error. + if (!RB_TYPE_P(local, T_SYMBOL)) { + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(local)); + } + + // Add the local to the scope. + pm_string_t *scope_local = pm_options_scope_local_mut(options_scope, local_index); + const char *name = rb_id2name(SYM2ID(local)); + pm_string_constant_init(scope_local, name, strlen(name)); + } + + // Now set the forwarding options. + pm_options_scope_forwarding_set(options_scope, forwarding); + } +} + +/** + * An iterator function that is called for each key-value in the keywords hash. + */ +static int +build_options_i(VALUE key, VALUE value, VALUE argument) { + pm_options_t *options = (pm_options_t *) argument; + ID key_id = SYM2ID(key); + + if (key_id == rb_id_option_filepath) { + if (!NIL_P(value)) pm_options_filepath_set(options, check_string(value)); + } else if (key_id == rb_id_option_encoding) { + if (!NIL_P(value)) { + if (value == Qfalse) { + pm_options_encoding_locked_set(options, true); + } else { + pm_options_encoding_set(options, rb_enc_name(rb_to_encoding(value))); + } + } + } else if (key_id == rb_id_option_line) { + if (!NIL_P(value)) pm_options_line_set(options, NUM2INT(value)); + } else if (key_id == rb_id_option_frozen_string_literal) { + if (!NIL_P(value)) pm_options_frozen_string_literal_set(options, RTEST(value)); + } else if (key_id == rb_id_option_version) { + if (!NIL_P(value)) { + const char *version = check_string(value); + + if (RSTRING_LEN(value) == 7 && strncmp(version, "current", 7) == 0) { + if (!pm_options_version_set(options, ruby_version, 3)) { + rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, ruby_version)); + } + } else if (RSTRING_LEN(value) == 7 && strncmp(version, "nearest", 7) == 0) { + if (!pm_options_version_set(options, ruby_version, 3)) { + // Prism doesn't know this specific version. Is it lower? + if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) { + pm_options_version_set_lowest(options); + } else { + // Must be higher. + pm_options_version_set_highest(options); + } + } + } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { + rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value); + } + } + } else if (key_id == rb_id_option_scopes) { + if (!NIL_P(value)) build_options_scopes(options, value); + } else if (key_id == rb_id_option_command_line) { + if (!NIL_P(value)) { + const char *string = check_string(value); + uint8_t command_line = 0; + + for (size_t index = 0; index < strlen(string); index++) { + switch (string[index]) { + case 'a': command_line |= PM_OPTIONS_COMMAND_LINE_A; break; + case 'e': command_line |= PM_OPTIONS_COMMAND_LINE_E; break; + case 'l': command_line |= PM_OPTIONS_COMMAND_LINE_L; break; + case 'n': command_line |= PM_OPTIONS_COMMAND_LINE_N; break; + case 'p': command_line |= PM_OPTIONS_COMMAND_LINE_P; break; + case 'x': command_line |= PM_OPTIONS_COMMAND_LINE_X; break; + default: rb_raise(rb_eArgError, "invalid command line flag: '%c'", string[index]); break; + } + } + + pm_options_command_line_set(options, command_line); + } + } else if (key_id == rb_id_option_main_script) { + if (!NIL_P(value)) pm_options_main_script_set(options, RTEST(value)); + } else if (key_id == rb_id_option_partial_script) { + if (!NIL_P(value)) pm_options_partial_script_set(options, RTEST(value)); + } else if (key_id == rb_id_option_freeze) { + if (!NIL_P(value)) pm_options_freeze_set(options, RTEST(value)); + } else { + rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, key); + } + + return ST_CONTINUE; +} + +/** + * We need a struct here to pass through rb_protect and it has to be a single + * value. Because the sizeof(VALUE) == sizeof(void *), we're going to pass this + * through as an opaque pointer and cast it on both sides. + */ +struct build_options_data { + pm_options_t *options; + VALUE keywords; +}; + +/** + * Build the set of options from the given keywords. Note that this can raise a + * Ruby error if the options are not valid. + */ +static VALUE +build_options(VALUE argument) { + struct build_options_data *data = (struct build_options_data *) argument; + rb_hash_foreach(data->keywords, build_options_i, (VALUE) data->options); + return Qnil; +} + +/** + * Extract the options from the given keyword arguments. + */ +static void +extract_options(pm_options_t *options, VALUE filepath, VALUE keywords) { + pm_options_line_set(options, 1); /* default */ + + if (!NIL_P(keywords)) { + struct build_options_data data = { .options = options, .keywords = keywords }; + struct build_options_data *argument = &data; + + int state = 0; + rb_protect(build_options, (VALUE) argument, &state); + + if (state != 0) { + pm_options_free(options); + rb_jump_tag(state); + } + } + + if (!NIL_P(filepath)) { + if (!RB_TYPE_P(filepath, T_STRING)) { + pm_options_free(options); + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected String)", rb_obj_class(filepath)); + } + + pm_options_filepath_set(options, RSTRING_PTR(filepath)); + } +} + +/** + * Read options for methods that look like (source, **options). + */ +static VALUE +string_options(int argc, VALUE *argv, pm_options_t *options) { + VALUE string; + VALUE keywords; + rb_scan_args(argc, argv, "1:", &string, &keywords); + + if (!RB_TYPE_P(string, T_STRING)) { + pm_options_free(options); + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected String)", rb_obj_class(string)); + } + + extract_options(options, Qnil, keywords); + return string; +} + +/** + * Read options for methods that look like (filepath, **options). + */ +static pm_source_t * +file_options(int argc, VALUE *argv, pm_options_t *options, VALUE *encoded_filepath) { + VALUE filepath; + VALUE keywords; + rb_scan_args(argc, argv, "1:", &filepath, &keywords); + + if (!RB_TYPE_P(filepath, T_STRING)) { + pm_options_free(options); + rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected String)", rb_obj_class(filepath)); + } + + *encoded_filepath = rb_str_encode_ospath(filepath); + extract_options(options, *encoded_filepath, keywords); + + const char *source = (const char *) pm_string_source(pm_options_filepath(options)); + pm_source_init_result_t result; + pm_source_t *pm_src = pm_source_file_new(source, &result); + + switch (result) { + case PM_SOURCE_INIT_SUCCESS: + break; + case PM_SOURCE_INIT_ERROR_GENERIC: { + pm_options_free(options); + +#ifdef _WIN32 + int e = rb_w32_map_errno(GetLastError()); +#else + int e = errno; +#endif + + rb_syserr_fail(e, source); + break; + } + case PM_SOURCE_INIT_ERROR_DIRECTORY: + pm_options_free(options); + rb_syserr_fail(EISDIR, source); + break; + default: + pm_options_free(options); + rb_raise(rb_eRuntimeError, "Unknown error (%d) initializing file: %s", result, source); + break; + } + + return pm_src; +} + +#ifndef PRISM_EXCLUDE_SERIALIZATION + +/******************************************************************************/ +/* Serializing the AST */ +/******************************************************************************/ + +/** + * Dump the AST corresponding to the given input to a string. + */ +static VALUE +dump_input(const uint8_t *input, size_t input_length, const pm_options_t *options) { + pm_buffer_t *buffer = pm_buffer_new(); + if (!buffer) { + rb_raise(rb_eNoMemError, "failed to allocate memory"); + } + + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser = pm_parser_new(arena, input, input_length, options); + + pm_node_t *node = pm_parse(parser); + pm_serialize(parser, node, buffer); + + VALUE result = rb_str_new(pm_buffer_value(buffer), pm_buffer_length(buffer)); + pm_buffer_free(buffer); + pm_parser_free(parser); + pm_arena_free(arena); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * dump(source, **options) -> String + * + * Dump the AST corresponding to the given string to a string. For supported + * options, see Prism.parse. + */ +static VALUE +dump(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + const uint8_t *source = (const uint8_t *) RSTRING_PTR(string); + size_t length = RSTRING_LEN(string); + +#ifdef PRISM_BUILD_DEBUG + char* dup = xmalloc(length); + memcpy(dup, source, length); + source = (const uint8_t *) dup; +#endif + + VALUE value = dump_input(source, length, options); + if (pm_options_freeze(options)) rb_obj_freeze(value); + +#ifdef PRISM_BUILD_DEBUG +#ifdef xfree_sized + xfree_sized(dup, length); +#else + xfree(dup); +#endif +#endif + + pm_options_free(options); + + return value; +} + +/** + * :markup: markdown + * call-seq: + * dump_file(filepath, **options) -> String + * + * Dump the AST corresponding to the given file to a string. For supported + * options, see Prism.parse. + */ +static VALUE +dump_file(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + VALUE value = dump_input(pm_source_source(src), pm_source_length(src), options); + pm_source_free(src); + pm_options_free(options); + + return value; +} + +#endif + +/******************************************************************************/ +/* Extracting values for the parse result */ +/******************************************************************************/ + +/** + * The same as rb_class_new_instance, but accepts an additional boolean to + * indicate whether or not the resulting class instance should be frozen. + */ +static inline VALUE +rb_class_new_instance_freeze(int argc, const VALUE *argv, VALUE klass, bool freeze) { + VALUE value = rb_class_new_instance(argc, argv, klass); + if (freeze) rb_obj_freeze(value); + return value; +} + +/** + * Create a new Location instance from the given parser and bounds. + */ +static inline VALUE +parser_location(VALUE source, bool freeze, uint32_t start, uint32_t length) { + VALUE argv[] = { source, LONG2FIX(start), LONG2FIX(length) }; + return rb_class_new_instance_freeze(3, argv, rb_cPrismLocation, freeze); +} + +/** + * Create a new Location instance from the given parser and location. + */ +#define PARSER_LOCATION(source, freeze, location) \ + parser_location(source, freeze, location.start, location.length) + +/** + * Build a new Comment instance from the given parser and comment. + */ +static inline VALUE +parser_comment(VALUE source, bool freeze, const pm_comment_t *comment) { + VALUE argv[] = { PARSER_LOCATION(source, freeze, pm_comment_location(comment)) }; + VALUE type = (pm_comment_type(comment) == PM_COMMENT_EMBDOC) ? rb_cPrismEmbDocComment : rb_cPrismInlineComment; + return rb_class_new_instance_freeze(1, argv, type, freeze); +} + +typedef struct { + VALUE comments; + VALUE source; + bool freeze; +} parser_comments_each_data_t; + +static void +parser_comments_each(const pm_comment_t *comment, void *data) { + parser_comments_each_data_t *each_data = (parser_comments_each_data_t *) data; + VALUE value = parser_comment(each_data->source, each_data->freeze, comment); + rb_ary_push(each_data->comments, value); +} + +/** + * Extract the comments out of the parser into an array. + */ +static VALUE +parser_comments(const pm_parser_t *parser, VALUE source, bool freeze) { + VALUE comments = rb_ary_new_capa(pm_parser_comments_size(parser)); + + parser_comments_each_data_t each_data = { comments, source, freeze }; + pm_parser_comments_each(parser, parser_comments_each, &each_data); + + if (freeze) rb_obj_freeze(comments); + return comments; +} + +/** + * Build a new MagicComment instance from the given parser and magic comment. + */ +static inline VALUE +parser_magic_comment(VALUE source, bool freeze, const pm_magic_comment_t *magic_comment) { + pm_location_t key = pm_magic_comment_key(magic_comment); + pm_location_t value = pm_magic_comment_value(magic_comment); + + VALUE key_loc = parser_location(source, freeze, key.start, key.length); + VALUE value_loc = parser_location(source, freeze, value.start, value.length); + + VALUE argv[] = { key_loc, value_loc }; + return rb_class_new_instance_freeze(2, argv, rb_cPrismMagicComment, freeze); +} + +typedef struct { + VALUE magic_comments; + VALUE source; + bool freeze; +} parser_magic_comments_each_data_t; + +static void +parser_magic_comments_each(const pm_magic_comment_t *magic_comment, void *data) { + parser_magic_comments_each_data_t *each_data = (parser_magic_comments_each_data_t *) data; + VALUE value = parser_magic_comment(each_data->source, each_data->freeze, magic_comment); + rb_ary_push(each_data->magic_comments, value); +} + +/** + * Extract the magic comments out of the parser into an array. + */ +static VALUE +parser_magic_comments(const pm_parser_t *parser, VALUE source, bool freeze) { + VALUE magic_comments = rb_ary_new_capa(pm_parser_magic_comments_size(parser)); + + parser_magic_comments_each_data_t each_data = { magic_comments, source, freeze }; + pm_parser_magic_comments_each(parser, parser_magic_comments_each, &each_data); + + if (freeze) rb_obj_freeze(magic_comments); + return magic_comments; +} + +/** + * Extract out the data location from the parser into a Location instance if one + * exists. + */ +static VALUE +parser_data_loc(const pm_parser_t *parser, VALUE source, bool freeze) { + const pm_location_t *data_loc = pm_parser_data_loc(parser); + + if (data_loc->length == 0) { + return Qnil; + } else { + return parser_location(source, freeze, data_loc->start, data_loc->length); + } +} + +typedef struct { + VALUE errors; + rb_encoding *encoding; + VALUE source; + bool freeze; +} parser_errors_each_data_t; + +static void +parser_errors_each(const pm_diagnostic_t *diagnostic, void *data) { + parser_errors_each_data_t *each_data = (parser_errors_each_data_t *) data; + + VALUE type = ID2SYM(rb_intern(pm_diagnostic_type(diagnostic))); + VALUE message = rb_obj_freeze(rb_enc_str_new_cstr(pm_diagnostic_message(diagnostic), each_data->encoding)); + VALUE location = PARSER_LOCATION(each_data->source, each_data->freeze, pm_diagnostic_location(diagnostic)); + + pm_error_level_t error_level = pm_diagnostic_error_level(diagnostic); + VALUE level = Qnil; + + switch (error_level) { + case PM_ERROR_LEVEL_SYNTAX: + level = ID2SYM(rb_intern("syntax")); + break; + case PM_ERROR_LEVEL_ARGUMENT: + level = ID2SYM(rb_intern("argument")); + break; + case PM_ERROR_LEVEL_LOAD: + level = ID2SYM(rb_intern("load")); + break; + default: + rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, error_level); + } + + VALUE argv[] = { type, message, location, level }; + VALUE value = rb_class_new_instance_freeze(4, argv, rb_cPrismParseError, each_data->freeze); + rb_ary_push(each_data->errors, value); +} + +/** + * Extract the errors out of the parser into an array. + */ +static VALUE +parser_errors(const pm_parser_t *parser, rb_encoding *encoding, VALUE source, bool freeze) { + VALUE errors = rb_ary_new_capa(pm_parser_errors_size(parser)); + + parser_errors_each_data_t each_data = { errors, encoding, source, freeze }; + pm_parser_errors_each(parser, parser_errors_each, &each_data); + + if (freeze) rb_obj_freeze(errors); + return errors; +} + +typedef struct { + VALUE warnings; + rb_encoding *encoding; + VALUE source; + bool freeze; +} parser_warnings_each_data_t; + +static void +parser_warnings_each(const pm_diagnostic_t *diagnostic, void *data) { + parser_warnings_each_data_t *each_data = (parser_warnings_each_data_t *) data; + + VALUE type = ID2SYM(rb_intern(pm_diagnostic_type(diagnostic))); + VALUE message = rb_obj_freeze(rb_enc_str_new_cstr(pm_diagnostic_message(diagnostic), each_data->encoding)); + VALUE location = PARSER_LOCATION(each_data->source, each_data->freeze, pm_diagnostic_location(diagnostic)); + + pm_warning_level_t warning_level = pm_diagnostic_warning_level(diagnostic); + VALUE level = Qnil; + + switch (warning_level) { + case PM_WARNING_LEVEL_DEFAULT: + level = ID2SYM(rb_intern("default")); + break; + case PM_WARNING_LEVEL_VERBOSE: + level = ID2SYM(rb_intern("verbose")); + break; + default: + rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, warning_level); + } + + VALUE argv[] = { type, message, location, level }; + VALUE value = rb_class_new_instance_freeze(4, argv, rb_cPrismParseWarning, each_data->freeze); + rb_ary_push(each_data->warnings, value); +} + +/** + * Extract the warnings out of the parser into an array. + */ +static VALUE +parser_warnings(const pm_parser_t *parser, rb_encoding *encoding, VALUE source, bool freeze) { + VALUE warnings = rb_ary_new_capa(pm_parser_warnings_size(parser)); + + parser_warnings_each_data_t each_data = { warnings, encoding, source, freeze }; + pm_parser_warnings_each(parser, parser_warnings_each, &each_data); + + if (freeze) rb_obj_freeze(warnings); + return warnings; +} + +/** + * Create a new parse result from the given parser, value, encoding, and source. + */ +static VALUE +parse_result_create(VALUE class, const pm_parser_t *parser, VALUE value, rb_encoding *encoding, VALUE source, bool freeze) { + VALUE result_argv[] = { + value, + parser_comments(parser, source, freeze), + parser_magic_comments(parser, source, freeze), + parser_data_loc(parser, source, freeze), + parser_errors(parser, encoding, source, freeze), + parser_warnings(parser, encoding, source, freeze), + pm_parser_continuable(parser) ? Qtrue : Qfalse, + source + }; + + return rb_class_new_instance_freeze(8, result_argv, class, freeze); +} + +/******************************************************************************/ +/* Lexing Ruby code */ +/******************************************************************************/ + +/** + * This struct gets stored in the parser and passed in to the lex callback any + * time a new token is found. We use it to store the necessary information to + * initialize a Token instance. + */ +typedef struct { + VALUE source; + VALUE tokens; + rb_encoding *encoding; + bool freeze; +} parse_lex_data_t; + +/** + * This is passed as a callback to the parser. It gets called every time a new + * token is found. Once found, we initialize a new instance of Token and push it + * onto the tokens array. + */ +static void +parse_lex_token(pm_parser_t *parser, pm_token_t *token, void *data) { + parse_lex_data_t *parse_lex_data = (parse_lex_data_t *) data; + + VALUE value = pm_token_new(parser, token, parse_lex_data->encoding, parse_lex_data->source, parse_lex_data->freeze); + VALUE yields = rb_assoc_new(value, INT2FIX(pm_parser_lex_state(parser))); + + if (parse_lex_data->freeze) { + rb_obj_freeze(value); + rb_obj_freeze(yields); + } + + rb_ary_push(parse_lex_data->tokens, yields); +} + +/** + * This is called whenever the encoding changes based on the magic comment at + * the top of the file. We use it to update the encoding that we are using to + * create tokens. + */ +static void +parse_lex_encoding_changed_callback(pm_parser_t *parser) { + parse_lex_data_t *parse_lex_data = (parse_lex_data_t *) pm_parser_lex_callback_data(parser); + parse_lex_data->encoding = rb_enc_find(pm_parser_encoding_name(parser)); + + // Since the encoding changed, we need to go back and change the encoding of + // the tokens that were already lexed. This is only going to end up being + // one or two tokens, since the encoding can only change at the top of the + // file. + VALUE tokens = parse_lex_data->tokens; + VALUE next_tokens = rb_ary_new(); + + for (long index = 0; index < RARRAY_LEN(tokens); index++) { + VALUE yields = rb_ary_entry(tokens, index); + VALUE token = rb_ary_entry(yields, 0); + + VALUE value = rb_ivar_get(token, rb_intern("@value")); + VALUE next_value = rb_str_dup(value); + + rb_enc_associate(next_value, parse_lex_data->encoding); + if (parse_lex_data->freeze) rb_obj_freeze(next_value); + + VALUE next_token_argv[] = { + parse_lex_data->source, + rb_ivar_get(token, rb_intern("@type")), + next_value, + rb_ivar_get(token, rb_intern("@location")) + }; + + VALUE next_token = rb_class_new_instance(4, next_token_argv, rb_cPrismToken); + VALUE next_yields = rb_assoc_new(next_token, rb_ary_entry(yields, 1)); + + if (parse_lex_data->freeze) { + rb_obj_freeze(next_token); + rb_obj_freeze(next_yields); + } + + rb_ary_push(next_tokens, next_yields); + } + + rb_ary_replace(parse_lex_data->tokens, next_tokens); +} + +/** + * Parse the given input and return a ParseResult containing just the tokens or + * the nodes and tokens. + */ +static VALUE +parse_lex_input(const uint8_t *input, size_t input_length, const pm_options_t *options, bool return_nodes) { + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser = pm_parser_new(arena, input, input_length, options); + pm_parser_encoding_changed_callback_set(parser, parse_lex_encoding_changed_callback); + + VALUE source_string = rb_str_new((const char *) input, input_length); + VALUE offsets = rb_ary_new_capa(pm_parser_line_offsets(parser)->size); + VALUE source = rb_funcall(rb_cPrismSource, rb_id_source_for, 3, source_string, LONG2NUM(pm_parser_start_line(parser)), offsets); + + parse_lex_data_t parse_lex_data = { + .source = source, + .tokens = rb_ary_new(), + .encoding = rb_enc_find(pm_parser_encoding_name(parser)), + .freeze = pm_options_freeze(options), + }; + + parse_lex_data_t *data = &parse_lex_data; + pm_parser_lex_callback_set(parser, parse_lex_token, data); + + pm_node_t *node = pm_parse(parser); + + /* Update the Source object with the correct encoding and line offsets, + * which are only available after pm_parse() completes. */ + rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser)); + rb_enc_associate(source_string, encoding); + + const pm_line_offset_list_t *line_offsets = pm_parser_line_offsets(parser); + for (size_t index = 0; index < line_offsets->size; index++) { + rb_ary_store(offsets, (long) index, ULONG2NUM(line_offsets->offsets[index])); + } + + if (pm_options_freeze(options)) { + rb_obj_freeze(source_string); + rb_obj_freeze(offsets); + rb_obj_freeze(source); + rb_obj_freeze(parse_lex_data.tokens); + } + + VALUE result; + if (return_nodes) { + VALUE value = rb_ary_new_capa(2); + rb_ary_push(value, pm_ast_new(parser, node, parse_lex_data.encoding, source, pm_options_freeze(options))); + rb_ary_push(value, parse_lex_data.tokens); + if (pm_options_freeze(options)) rb_obj_freeze(value); + result = parse_result_create(rb_cPrismParseLexResult, parser, value, parse_lex_data.encoding, source, pm_options_freeze(options)); + } else { + result = parse_result_create(rb_cPrismLexResult, parser, parse_lex_data.tokens, parse_lex_data.encoding, source, pm_options_freeze(options)); + } + + pm_parser_free(parser); + pm_arena_free(arena); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * lex(source, **options) -> LexResult + * + * Return a LexResult instance that contains an array of Token instances + * corresponding to the given string. For supported options, see Prism.parse. + */ +static VALUE +lex(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + VALUE result = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, false); + pm_options_free(options); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * lex_file(filepath, **options) -> LexResult + * + * Return a LexResult instance that contains an array of Token instances + * corresponding to the given file. For supported options, see Prism.parse. + */ +static VALUE +lex_file(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + VALUE value = parse_lex_input(pm_source_source(src), pm_source_length(src), options, false); + pm_source_free(src); + pm_options_free(options); + + return value; +} + +/******************************************************************************/ +/* Parsing Ruby code */ +/******************************************************************************/ + +/** + * Parse the given input and return a ParseResult instance. + */ +static VALUE +parse_input(const uint8_t *input, size_t input_length, const pm_options_t *options) { + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser = pm_parser_new(arena, input, input_length, options); + + pm_node_t *node = pm_parse(parser); + rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser)); + + bool freeze = pm_options_freeze(options); + VALUE source = pm_source_new(parser, encoding, freeze); + VALUE value = pm_ast_new(parser, node, encoding, source, freeze); + VALUE result = parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, freeze); + + if (freeze) { + rb_obj_freeze(source); + } + + pm_parser_free(parser); + pm_arena_free(arena); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * parse(source, **options) -> ParseResult + * + * Parse the given string and return a ParseResult instance. The options that + * are supported are: + * + * * `command_line` - either nil or a string of the various options that were + * set on the command line. Valid values are combinations of "a", "l", + * "n", "p", and "x". + * * `encoding` - the encoding of the source being parsed. This should be an + * encoding or nil. + * * `filepath` - the filepath of the source being parsed. This should be a + * string or nil. + * * `freeze` - whether or not to deeply freeze the AST. This should be a + * boolean or nil. + * * `frozen_string_literal` - whether or not the frozen string literal pragma + * has been set. This should be a boolean or nil. + * * `line` - the line number that the parse starts on. This should be an + * integer or nil. Note that this is 1-indexed. + * * `main_script` - a boolean indicating whether or not the source being parsed + * is the main script being run by the interpreter. This controls whether + * or not shebangs are parsed for additional flags and whether or not the + * parser will attempt to find a matching shebang if the first one does + * not contain the word "ruby". + * * `partial_script` - when the file being parsed is considered a "partial" + * script, jumps will not be marked as errors if they are not contained + * within loops/blocks. This is used in the case that you're parsing a + * script that you know will be embedded inside another script later, but + * you do not have that context yet. For example, when parsing an ERB + * template that will be evaluated inside another script. + * * `scopes` - the locals that are in scope surrounding the code that is being + * parsed. This should be an array of arrays of symbols or nil. Scopes are + * ordered from the outermost scope to the innermost one. + * * `version` - the version of Ruby syntax that prism should used to parse Ruby + * code. By default prism assumes you want to parse with the latest + * version of Ruby syntax (which you can trigger with `nil` or + * `"latest"`). You may also restrict the syntax to a specific version of + * Ruby, e.g., with `"3.3.0"`. To parse with the same syntax version that + * the current Ruby is running use `version: "current"`. To parse with the + * nearest version to the current Ruby that is running, use + * `version: "nearest"`. Raises ArgumentError if the version is not + * currently supported by Prism. + */ +static VALUE +parse(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + const uint8_t *source = (const uint8_t *) RSTRING_PTR(string); + size_t length = RSTRING_LEN(string); + +#ifdef PRISM_BUILD_DEBUG + char* dup = xmalloc(length); + memcpy(dup, source, length); + source = (const uint8_t *) dup; +#endif + + VALUE value = parse_input(source, length, options); + +#ifdef PRISM_BUILD_DEBUG +#ifdef xfree_sized + xfree_sized(dup, length); +#else + xfree(dup); +#endif +#endif + + pm_options_free(options); + return value; +} + +/** + * :markup: markdown + * call-seq: + * parse_file(filepath, **options) -> ParseResult + * + * Parse the given file and return a ParseResult instance. For supported + * options, see Prism.parse. + */ +static VALUE +parse_file(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + VALUE value = parse_input(pm_source_source(src), pm_source_length(src), options); + pm_source_free(src); + pm_options_free(options); + + return value; +} + +/** + * Parse the given input and return nothing. + */ +static void +profile_input(const uint8_t *input, size_t input_length, const pm_options_t *options) { + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser = pm_parser_new(arena, input, input_length, options); + + pm_parse(parser); + pm_parser_free(parser); + pm_arena_free(arena); +} + +/** + * :markup: markdown + * call-seq: + * profile(source, **options) -> nil + * + * Parse the given string and return nothing. This method is meant to allow + * profilers to avoid the overhead of reifying the AST to Ruby. For supported + * options, see Prism.parse. + */ +static VALUE +profile(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + profile_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options); + pm_options_free(options); + + return Qnil; +} + +/** + * :markup: markdown + * call-seq: + * profile_file(filepath, **options) -> nil + * + * Parse the given file and return nothing. This method is meant to allow + * profilers to avoid the overhead of reifying the AST to Ruby. For supported + * options, see Prism.parse. + */ +static VALUE +profile_file(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + profile_input(pm_source_source(src), pm_source_length(src), options); + pm_source_free(src); + pm_options_free(options); + + return Qnil; +} + +static int +parse_stream_eof(void *stream) { + if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) { + return 1; + } + return 0; +} + +/** + * An implementation of fgets that is suitable for use with Ruby IO objects. + */ +static char * +parse_stream_fgets(char *string, int size, void *stream) { + RUBY_ASSERT(size > 0); + + VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - 1)); + if (NIL_P(line)) { + return NULL; + } + + const char *cstr = RSTRING_PTR(line); + long length = RSTRING_LEN(line); + + memcpy(string, cstr, length); + string[length] = '\0'; + + return string; +} + +/** + * :markup: markdown + * call-seq: + * parse_stream(stream, **options) -> ParseResult + * + * Parse the given object that responds to `gets` and return a ParseResult + * instance. The options that are supported are the same as Prism.parse. + */ +static VALUE +parse_stream(int argc, VALUE *argv, VALUE self) { + VALUE stream; + VALUE keywords; + rb_scan_args(argc, argv, "1:", &stream, &keywords); + + pm_options_t *options = pm_options_new(); + extract_options(options, Qnil, keywords); + + pm_source_t *src = pm_source_stream_new((void *) stream, parse_stream_fgets, parse_stream_eof); + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser; + + pm_node_t *node = pm_parse_stream(&parser, arena, src, options); + rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser)); + + VALUE source = pm_source_new(parser, encoding, pm_options_freeze(options)); + VALUE value = pm_ast_new(parser, node, encoding, source, pm_options_freeze(options)); + VALUE result = parse_result_create(rb_cPrismParseResult, parser, value, encoding, source, pm_options_freeze(options)); + + pm_source_free(src); + pm_parser_free(parser); + pm_arena_free(arena); + pm_options_free(options); + + return result; +} + +/** + * Parse the given input and return an array of Comment objects. + */ +static VALUE +parse_input_comments(const uint8_t *input, size_t input_length, const pm_options_t *options) { + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser = pm_parser_new(arena, input, input_length, options); + + pm_parse(parser); + rb_encoding *encoding = rb_enc_find(pm_parser_encoding_name(parser)); + + VALUE source = pm_source_new(parser, encoding, pm_options_freeze(options)); + VALUE comments = parser_comments(parser, source, pm_options_freeze(options)); + + pm_parser_free(parser); + pm_arena_free(arena); + + return comments; +} + +/** + * :markup: markdown + * call-seq: + * parse_comments(source, **options) -> Array + * + * Parse the given string and return an array of Comment objects. For supported + * options, see Prism.parse. + */ +static VALUE +parse_comments(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + VALUE result = parse_input_comments((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options); + pm_options_free(options); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * parse_file_comments(filepath, **options) -> Array + * + * Parse the given file and return an array of Comment objects. For supported + * options, see Prism.parse. + */ +static VALUE +parse_file_comments(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + VALUE value = parse_input_comments(pm_source_source(src), pm_source_length(src), options); + pm_source_free(src); + pm_options_free(options); + + return value; +} + +/** + * :markup: markdown + * call-seq: + * parse_lex(source, **options) -> ParseLexResult + * + * Parse the given string and return a ParseLexResult instance that contains a + * 2-element array, where the first element is the AST and the second element is + * an array of Token instances. + * + * This API is only meant to be used in the case where you need both the AST and + * the tokens. If you only need one or the other, use either Prism.parse or + * Prism.lex. + * + * For supported options, see Prism.parse. + */ +static VALUE +parse_lex(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + VALUE value = parse_lex_input((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options, true); + pm_options_free(options); + + return value; +} + +/** + * :markup: markdown + * call-seq: + * parse_lex_file(filepath, **options) -> ParseLexResult + * + * Parse the given file and return a ParseLexResult instance that contains a + * 2-element array, where the first element is the AST and the second element is + * an array of Token instances. + * + * This API is only meant to be used in the case where you need both the AST and + * the tokens. If you only need one or the other, use either Prism.parse_file + * or Prism.lex_file. + * + * For supported options, see Prism.parse. + */ +static VALUE +parse_lex_file(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + VALUE value = parse_lex_input(pm_source_source(src), pm_source_length(src), options, true); + pm_source_free(src); + pm_options_free(options); + + return value; +} + +/** + * Parse the given input and return true if it parses without errors. + */ +static VALUE +parse_input_success_p(const uint8_t *input, size_t input_length, const pm_options_t *options) { + pm_arena_t *arena = pm_arena_new(); + pm_parser_t *parser = pm_parser_new(arena, input, input_length, options); + + pm_parse(parser); + + VALUE result = pm_parser_errors_size(parser) == 0 ? Qtrue : Qfalse; + pm_parser_free(parser); + pm_arena_free(arena); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * parse_success?(source, **options) -> bool + * + * Parse the given string and return true if it parses without errors. For + * supported options, see Prism.parse. + */ +static VALUE +parse_success_p(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + VALUE string = string_options(argc, argv, options); + + VALUE result = parse_input_success_p((const uint8_t *) RSTRING_PTR(string), RSTRING_LEN(string), options); + pm_options_free(options); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * parse_failure?(source, **options) -> bool + * + * Parse the given string and return true if it parses with errors. For + * supported options, see Prism.parse. + */ +static VALUE +parse_failure_p(int argc, VALUE *argv, VALUE self) { + return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue; +} + +/** + * :markup: markdown + * call-seq: + * parse_file_success?(filepath, **options) -> bool + * + * Parse the given file and return true if it parses without errors. For + * supported options, see Prism.parse. + */ +static VALUE +parse_file_success_p(int argc, VALUE *argv, VALUE self) { + pm_options_t *options = pm_options_new(); + + VALUE encoded_filepath; + pm_source_t *src = file_options(argc, argv, options, &encoded_filepath); + + VALUE result = parse_input_success_p(pm_source_source(src), pm_source_length(src), options); + pm_source_free(src); + pm_options_free(options); + + return result; +} + +/** + * :markup: markdown + * call-seq: + * parse_file_failure?(filepath, **options) -> bool + * + * Parse the given file and return true if it parses with errors. For + * supported options, see Prism.parse. + */ +static VALUE +parse_file_failure_p(int argc, VALUE *argv, VALUE self) { + return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue; +} + +/******************************************************************************/ +/* String query methods */ +/******************************************************************************/ + +/** + * Process the result of a call to a string query method and return an + * appropriate value. + */ +static VALUE +string_query(pm_string_query_t result) { + switch (result) { + case PM_STRING_QUERY_ERROR: + rb_raise(rb_eArgError, "Invalid or non ascii-compatible encoding"); + return Qfalse; + case PM_STRING_QUERY_FALSE: + return Qfalse; + case PM_STRING_QUERY_TRUE: + return Qtrue; + } + return Qfalse; +} + +/** + * :markup: markdown + * call-seq: + * local?(string) -> bool + * + * Returns true if the string constitutes a valid local variable name. Note that + * this means the names that can be set through Binding#local_variable_set, not + * necessarily the ones that can be set through a local variable assignment. + */ +static VALUE +string_query_local_p(VALUE self, VALUE string) { + const uint8_t *source = (const uint8_t *) check_string(string); + return string_query(pm_string_query_local(source, RSTRING_LEN(string), rb_enc_get(string)->name)); +} + +/** + * :markup: markdown + * call-seq: + * constant?(string) -> bool + * + * Returns true if the string constitutes a valid constant name. Note that this + * means the names that can be set through Module#const_set, not necessarily the + * ones that can be set through a constant assignment. + */ +static VALUE +string_query_constant_p(VALUE self, VALUE string) { + const uint8_t *source = (const uint8_t *) check_string(string); + return string_query(pm_string_query_constant(source, RSTRING_LEN(string), rb_enc_get(string)->name)); +} + +/** + * :markup: markdown + * call-seq: + * method_name?(string) -> bool + * + * Returns true if the string constitutes a valid method name. + */ +static VALUE +string_query_method_name_p(VALUE self, VALUE string) { + const uint8_t *source = (const uint8_t *) check_string(string); + return string_query(pm_string_query_method_name(source, RSTRING_LEN(string), rb_enc_get(string)->name)); +} + +/******************************************************************************/ +/* Initialization of the extension */ +/******************************************************************************/ + +/** + * The init function that Ruby calls when loading this extension. + */ +RUBY_FUNC_EXPORTED void +Init_prism(void) { + // Make sure that the prism library version matches the expected version. + // Otherwise something was compiled incorrectly. + if (strcmp(pm_version(), EXPECTED_PRISM_VERSION) != 0) { + rb_raise( + rb_eRuntimeError, + "The prism library version (%s) does not match the expected version (%s)", + pm_version(), + EXPECTED_PRISM_VERSION + ); + } + +#ifdef HAVE_RB_EXT_RACTOR_SAFE + // Mark this extension as Ractor-safe. + rb_ext_ractor_safe(true); +#endif + + // Grab up references to all of the constants that we're going to need to + // reference throughout this extension. + rb_cPrism = rb_define_module("Prism"); + rb_cPrismNode = rb_define_class_under(rb_cPrism, "Node", rb_cObject); + rb_cPrismSource = rb_define_class_under(rb_cPrism, "Source", rb_cObject); + rb_cPrismToken = rb_define_class_under(rb_cPrism, "Token", rb_cObject); + rb_cPrismLocation = rb_define_class_under(rb_cPrism, "Location", rb_cObject); + rb_cPrismComment = rb_define_class_under(rb_cPrism, "Comment", rb_cObject); + rb_cPrismInlineComment = rb_define_class_under(rb_cPrism, "InlineComment", rb_cPrismComment); + rb_cPrismEmbDocComment = rb_define_class_under(rb_cPrism, "EmbDocComment", rb_cPrismComment); + rb_cPrismMagicComment = rb_define_class_under(rb_cPrism, "MagicComment", rb_cObject); + rb_cPrismParseError = rb_define_class_under(rb_cPrism, "ParseError", rb_cObject); + rb_cPrismParseWarning = rb_define_class_under(rb_cPrism, "ParseWarning", rb_cObject); + rb_cPrismResult = rb_define_class_under(rb_cPrism, "Result", rb_cObject); + rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cPrismResult); + rb_cPrismLexResult = rb_define_class_under(rb_cPrism, "LexResult", rb_cPrismResult); + rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult); + rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject); + rb_cPrismScope = rb_define_class_under(rb_cPrism, "Scope", rb_cObject); + + rb_cPrismCurrentVersionError = rb_const_get(rb_cPrism, rb_intern("CurrentVersionError")); + + // Intern all of the IDs eagerly that we support so that we don't have to do + // it every time we parse. + rb_id_option_command_line = rb_intern_const("command_line"); + rb_id_option_encoding = rb_intern_const("encoding"); + rb_id_option_filepath = rb_intern_const("filepath"); + rb_id_option_freeze = rb_intern_const("freeze"); + rb_id_option_frozen_string_literal = rb_intern_const("frozen_string_literal"); + rb_id_option_line = rb_intern_const("line"); + rb_id_option_main_script = rb_intern_const("main_script"); + rb_id_option_partial_script = rb_intern_const("partial_script"); + rb_id_option_scopes = rb_intern_const("scopes"); + rb_id_option_version = rb_intern_const("version"); + rb_id_source_for = rb_intern("for"); + rb_id_forwarding_positionals = rb_intern("*"); + rb_id_forwarding_keywords = rb_intern("**"); + rb_id_forwarding_block = rb_intern("&"); + rb_id_forwarding_all = rb_intern("..."); + + /** + * The version of the prism library. + */ + rb_define_const(rb_cPrism, "VERSION", rb_str_freeze(rb_str_new_cstr(EXPECTED_PRISM_VERSION))); + + // First, the functions that have to do with lexing and parsing. + rb_define_singleton_method(rb_cPrism, "lex", lex, -1); + rb_define_singleton_method(rb_cPrism, "lex_file", lex_file, -1); + rb_define_singleton_method(rb_cPrism, "parse", parse, -1); + rb_define_singleton_method(rb_cPrism, "parse_file", parse_file, -1); + rb_define_singleton_method(rb_cPrism, "profile", profile, -1); + rb_define_singleton_method(rb_cPrism, "profile_file", profile_file, -1); + rb_define_singleton_method(rb_cPrism, "parse_stream", parse_stream, -1); + rb_define_singleton_method(rb_cPrism, "parse_comments", parse_comments, -1); + rb_define_singleton_method(rb_cPrism, "parse_file_comments", parse_file_comments, -1); + rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1); + rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1); + rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_failure?", parse_failure_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1); + +#ifndef PRISM_EXCLUDE_SERIALIZATION + rb_define_singleton_method(rb_cPrism, "dump", dump, -1); + rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1); +#endif + + rb_define_singleton_method(rb_cPrismStringQuery, "local?", string_query_local_p, 1); + rb_define_singleton_method(rb_cPrismStringQuery, "constant?", string_query_constant_p, 1); + rb_define_singleton_method(rb_cPrismStringQuery, "method_name?", string_query_method_name_p, 1); + + // Next, initialize the other APIs. + Init_prism_api_node(); +} |
