Skip to content

Commit f82b9ad

Browse files
committed
Pinned expressions and variables in pattern matching
1 parent 001b15d commit f82b9ad

File tree

4 files changed

+169
-20
lines changed

4 files changed

+169
-20
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- Support for Ruby 3.1 syntax, including: blocks without names, hash keys without values, endless methods without parentheses, and new argument forwarding.
12+
- Support for pinned expressions and variables within pattern matching.
13+
914
## [1.1.1] - 2021-12-09
1015

1116
### Added

lib/syntax_tree.rb

+150-20
Original file line numberDiff line numberDiff line change
@@ -1695,10 +1695,13 @@ def format(q)
16951695
parts += posts
16961696

16971697
if constant
1698-
q.format(constant)
1699-
q.text("[")
1700-
q.seplist(parts) { |part| q.format(part) }
1701-
q.text("]")
1698+
q.group do
1699+
q.format(constant)
1700+
q.text("[")
1701+
q.seplist(parts) { |part| q.format(part) }
1702+
q.text("]")
1703+
end
1704+
17021705
return
17031706
end
17041707

@@ -1708,7 +1711,7 @@ def format(q)
17081711
q.seplist(parts) { |part| q.format(part) }
17091712
q.text("]")
17101713
else
1711-
q.seplist(parts) { |part| q.format(part) }
1714+
q.group { q.seplist(parts) { |part| q.format(part) } }
17121715
end
17131716
end
17141717

@@ -2340,24 +2343,93 @@ def to_json(*opts)
23402343
end
23412344
end
23422345

2346+
# PinnedBegin represents a pinning a nested statement within pattern matching.
2347+
#
2348+
# case value
2349+
# in ^(statement)
2350+
# end
2351+
#
2352+
class PinnedBegin
2353+
# [untyped] the expression being pinned
2354+
attr_reader :statement
2355+
2356+
# [Location] the location of this node
2357+
attr_reader :location
2358+
2359+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
2360+
attr_reader :comments
2361+
2362+
def initialize(statement:, location:, comments: [])
2363+
@statement = statement
2364+
@location = location
2365+
@comments = comments
2366+
end
2367+
2368+
def child_nodes
2369+
[statement]
2370+
end
2371+
2372+
def format(q)
2373+
q.group do
2374+
q.text("^(")
2375+
q.nest(1) do
2376+
q.indent do
2377+
q.breakable("")
2378+
q.format(statement)
2379+
end
2380+
q.breakable("")
2381+
q.text(")")
2382+
end
2383+
end
2384+
end
2385+
2386+
def pretty_print(q)
2387+
q.group(2, "(", ")") do
2388+
q.text("pinned_begin")
2389+
2390+
q.breakable
2391+
q.pp(statement)
2392+
2393+
q.pp(Comment::List.new(comments))
2394+
end
2395+
end
2396+
2397+
def to_json(*opts)
2398+
{
2399+
type: :pinned_begin,
2400+
stmt: statement,
2401+
loc: location,
2402+
cmts: comments
2403+
}.to_json(*opts)
2404+
end
2405+
end
2406+
23432407
# :call-seq:
2344-
# on_begin: (BodyStmt bodystmt) -> Begin
2408+
# on_begin: (untyped bodystmt) -> Begin | PinnedBegin
23452409
def on_begin(bodystmt)
2346-
keyword = find_token(Kw, "begin")
2347-
end_char =
2348-
if bodystmt.rescue_clause || bodystmt.ensure_clause ||
2349-
bodystmt.else_clause
2350-
bodystmt.location.end_char
2351-
else
2352-
find_token(Kw, "end").location.end_char
2353-
end
2410+
if beginning = find_token(Op, "^", consume: false)
2411+
tokens.delete(beginning)
2412+
find_token(LParen)
23542413

2355-
bodystmt.bind(keyword.location.end_char, end_char)
2414+
ending = find_token(RParen)
2415+
location = beginning.location.to(ending.location)
23562416

2357-
Begin.new(
2358-
bodystmt: bodystmt,
2359-
location: keyword.location.to(bodystmt.location)
2360-
)
2417+
PinnedBegin.new(statement: bodystmt, location: location)
2418+
else
2419+
keyword = find_token(Kw, "begin")
2420+
end_char =
2421+
if bodystmt.rescue_clause || bodystmt.ensure_clause ||
2422+
bodystmt.else_clause
2423+
bodystmt.location.end_char
2424+
else
2425+
find_token(Kw, "end").location.end_char
2426+
end
2427+
2428+
bodystmt.bind(keyword.location.end_char, end_char)
2429+
location = keyword.location.to(bodystmt.location)
2430+
2431+
Begin.new(bodystmt: bodystmt, location: location)
2432+
end
23612433
end
23622434

23632435
# Binary represents any expression that involves two sub-expressions with an
@@ -12952,10 +13024,68 @@ def to_json(*opts)
1295213024
end
1295313025
end
1295413026

13027+
# PinnedVarRef represents a pinned variable reference within a pattern
13028+
# matching pattern.
13029+
#
13030+
# case value
13031+
# in ^variable
13032+
# end
13033+
#
13034+
# This can be a plain local variable like the example above. It can also be a
13035+
# a class variable, a global variable, or an instance variable.
13036+
class PinnedVarRef
13037+
# [VarRef] the value of this node
13038+
attr_reader :value
13039+
13040+
# [Location] the location of this node
13041+
attr_reader :location
13042+
13043+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
13044+
attr_reader :comments
13045+
13046+
def initialize(value:, location:, comments: [])
13047+
@value = value
13048+
@location = location
13049+
@comments = comments
13050+
end
13051+
13052+
def child_nodes
13053+
[value]
13054+
end
13055+
13056+
def format(q)
13057+
q.group do
13058+
q.text("^")
13059+
q.format(value)
13060+
end
13061+
end
13062+
13063+
def pretty_print(q)
13064+
q.group(2, "(", ")") do
13065+
q.text("pinned_var_ref")
13066+
13067+
q.breakable
13068+
q.pp(value)
13069+
13070+
q.pp(Comment::List.new(comments))
13071+
end
13072+
end
13073+
13074+
def to_json(*opts)
13075+
{ type: :pinned_var_ref, value: value, loc: location, cmts: comments }
13076+
.to_json(*opts)
13077+
end
13078+
end
13079+
1295513080
# :call-seq:
1295613081
# on_var_ref: ((Const | CVar | GVar | Ident | IVar | Kw) value) -> VarRef
1295713082
def on_var_ref(value)
12958-
VarRef.new(value: value, location: value.location)
13083+
if pin = find_token(Op, "^", consume: false)
13084+
tokens.delete(pin)
13085+
PinnedVarRef.new(value: value, location: pin.location.to(value.location))
13086+
else
13087+
VarRef.new(value: value, location: value.location)
13088+
end
1295913089
end
1296013090

1296113091
# VCall represent any plain named object with Ruby that could be either a

test/fixtures/begin.rb

+4
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
begin
66
expression
77
end
8+
%
9+
case value
10+
in ^(expression)
11+
end

test/fixtures/var_field.rb

+10
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@
88
foo = bar
99
%
1010
@foo = bar
11+
%
12+
foo in bar
13+
%
14+
foo in ^bar
15+
%
16+
foo in ^@bar
17+
%
18+
foo in ^@@bar
19+
%
20+
foo in ^$gvar

0 commit comments

Comments
 (0)