Class: RuboCop::NodePattern::Compiler
- Inherits:
-
Object
- Object
- RuboCop::NodePattern::Compiler
- Defined in:
- lib/rubocop/node_pattern.rb
Overview
Builds Ruby code which implements a pattern
Constant Summary collapse
- RSYM =
%r{:(?:[\w+-@_*/?!<>~|%^]+|==|\[\]=?)}
- ID_CHAR =
/[a-zA-Z_]/
- META_CHAR =
/\(|\)|\{|\}|\[|\]|\$\.\.\.|\$|!|\^|\.\.\./
- TOKEN =
/\G(?:\s+|#{META_CHAR}|#{ID_CHAR}+\??|%\d*|\d+|#{RSYM}|.)/
- NODE =
/\A#{ID_CHAR}+\Z/
- PREDICATE =
/\A#{ID_CHAR}+\?\Z/
- LITERAL =
/\A(?:#{RSYM}|\d+|nil)\Z/
- WILDCARD =
/\A_#{ID_CHAR}*\Z/
- PARAM =
/\A%\d*\Z/
- CLOSING =
/\A(?:\)|\}|\])\Z/
Instance Attribute Summary collapse
-
#match_code ⇒ Object
readonly
Returns the value of attribute match_code.
Instance Method Summary collapse
- #compile_ascend(tokens, cur_node, seq_head) ⇒ Object
- #compile_capt_ellip(tokens, cur_node, terms, index) ⇒ Object
- #compile_capture(tokens, cur_node, seq_head) ⇒ Object
- #compile_ellipsis(tokens, cur_node, terms, index) ⇒ Object
- #compile_expr(tokens, cur_node, seq_head) ⇒ Object
- #compile_intersect(tokens, cur_node, seq_head) ⇒ Object
- #compile_literal(cur_node, literal, seq_head) ⇒ Object
- #compile_negation(tokens, cur_node, seq_head) ⇒ Object
- #compile_nodetype(cur_node, type) ⇒ Object
- #compile_param(cur_node, number, seq_head) ⇒ Object
- #compile_predicate(cur_node, predicate, seq_head) ⇒ Object
- #compile_seq(tokens, cur_node, seq_head) ⇒ Object
- #compile_seq_tail(tokens, cur_node) ⇒ Object
- #compile_seq_terms(tokens, cur_node) ⇒ Object
- #compile_union(tokens, cur_node, seq_head) ⇒ Object
- #compile_wildcard(cur_node, name, seq_head) ⇒ Object
- #emit_capture_list ⇒ Object
- #emit_method_code ⇒ Object
- #emit_param_list ⇒ Object
- #emit_retval ⇒ Object
- #emit_trailing_param_list ⇒ Object
- #fail_due_to(message) ⇒ Object
-
#initialize(str, node_var = 'node0') ⇒ Compiler
constructor
A new instance of Compiler.
- #join_terms(init, terms, operator) ⇒ Object
- #next_capture ⇒ Object
- #run(node_var) ⇒ Object
Constructor Details
#initialize(str, node_var = 'node0') ⇒ Compiler
Returns a new instance of Compiler.
100 101 102 103 104 105 106 107 108 109 |
# File 'lib/rubocop/node_pattern.rb', line 100 def initialize(str, node_var = 'node0') @string = str @temps = 0 # avoid name clashes between temp variables @captures = 0 # number of captures seen @unify = {} # named wildcard -> temp variable number @params = 0 # highest % (param) number seen run(node_var) end |
Instance Attribute Details
#match_code ⇒ Object (readonly)
Returns the value of attribute match_code.
98 99 100 |
# File 'lib/rubocop/node_pattern.rb', line 98 def match_code @match_code end |
Instance Method Details
#compile_ascend(tokens, cur_node, seq_head) ⇒ Object
254 255 256 257 258 |
# File 'lib/rubocop/node_pattern.rb', line 254 def compile_ascend(tokens, cur_node, seq_head) "(#{cur_node}.parent && " << compile_expr(tokens, "#{cur_node}.parent", seq_head) << ')' end |
#compile_capt_ellip(tokens, cur_node, terms, index) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/rubocop/node_pattern.rb', line 181 def compile_capt_ellip(tokens, cur_node, terms, index) capture = next_capture if (term = compile_seq_tail(tokens, "#{cur_node}.children.last")) terms << "(#{cur_node}.children.size > #{index})" terms << term terms << "(#{capture} = #{cur_node}.children[#{index}..-2])" else terms << "(#{cur_node}.children.size >= #{index})" if index > 0 terms << "(#{capture} = #{cur_node}.children[#{index}..-1])" end terms end |
#compile_capture(tokens, cur_node, seq_head) ⇒ Object
244 245 246 247 248 |
# File 'lib/rubocop/node_pattern.rb', line 244 def compile_capture(tokens, cur_node, seq_head) "(#{next_capture} = #{cur_node}#{'.type' if seq_head}; " << compile_expr(tokens, cur_node, seq_head) << ')' end |
#compile_ellipsis(tokens, cur_node, terms, index) ⇒ Object
171 172 173 174 175 176 177 178 179 |
# File 'lib/rubocop/node_pattern.rb', line 171 def compile_ellipsis(tokens, cur_node, terms, index) if (term = compile_seq_tail(tokens, "#{cur_node}.children.last")) terms << "(#{cur_node}.children.size > #{index})" terms << term elsif index > 0 terms << "(#{cur_node}.children.size >= #{index})" end terms end |
#compile_expr(tokens, cur_node, seq_head) ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/rubocop/node_pattern.rb', line 118 def compile_expr(tokens, cur_node, seq_head) token = tokens.shift case token when '(' then compile_seq(tokens, cur_node, seq_head) when '{' then compile_union(tokens, cur_node, seq_head) when '[' then compile_intersect(tokens, cur_node, seq_head) when '!' then compile_negation(tokens, cur_node, seq_head) when '$' then compile_capture(tokens, cur_node, seq_head) when '^' then compile_ascend(tokens, cur_node, seq_head) when WILDCARD then compile_wildcard(cur_node, token[1..-1], seq_head) when LITERAL then compile_literal(cur_node, token, seq_head) when PREDICATE then compile_predicate(cur_node, token, seq_head) when NODE then compile_nodetype(cur_node, token) when PARAM then compile_param(cur_node, token[1..-1], seq_head) when CLOSING then fail_due_to("#{token} in invalid position") when nil then fail_due_to('pattern ended prematurely') else fail_due_to("invalid token #{token.inspect}") end end |
#compile_intersect(tokens, cur_node, seq_head) ⇒ Object
229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/rubocop/node_pattern.rb', line 229 def compile_intersect(tokens, cur_node, seq_head) fail_due_to('empty intersection') if tokens.first == ']' init = "temp#{@temps += 1} = #{cur_node}" cur_node = "temp#{@temps}" terms = [] until tokens.first == ']' terms << compile_expr(tokens, cur_node, seq_head) end tokens.shift join_terms(init, terms, ' && ') end |
#compile_literal(cur_node, literal, seq_head) ⇒ Object
274 275 276 |
# File 'lib/rubocop/node_pattern.rb', line 274 def compile_literal(cur_node, literal, seq_head) "(#{cur_node}#{'.type' if seq_head} == #{literal})" end |
#compile_negation(tokens, cur_node, seq_head) ⇒ Object
250 251 252 |
# File 'lib/rubocop/node_pattern.rb', line 250 def compile_negation(tokens, cur_node, seq_head) '(!' << compile_expr(tokens, cur_node, seq_head) << ')' end |
#compile_nodetype(cur_node, type) ⇒ Object
282 283 284 |
# File 'lib/rubocop/node_pattern.rb', line 282 def compile_nodetype(cur_node, type) "(#{cur_node} && #{cur_node}.#{type}_type?)" end |
#compile_param(cur_node, number, seq_head) ⇒ Object
286 287 288 289 290 |
# File 'lib/rubocop/node_pattern.rb', line 286 def compile_param(cur_node, number, seq_head) number = number.empty? ? 1 : Integer(number) @params = number if number > @params "(#{cur_node}#{'.type' if seq_head} == param#{number})" end |
#compile_predicate(cur_node, predicate, seq_head) ⇒ Object
278 279 280 |
# File 'lib/rubocop/node_pattern.rb', line 278 def compile_predicate(cur_node, predicate, seq_head) "(#{cur_node}#{'.type' if seq_head}.#{predicate})" end |
#compile_seq(tokens, cur_node, seq_head) ⇒ Object
138 139 140 141 142 143 144 145 146 147 |
# File 'lib/rubocop/node_pattern.rb', line 138 def compile_seq(tokens, cur_node, seq_head) fail_due_to('empty parentheses') if tokens.first == ')' fail_due_to('parentheses at sequence head') if seq_head init = "temp#{@temps += 1} = #{cur_node}" cur_node = "temp#{@temps}" terms = compile_seq_terms(tokens, cur_node) join_terms(init, terms, ' && ') end |
#compile_seq_tail(tokens, cur_node) ⇒ Object
194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/rubocop/node_pattern.rb', line 194 def compile_seq_tail(tokens, cur_node) tokens.shift if tokens.first == ')' tokens.shift nil else expr = compile_expr(tokens, cur_node, false) fail_due_to('missing )') unless tokens.shift == ')' expr end end |
#compile_seq_terms(tokens, cur_node) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/rubocop/node_pattern.rb', line 149 def compile_seq_terms(tokens, cur_node) terms = [] index = nil until tokens.first == ')' if tokens.first == '...' return compile_ellipsis(tokens, cur_node, terms, index || 0) elsif tokens.first == '$...' return compile_capt_ellip(tokens, cur_node, terms, index || 0) elsif index.nil? terms << compile_expr(tokens, cur_node, true) index = 0 else child_node = "#{cur_node}.children[#{index}]" terms << compile_expr(tokens, child_node, false) index += 1 end end terms << "(#{cur_node}.children.size == #{index})" tokens.shift # drop concluding ) terms end |
#compile_union(tokens, cur_node, seq_head) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/rubocop/node_pattern.rb', line 206 def compile_union(tokens, cur_node, seq_head) fail_due_to('empty union') if tokens.first == '}' init = "temp#{@temps += 1} = #{cur_node}" cur_node = "temp#{@temps}" terms = [] captures_before = @captures terms << compile_expr(tokens, cur_node, seq_head) captures_after = @captures until tokens.first == '}' @captures = captures_before terms << compile_expr(tokens, cur_node, seq_head) if @captures != captures_after fail_due_to('each branch of {} must have same # of captures') end end tokens.shift join_terms(init, terms, ' || ') end |
#compile_wildcard(cur_node, name, seq_head) ⇒ Object
260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/rubocop/node_pattern.rb', line 260 def compile_wildcard(cur_node, name, seq_head) if name.empty? 'true' elsif @unify.key?(name) # we have already seen a wildcard with this name before # so the value it matched the first time will already be stored # in a temp. check if this value matches the one stored in the temp "(#{cur_node}#{'.type' if seq_head} == temp#{@unify[name]})" else n = @unify[name] = (@temps += 1) "(temp#{n} = #{cur_node}#{'.type' if seq_head}; true)" end end |
#emit_capture_list ⇒ Object
300 301 302 |
# File 'lib/rubocop/node_pattern.rb', line 300 def emit_capture_list (1..@captures).map { |n| "capture#{n}" }.join(',') end |
#emit_method_code ⇒ Object
323 324 325 326 327 328 |
# File 'lib/rubocop/node_pattern.rb', line 323 def emit_method_code <<-CODE return nil unless #{@match_code} block_given? ? yield(#{emit_capture_list}) : (return #{emit_retval}) CODE end |
#emit_param_list ⇒ Object
314 315 316 |
# File 'lib/rubocop/node_pattern.rb', line 314 def emit_param_list (1..@params).map { |n| "param#{n}" }.join(',') end |
#emit_retval ⇒ Object
304 305 306 307 308 309 310 311 312 |
# File 'lib/rubocop/node_pattern.rb', line 304 def emit_retval if @captures.zero? 'true' elsif @captures == 1 'capture1' else "[#{emit_capture_list}]" end end |
#emit_trailing_param_list ⇒ Object
318 319 320 321 |
# File 'lib/rubocop/node_pattern.rb', line 318 def emit_trailing_param_list params = emit_param_list params.empty? ? '' : ',' << params end |
#fail_due_to(message) ⇒ Object
330 331 332 |
# File 'lib/rubocop/node_pattern.rb', line 330 def fail_due_to() fail Invalid, "Couldn't compile due to #{}. Pattern: #{@string}" end |
#join_terms(init, terms, operator) ⇒ Object
296 297 298 |
# File 'lib/rubocop/node_pattern.rb', line 296 def join_terms(init, terms, operator) '(' << init << ';' << terms.join(operator) << ')' end |
#next_capture ⇒ Object
292 293 294 |
# File 'lib/rubocop/node_pattern.rb', line 292 def next_capture "capture#{@captures += 1}" end |
#run(node_var) ⇒ Object
111 112 113 114 115 116 |
# File 'lib/rubocop/node_pattern.rb', line 111 def run(node_var) tokens = @string.scan(TOKEN) tokens.reject! { |token| token =~ /\A\s+\Z/ } @match_code = compile_expr(tokens, node_var, false) fail_due_to('unbalanced pattern') unless tokens.empty? end |