lib/opal/parser/patch.rb
# backtick_javascript: true
# frozen_string_literal: true
if RUBY_ENGINE == 'opal'
class Parser::Lexer
def source_buffer=(source_buffer)
@source_buffer = source_buffer
if @source_buffer
source = @source_buffer.source
# Force UTF8 unpacking even if JS works with UTF-16/UCS-2
# See: https://mathiasbynens.be/notes/javascript-encoding
@source_pts = source.unpack('U*')
else
@source_pts = nil
end
# Since parser v3.2.1 Parser::Lexer has @strings
if @strings
@strings.source_buffer = @source_buffer
@strings.source_pts = @source_pts
end
end
end
class Parser::Lexer::Literal
undef :extend_string
def extend_string(string, ts, te)
@buffer_s ||= ts
@buffer_e = te
# Patch for opal-parser, original:
# @buffer << string
@buffer += string
end
end
class Parser::Source::Buffer
def source_lines
@lines ||= begin
lines = @source.lines.to_a
lines << '' if @source.end_with?("\n")
lines.map { |line| line.chomp("\n") }
end
end
end
class Parser::Builders::Default
def check_lvar_name(name, loc)
# https://javascript.info/regexp-unicode
if name =~ `new RegExp('^[\\p{Ll}|_][\\p{L}\\p{Nl}\\p{Nd}_]*$', 'u')`
# OK
else
diagnostic :error, :lvar_name, { name: name }, loc
end
end
# Taken From:
# https://github.com/whitequark/parser/blob/a7c638b7b205db9213a56897b41a8e5620df766e/lib/parser/builders/default.rb#L388
def dedent_string(node, dedent_level)
unless dedent_level.nil?
dedenter = ::Parser::Lexer::Dedenter.new(dedent_level)
case node.type
when :str
node = node.updated(nil, [dedenter.dedent(node.children.first)])
when :dstr, :xstr
children = node.children.map do |str_node|
if str_node.type == :str
str_node = str_node.updated(nil, [dedenter.dedent(str_node.children.first)])
next nil if str_node.children.first.empty?
else
dedenter.interrupt
end
str_node
end
node = node.updated(nil, children.compact)
end
end
node
end
end
class Parser::Lexer::Dedenter
# Taken From:
# https://github.com/whitequark/parser/blob/b7a08031523d05b2f76b0bab22fac00b1d3fe653/lib/parser/lexer/dedenter.rb#L36
def dedent(string)
original_encoding = string.encoding
# Prevent the following error when processing binary encoded source.
# "\xC0".split # => ArgumentError (invalid byte sequence in UTF-8)
lines = string.force_encoding(Encoding::BINARY).split("\\\n")
if lines.length == 1
# If the line continuation sequence was found but there is no second
# line, it was not really a line continuation and must be ignored.
lines = [string.force_encoding(original_encoding)]
else
lines.map! { |s| s.force_encoding(original_encoding) }
end
lines.each_with_index do |line, index|
next if (index == 0) && !@at_line_begin
left_to_remove = @dedent_level
remove = 0
line.each_char do |char|
break if left_to_remove <= 0
case char
when "\s"
remove += 1
left_to_remove -= 1
when "\t"
break if TAB_WIDTH * (remove / TAB_WIDTH + 1) > @dedent_level
remove += 1
left_to_remove -= TAB_WIDTH
else
# no more spaces or tabs
break
end
end
lines[index] = line[remove..-1]
end
string = lines.join
@at_line_begin = string.end_with?("\n")
string
end
end
end
module AST::Processor::Mixin
undef process
# This patch to #process removes a bit of dynamic abilities (removed
# call to node.to_ast) and it tries to optimize away the string
# operations and method existence check by caching them inside a
# processor.
#
# This is the second most inefficient call in the compilation phase
# so an optimization may be warranted.
def process(node)
return if node.nil?
@_on_handler_cache ||= {}
type = node.type
on_handler = @_on_handler_cache[type] ||= begin
handler = :"on_#{type}"
handler = :handler_missing unless respond_to?(handler)
handler
end
send(on_handler, node) || node
end
end
class Parser::Builders::Default
# string_value raises on invalid UTF-8 strings, like "\x80",
# otherwise it's the same as value.
undef string_value
def string_value(token)
value(token)
end
end