class Opal::Nodes::RegexpNode
def compile
def compile flags.select! do |flag| if SUPPORTED_FLAGS =~ flag true else compiler.warning "Skipping the '#{flag}' Regexp flag as it's not widely supported by JavaScript vendors.", @sexp.line false end end if value.type == :str compile_static_regexp else compile_dynamic_regexp end end
def compile_dynamic_regexp
def compile_dynamic_regexp helper :regexp push '$regexp([' value.children.each_with_index do |v, index| push ', ' unless index.zero? push expr(v) end push ']' push ", '#{flags.join}'" if flags.any? push ")" end
def compile_static_regexp
def compile_static_regexp value = self.value.children[0] case value when '' push('/(?:)/') when /\(\?[(<>#]|[*+?]\+/ # Safari/WebKit will not execute javascript code if it contains a lookbehind literal RegExp # and they fail with "Syntax Error". This tricks their parser by disguising the literal RegExp # as string for the dynamic $regexp helper. Safari/Webkit will still fail to execute the RegExp, # but at least they will parse and run everything else. # # Also, let's compile a couple of more patterns into $regexp calls, as there are many syntax # errors in RubySpec when ran in reverse, while there shouldn't be (they should be catchable # errors) - at least since Node 17. static_as_dynamic(value) else push "#{Regexp.new(value).inspect}#{flags.join}" end end
def extract_flags_and_value
def extract_flags_and_value *values, flags_sexp = *children self.flags = flags_sexp.children.map(&:to_s) self.value = if values.empty? # empty regexp, we can process it inline s(:str, '') elsif single_line?(values) # simple plain regexp, we can put it inline values[0] else s(:dstr, *values) end # trimming when //x provided # required by parser gem, but JS doesn't support 'x' flag if flags.include?('x') parts = value.children.map do |part| if part.is_a?(::Opal::AST::Node) && part.type == :str trimmed_value = part.children[0].gsub(/\A\s*\#.*/, '').gsub(/\s/, '') s(:str, trimmed_value) else part end end self.value = value.updated(nil, parts) flags.delete('x') end if value.type == :str # Replacing \A -> ^, \z -> $, required for the parser gem self.value = s(:str, value.children[0].gsub('\A', '^').gsub('\z', '$')) end end
def initialize(*)
def initialize(*) super extract_flags_and_value end
def raw_value
def raw_value self.value = @sexp.loc.expression.source end
def single_line?(values)
def single_line?(values) return false if values.length > 1 value = values[0] # JavaScript doesn't support multiline regexp value.type != :str || !value.children[0].include?("\n") end
def static_as_dynamic(value)
def static_as_dynamic(value) helper :regexp push '$regexp(["' push value.gsub('\\', '\\\\\\\\') push '"]' push ", '#{flags.join}'" if flags.any? push ")" end