Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cont.c
Original file line number Diff line number Diff line change
Expand Up @@ -2869,6 +2869,13 @@ fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fi

VM_ASSERT(FIBER_RUNNABLE_P(fiber));

/*
* Keep the target fiber object alive across fiber_store. The raw
* rb_fiber_t pointer is used after the coroutine switch, and GC may run
* while this C frame is suspended.
*/
VALUE fiber_value = fiber->cont.self;

rb_fiber_t *current_fiber = fiber_current();

VM_ASSERT(!current_fiber->resuming_fiber);
Expand Down Expand Up @@ -2902,6 +2909,7 @@ fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fi
}
}
#endif
RB_GC_GUARD(fiber_value);

if (fiber_current()->blocking) {
th->blocking += 1;
Expand Down
25 changes: 24 additions & 1 deletion lib/prism.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,30 @@ def self.find(callable, rubyvm: !!defined?(RubyVM))
# The C extension is the default backend on CRuby.
Prism::BACKEND = :CEXT

require "prism/prism"
begin
# The precompiled native libraries are in <gem_dir>/lib/prism/<ruby_version>
require_relative "prism/#{RUBY_VERSION[/\d+\.\d+/]}/prism"
rescue LoadError => e
if e.message.include?("GLIBC")
warn(<<~EOM)

ERROR: It looks like you're trying to use Prism as a precompiled native gem on a system
with an unsupported version of glibc.

#{e.message}

If that's the case, then please install Prism via the `ruby` platform gem:
gem install prism --platform=ruby
or, in your Gemfile:
gem "prism", force_ruby_platform: true

EOM
raise e
end

# Precompiled library isn't available, fall back to the library compiled at installation time.
require "prism/prism"
end
else
# The FFI backend is used on other Ruby implementations.
Prism::BACKEND = :FFI
Expand Down
70 changes: 49 additions & 21 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -12710,14 +12710,6 @@ match6(const pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2,
return match1(parser, type1) || match1(parser, type2) || match1(parser, type3) || match1(parser, type4) || match1(parser, type5) || match1(parser, type6);
}

/**
* Returns true if the current token is any of the seven given types.
*/
static PRISM_INLINE bool
match7(const pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_token_type_t type3, pm_token_type_t type4, pm_token_type_t type5, pm_token_type_t type6, pm_token_type_t type7) {
return match1(parser, type1) || match1(parser, type2) || match1(parser, type3) || match1(parser, type4) || match1(parser, type5) || match1(parser, type6) || match1(parser, type7);
}

/**
* Returns true if the current token is any of the eight given types.
*/
Expand Down Expand Up @@ -12930,6 +12922,31 @@ token_begins_expression_p(pm_token_type_t type) {
}
}

/**
* Returns true if the given token can begin a pattern element. This is the
* set of tokens that can begin an expression plus the tokens that begin
* pattern-only constructs — `*` (rest patterns), `**` (keyword rest
* patterns), and `^` (pin patterns) — which are binary operator tokens in
* expression contexts and therefore excluded from token_begins_expression_p.
*
* When a token fails this predicate at a decision point, the pattern ends
* there and the token is left for the enclosing context to accept or reject.
* This mirrors the grammar, whose pattern reductions (`p_top_expr_body:
* p_expr ','`, `p_kw: p_kw_label`, `p_kwargs: p_kwarg ','`) fire by default
* on any token that cannot start a pattern. Tokens that pass this predicate
* but are invalid in the specific context (e.g. `**` in an array pattern)
* are rejected by the pattern parser itself with a more targeted diagnostic.
*/
static PRISM_INLINE bool
token_begins_pattern_p(pm_token_type_t type) {
return (
token_begins_expression_p(type) ||
type == PM_TOKEN_USTAR ||
type == PM_TOKEN_USTAR_STAR ||
type == PM_TOKEN_CARET
);
}

/**
* Parse an expression with the given binding power that may be optionally
* prefixed by the * operator.
Expand Down Expand Up @@ -17097,7 +17114,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node

pm_node_t *value;

if (match8(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) {
/*
* The label has an implicit value when the next token cannot
* begin a pattern, mirroring the grammar's `p_kw: p_kw_label`
* reduction.
*/
if (!token_begins_pattern_p(parser->current.type)) {
if (PM_NODE_TYPE_P(first_node, PM_SYMBOL_NODE)) {
value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) first_node);
} else {
Expand Down Expand Up @@ -17132,8 +17154,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node

// If there are any other assocs, then we'll parse them now.
while (accept1(parser, PM_TOKEN_COMMA)) {
// Here we need to break to support trailing commas.
if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) {
/*
* A trailing comma ends the pattern when the next token cannot begin
* another element, mirroring the grammar's `p_kwargs: p_kwarg ','`
* reduction.
*/
if (!token_begins_pattern_p(parser->current.type)) {
// Trailing commas are not allowed to follow a rest pattern.
if (rest != NULL) {
pm_parser_err_token(parser, &parser->current, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
Expand Down Expand Up @@ -17174,7 +17200,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node
parse_pattern_hash_key(parser, &keys, key);
pm_node_t *value = NULL;

if (match8(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) {
/*
* The label has an implicit value when the next token cannot
* begin a pattern, mirroring the grammar's `p_kw: p_kw_label`
* reduction.
*/
if (!token_begins_pattern_p(parser->current.type)) {
if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) {
value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) key);
} else {
Expand Down Expand Up @@ -17674,15 +17705,12 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag

// Gather up all of the patterns into the list.
while (accept1(parser, PM_TOKEN_COMMA)) {
// Break early here in case we have a trailing comma. The newline and
// EOF terminators cover a one-line match (`x => a,`) or a `case`/`in`
// clause (`in a,\n ...`); a newline is only lexed as a token here
// when `pattern_matching_newlines` is set, so this does not affect
// patterns nested in brackets or parentheses.
if (
match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR) ||
match2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_EOF)
) {
/*
* A trailing comma ends the pattern when the next token cannot
* begin another pattern element, leaving the token for the
* enclosing context to accept or reject.
*/
if (!token_begins_pattern_p(parser->current.type)) {
// A trailing comma forms an implicit rest pattern (`[a,]` is
// `[a, *]`). If a rest pattern has already been parsed, then
// this is a second rest, which is not allowed (e.g. `[a, *b,]`
Expand Down
3 changes: 2 additions & 1 deletion test/prism/errors/dynamic_label_pattern.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
:a => 'a': | 1
^ expected a pattern expression after the key
^ unexpected '|', expecting end-of-input
^ unexpected '|', ignoring it

21 changes: 21 additions & 0 deletions test/prism/fixtures/pattern_terminators.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
while a in b, do 1 end

until a in b, do 1 end

for x in a in b, do 1 end

loop do a in b, end

"#{a in b,}"

while a in x: do 1 end

while a in x: 1, do 1 end

while a in x:, y: do 1 end

loop do a in x: end

"#{a in x:}"

a in x: and 1
Loading