rubocop-ast 0.5.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rubocop/ast.rb +17 -0
- data/lib/rubocop/ast/builder.rb +1 -0
- data/lib/rubocop/ast/node.rb +44 -125
- data/lib/rubocop/ast/node/array_node.rb +1 -0
- data/lib/rubocop/ast/node/block_node.rb +1 -0
- data/lib/rubocop/ast/node/def_node.rb +5 -0
- data/lib/rubocop/ast/node/keyword_splat_node.rb +1 -0
- data/lib/rubocop/ast/node/mixin/collection_node.rb +1 -0
- data/lib/rubocop/ast/node/mixin/descendence.rb +116 -0
- data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +2 -0
- data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +9 -0
- data/lib/rubocop/ast/node/mixin/numeric_node.rb +1 -0
- data/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +7 -3
- data/lib/rubocop/ast/node/pair_node.rb +4 -0
- data/lib/rubocop/ast/node/regexp_node.rb +9 -4
- data/lib/rubocop/ast/node_pattern.rb +44 -870
- data/lib/rubocop/ast/node_pattern/builder.rb +72 -0
- data/lib/rubocop/ast/node_pattern/comment.rb +45 -0
- data/lib/rubocop/ast/node_pattern/compiler.rb +104 -0
- data/lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb +56 -0
- data/lib/rubocop/ast/node_pattern/compiler/binding.rb +78 -0
- data/lib/rubocop/ast/node_pattern/compiler/debug.rb +168 -0
- data/lib/rubocop/ast/node_pattern/compiler/node_pattern_subcompiler.rb +146 -0
- data/lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb +420 -0
- data/lib/rubocop/ast/node_pattern/compiler/subcompiler.rb +57 -0
- data/lib/rubocop/ast/node_pattern/lexer.rb +70 -0
- data/lib/rubocop/ast/node_pattern/lexer.rex +39 -0
- data/lib/rubocop/ast/node_pattern/lexer.rex.rb +182 -0
- data/lib/rubocop/ast/node_pattern/method_definer.rb +143 -0
- data/lib/rubocop/ast/node_pattern/node.rb +275 -0
- data/lib/rubocop/ast/node_pattern/parser.racc.rb +470 -0
- data/lib/rubocop/ast/node_pattern/parser.rb +66 -0
- data/lib/rubocop/ast/node_pattern/parser.y +103 -0
- data/lib/rubocop/ast/node_pattern/sets.rb +37 -0
- data/lib/rubocop/ast/node_pattern/with_meta.rb +111 -0
- data/lib/rubocop/ast/processed_source.rb +5 -1
- data/lib/rubocop/ast/traversal.rb +149 -172
- data/lib/rubocop/ast/version.rb +1 -1
- metadata +37 -3
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
class NodePattern
|
6
|
+
class Compiler
|
7
|
+
# Base class for subcompilers
|
8
|
+
# Implements visitor pattern
|
9
|
+
#
|
10
|
+
# Doc on how this fits in the compiling process:
|
11
|
+
# /doc/modules/ROOT/pages/node_pattern.md
|
12
|
+
class Subcompiler
|
13
|
+
attr_reader :compiler
|
14
|
+
|
15
|
+
def initialize(compiler)
|
16
|
+
@compiler = compiler
|
17
|
+
@node = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile(node)
|
21
|
+
prev = @node
|
22
|
+
@node = node
|
23
|
+
do_compile
|
24
|
+
ensure
|
25
|
+
@node = prev
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :node
|
33
|
+
|
34
|
+
def do_compile
|
35
|
+
send(self.class.registry.fetch(node.type, :visit_other_type))
|
36
|
+
end
|
37
|
+
|
38
|
+
@registry = {}
|
39
|
+
class << self
|
40
|
+
attr_reader :registry
|
41
|
+
|
42
|
+
def method_added(method)
|
43
|
+
@registry[Regexp.last_match(1).to_sym] = method if method =~ /^visit_(.*)/
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def inherited(base)
|
48
|
+
us = self
|
49
|
+
base.class_eval { @registry = us.registry.dup }
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require_relative 'lexer.rex'
|
5
|
+
rescue LoadError
|
6
|
+
msg = '*** You must run `rake generate` to generate the lexer and the parser ***'
|
7
|
+
puts '*' * msg.length, msg, '*' * msg.length
|
8
|
+
raise
|
9
|
+
end
|
10
|
+
|
11
|
+
module RuboCop
|
12
|
+
module AST
|
13
|
+
class NodePattern
|
14
|
+
# Lexer class for `NodePattern`
|
15
|
+
#
|
16
|
+
# Doc on how this fits in the compiling process:
|
17
|
+
# /doc/modules/ROOT/pages/node_pattern.md
|
18
|
+
class Lexer < LexerRex
|
19
|
+
Error = ScanError
|
20
|
+
|
21
|
+
REGEXP_OPTIONS = {
|
22
|
+
'i' => ::Regexp::IGNORECASE,
|
23
|
+
'm' => ::Regexp::MULTILINE,
|
24
|
+
'x' => ::Regexp::EXTENDED,
|
25
|
+
'o' => 0
|
26
|
+
}.freeze
|
27
|
+
private_constant :REGEXP_OPTIONS
|
28
|
+
|
29
|
+
attr_reader :source_buffer, :comments, :tokens
|
30
|
+
|
31
|
+
def initialize(source)
|
32
|
+
@tokens = []
|
33
|
+
super()
|
34
|
+
parse(source)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @return [token]
|
40
|
+
def emit(type)
|
41
|
+
value = ss[1] || ss.matched
|
42
|
+
value = yield value if block_given?
|
43
|
+
token = token(type, value)
|
44
|
+
@tokens << token
|
45
|
+
token
|
46
|
+
end
|
47
|
+
|
48
|
+
def emit_comment
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def emit_regexp
|
53
|
+
body = ss[1]
|
54
|
+
options = ss[2]
|
55
|
+
flag = options.each_char.map { |c| REGEXP_OPTIONS[c] }.sum
|
56
|
+
|
57
|
+
emit(:tREGEXP) { Regexp.new(body, flag) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def do_parse
|
61
|
+
# Called by the generated `parse` method, do nothing here.
|
62
|
+
end
|
63
|
+
|
64
|
+
def token(type, value)
|
65
|
+
[type, value]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# The only difficulty is to distinguish: `fn(argument)` from `fn (sequence)`.
|
2
|
+
# The presence of the whitespace determines if it is an _argument_ to the
|
3
|
+
# function call `fn` or if a _sequence_ follows the function call.
|
4
|
+
#
|
5
|
+
# If there is the potential for an argument list, the lexer enters the state `:ARG`.
|
6
|
+
# The rest of the times, the state is `nil`.
|
7
|
+
#
|
8
|
+
# In case of an argument list, :tARG_LIST is emitted instead of a '('.
|
9
|
+
# Therefore, the token '(' always signals the beginning of a sequence.
|
10
|
+
|
11
|
+
class RuboCop::AST::NodePattern::LexerRex
|
12
|
+
|
13
|
+
macros
|
14
|
+
SYMBOL_NAME /[\w+@*\/?!<>=~|%^-]+|\[\]=?/
|
15
|
+
IDENTIFIER /[a-zA-Z_][a-zA-Z0-9_-]*/
|
16
|
+
REGEXP_BODY /(?:[^\/]|\\\/)*/
|
17
|
+
REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
18
|
+
rules
|
19
|
+
/\s+/
|
20
|
+
/:(#{SYMBOL_NAME})/o { emit :tSYMBOL, &:to_sym }
|
21
|
+
/"(.+?)"/ { emit :tSTRING }
|
22
|
+
/[-+]?\d+\.\d+/ { emit :tNUMBER, &:to_f }
|
23
|
+
/[-+]?\d+/ { emit :tNUMBER, &:to_i }
|
24
|
+
/#{Regexp.union(
|
25
|
+
%w"( ) { | } [ ] < > $ ! ^ ` ... + * ? ,"
|
26
|
+
)}/o { emit ss.matched, &:to_sym }
|
27
|
+
/#{REGEXP}/o { emit_regexp }
|
28
|
+
/%([A-Z:][a-zA-Z_:]+)/ { emit :tPARAM_CONST }
|
29
|
+
/%([a-z_]+)/ { emit :tPARAM_NAMED }
|
30
|
+
/%(\d*)/ { emit(:tPARAM_NUMBER) { |s| s.empty? ? 1 : s.to_i } } # Map `%` to `%1`
|
31
|
+
/_(#{IDENTIFIER})/o { emit :tUNIFY }
|
32
|
+
/_/o { emit :tWILDCARD }
|
33
|
+
/\#(#{IDENTIFIER}[!?]?)/o { @state = :ARG; emit :tFUNCTION_CALL, &:to_sym }
|
34
|
+
/#{IDENTIFIER}\?/o { @state = :ARG; emit :tPREDICATE, &:to_sym }
|
35
|
+
/#{IDENTIFIER}/o { emit :tNODE_TYPE, &:to_sym }
|
36
|
+
:ARG /\(/ { @state = nil; emit :tARG_LIST }
|
37
|
+
:ARG // { @state = nil }
|
38
|
+
/\#.*/ { emit_comment }
|
39
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
#--
|
4
|
+
# This file is automatically generated. Do not modify it.
|
5
|
+
# Generated by: oedipus_lex version 2.5.2.
|
6
|
+
# Source: lib/rubocop/ast/node_pattern/lexer.rex
|
7
|
+
#++
|
8
|
+
|
9
|
+
# The only difficulty is to distinguish: `fn(argument)` from `fn (sequence)`.
|
10
|
+
# The presence of the whitespace determines if it is an _argument_ to the
|
11
|
+
# function call `fn` or if a _sequence_ follows the function call.
|
12
|
+
#
|
13
|
+
# If there is the potential for an argument list, the lexer enters the state `:ARG`.
|
14
|
+
# The rest of the times, the state is `nil`.
|
15
|
+
#
|
16
|
+
# In case of an argument list, :tARG_LIST is emitted instead of a '('.
|
17
|
+
# Therefore, the token '(' always signals the beginning of a sequence.
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
# The generated lexer RuboCop::AST::NodePattern::LexerRex
|
22
|
+
|
23
|
+
class RuboCop::AST::NodePattern::LexerRex
|
24
|
+
require 'strscan'
|
25
|
+
|
26
|
+
# :stopdoc:
|
27
|
+
SYMBOL_NAME = /[\w+@*\/?!<>=~|%^-]+|\[\]=?/
|
28
|
+
IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/
|
29
|
+
REGEXP_BODY = /(?:[^\/]|\\\/)*/
|
30
|
+
REGEXP = /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
31
|
+
# :startdoc:
|
32
|
+
# :stopdoc:
|
33
|
+
class LexerError < StandardError ; end
|
34
|
+
class ScanError < LexerError ; end
|
35
|
+
# :startdoc:
|
36
|
+
|
37
|
+
##
|
38
|
+
# The file name / path
|
39
|
+
|
40
|
+
attr_accessor :filename
|
41
|
+
|
42
|
+
##
|
43
|
+
# The StringScanner for this lexer.
|
44
|
+
|
45
|
+
attr_accessor :ss
|
46
|
+
|
47
|
+
##
|
48
|
+
# The current lexical state.
|
49
|
+
|
50
|
+
attr_accessor :state
|
51
|
+
|
52
|
+
alias :match :ss
|
53
|
+
|
54
|
+
##
|
55
|
+
# The match groups for the current scan.
|
56
|
+
|
57
|
+
def matches
|
58
|
+
m = (1..9).map { |i| ss[i] }
|
59
|
+
m.pop until m[-1] or m.empty?
|
60
|
+
m
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Yields on the current action.
|
65
|
+
|
66
|
+
def action
|
67
|
+
yield
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
##
|
72
|
+
# The current scanner class. Must be overridden in subclasses.
|
73
|
+
|
74
|
+
def scanner_class
|
75
|
+
StringScanner
|
76
|
+
end unless instance_methods(false).map(&:to_s).include?("scanner_class")
|
77
|
+
|
78
|
+
##
|
79
|
+
# Parse the given string.
|
80
|
+
|
81
|
+
def parse str
|
82
|
+
self.ss = scanner_class.new str
|
83
|
+
self.state ||= nil
|
84
|
+
|
85
|
+
do_parse
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Read in and parse the file at +path+.
|
90
|
+
|
91
|
+
def parse_file path
|
92
|
+
self.filename = path
|
93
|
+
open path do |f|
|
94
|
+
parse f.read
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# The current location in the parse.
|
100
|
+
|
101
|
+
def location
|
102
|
+
[
|
103
|
+
(filename || "<input>"),
|
104
|
+
].compact.join(":")
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Lex the next token.
|
109
|
+
|
110
|
+
def next_token
|
111
|
+
|
112
|
+
token = nil
|
113
|
+
|
114
|
+
until ss.eos? or token do
|
115
|
+
token =
|
116
|
+
case state
|
117
|
+
when nil then
|
118
|
+
case
|
119
|
+
when ss.skip(/\s+/) then
|
120
|
+
# do nothing
|
121
|
+
when ss.skip(/:(#{SYMBOL_NAME})/o) then
|
122
|
+
action { emit :tSYMBOL, &:to_sym }
|
123
|
+
when ss.skip(/"(.+?)"/) then
|
124
|
+
action { emit :tSTRING }
|
125
|
+
when ss.skip(/[-+]?\d+\.\d+/) then
|
126
|
+
action { emit :tNUMBER, &:to_f }
|
127
|
+
when ss.skip(/[-+]?\d+/) then
|
128
|
+
action { emit :tNUMBER, &:to_i }
|
129
|
+
when ss.skip(/#{Regexp.union(
|
130
|
+
%w"( ) { | } [ ] < > $ ! ^ ` ... + * ? ,"
|
131
|
+
)}/o) then
|
132
|
+
action { emit ss.matched, &:to_sym }
|
133
|
+
when ss.skip(/#{REGEXP}/o) then
|
134
|
+
action { emit_regexp }
|
135
|
+
when ss.skip(/%([A-Z:][a-zA-Z_:]+)/) then
|
136
|
+
action { emit :tPARAM_CONST }
|
137
|
+
when ss.skip(/%([a-z_]+)/) then
|
138
|
+
action { emit :tPARAM_NAMED }
|
139
|
+
when ss.skip(/%(\d*)/) then
|
140
|
+
action { emit(:tPARAM_NUMBER) { |s| s.empty? ? 1 : s.to_i } } # Map `%` to `%1`
|
141
|
+
when ss.skip(/_(#{IDENTIFIER})/o) then
|
142
|
+
action { emit :tUNIFY }
|
143
|
+
when ss.skip(/_/o) then
|
144
|
+
action { emit :tWILDCARD }
|
145
|
+
when ss.skip(/\#(#{IDENTIFIER}[!?]?)/o) then
|
146
|
+
action { @state = :ARG; emit :tFUNCTION_CALL, &:to_sym }
|
147
|
+
when ss.skip(/#{IDENTIFIER}\?/o) then
|
148
|
+
action { @state = :ARG; emit :tPREDICATE, &:to_sym }
|
149
|
+
when ss.skip(/#{IDENTIFIER}/o) then
|
150
|
+
action { emit :tNODE_TYPE, &:to_sym }
|
151
|
+
when ss.skip(/\#.*/) then
|
152
|
+
action { emit_comment }
|
153
|
+
else
|
154
|
+
text = ss.string[ss.pos .. -1]
|
155
|
+
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
156
|
+
end
|
157
|
+
when :ARG then
|
158
|
+
case
|
159
|
+
when ss.skip(/\(/) then
|
160
|
+
action { @state = nil; emit :tARG_LIST }
|
161
|
+
when ss.skip(//) then
|
162
|
+
action { @state = nil }
|
163
|
+
else
|
164
|
+
text = ss.string[ss.pos .. -1]
|
165
|
+
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
166
|
+
end
|
167
|
+
else
|
168
|
+
raise ScanError, "undefined state at #{location}: '#{state}'"
|
169
|
+
end # token = case state
|
170
|
+
|
171
|
+
next unless token # allow functions to trigger redo w/ nil
|
172
|
+
end # while
|
173
|
+
|
174
|
+
raise LexerError, "bad lexical result at #{location}: #{token.inspect}" unless
|
175
|
+
token.nil? || (Array === token && token.size >= 2)
|
176
|
+
|
177
|
+
# auto-switch state
|
178
|
+
self.state = token.last if token && token.first == :state
|
179
|
+
|
180
|
+
token
|
181
|
+
end # def next_token
|
182
|
+
end # class
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
class NodePattern
|
6
|
+
# Functionality to turn `match_code` into methods/lambda
|
7
|
+
module MethodDefiner
|
8
|
+
def def_node_matcher(base, method_name, **defaults)
|
9
|
+
def_helper(base, method_name, **defaults) do |name|
|
10
|
+
params = emit_params('param0 = self')
|
11
|
+
<<~RUBY
|
12
|
+
def #{name}(#{params})
|
13
|
+
#{VAR} = param0
|
14
|
+
#{compile_init}
|
15
|
+
#{emit_method_code}
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def def_node_search(base, method_name, **defaults)
|
22
|
+
def_helper(base, method_name, **defaults) do |name|
|
23
|
+
emit_node_search(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile_as_lambda
|
28
|
+
<<~RUBY
|
29
|
+
->(#{emit_params('param0')}, block: nil) do
|
30
|
+
#{VAR} = param0
|
31
|
+
#{compile_init}
|
32
|
+
#{emit_lambda_code}
|
33
|
+
end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
|
37
|
+
def as_lambda
|
38
|
+
eval(compile_as_lambda) # rubocop:disable Security/Eval
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# This method minimizes the closure for our method
|
44
|
+
def wrapping_block(method_name, **defaults)
|
45
|
+
proc do |*args, **values|
|
46
|
+
send method_name, *args, **defaults, **values
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def def_helper(base, method_name, **defaults)
|
51
|
+
location = caller_locations(3, 1).first
|
52
|
+
unless defaults.empty?
|
53
|
+
call = :"without_defaults_#{method_name}"
|
54
|
+
base.send :define_method, method_name, &wrapping_block(call, **defaults)
|
55
|
+
method_name = call
|
56
|
+
end
|
57
|
+
src = yield method_name
|
58
|
+
base.class_eval(src, location.path, location.lineno)
|
59
|
+
end
|
60
|
+
|
61
|
+
def emit_node_search(method_name)
|
62
|
+
if method_name.to_s.end_with?('?')
|
63
|
+
on_match = 'return true'
|
64
|
+
else
|
65
|
+
args = emit_params(":#{method_name}", 'param0', forwarding: true)
|
66
|
+
prelude = "return enum_for(#{args}) unless block_given?\n"
|
67
|
+
on_match = emit_yield_capture(VAR)
|
68
|
+
end
|
69
|
+
emit_node_search_body(method_name, prelude: prelude, on_match: on_match)
|
70
|
+
end
|
71
|
+
|
72
|
+
def emit_node_search_body(method_name, prelude:, on_match:)
|
73
|
+
<<~RUBY
|
74
|
+
def #{method_name}(#{emit_params('param0')})
|
75
|
+
#{compile_init}
|
76
|
+
#{prelude}
|
77
|
+
param0.each_node do |#{VAR}|
|
78
|
+
if #{match_code}
|
79
|
+
#{on_match}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
RUBY
|
85
|
+
end
|
86
|
+
|
87
|
+
def emit_yield_capture(when_no_capture = '', yield_with: 'yield')
|
88
|
+
yield_val = if captures.zero?
|
89
|
+
when_no_capture
|
90
|
+
elsif captures == 1
|
91
|
+
'captures[0]' # Circumvent https://github.com/jruby/jruby/issues/5710
|
92
|
+
else
|
93
|
+
'*captures'
|
94
|
+
end
|
95
|
+
"#{yield_with}(#{yield_val})"
|
96
|
+
end
|
97
|
+
|
98
|
+
def emit_retval
|
99
|
+
if captures.zero?
|
100
|
+
'true'
|
101
|
+
elsif captures == 1
|
102
|
+
'captures[0]'
|
103
|
+
else
|
104
|
+
'captures'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def emit_param_list
|
109
|
+
(1..positional_parameters).map { |n| "param#{n}" }.join(',')
|
110
|
+
end
|
111
|
+
|
112
|
+
def emit_keyword_list(forwarding: false)
|
113
|
+
pattern = "%<keyword>s: #{'%<keyword>s' if forwarding}"
|
114
|
+
named_parameters.map { |k| format(pattern, keyword: k) }.join(',')
|
115
|
+
end
|
116
|
+
|
117
|
+
def emit_params(*first, forwarding: false)
|
118
|
+
params = emit_param_list
|
119
|
+
keywords = emit_keyword_list(forwarding: forwarding)
|
120
|
+
[*first, params, keywords].reject(&:empty?).join(',')
|
121
|
+
end
|
122
|
+
|
123
|
+
def emit_method_code
|
124
|
+
<<~RUBY
|
125
|
+
return unless #{match_code}
|
126
|
+
block_given? ? #{emit_yield_capture} : (return #{emit_retval})
|
127
|
+
RUBY
|
128
|
+
end
|
129
|
+
|
130
|
+
def emit_lambda_code
|
131
|
+
<<~RUBY
|
132
|
+
return unless #{match_code}
|
133
|
+
block ? #{emit_yield_capture(yield_with: 'block.call')} : (return #{emit_retval})
|
134
|
+
RUBY
|
135
|
+
end
|
136
|
+
|
137
|
+
def compile_init
|
138
|
+
"captures = Array.new(#{captures})" if captures.positive?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|