lib/opal/nodes/call/match3.rb
# frozen_string_literal: true
require 'opal/nodes/base'
require 'opal/nodes/call'
module Opal
module Nodes
# Handles match_with_lvasgn nodes which represent matching a regular expression
# with a right-hand side value and assigning the match result to a left-hand side variable.
class Match3Node < Base
handle :match_with_lvasgn
children :lhs, :rhs
def compile
sexp = s(:send, lhs, :=~, rhs)
# Handle named matches like: /(?<abc>b)/ =~ 'b'
if lhs.type == :regexp && lhs.children.first.type == :str
names = extract_names(lhs)
unless names.empty?
names_def = generate_names_definition
names_assignments = generate_names_assignments(names)
sexp = if stmt?
handle_statement(sexp, names_def, names_assignments)
else
handle_non_statement(sexp, names_def, names_assignments)
end
end
end
push process(sexp, @level)
end
private
def extract_names(regexp_node)
re = regexp_node.children.first.children.first
re.scan(/\(\?<([^>]*)>/).flatten.map(&:to_sym)
end
def generate_names_definition
# Generate names definition: $m3names = $~ ? $~.named_captures : {}
s(:lvasgn, :$m3names,
s(:if,
s(:gvar, :$~),
s(:send, s(:gvar, :$~), :named_captures),
s(:hash)
)
)
end
def generate_names_assignments(names)
# Generate names assignments: abc = $m3names[:abc]
names.map do |name|
s(:lvasgn, name,
s(:send,
s(:lvar, :$m3names),
:[],
s(:sym, name)
)
)
end
end
def handle_statement(sexp, names_def, names_assignments)
# We don't care about a return value of this one, so we
# ignore it and just assign the local variables.
#
# (/(?<abc>b)/ =~ 'f')
# $m3names = $~ ? $~.named_captures : {}
# abc = $m3names[:abc]
s(:begin, sexp, names_def, *names_assignments)
end
def handle_non_statement(sexp, names_def, names_assignments)
# We actually do care about a return value, so we must
# keep it saved.
#
# $m3tmp = (/(?<abc>b)/ =~ 'f')
# $m3names = $~ ? $~.named_captures : {}
# abc = $m3names[:abc]
# $m3tmp
s(:begin,
s(:lvasgn, :$m3tmp, sexp),
names_def,
*names_assignments,
s(:lvar, :$m3tmp)
)
end
end
end
end