Skip to content

Commit b28877f

Browse files
committed
Introduce partial_script option
1 parent 779c49b commit b28877f

File tree

10 files changed

+91
-9
lines changed

10 files changed

+91
-9
lines changed

ext/prism/extension.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ ID rb_id_option_filepath;
3232
ID rb_id_option_frozen_string_literal;
3333
ID rb_id_option_line;
3434
ID rb_id_option_main_script;
35+
ID rb_id_option_partial_script;
3536
ID rb_id_option_scopes;
3637
ID rb_id_option_version;
3738
ID rb_id_source_for;
@@ -182,6 +183,8 @@ build_options_i(VALUE key, VALUE value, VALUE argument) {
182183
}
183184
} else if (key_id == rb_id_option_main_script) {
184185
if (!NIL_P(value)) pm_options_main_script_set(options, RTEST(value));
186+
} else if (key_id == rb_id_option_partial_script) {
187+
if (!NIL_P(value)) pm_options_partial_script_set(options, RTEST(value));
185188
} else {
186189
rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, key);
187190
}
@@ -761,6 +764,12 @@ parse_input(pm_string_t *input, const pm_options_t *options) {
761764
* or not shebangs are parsed for additional flags and whether or not the
762765
* parser will attempt to find a matching shebang if the first one does
763766
* not contain the word "ruby".
767+
* * `partial_script` - when the file being parsed is considered a "partial"
768+
* script, jumps will not be marked as errors if they are not contained
769+
* within loops/blocks. This is used in the case that you're parsing a
770+
* script that you know will be embedded inside another script later, but
771+
* you do not have that context yet. For example, when parsing an ERB
772+
* template that will be evaluated inside another script.
764773
* * `scopes` - the locals that are in scope surrounding the code that is being
765774
* parsed. This should be an array of arrays of symbols or nil. Scopes are
766775
* ordered from the outermost scope to the innermost one.
@@ -1174,6 +1183,7 @@ Init_prism(void) {
11741183
rb_id_option_frozen_string_literal = rb_intern_const("frozen_string_literal");
11751184
rb_id_option_line = rb_intern_const("line");
11761185
rb_id_option_main_script = rb_intern_const("main_script");
1186+
rb_id_option_partial_script = rb_intern_const("partial_script");
11771187
rb_id_option_scopes = rb_intern_const("scopes");
11781188
rb_id_option_version = rb_intern_const("version");
11791189
rb_id_source_for = rb_intern("for");

include/prism/options.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ typedef struct pm_options {
146146
* to pass this information to the parser so that it can behave correctly.
147147
*/
148148
bool main_script;
149+
150+
/**
151+
* When the file being parsed is considered a "partial" script, jumps will
152+
* not be marked as errors if they are not contained within loops/blocks.
153+
* This is used in the case that you're parsing a script that you know will
154+
* be embedded inside another script later, but you do not have that context
155+
* yet. For example, when parsing an ERB template that will be evaluated
156+
* inside another script.
157+
*/
158+
bool partial_script;
149159
} pm_options_t;
150160

151161
/**
@@ -263,6 +273,14 @@ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const
263273
*/
264274
PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, bool main_script);
265275

276+
/**
277+
* Set the partial script option on the given options struct.
278+
*
279+
* @param options The options struct to set the partial script value on.
280+
* @param partial_script The partial script value to set.
281+
*/
282+
PRISM_EXPORTED_FUNCTION void pm_options_partial_script_set(pm_options_t *options, bool partial_script);
283+
266284
/**
267285
* Allocate and zero out the scopes array on the given options struct.
268286
*
@@ -330,6 +348,9 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
330348
* | `1` | -l command line option |
331349
* | `1` | -a command line option |
332350
* | `1` | the version |
351+
* | `1` | encoding locked |
352+
* | `1` | main script |
353+
* | `1` | partial script |
333354
* | `4` | the number of scopes |
334355
* | ... | the scopes |
335356
*
@@ -362,8 +383,8 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
362383
* * The encoding can have a length of 0, in which case we'll use the default
363384
* encoding (UTF-8). If it's not 0, it should correspond to a name of an
364385
* encoding that can be passed to `Encoding.find` in Ruby.
365-
* * The frozen string literal and suppress warnings fields are booleans, so
366-
* their values should be either 0 or 1.
386+
* * The frozen string literal, encoding locked, main script, and partial script
387+
* fields are booleans, so their values should be either 0 or 1.
367388
* * The number of scopes can be 0.
368389
*
369390
* @param options The options struct to deserialize into.

include/prism/parser.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,13 @@ struct pm_parser {
861861
*/
862862
bool parsing_eval;
863863

864+
/**
865+
* Whether or not we are parsing a "partial" script, which is a script that
866+
* will be evaluated in the context of another script, so we should not
867+
* check jumps (next/break/etc.) for validity.
868+
*/
869+
bool partial_script;
870+
864871
/** Whether or not we're at the beginning of a command. */
865872
bool command_start;
866873

java-wasm/src/test/java/org/prism/DummyTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public void test1() {
4242
ParsingOptions.SyntaxVersion.LATEST,
4343
false,
4444
false,
45+
false,
4546
new byte[][][] {}
4647
);
4748

@@ -95,6 +96,7 @@ public void test2() {
9596
ParsingOptions.SyntaxVersion.LATEST,
9697
false,
9798
false,
99+
false,
98100
new byte[][][] {}
99101
);
100102

java/org/prism/ParsingOptions.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ public enum CommandLine { A, E, L, N, P, X };
4545
* @param version code of Ruby version which syntax will be used to parse
4646
* @param encodingLocked whether the encoding is locked (should almost always be false)
4747
* @param mainScript whether the file is the main script
48+
* @param partialScript whether the file is a partial script
4849
* @param scopes scopes surrounding the code that is being parsed with local variable names defined in every scope
4950
* ordered from the outermost scope to the innermost one
5051
*/
51-
public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boolean frozenStringLiteral, EnumSet<CommandLine> commandLine, SyntaxVersion version, boolean encodingLocked, boolean mainScript, byte[][][] scopes) {
52+
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) {
5253
final ByteArrayOutputStream output = new ByteArrayOutputStream();
5354

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

81+
// partialScript
82+
output.write(partialScript ? 1 : 0);
83+
8084
// scopes
8185

8286
// number of scopes

javascript/src/parsePrism.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ function dumpCommandLineOptions(options) {
6262
return value;
6363
}
6464

65+
// Convert a boolean value into a serialized byte.
66+
function dumpBooleanOption(value) {
67+
return (value === undefined || value === false || value === null) ? 0 : 1;
68+
}
69+
6570
// Converts the given options into a serialized options string.
6671
function dumpOptions(options) {
6772
const values = [];
@@ -92,7 +97,7 @@ function dumpOptions(options) {
9297
}
9398

9499
template.push("C");
95-
values.push((options.frozen_string_literal === undefined || options.frozen_string_literal === false || options.frozen_string_literal === null) ? 0 : 1);
100+
values.push(dumpBooleanOption(options.frozen_string_literal));
96101

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

112117
template.push("C");
113-
values.push((options.main_script === undefined || options.main_script === false || options.main_script === null) ? 0 : 1);
118+
values.push(dumpBooleanOption(options.main_script));
119+
120+
template.push("C");
121+
values.push(dumpBooleanOption(options.partial_script));
114122

115123
template.push("L");
116124
if (options.scopes) {

lib/prism/ffi.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,9 @@ def dump_options(options)
451451
template << "C"
452452
values << (options.fetch(:main_script, false) ? 1 : 0)
453453

454+
template << "C"
455+
values << (options.fetch(:partial_script, false) ? 1 : 0)
456+
454457
template << "L"
455458
if (scopes = options[:scopes])
456459
values << scopes.length

src/options.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ pm_options_main_script_set(pm_options_t *options, bool main_script) {
108108
options->main_script = main_script;
109109
}
110110

111+
/**
112+
* Set the partial script option on the given options struct.
113+
*/
114+
PRISM_EXPORTED_FUNCTION void
115+
pm_options_partial_script_set(pm_options_t *options, bool partial_script) {
116+
options->partial_script = partial_script;
117+
}
118+
111119
// For some reason, GCC analyzer thinks we're leaking allocated scopes and
112120
// locals here, even though we definitely aren't. This is a false positive.
113121
// Ideally we wouldn't need to suppress this.
@@ -242,6 +250,7 @@ pm_options_read(pm_options_t *options, const char *data) {
242250
options->version = (pm_options_version_t) *data++;
243251
options->encoding_locked = ((uint8_t) *data++) > 0;
244252
options->main_script = ((uint8_t) *data++) > 0;
253+
options->partial_script = ((uint8_t) *data++) > 0;
245254

246255
uint32_t scopes_count = pm_options_read_u32(data);
247256
data += 4;

src/prism.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18852,12 +18852,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1885218852
switch (keyword.type) {
1885318853
case PM_TOKEN_KEYWORD_BREAK: {
1885418854
pm_node_t *node = (pm_node_t *) pm_break_node_create(parser, &keyword, arguments.arguments);
18855-
parse_block_exit(parser, node);
18855+
if (!parser->partial_script) parse_block_exit(parser, node);
1885618856
return node;
1885718857
}
1885818858
case PM_TOKEN_KEYWORD_NEXT: {
1885918859
pm_node_t *node = (pm_node_t *) pm_next_node_create(parser, &keyword, arguments.arguments);
18860-
parse_block_exit(parser, node);
18860+
if (!parser->partial_script) parse_block_exit(parser, node);
1886118861
return node;
1886218862
}
1886318863
case PM_TOKEN_KEYWORD_RETURN: {
@@ -18905,7 +18905,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1890518905
}
1890618906

1890718907
pm_node_t *node = (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc);
18908-
if (!parser->parsing_eval) parse_yield(parser, node);
18908+
if (!parser->parsing_eval && !parser->partial_script) parse_yield(parser, node);
1890918909

1891018910
return node;
1891118911
}
@@ -19574,7 +19574,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1957419574
parser_lex(parser);
1957519575

1957619576
pm_node_t *node = (pm_node_t *) pm_redo_node_create(parser, &parser->previous);
19577-
parse_block_exit(parser, node);
19577+
if (!parser->partial_script) parse_block_exit(parser, node);
1957819578

1957919579
return node;
1958019580
}
@@ -21899,6 +21899,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
2189921899
.explicit_encoding = NULL,
2190021900
.command_line = 0,
2190121901
.parsing_eval = false,
21902+
.partial_script = false,
2190221903
.command_start = true,
2190321904
.recovering = false,
2190421905
.encoding_locked = false,
@@ -21962,6 +21963,9 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
2196221963
// version option
2196321964
parser->version = options->version;
2196421965

21966+
// partial_script
21967+
parser->partial_script = options->partial_script;
21968+
2196521969
// scopes option
2196621970
parser->parsing_eval = options->scopes_count > 0;
2196721971
if (parser->parsing_eval) parser->warn_mismatched_indentation = false;

test/prism/api/parse_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ def test_parse_directory
9090
assert_kind_of Errno::EISDIR, error
9191
end
9292

93+
def test_partial_script
94+
assert Prism.parse_failure?("break")
95+
assert Prism.parse_success?("break", partial_script: true)
96+
97+
assert Prism.parse_failure?("next")
98+
assert Prism.parse_success?("next", partial_script: true)
99+
100+
assert Prism.parse_failure?("redo")
101+
assert Prism.parse_success?("redo", partial_script: true)
102+
103+
assert Prism.parse_failure?("yield")
104+
assert Prism.parse_success?("yield", partial_script: true)
105+
end
106+
93107
private
94108

95109
def find_source_file_node(program)

0 commit comments

Comments
 (0)