Skip to content

Commit

Permalink
+ ruby30.y: reintroduce expr in pat (#777)
Browse files Browse the repository at this point in the history
This commit tracks upstream commit ruby/ruby@88f3ce1.
  • Loading branch information
iliabylich authored Dec 16, 2020
1 parent 78bc319 commit a7c638b
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ below for explanation of `emit_*` calls):
Parser::Builders::Default.emit_arg_inside_procarg0 = true
Parser::Builders::Default.emit_forward_arg = true
Parser::Builders::Default.emit_kwargs = true
Parser::Builders::Default.emit_match_pattern = true

Parse a chunk of code:

Expand Down
45 changes: 43 additions & 2 deletions doc/AST_FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1910,9 +1910,11 @@ Format:

## Pattern matching

### Using `in` modifier
### Using `in` operator

Format:
Ruby 2.7 throws a `NoMatchingPatternError` for `foo in bar` if given value doesn't match pattern.

Format when `emit_match_pattern` compatibility attribute is disabled (the default):

~~~
(in-match
Expand All @@ -1923,6 +1925,45 @@ Format:
~~~~~~ expression
~~~

Format when `emit_match_pattern` is enabled:

~~~
(match-pattern
(int 1)
(match-var :a))
"1 in a"
~~ operator
~~~~~~ expression
~~~

Starting from 3.0 Ruby returns `true`/`false` for the same code construction.

Ruby 3.0 format (compatibility attribute has no effect):

~~~
(match-pattern-p
(int 1)
(match-var :a))
"1 in a"
~~ operator
~~~~~~ expression
~~~

### Using `=>` operator

This node appears in AST only starting from Ruby 3.0.

Format:

~~~
(match-pattern
(int 1)
(match-var :a))
"1 => a"
~~ operator
~~~~~~ expression
~~~

### Case with pattern matching

#### Without else
Expand Down
2 changes: 2 additions & 0 deletions lib/parser/ast/processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ def on_numblock(node)

alias on_case_match process_regular_node
alias on_in_match process_regular_node
alias on_match_pattern process_regular_node
alias on_match_pattern_p process_regular_node
alias on_in_pattern process_regular_node
alias on_if_guard process_regular_node
alias on_unless_guard process_regular_node
Expand Down
40 changes: 40 additions & 0 deletions lib/parser/builders/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,35 @@ class << self

@emit_kwargs = false

class << self
##
# AST compatibility attribute; Starting from 3.0 Ruby returns
# true/false from single-line pattern matching with `in` keyword.
#
# Before 3.0 there was an exception if given value doesn't match pattern.
#
# NOTE: This attribute affects only Ruby 2.7 grammar.
# 3.0 grammar always emits `match_pattern`/`match_pattern_p`
#
# If compatibility attribute set to false `foo in bar` is emitted as `in_match`:
#
# ```
# (in-match
# (send nil :foo)
# (match-var :bar))
# ```
#
# If set to true it's emitted as `match_pattern_p`:
# ```
# (match-pattern-p
# (send nil :foo)
# (match-var :bar))
# ```
attr_accessor :emit_match_pattern
end

@emit_match_pattern = false

class << self
##
# @api private
Expand All @@ -187,6 +216,7 @@ def modernize
@emit_arg_inside_procarg0 = true
@emit_forward_arg = true
@emit_kwargs = true
@emit_match_pattern = true
end
end

Expand Down Expand Up @@ -1366,6 +1396,16 @@ def in_match(lhs, in_t, rhs)
binary_op_map(lhs, in_t, rhs))
end

def match_pattern(lhs, match_t, rhs)
n(:match_pattern, [lhs, rhs],
binary_op_map(lhs, match_t, rhs))
end

def match_pattern_p(lhs, match_t, rhs)
n(:match_pattern_p, [lhs, rhs],
binary_op_map(lhs, match_t, rhs))
end

def in_pattern(in_t, pattern, guard, then_t, body)
children = [pattern, guard, body]
n(:in_pattern, children,
Expand Down
1 change: 1 addition & 0 deletions lib/parser/meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module class sclass def defs undef alias args
array_pattern match_with_trailing_comma array_pattern_with_tail
hash_pattern const_pattern if_guard unless_guard match_nil_pattern
empty_else find_pattern kwargs
match_pattern_p match_pattern
).to_set.freeze

end # Meta
Expand Down
6 changes: 5 additions & 1 deletion lib/parser/ruby27.y
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,11 @@ rule
p_expr
{
@lexer.in_kwarg = val[2]
result = @builder.in_match(val[0], val[1], val[3])
if @builder.class.emit_match_pattern
result = @builder.match_pattern(val[0], val[1], val[3])
else
result = @builder.in_match(val[0], val[1], val[3])
end
}
| arg =tLBRACE_ARG

Expand Down
16 changes: 15 additions & 1 deletion lib/parser/ruby30.y
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,21 @@ rule
p_expr
{
@lexer.in_kwarg = val[2]
result = @builder.in_match(val[0], val[1], val[3])
result = @builder.match_pattern(val[0], val[1], val[3])
}
| arg kIN
{
@lexer.state = :expr_beg
@lexer.command_start = false
pattern_variables.push

result = @lexer.in_kwarg
@lexer.in_kwarg = true
}
p_expr
{
@lexer.in_kwarg = val[2]
result = @builder.match_pattern_p(val[0], val[1], val[3])
}
| arg =tLBRACE_ARG

Expand Down
2 changes: 1 addition & 1 deletion lib/parser/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def execute(options)

private

LEGACY_MODES = %i[lambda procarg0 encoding index arg_inside_procarg0 forward_arg kwargs].freeze
LEGACY_MODES = %i[lambda procarg0 encoding index arg_inside_procarg0 forward_arg kwargs match_pattern].freeze

def runner_name
raise NotImplementedError, "implement #{self.class}##{__callee__}"
Expand Down
37 changes: 33 additions & 4 deletions test/test_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9556,7 +9556,8 @@ def test_pattern_matching_nil_pattern
)
end

def test_pattern_matching_single_line__27
def test_pattern_matching_single_line__27__legacy
Parser::Builders::Default.emit_match_pattern = false
assert_parses(
s(:begin,
s(:in_match,
Expand All @@ -9568,19 +9569,47 @@ def test_pattern_matching_single_line__27
%q{~~~~~~~~ expression (in_match)
| ~~ operator (in_match)},
%w(2.7))
ensure
Parser::Builders::Default.emit_match_pattern = true
end

def test_pattern_matching_single_line__27
assert_parses(
s(:begin,
s(:match_pattern,
s(:int, 1),
s(:array_pattern,
s(:match_var, :a))),
s(:lvar, :a)),
%q{1 in [a]; a},
%q{~~~~~~~~ expression (match_pattern)
| ~~ operator (match_pattern)},
%w(2.7))
end

def test_pattern_matching_single_line
assert_parses(
s(:begin,
s(:in_match,
s(:match_pattern,
s(:int, 1),
s(:array_pattern,
s(:match_var, :a))),
s(:lvar, :a)),
%q{1 => [a]; a},
%q{~~~~~~~~ expression (in_match)
| ~~ operator (in_match)},
%q{~~~~~~~~ expression (match_pattern)
| ~~ operator (match_pattern)},
SINCE_3_0)

assert_parses(
s(:begin,
s(:match_pattern_p,
s(:int, 1),
s(:array_pattern,
s(:match_var, :a))),
s(:lvar, :a)),
%q{1 in [a]; a},
%q{~~~~~~~~ expression (match_pattern_p)
| ~~ operator (match_pattern_p)},
SINCE_3_0)
end

Expand Down

0 comments on commit a7c638b

Please sign in to comment.