[ruby/prism] Introduce partial_script option

https://github.com/ruby/prism/commit/b28877fa4f
This commit is contained in:
Kevin Newton 2024-09-20 11:20:04 -04:00 коммит произвёл git
Родитель 2882408dcb
Коммит f515a1ab4b
7 изменённых файлов: 74 добавлений и 6 удалений

Просмотреть файл

@ -451,6 +451,9 @@ module Prism
template << "C"
values << (options.fetch(:main_script, false) ? 1 : 0)
template << "C"
values << (options.fetch(:partial_script, false) ? 1 : 0)
template << "L"
if (scopes = options[:scopes])
values << scopes.length

Просмотреть файл

@ -32,6 +32,7 @@ ID rb_id_option_filepath;
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;
@ -182,6 +183,8 @@ build_options_i(VALUE key, VALUE value, VALUE argument) {
}
} 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 {
rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, key);
}
@ -761,6 +764,12 @@ parse_input(pm_string_t *input, const pm_options_t *options) {
* 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.
@ -1174,6 +1183,7 @@ Init_prism(void) {
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");

Просмотреть файл

@ -108,6 +108,14 @@ pm_options_main_script_set(pm_options_t *options, bool main_script) {
options->main_script = main_script;
}
/**
* Set the partial script option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_partial_script_set(pm_options_t *options, bool partial_script) {
options->partial_script = partial_script;
}
// For some reason, GCC analyzer thinks we're leaking allocated scopes and
// locals here, even though we definitely aren't. This is a false positive.
// Ideally we wouldn't need to suppress this.
@ -242,6 +250,7 @@ pm_options_read(pm_options_t *options, const char *data) {
options->version = (pm_options_version_t) *data++;
options->encoding_locked = ((uint8_t) *data++) > 0;
options->main_script = ((uint8_t) *data++) > 0;
options->partial_script = ((uint8_t) *data++) > 0;
uint32_t scopes_count = pm_options_read_u32(data);
data += 4;

Просмотреть файл

@ -146,6 +146,16 @@ typedef struct pm_options {
* to pass this information to the parser so that it can behave correctly.
*/
bool main_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.
*/
bool partial_script;
} pm_options_t;
/**
@ -263,6 +273,14 @@ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const
*/
PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, bool main_script);
/**
* Set the partial script option on the given options struct.
*
* @param options The options struct to set the partial script value on.
* @param partial_script The partial script value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_partial_script_set(pm_options_t *options, bool partial_script);
/**
* Allocate and zero out the scopes array on the given options struct.
*
@ -330,6 +348,9 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
* | `1` | -l command line option |
* | `1` | -a command line option |
* | `1` | the version |
* | `1` | encoding locked |
* | `1` | main script |
* | `1` | partial script |
* | `4` | the number of scopes |
* | ... | the scopes |
*
@ -362,8 +383,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
* * The encoding can have a length of 0, in which case we'll use the default
* encoding (UTF-8). If it's not 0, it should correspond to a name of an
* encoding that can be passed to `Encoding.find` in Ruby.
* * The frozen string literal and suppress warnings fields are booleans, so
* their values should be either 0 or 1.
* * The frozen string literal, encoding locked, main script, and partial script
* fields are booleans, so their values should be either 0 or 1.
* * The number of scopes can be 0.
*
* @param options The options struct to deserialize into.

Просмотреть файл

@ -861,6 +861,13 @@ struct pm_parser {
*/
bool parsing_eval;
/**
* Whether or not we are parsing a "partial" script, which is a script that
* will be evaluated in the context of another script, so we should not
* check jumps (next/break/etc.) for validity.
*/
bool partial_script;
/** Whether or not we're at the beginning of a command. */
bool command_start;

Просмотреть файл

@ -18852,12 +18852,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
switch (keyword.type) {
case PM_TOKEN_KEYWORD_BREAK: {
pm_node_t *node = (pm_node_t *) pm_break_node_create(parser, &keyword, arguments.arguments);
parse_block_exit(parser, node);
if (!parser->partial_script) parse_block_exit(parser, node);
return node;
}
case PM_TOKEN_KEYWORD_NEXT: {
pm_node_t *node = (pm_node_t *) pm_next_node_create(parser, &keyword, arguments.arguments);
parse_block_exit(parser, node);
if (!parser->partial_script) parse_block_exit(parser, node);
return node;
}
case PM_TOKEN_KEYWORD_RETURN: {
@ -18905,7 +18905,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
}
pm_node_t *node = (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc);
if (!parser->parsing_eval) parse_yield(parser, node);
if (!parser->parsing_eval && !parser->partial_script) parse_yield(parser, node);
return node;
}
@ -19574,7 +19574,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
parser_lex(parser);
pm_node_t *node = (pm_node_t *) pm_redo_node_create(parser, &parser->previous);
parse_block_exit(parser, node);
if (!parser->partial_script) parse_block_exit(parser, node);
return node;
}
@ -21899,6 +21899,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
.explicit_encoding = NULL,
.command_line = 0,
.parsing_eval = false,
.partial_script = false,
.command_start = true,
.recovering = false,
.encoding_locked = false,
@ -21962,6 +21963,9 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
// version option
parser->version = options->version;
// partial_script
parser->partial_script = options->partial_script;
// scopes option
parser->parsing_eval = options->scopes_count > 0;
if (parser->parsing_eval) parser->warn_mismatched_indentation = false;

Просмотреть файл

@ -90,6 +90,20 @@ module Prism
assert_kind_of Errno::EISDIR, error
end
def test_partial_script
assert Prism.parse_failure?("break")
assert Prism.parse_success?("break", partial_script: true)
assert Prism.parse_failure?("next")
assert Prism.parse_success?("next", partial_script: true)
assert Prism.parse_failure?("redo")
assert Prism.parse_success?("redo", partial_script: true)
assert Prism.parse_failure?("yield")
assert Prism.parse_success?("yield", partial_script: true)
end
private
def find_source_file_node(program)