@@ -1940,6 +1940,41 @@ def on_lambda(params, statements)
19401940 token . location . start_char > beginning . location . start_char
19411941 end
19421942
1943+ # We need to do some special mapping here. Since ripper doesn't support
1944+ # capturing lambda var until 3.2, we need to normalize all of that here.
1945+ params =
1946+ case params
1947+ in Paren [ contents : Params ]
1948+ # In this case we've gotten to the <3.2 parentheses wrapping a set of
1949+ # parameters case. Here we need to manually scan for lambda locals.
1950+ range = ( params . location . start_char + 1 ) ...params . location . end_char
1951+ locals = lambda_locals ( source [ range ] )
1952+
1953+ location = params . contents . location
1954+ location = location . to ( locals . last . location ) if locals . any?
1955+
1956+ Paren . new (
1957+ lparen : params . lparen ,
1958+ contents :
1959+ LambdaVar . new (
1960+ params : params . contents ,
1961+ locals : locals ,
1962+ location : location
1963+ ) ,
1964+ location : params . location ,
1965+ comments : params . comments
1966+ )
1967+ in Params
1968+ # In this case we've gotten to the <3.2 plain set of parameters. In
1969+ # this case there cannot be lambda locals, so we will wrap the
1970+ # parameters into a lambda var that has no locals.
1971+ LambdaVar . new ( params : params , locals : [ ] , location : params . location )
1972+ in LambdaVar
1973+ # In this case we've gotten to 3.2+ lambda var. In this case we don't
1974+ # need to do anything and can just the value as given.
1975+ params
1976+ end
1977+
19431978 if braces
19441979 opening = find_token ( TLamBeg )
19451980 closing = find_token ( RBrace )
@@ -1962,6 +1997,84 @@ def on_lambda(params, statements)
19621997 )
19631998 end
19641999
2000+ # :call-seq:
2001+ # on_lambda_var: (Params params, Array[ Ident ] locals) -> LambdaVar
2002+ def on_lambda_var ( params , locals )
2003+ location = params . location
2004+ location = location . to ( locals . last . location ) if locals . any?
2005+
2006+ LambdaVar . new ( params : params , locals : locals || [ ] , location : location )
2007+ end
2008+
2009+ # Ripper doesn't support capturing lambda local variables until 3.2. To
2010+ # mitigate this, we have to parse that code for ourselves. We use the range
2011+ # from the parentheses to find where we _should_ be looking. Then we check
2012+ # if the resulting tokens match a pattern that we determine means that the
2013+ # declaration has block-local variables. Once it does, we parse those out
2014+ # and convert them into Ident nodes.
2015+ def lambda_locals ( source )
2016+ tokens = Ripper . lex ( source )
2017+
2018+ # First, check that we have a semi-colon. If we do, then we can start to
2019+ # parse the tokens _after_ the semicolon.
2020+ index = tokens . rindex { |token | token [ 1 ] == :on_semicolon }
2021+ return [ ] unless index
2022+
2023+ # Next, map over the tokens and convert them into Ident nodes. Bail out
2024+ # midway through if we encounter a token we didn't expect. Basically we're
2025+ # making our own mini-parser here. To do that we'll walk through a small
2026+ # state machine:
2027+ #
2028+ # ┌────────┐ ┌────────┐ ┌─────────┐
2029+ # │ │ │ │ │┌───────┐│
2030+ # ──> │ item │ ─── ident ──> │ next │ ─── rparen ──> ││ final ││
2031+ # │ │ <── comma ─── │ │ │└───────┘│
2032+ # └────────┘ └────────┘ └─────────┘
2033+ # │ ^ │ ^
2034+ # └──┘ └──┘
2035+ # ignored_nl, sp nl, sp
2036+ #
2037+ state = :item
2038+ transitions = {
2039+ item : {
2040+ on_ignored_nl : :item ,
2041+ on_sp : :item ,
2042+ on_ident : :next
2043+ } ,
2044+ next : {
2045+ on_nl : :next ,
2046+ on_sp : :next ,
2047+ on_comma : :item ,
2048+ on_rparen : :final
2049+ } ,
2050+ final : {
2051+ }
2052+ }
2053+
2054+ tokens [ ( index + 1 ) ..] . each_with_object ( [ ] ) do |token , locals |
2055+ ( lineno , column ) , type , value , = token
2056+
2057+ # Make the state transition for the parser. This is going to raise a
2058+ # KeyError if we don't have a transition for the current state and type.
2059+ # But that shouldn't actually be possible because ripper would have
2060+ # found a syntax error by then.
2061+ state = transitions [ state ] . fetch ( type )
2062+
2063+ # If we hit an identifier, then add it to our list.
2064+ next if type != :on_ident
2065+
2066+ location =
2067+ Location . token (
2068+ line : lineno ,
2069+ char : line_counts [ lineno - 1 ] [ column ] ,
2070+ column : column ,
2071+ size : value . size
2072+ )
2073+
2074+ locals << Ident . new ( value : value , location : location )
2075+ end
2076+ end
2077+
19652078 # :call-seq:
19662079 # on_lbrace: (String value) -> LBrace
19672080 def on_lbrace ( value )
0 commit comments