class Interscript::Compiler::Javascript

Attributes

ctx[RW]
maps_loaded[RW]

Public Class Methods

read_debug_data() click to toggle source
# File lib/interscript/compiler/javascript.rb, line 290
def self.read_debug_data
  self.ctx.eval "globalThis.map_debug || []"
end
reset_debug_data() click to toggle source
# File lib/interscript/compiler/javascript.rb, line 294
def self.reset_debug_data
  self.ctx.eval "globalThis.map_debug = []"
end

Public Instance Methods

build_regexp(r, map=@map) click to toggle source
# File lib/interscript/compiler/javascript.rb, line 130
def build_regexp(r, map=@map)
  from = compile_item(r.from, map, :re)
  before = compile_item(r.before, map, :re) if r.before
  after = compile_item(r.after, map, :re) if r.after
  not_before = compile_item(r.not_before, map, :re) if r.not_before
  not_after = compile_item(r.not_after, map, :re) if r.not_after

  re = ""
  re += "(?<=#{before})" if before
  re += "(?<!#{not_before})" if not_before
  re += from
  re += "(?!#{not_after})" if not_after
  re += "(?=#{after})" if after

  re
end
call(str, stage=:main) click to toggle source
# File lib/interscript/compiler/javascript.rb, line 285
def call(str, stage=:main)
  load
  self.class.ctx.eval "Interscript.transliterate(#{@map.name.to_json}, #{str.to_json}, #{stage.to_json})"
end
compile(map, debug: false) click to toggle source
# File lib/interscript/compiler/javascript.rb, line 9
def compile(map, debug: false)
  @map = map
  @parallel_trees = {}
  @parallel_regexps = {}
  @debug = debug
  c = "var map = function(Interscript) {"
  c << "Interscript.define_map(#{map.name.inspect}, function(Interscript, map) {\n";
  c << "map.dependencies = #{map.dependencies.map(&:full_name).to_json};\n"
  c

  map.aliases.each do |name, value|
    val = compile_item(value.data, map, :str)
    c << "map.aliases.#{name} = #{val};\n"
    val = '"'+compile_item(value.data, map, :re)+'"'
    c << "map.aliases_re.#{name} = #{val};\n"
  end

  map.stages.each do |_, stage|
    c << compile_rule(stage, @map, true)
  end
  @parallel_trees.each do |k,v|
    c << "map.cache.PTREE_#{k} = #{v.to_json};\n"
  end
  @parallel_regexps.each do |k,v|
    v = "[\"#{v[0]}\", #{v[1].to_json}]"
    c << "map.cache.PRE_#{k} = #{v};\n"
  end

  c << "});"
  c << "};"
  c << "if (typeof module !== 'undefined') { module.exports = map; }"
  c << "else if (typeof Interscript !== 'undefined') { map(Interscript); }"
  c << 'else { throw "We couldn\'t dispatch Interscript from a map!"; }'
  @code = c
end
compile_item(i, doc=@map, target=nil) click to toggle source
# File lib/interscript/compiler/javascript.rb, line 147
def compile_item i, doc=@map, target=nil
  i = i.first_string if %i[str parstr].include? target
  i = Interscript::Node::Item.try_convert(i)
  if target == :parstr
    parstr = true
    target = :par
  end

  out = case i
  when Interscript::Node::Item::Alias
    astr = if i.map
      d = doc.dep_aliases[i.map].document
      a = d.imported_aliases[i.name]
      raise ArgumentError, "Alias #{i.name} of #{i.stage.map} not found" unless a
      "Interscript.get_alias_ALIASTYPE(#{a.doc_name.to_json}, #{a.name.to_json})"
    elsif Interscript::Stdlib::ALIASES.include?(i.name)
      if target != :re && Interscript::Stdlib.re_only_alias?(i.name)
        raise ArgumentError, "Can't use #{i.name} in a #{target} context"
      end
      stdlib_alias = true
      "Interscript.aliases.#{i.name}"
    else
      a = doc.imported_aliases[i.name]
      raise ArgumentError, "Alias #{i.name} not found" unless a

      "Interscript.get_alias_ALIASTYPE(#{a.doc_name.to_json}, #{a.name.to_json})"
    end

    if target == :str
      astr = astr.sub("_ALIASTYPE(", "(")
    elsif target == :re
      astr = %{"+#{astr.sub("_ALIASTYPE(", "_re(")}+"}
    elsif parstr && stdlib_alias
      astr = Interscript::Stdlib::ALIASES[i.name]
    elsif target == :par
      # raise NotImplementedError, "Can't use aliases in parallel mode yet"
      astr = Interscript::Stdlib::ALIASES[i.name]
    end
  when Interscript::Node::Item::String
    if target == :str
      # Replace $1 with \$1, this is weird, but it works!
      i.data.gsub("$", "\\\\$").to_json
    elsif target == :par
      i.data
    elsif target == :re
      Regexp.escape(i.data)
    end
  when Interscript::Node::Item::Group
    if target == :par
      i.children.map do |j|
        compile_item(j, doc, target)
      end.reduce([""]) do |j,k|
        Array(j).product(Array(k)).map(&:join)
      end
    elsif target == :str
      i.children.map { |j| compile_item(j, doc, target) }.join("+")
    elsif target == :re
      i.children.map { |j| compile_item(j, doc, target) }.join
    end
  when Interscript::Node::Item::CaptureGroup
    if target != :re
      raise ArgumentError, "Can't use a CaptureGroup in a #{target} context"
    end
    "(" + compile_item(i.data, doc, target) + ")"
  when Interscript::Node::Item::Maybe,
       Interscript::Node::Item::MaybeSome,
       Interscript::Node::Item::Some

    resuffix = { Interscript::Node::Item::Maybe     => "?" ,
                 Interscript::Node::Item::Some      => "+" ,
                 Interscript::Node::Item::MaybeSome => "*" }[i.class]

    if target == :par
      raise ArgumentError, "Can't use a MaybeSome in a #{target} context"
    end
    if Interscript::Node::Item::String === i.data && i.data.data.length != 1
      "(?:" + compile_item(i.data, doc, target) + ")" + resuffix
    else
      compile_item(i.data, doc, target) + resuffix
    end
  when Interscript::Node::Item::CaptureRef
    if target == :par
      raise ArgumentError, "Can't use CaptureRef in parallel mode"
    elsif target == :re
      "\\\\#{i.id}"
    elsif target == :str
      "\"$#{i.id}\""
    end
  when Interscript::Node::Item::Any
    if target == :str
      raise ArgumentError, "Can't use Any in a string context" # A linter could find this!
    elsif target == :par
      i.data.map(&:data)
    elsif target == :re
      case i.value
      when Array
        data = i.data.map { |j| compile_item(j, doc, target) }
        "(?:"+data.join("|")+")"
      when String
        "[#{Regexp.escape(i.value)}]"
      when Range
        "[#{Regexp.escape(i.value.first)}-#{Regexp.escape(i.value.last)}]"
      end
    end
  end
end
compile_rule(r, map = @map, wrapper = false) click to toggle source
# File lib/interscript/compiler/javascript.rb, line 54
def compile_rule(r, map = @map, wrapper = false)
  c = ""
  return c if r.reverse_run == true
  case r
  when Interscript::Node::Stage
    c += "map.stages.#{r.name} = function(s) {\n"
    c += "globalThis.map_debug = globalThis.map_debug || [];\n" if @debug
    r.children.each do |t|
      comp = compile_rule(t, map)
      c += comp
      c += %{globalThis.map_debug.push([s, #{@map.name.to_s.to_json}, #{r.name.to_s.to_json}, #{t.inspect.to_json}, #{comp.to_json}]);\n} if @debug
    end
    c += "return s;\n"
    c += "};\n"
  when Interscript::Node::Group::Parallel
    begin
      # Try to build a tree
      a = []
      r.children.each do |i|
        raise ArgumentError, "Can't parallelize #{i.class}" unless Interscript::Node::Rule::Sub === i
        raise ArgumentError, "Can't parallelize rules with :before" if i.before
        raise ArgumentError, "Can't parallelize rules with :after" if i.after
        raise ArgumentError, "Can't parallelize rules with :not_before" if i.not_before
        raise ArgumentError, "Can't parallelize rules with :not_after" if i.not_after

        next if i.reverse_run == true
        a << [compile_item(i.from, map, :par), compile_item(i.to, map, :parstr)]
      end
      ah = a.hash.abs
      unless @parallel_trees.include? ah
        tree = Interscript::Stdlib.parallel_replace_compile_tree(a)
        @parallel_trees[ah] = tree
      end
      c += "s = Interscript.parallel_replace_tree(s, map.cache.PTREE_#{ah});\n"
    rescue
      # Otherwise let's build a megaregexp
      a = []
      Interscript::Stdlib.deterministic_sort_by_max_length(r.children).each do |i|
        raise ArgumentError, "Can't parallelize #{i.class}" unless Interscript::Node::Rule::Sub === i
        
        next if i.reverse_run == true
        a << [build_regexp(i, map), compile_item(i.to, map, :parstr)]
      end
      ah = a.hash.abs
      unless @parallel_regexps.include? ah
        re = parallel_regexp_compile(a)
        @parallel_regexps[ah] = [re, a.map(&:last)]
      end
      c += "s = Interscript.parallel_regexp_gsub(s, map.cache.PRE_#{ah});\n"
    end
  when Interscript::Node::Rule::Sub
    from = %{"#{build_regexp(r, map).gsub("/", "\\\\/")}"}
    if r.to == :upcase
      to = 'function(a){return a.toUpperCase();}'
    elsif r.to == :downcase
      to = 'function(a){return a.toLowerCase();}'
    else
      to = compile_item(r.to, map, :str)
    end
    c += "s = Interscript.gsub(s, #{from}, #{to});\n"
  when Interscript::Node::Rule::Funcall
    c += "s = Interscript.functions.#{r.name}(s, #{r.kwargs.to_json});\n"
  when Interscript::Node::Rule::Run
    if r.stage.map
      doc = map.dep_aliases[r.stage.map].document
      stage = doc.imported_stages[r.stage.name]
    else
      stage = map.imported_stages[r.stage.name]
    end
    c += "s = Interscript.transliterate(#{stage.doc_name.to_json}, s, #{stage.name.to_json});\n"
  else
    raise ArgumentError, "Can't compile unhandled #{r.class}"
  end
  c
end
load() click to toggle source
# File lib/interscript/compiler/javascript.rb, line 261
def load
  if !self.class.maps_loaded[@map.name]
    @map.dependencies.each do |dep|
      dep = dep.full_name
      if !self.class.maps_loaded[dep]
        Interscript.load(dep, compiler: self.class).load
      end
    end

    ctx = self.class.ctx
    unless ctx
      ctx = MiniRacer::Context.new
      ctx.eval File.read(__dir__+"/../../../../js/test-compiler/xregexp.js")
      # Compatibility with Safari: will come later
      #ctx.eval File.read(__dir__+"/../../../js/xregexp-oniguruma.js")
      ctx.eval File.read(__dir__+"/../../../../js/src/stdlib.js")
      self.class.ctx = ctx
    end
    #puts @code
    ctx.eval @code
    self.class.maps_loaded[@map.name] = true
  end
end
parallel_regexp_compile(subs_hash) click to toggle source
# File lib/interscript/compiler/javascript.rb, line 45
def parallel_regexp_compile(subs_hash)
  # puts subs_hash.inspect
  regexp = subs_hash.each_with_index.map do |p,i|
    "(?<_%d>%s)" % [i,p[0]]
  end.join("|")
  subs_regexp = regexp
  # puts subs_regexp.inspect
end