Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 2276582

Browse files
committed
Various pattern matching fixes
Found by building the translator and using the parser gem's test suite.
1 parent f8abd8e commit 2276582

File tree

5 files changed

+183
-10
lines changed

5 files changed

+183
-10
lines changed

lib/syntax_tree/node.rb

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ def to(other)
3535
)
3636
end
3737

38+
def deconstruct
39+
[start_line, start_char, start_column, end_line, end_char, end_column]
40+
end
41+
42+
def deconstruct_keys(keys)
43+
{
44+
start_line: start_line,
45+
start_char: start_char,
46+
start_column: start_column,
47+
end_line: end_line,
48+
end_char: end_char,
49+
end_column: end_column
50+
}
51+
end
52+
3853
def self.token(line:, char:, column:, size:)
3954
new(
4055
start_line: line,
@@ -4334,16 +4349,20 @@ class Heredoc < Node
43344349
# [String] the ending of the heredoc
43354350
attr_reader :ending
43364351

4352+
# [Integer] how far to dedent the heredoc
4353+
attr_reader :dedent
4354+
43374355
# [Array[ StringEmbExpr | StringDVar | TStringContent ]] the parts of the
43384356
# heredoc string literal
43394357
attr_reader :parts
43404358

43414359
# [Array[ Comment | EmbDoc ]] the comments attached to this node
43424360
attr_reader :comments
43434361

4344-
def initialize(beginning:, ending: nil, parts: [], location:, comments: [])
4362+
def initialize(beginning:, ending: nil, dedent: 0, parts: [], location:, comments: [])
43454363
@beginning = beginning
43464364
@ending = ending
4365+
@dedent = dedent
43474366
@parts = parts
43484367
@location = location
43494368
@comments = comments
@@ -4538,7 +4557,12 @@ def format(q)
45384557
parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
45394558

45404559
contents = -> do
4541-
q.seplist(parts) { |part| q.format(part, stackable: false) }
4560+
q.group { q.seplist(parts) { |part| q.format(part, stackable: false) } }
4561+
4562+
# If there isn't a constant, and there's a blank keyword_rest, then we
4563+
# have an plain ** that needs to have a `then` after it in order to
4564+
# parse correctly on the next parse.
4565+
q.text(" then") if !constant && keyword_rest && keyword_rest.value.nil?
45424566
end
45434567

45444568
if constant
@@ -5594,11 +5618,17 @@ class MLHSParen < Node
55945618
# [MLHS | MLHSParen] the contents inside of the parentheses
55955619
attr_reader :contents
55965620

5621+
# [boolean] whether or not there is a trailing comma at the end of this
5622+
# list, which impacts destructuring. It's an attr_accessor so that while
5623+
# the syntax tree is being built it can be set by its parent node
5624+
attr_accessor :comma
5625+
55975626
# [Array[ Comment | EmbDoc ]] the comments attached to this node
55985627
attr_reader :comments
55995628

5600-
def initialize(contents:, location:, comments: [])
5629+
def initialize(contents:, comma: false, location:, comments: [])
56015630
@contents = contents
5631+
@comma = comma
56025632
@location = location
56035633
@comments = comments
56045634
end
@@ -5622,13 +5652,15 @@ def format(q)
56225652

56235653
if parent.is_a?(MAssign) || parent.is_a?(MLHSParen)
56245654
q.format(contents)
5655+
q.text(",") if comma
56255656
else
56265657
q.group(0, "(", ")") do
56275658
q.indent do
56285659
q.breakable("")
56295660
q.format(contents)
56305661
end
56315662

5663+
q.text(",") if comma
56325664
q.breakable("")
56335665
end
56345666
end

lib/syntax_tree/parser.rb

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -516,12 +516,46 @@ def on_array(contents)
516516
def on_aryptn(constant, requireds, rest, posts)
517517
parts = [constant, *requireds, rest, *posts].compact
518518

519+
# If there aren't any parts (no constant, no positional arguments), then
520+
# we're matching an empty array. In this case, we're going to look for the
521+
# left and right brackets explicitly. Otherwise, we'll just use the bounds
522+
# of the various parts.
523+
location =
524+
if parts.empty?
525+
find_token(LBracket).location.to(find_token(RBracket).location)
526+
else
527+
parts[0].location.to(parts[-1].location)
528+
end
529+
530+
# If there's the optional then keyword, then we'll delete that and use it
531+
# as the end bounds of the location.
532+
if token = find_token(Kw, "then", consume: false)
533+
tokens.delete(token)
534+
location = location.to(token.location)
535+
end
536+
537+
# If there is a plain *, then we're going to fix up the location of it
538+
# here because it currently doesn't have anything to use for its precise
539+
# location. If we hit a comma, then we've gone too far.
540+
if rest.is_a?(VarField) && rest.value.nil?
541+
tokens.rindex do |token|
542+
case token
543+
in Op[value: "*"]
544+
rest = VarField.new(value: nil, location: token.location)
545+
break
546+
in Comma
547+
break
548+
else
549+
end
550+
end
551+
end
552+
519553
AryPtn.new(
520554
constant: constant,
521555
requireds: requireds || [],
522556
rest: rest,
523557
posts: posts || [],
524-
location: parts[0].location.to(parts[-1].location)
558+
location: location
525559
)
526560
end
527561

@@ -1373,15 +1407,35 @@ def on_float(value)
13731407
# VarField right
13741408
# ) -> FndPtn
13751409
def on_fndptn(constant, left, values, right)
1376-
beginning = constant || find_token(LBracket)
1377-
ending = find_token(RBracket)
1410+
# The opening of this find pattern is either going to be a left bracket, a
1411+
# right left parenthesis, or the left splat. We're going to use this to
1412+
# determine how to find the closing of the pattern, as well as determining
1413+
# the location of the node.
1414+
opening =
1415+
find_token(LBracket, consume: false) ||
1416+
find_token(LParen, consume: false) ||
1417+
left
1418+
1419+
# The closing is based on the opening, which is either the matched
1420+
# punctuation or the right splat.
1421+
closing =
1422+
case opening
1423+
in LBracket
1424+
tokens.delete(opening)
1425+
find_token(RBracket)
1426+
in LParen
1427+
tokens.delete(opening)
1428+
find_token(RParen)
1429+
else
1430+
right
1431+
end
13781432

13791433
FndPtn.new(
13801434
constant: constant,
13811435
left: left,
13821436
values: values,
13831437
right: right,
1384-
location: beginning.location.to(ending.location)
1438+
location: (constant || opening).location.to(closing.location)
13851439
)
13861440
end
13871441

@@ -1468,6 +1522,7 @@ def on_heredoc_dedent(string, width)
14681522
@heredocs[-1] = Heredoc.new(
14691523
beginning: heredoc.beginning,
14701524
ending: heredoc.ending,
1525+
dedent: width,
14711526
parts: string.parts,
14721527
location: heredoc.location
14731528
)
@@ -1481,6 +1536,7 @@ def on_heredoc_end(value)
14811536
@heredocs[-1] = Heredoc.new(
14821537
beginning: heredoc.beginning,
14831538
ending: value.chomp,
1539+
dedent: heredoc.dedent,
14841540
parts: heredoc.parts,
14851541
location:
14861542
Location.new(
@@ -1501,12 +1557,23 @@ def on_heredoc_end(value)
15011557
# (nil | VarField) keyword_rest
15021558
# ) -> HshPtn
15031559
def on_hshptn(constant, keywords, keyword_rest)
1560+
# Create an artificial VarField if we find an extra ** on the end
1561+
if !keyword_rest && (token = find_token(Op, "**", consume: false))
1562+
tokens.delete(token)
1563+
keyword_rest = VarField.new(value: nil, location: token.location)
1564+
end
1565+
1566+
# Delete the optional then keyword
1567+
if token = find_token(Kw, "then", consume: false)
1568+
tokens.delete(token)
1569+
end
1570+
15041571
parts = [constant, *keywords&.flatten(1), keyword_rest].compact
15051572
location =
1506-
if parts.empty?
1507-
find_token(LBrace).location.to(find_token(RBrace).location)
1508-
else
1573+
if parts.any?
15091574
parts[0].location.to(parts[-1].location)
1575+
else
1576+
find_token(LBrace).location.to(find_token(RBrace).location)
15101577
end
15111578

15121579
HshPtn.new(
@@ -2638,6 +2705,7 @@ def on_string_literal(string)
26382705
Heredoc.new(
26392706
beginning: heredoc.beginning,
26402707
ending: heredoc.ending,
2708+
dedent: heredoc.dedent,
26412709
parts: string.parts,
26422710
location: heredoc.location
26432711
)
@@ -3190,6 +3258,7 @@ def on_xstring_literal(xstring)
31903258
Heredoc.new(
31913259
beginning: heredoc.beginning,
31923260
ending: heredoc.ending,
3261+
dedent: heredoc.dedent,
31933262
parts: xstring.parts,
31943263
location: heredoc.location
31953264
)

test/fixtures/fndptn.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
%
22
case foo
3+
in *, bar, * then
4+
end
5+
-
6+
case foo
7+
in [*, bar, *]
8+
end
9+
%
10+
case foo
11+
in *, bar, *baz
12+
end
13+
-
14+
case foo
15+
in [*, bar, *baz]
16+
end
17+
%
18+
case foo
19+
in *foo, bar, *baz
20+
end
21+
-
22+
case foo
23+
in [*foo, bar, *baz]
24+
end
25+
%
26+
case foo
327
in [*, bar, *]
428
end
529
%
@@ -38,3 +62,43 @@
3862
case foo
3963
in Foo[*foo, bar, *baz]
4064
end
65+
%
66+
case foo
67+
in Foo(*, bar, *)
68+
end
69+
-
70+
case foo
71+
in Foo[*, bar, *]
72+
end
73+
%
74+
case foo
75+
in Foo(*, bar, baz, qux, *)
76+
end
77+
-
78+
case foo
79+
in Foo[*, bar, baz, qux, *]
80+
end
81+
%
82+
case foo
83+
in Foo(*foo, bar, *)
84+
end
85+
-
86+
case foo
87+
in Foo[*foo, bar, *]
88+
end
89+
%
90+
case foo
91+
in Foo(*, bar, *baz)
92+
end
93+
-
94+
case foo
95+
in Foo[*, bar, *baz]
96+
end
97+
%
98+
case foo
99+
in Foo(*foo, bar, *baz)
100+
end
101+
-
102+
case foo
103+
in Foo[*foo, bar, *baz]
104+
end

test/fixtures/hshptn.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
%
22
case foo
3+
in ** then
4+
end
5+
%
6+
case foo
37
in bar:
48
end
59
%

test/fixtures/mlhs_paren.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@
88
(foo, bar), baz = baz
99
%
1010
foo, (bar, baz,) = baz
11+
%
12+
((foo,)) = bar
13+
-
14+
foo, = bar

0 commit comments

Comments
 (0)