Skip to content

Commit

Permalink
Merge pull request #3079 from ruby/partial-script
Browse files Browse the repository at this point in the history
Introduce partial_script option
  • Loading branch information
kddnewton authored Sep 20, 2024
2 parents 2168f3d + b28877f commit 069d610
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 9 deletions.
10 changes: 10 additions & 0 deletions ext/prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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");
Expand Down
25 changes: 23 additions & 2 deletions include/prism/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 |
*
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions include/prism/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions java-wasm/src/test/java/org/prism/DummyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public void test1() {
ParsingOptions.SyntaxVersion.LATEST,
false,
false,
false,
new byte[][][] {}
);

Expand Down Expand Up @@ -95,6 +96,7 @@ public void test2() {
ParsingOptions.SyntaxVersion.LATEST,
false,
false,
false,
new byte[][][] {}
);

Expand Down
6 changes: 5 additions & 1 deletion java/org/prism/ParsingOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ public enum CommandLine { A, E, L, N, P, X };
* @param version code of Ruby version which syntax will be used to parse
* @param encodingLocked whether the encoding is locked (should almost always be false)
* @param mainScript whether the file is the main script
* @param partialScript whether the file is a partial script
* @param scopes scopes surrounding the code that is being parsed with local variable names defined in every scope
* ordered from the outermost scope to the innermost one
*/
public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boolean frozenStringLiteral, EnumSet<CommandLine> commandLine, SyntaxVersion version, boolean encodingLocked, boolean mainScript, byte[][][] scopes) {
public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boolean frozenStringLiteral, EnumSet<CommandLine> commandLine, SyntaxVersion version, boolean encodingLocked, boolean mainScript, boolean partialScript, byte[][][] scopes) {
final ByteArrayOutputStream output = new ByteArrayOutputStream();

// filepath
Expand Down Expand Up @@ -77,6 +78,9 @@ public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boole
// mainScript
output.write(mainScript ? 1 : 0);

// partialScript
output.write(partialScript ? 1 : 0);

// scopes

// number of scopes
Expand Down
12 changes: 10 additions & 2 deletions javascript/src/parsePrism.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ function dumpCommandLineOptions(options) {
return value;
}

// Convert a boolean value into a serialized byte.
function dumpBooleanOption(value) {
return (value === undefined || value === false || value === null) ? 0 : 1;
}

// Converts the given options into a serialized options string.
function dumpOptions(options) {
const values = [];
Expand Down Expand Up @@ -92,7 +97,7 @@ function dumpOptions(options) {
}

template.push("C");
values.push((options.frozen_string_literal === undefined || options.frozen_string_literal === false || options.frozen_string_literal === null) ? 0 : 1);
values.push(dumpBooleanOption(options.frozen_string_literal));

template.push("C");
values.push(dumpCommandLineOptions(options));
Expand All @@ -110,7 +115,10 @@ function dumpOptions(options) {
values.push(options.encoding === false ? 1 : 0);

template.push("C");
values.push((options.main_script === undefined || options.main_script === false || options.main_script === null) ? 0 : 1);
values.push(dumpBooleanOption(options.main_script));

template.push("C");
values.push(dumpBooleanOption(options.partial_script));

template.push("L");
if (options.scopes) {
Expand Down
3 changes: 3 additions & 0 deletions lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ def dump_options(options)
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
Expand Down
9 changes: 9 additions & 0 deletions src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 8 additions & 4 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
if (!parser->parsing_eval) 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);
if (!parser->parsing_eval) parse_block_exit(parser, node);
if (!parser->partial_script) parse_block_exit(parser, node);
return node;
}
case PM_TOKEN_KEYWORD_RETURN: {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
if (!parser->parsing_eval) parse_block_exit(parser, node);
if (!parser->partial_script) parse_block_exit(parser, node);

return node;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions test/prism/api/parse_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ def test_parse_directory
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)
Expand Down

0 comments on commit 069d610

Please sign in to comment.