Skip to content

Commit 21abb6b

Browse files
committed
Support forwarding flags on scopes
When parent scopes around an eval are forwarding parameters (like *, **, &, or ...) we need to know that information when we are in the parser. As such, we need to support passing that information into the scopes option. In order to do this, unfortunately we need a bunch of changes. The scopes option was previously an array of array of strings. These corresponded to the names of the locals in the parent scopes. We still support this, but now additionally support passing in a Prism::Scope instance at each index in the array. This Prism::Scope class holds both the names of the locals as well as an array of forwarding parameter names (symbols corresponding to the forwarding parameters). There is convenience function on the Prism module that creates a Prism::Scope object using Prism.scope. In JavaScript, we now additionally support an object much the same as the Ruby side. In Java, we now have a ParsingOptions.Scope class that holds that information. In the dump APIs, these objects in all 3 languages will add an additional byte for the forwarding flags in the middle of the scopes serialization. All of this is in service of properly parsing the following code: ```ruby def foo(*) = eval("bar(*)") ```
1 parent c8037df commit 21abb6b

File tree

15 files changed

+447
-80
lines changed

15 files changed

+447
-80
lines changed

ext/prism/extension.c

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ VALUE rb_cPrismParseResult;
2424
VALUE rb_cPrismLexResult;
2525
VALUE rb_cPrismParseLexResult;
2626
VALUE rb_cPrismStringQuery;
27+
VALUE rb_cPrismScope;
2728

2829
VALUE rb_cPrismDebugEncoding;
2930

@@ -38,6 +39,10 @@ ID rb_id_option_partial_script;
3839
ID rb_id_option_scopes;
3940
ID rb_id_option_version;
4041
ID rb_id_source_for;
42+
ID rb_id_forwarding_positionals;
43+
ID rb_id_forwarding_keywords;
44+
ID rb_id_forwarding_block;
45+
ID rb_id_forwarding_all;
4146

4247
/******************************************************************************/
4348
/* IO of Ruby code */
@@ -95,22 +100,61 @@ build_options_scopes(pm_options_t *options, VALUE scopes) {
95100
for (size_t scope_index = 0; scope_index < scopes_count; scope_index++) {
96101
VALUE scope = rb_ary_entry(scopes, scope_index);
97102

98-
// Check that the scope is an array. If it's not, then raise a type
99-
// error.
100-
if (!RB_TYPE_P(scope, T_ARRAY)) {
101-
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(scope));
103+
// The scope can be either an array or it can be a Prism::Scope object.
104+
// Parse out the correct values here from either.
105+
VALUE locals;
106+
uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE;
107+
108+
if (RB_TYPE_P(scope, T_ARRAY)) {
109+
locals = scope;
110+
} else if (rb_obj_is_kind_of(scope, rb_cPrismScope)) {
111+
locals = rb_ivar_get(scope, rb_intern("@locals"));
112+
if (!RB_TYPE_P(locals, T_ARRAY)) {
113+
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(locals));
114+
}
115+
116+
VALUE names = rb_ivar_get(scope, rb_intern("@forwarding"));
117+
if (!RB_TYPE_P(names, T_ARRAY)) {
118+
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array)", rb_obj_class(names));
119+
}
120+
121+
size_t names_count = RARRAY_LEN(names);
122+
for (size_t name_index = 0; name_index < names_count; name_index++) {
123+
VALUE name = rb_ary_entry(names, name_index);
124+
125+
// Check that the name is a symbol. If it's not, then raise
126+
// a type error.
127+
if (!RB_TYPE_P(name, T_SYMBOL)) {
128+
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Symbol)", rb_obj_class(name));
129+
}
130+
131+
ID id = SYM2ID(name);
132+
if (id == rb_id_forwarding_positionals) {
133+
forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS;
134+
} else if (id == rb_id_forwarding_keywords) {
135+
forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS;
136+
} else if (id == rb_id_forwarding_block) {
137+
forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK;
138+
} else if (id == rb_id_forwarding_all) {
139+
forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL;
140+
} else {
141+
rb_raise(rb_eArgError, "invalid forwarding value: %" PRIsVALUE, name);
142+
}
143+
}
144+
} else {
145+
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Array or Prism::Scope)", rb_obj_class(scope));
102146
}
103147

104148
// Initialize the scope array.
105-
size_t locals_count = RARRAY_LEN(scope);
149+
size_t locals_count = RARRAY_LEN(locals);
106150
pm_options_scope_t *options_scope = &options->scopes[scope_index];
107151
if (!pm_options_scope_init(options_scope, locals_count)) {
108152
rb_raise(rb_eNoMemError, "failed to allocate memory");
109153
}
110154

111155
// Iterate over the locals and add them to the scope.
112156
for (size_t local_index = 0; local_index < locals_count; local_index++) {
113-
VALUE local = rb_ary_entry(scope, local_index);
157+
VALUE local = rb_ary_entry(locals, local_index);
114158

115159
// Check that the local is a symbol. If it's not, then raise a
116160
// type error.
@@ -123,6 +167,9 @@ build_options_scopes(pm_options_t *options, VALUE scopes) {
123167
const char *name = rb_id2name(SYM2ID(local));
124168
pm_string_constant_init(scope_local, name, strlen(name));
125169
}
170+
171+
// Now set the forwarding options.
172+
pm_options_scope_forwarding_set(options_scope, forwarding);
126173
}
127174
}
128175

@@ -1302,6 +1349,7 @@ Init_prism(void) {
13021349
rb_cPrismLexResult = rb_define_class_under(rb_cPrism, "LexResult", rb_cPrismResult);
13031350
rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult);
13041351
rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject);
1352+
rb_cPrismScope = rb_define_class_under(rb_cPrism, "Scope", rb_cObject);
13051353

13061354
// Intern all of the IDs eagerly that we support so that we don't have to do
13071355
// it every time we parse.
@@ -1316,6 +1364,10 @@ Init_prism(void) {
13161364
rb_id_option_scopes = rb_intern_const("scopes");
13171365
rb_id_option_version = rb_intern_const("version");
13181366
rb_id_source_for = rb_intern("for");
1367+
rb_id_forwarding_positionals = rb_intern("*");
1368+
rb_id_forwarding_keywords = rb_intern("**");
1369+
rb_id_forwarding_block = rb_intern("&");
1370+
rb_id_forwarding_all = rb_intern("...");
13191371

13201372
/**
13211373
* The version of the prism library.

include/prism/options.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,26 @@ typedef struct pm_options_scope {
3939

4040
/** The names of the locals in the scope. */
4141
pm_string_t *locals;
42+
43+
/** Flags for the set of forwarding parameters in this scope. */
44+
uint8_t forwarding;
4245
} pm_options_scope_t;
4346

47+
/** The default value for parameters. */
48+
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_NONE = 0x0;
49+
50+
/** When the scope is fowarding with the * parameter. */
51+
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS = 0x1;
52+
53+
/** When the scope is fowarding with the ** parameter. */
54+
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS = 0x2;
55+
56+
/** When the scope is fowarding with the & parameter. */
57+
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_BLOCK = 0x4;
58+
59+
/** When the scope is fowarding with the ... parameter. */
60+
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_ALL = 0x8;
61+
4462
// Forward declaration needed by the callback typedef.
4563
struct pm_options;
4664

@@ -337,6 +355,14 @@ PRISM_EXPORTED_FUNCTION bool pm_options_scope_init(pm_options_scope_t *scope, si
337355
*/
338356
PRISM_EXPORTED_FUNCTION const pm_string_t * pm_options_scope_local_get(const pm_options_scope_t *scope, size_t index);
339357

358+
/**
359+
* Set the forwarding option on the given scope struct.
360+
*
361+
* @param scope The scope struct to set the forwarding on.
362+
* @param forwarding The forwarding value to set.
363+
*/
364+
PRISM_EXPORTED_FUNCTION void pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding);
365+
340366
/**
341367
* Free the internal memory associated with the options.
342368
*
@@ -386,6 +412,7 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
386412
* | # bytes | field |
387413
* | ------- | -------------------------- |
388414
* | `4` | the number of locals |
415+
* | `1` | the forwarding flags |
389416
* | ... | the locals |
390417
*
391418
* Each local is laid out as follows:

java/org/prism/ParsingOptions.java

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,69 @@ public byte getValue() {
3535
*/
3636
public enum CommandLine { A, E, L, N, P, X };
3737

38+
/**
39+
* The forwarding options for a given scope in the parser.
40+
*/
41+
public enum Forwarding {
42+
NONE(0),
43+
POSITIONAL(1),
44+
KEYWORD(2),
45+
BLOCK(4),
46+
ALL(8);
47+
48+
private final int value;
49+
50+
Forwarding(int value) {
51+
this.value = value;
52+
}
53+
54+
public byte getValue() {
55+
return (byte) value;
56+
}
57+
};
58+
59+
/**
60+
* Represents a scope in the parser.
61+
*/
62+
public static class Scope {
63+
private byte[][] locals;
64+
private Forwarding[] forwarding;
65+
66+
Scope(byte[][] locals) {
67+
this(locals, new Forwarding[0]);
68+
}
69+
70+
Scope(Forwarding[] forwarding) {
71+
this(new byte[0][], forwarding);
72+
}
73+
74+
Scope(byte[][] locals, Forwarding[] forwarding) {
75+
this.locals = locals;
76+
this.forwarding = forwarding;
77+
}
78+
79+
public byte[][] getLocals() {
80+
return locals;
81+
}
82+
83+
public int getForwarding() {
84+
int value = 0;
85+
for (Forwarding f : forwarding) {
86+
value |= f.getValue();
87+
}
88+
return value;
89+
}
90+
}
91+
92+
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) {
93+
Scope[] normalizedScopes = new Scope[scopes.length];
94+
for (int i = 0; i < scopes.length; i++) {
95+
normalizedScopes[i] = new Scope(scopes[i]);
96+
}
97+
98+
return serialize(filepath, line, encoding, frozenStringLiteral, commandLine, version, encodingLocked, mainScript, partialScript, normalizedScopes);
99+
}
100+
38101
/**
39102
* Serialize parsing options into byte array.
40103
*
@@ -50,7 +113,7 @@ public enum CommandLine { A, E, L, N, P, X };
50113
* @param scopes scopes surrounding the code that is being parsed with local variable names defined in every scope
51114
* ordered from the outermost scope to the innermost one
52115
*/
53-
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) {
116+
public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boolean frozenStringLiteral, EnumSet<CommandLine> commandLine, SyntaxVersion version, boolean encodingLocked, boolean mainScript, boolean partialScript, Scope[] scopes) {
54117
final ByteArrayOutputStream output = new ByteArrayOutputStream();
55118

56119
// filepath
@@ -91,12 +154,17 @@ public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boole
91154
write(output, serializeInt(scopes.length));
92155

93156
// local variables in each scope
94-
for (byte[][] scope : scopes) {
157+
for (Scope scope : scopes) {
158+
byte[][] locals = scope.getLocals();
159+
95160
// number of locals
96-
write(output, serializeInt(scope.length));
161+
write(output, serializeInt(locals.length));
162+
163+
// forwarding flags
164+
output.write(scope.getForwarding());
97165

98166
// locals
99-
for (byte[] local : scope) {
167+
for (byte[] local : locals) {
100168
write(output, serializeInt(local.length));
101169
write(output, local);
102170
}

javascript/src/index.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export * from "./nodes.js";
1111
/**
1212
* Load the prism wasm module and return a parse function.
1313
*
14-
* @returns {Promise<(source: string) => ParseResult>}
14+
* @typedef {import("./parsePrism.js").Options} Options
15+
*
16+
* @returns {Promise<(source: string, options?: Options) => ParseResult>}
1517
*/
1618
export async function loadPrism() {
1719
const wasm = await WebAssembly.compile(await readFile(fileURLToPath(new URL("prism.wasm", import.meta.url))));
@@ -20,7 +22,7 @@ export async function loadPrism() {
2022
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());
2123
wasi.initialize(instance);
2224

23-
return function (source) {
24-
return parsePrism(instance.exports, source);
25+
return function (source, options = {}) {
26+
return parsePrism(instance.exports, source, options);
2527
}
2628
}

0 commit comments

Comments
 (0)