class SyntaxTree::Parser
def lambda_locals(source)
declaration has block-local variables. Once it does, we parse those out
if the resulting tokens match a pattern that we determine means that the
from the parentheses to find where we _should_ be looking. Then we check
mitigate this, we have to parse that code for ourselves. We use the range
Ripper doesn't support capturing lambda local variables until 3.2. To
def lambda_locals(source) tokens = Ripper.lex(source) # First, check that we have a semi-colon. If we do, then we can start to # parse the tokens _after_ the semicolon. index = tokens.rindex { |token| token[1] == :on_semicolon } return [] unless index # Next, map over the tokens and convert them into Ident nodes. Bail out # midway through if we encounter a token we didn't expect. Basically we're # making our own mini-parser here. To do that we'll walk through a small # state machine: # # ┌────────┐ ┌────────┐ ┌─────────┐ # │ │ │ │ │┌───────┐│ # ──> │ item │ ─── ident ──> │ next │ ─── rparen ──> ││ final ││ # │ │ <── comma ─── │ │ │└───────┘│ # └────────┘ └────────┘ └─────────┘ # │ ^ │ ^ # └──┘ └──┘ # ignored_nl, sp nl, sp # state = :item transitions = { item: { on_ignored_nl: :item, on_sp: :item, on_ident: :next }, next: { on_nl: :next, on_sp: :next, on_comma: :item, on_rparen: :final }, final: { } } parent_line = lineno - 1 parent_column = consume_token(Semicolon).location.start_column - tokens[index][0][1] tokens[(index + 1)..].each_with_object([]) do |token, locals| (lineno, column), type, value, = token column += parent_column if lineno == 1 lineno += parent_line # Make the state transition for the parser. If there isn't a transition # from the current state to a new state for this type, then we're in a # pattern that isn't actually locals. In that case we can return []. state = transitions[state].fetch(type) { return [] } # If we hit an identifier, then add it to our list. next if type != :on_ident location = Location.token( line: lineno, char: line_counts[lineno - 1][column], column: column, size: value.size ) locals << Ident.new(value: value, location: location) end end