class Ruby2JS::Serializer

Constants

BASE64

Attributes

file_name[RW]
timestamps[R]

Public Class Methods

new() click to toggle source
# File lib/ruby2js/serializer.rb, line 47
def initialize
  @sep = '; '
  @nl = ''
  @ws = ' '

  @width = 80
  @indent = 0

  @lines = [Line.new]
  @line = @lines.last
  @timestamps = {}

  @ast = nil
  @file_name = ''
end

Public Instance Methods

+(value) click to toggle source
# File lib/ruby2js/serializer.rb, line 296
def +(value)
  to_s+value
end
capture(&block) click to toggle source

capture (and remove) tokens from the output stream

# File lib/ruby2js/serializer.rb, line 206
def capture(&block)
  mark = output_location
  block.call
  lines = @lines.slice!(mark.first+1..-1)
  @line = @lines.last

  if lines.empty?
    lines = [@line.slice!(mark.last..-1)]
  elsif @line.length != mark.last
    lines.unshift @line.slice!(mark.last..-1)
  end

  lines.map(&:join).join(@ws)
end
compact() { || ... } click to toggle source

compact small expressions into a single line

# File lib/ruby2js/serializer.rb, line 239
def compact
  mark = output_location
  yield
  return unless @lines.length - mark.first > 1
  return if @indent == 0

  # survey what we have to work with, keeping track of a possible
  # split of the last argument or value
  work = []
  len = 0
  trail = split = nil
  slice = @lines[mark.first..-1]
  reindent(slice)
  slice.each_with_index do |line, index|
    line << "" if line.empty?
    if line.first.start_with? '//'
      len += @width # comments are a deal breaker
    else
      (work.push ' '; len += 1) if trail == line.indent and @indent > 0
      len += line.map(&:length).inject(&:+)
      work += line

      if trail == @indent and line.indent == @indent
        split = [len, work.length, index]
        break if len >= @width - 10
      end
      trail = line.indent
    end
  end

  if len < @width - 10
    # full collapse
    @lines[mark.first..-1] = [Line.new(*work)]
    @line = @lines.last
  elsif split and split[0] < @width-10
    if slice[split[2]].indent < slice[split[2]+1].indent
      # collapse all but the last argument (typically a hash or function)
      close = slice.pop
      slice[-1].push(*close)
      @lines[mark.first] = Line.new(*work[0..split[1]-1])
      @lines[mark.first+1..-1] = slice[split[2]+1..-1]
      @line = @lines.last
    end
  end
end
enable_vertical_whitespace() click to toggle source
# File lib/ruby2js/serializer.rb, line 79
def enable_vertical_whitespace
  @sep = ";\n"
  @nl = "\n"
  @ws = @nl
  @indent = 2
end
insert(mark, line) click to toggle source

insert a line into the output

# File lib/ruby2js/serializer.rb, line 197
def insert(mark, line)
  if mark.last == 0
    @lines.insert(mark.first, Line.new(Token.new(line.chomp, @ast)))
  else
    @lines[mark.first].insert(mark.last, Token.new(line, @ast))
  end
end
mtime() click to toggle source
# File lib/ruby2js/serializer.rb, line 74
def mtime
  return Time.now if @timestamps.empty?
  return @timestamps.values.max
end
output_location() click to toggle source

current location: [line number, token number]

# File lib/ruby2js/serializer.rb, line 192
def output_location
  [@lines.length-1, @line.length]
end
put(string) click to toggle source

add a single token to the current line

# File lib/ruby2js/serializer.rb, line 149
def put(string)
  unless String === string and string.include? "\n"
    @line << Token.new(string, @ast)
  else
    parts = string.split("\n")
    first = parts.shift
    @line << Token.new(first, @ast) if first
    @lines += parts.map {|part| Line.new(Token.new(part, @ast))}
    @lines << Line.new if string.end_with?("\n")
    @line = @lines.last
  end
end
put!(string) click to toggle source

add a single token to the current line without checking for newline

# File lib/ruby2js/serializer.rb, line 163
def put!(string)
  @line << Token.new(string.gsub("\r", "\n"), @ast)
end
puts(string) click to toggle source

add a single token to the current line and then advance to next line

# File lib/ruby2js/serializer.rb, line 168
def puts(string)
  unless String === string and string.include? "\n"
    @line << Token.new(string, @ast)
  else
    put string
  end

  @line = Line.new
  @lines << @line
end
reindent(lines) click to toggle source

indent multi-line parameter lists, array constants, blocks

# File lib/ruby2js/serializer.rb, line 87
def reindent(lines)
  indent = 0
  lines.each do |line|
    first = line.find {|token| !token.empty?}
    if first
      last = line[line.rindex {|token| !token.empty?}]
      if (first.start_with? '<' and line.include? '>') or
         (last.end_with? '>' and line.include? '<')
      then
        node = line.join[/.*?(<.*)/, 1]
        indent -= @indent if node.start_with? '</'

        line.indent = indent

        node = line.join[/.*(<.*)/, 1]
        indent += @indent unless node.include? '</' or node.include? '/>'
      else
        indent -= @indent if ')}]'.include? first[0] and indent >= @indent
        line.indent = indent
        indent += @indent if '({['.include? last[-1]
      end
    else
      line.indent = indent
    end
  end
end
respace() click to toggle source

add horizontal (indentation) and vertical (blank lines) whitespace

# File lib/ruby2js/serializer.rb, line 115
def respace
  return if @indent == 0
  reindent @lines

  (@lines.length-3).downto(0) do |i|
    if \
      @lines[i].length == 0
    then
      @lines.delete i
    elsif \
      @lines[i+1].comment? and not @lines[i].comment? and
      @lines[i].indent == @lines[i+1].indent
    then
      # before a comment
      @lines.insert i+1, Line.new
    elsif \
      @lines[i].indent == @lines[i+1].indent and 
      @lines[i+1].indent < @lines[i+2].indent and
      not @lines[i].comment?
    then
      # start of indented block
      @lines.insert i+1, Line.new
    elsif \
      @lines[i].indent > @lines[i+1].indent and 
      @lines[i+1].indent == @lines[i+2].indent and
      not @lines[i+2].empty?
    then
      # end of indented block
      @lines.insert i+2, Line.new
    end
  end
end
sourcemap() click to toggle source
# File lib/ruby2js/serializer.rb, line 350
def sourcemap
  respace

  @mappings = ''
  sources = []
  names = []
  @mark = nil

  @lines.each_with_index do |line, row|
    col = line.indent
    line.each do |token|
      if token.respond_to? :loc and token.loc
        pos = token.loc.expression.begin_pos

        buffer = token.loc.expression.source_buffer
        source_index = sources.index(buffer)
        if not source_index
          source_index = sources.length
          timestamp buffer.name
          sources << buffer
        end

        line = buffer.line_for_position(pos) - 1
        column = buffer.column_for_position(pos)

        name = nil
        if %i{lvasgn lvar}.include? token.ast.type
          name = token.ast.children.first
        elsif %i{casgn const}.include? token.ast.type 
          if token.ast.children.first == nil
            name = token.ast.children[1]
          end
        end

        if name
          index = names.find_index(name)

          unless index
            index = names.length
            names << name
          end

          vlq row, col, source_index, line, column, index
        else
          vlq row, col, source_index, line, column
        end
      end
      col += token.length
    end
  end

  @sourcemap = {
    version: 3,
    file: @file_name,
    sources: sources.map(&:name),
    names: names.map(&:to_s),
    mappings: @mappings
  }
end
sput(string) click to toggle source

advance to next line and then add a single token to the current line

# File lib/ruby2js/serializer.rb, line 180
def sput(string)
  unless String === string and string.include? "\n"
    @line = Line.new(Token.new(string, @ast))
    @lines << @line
  else
    @line = Line.new
    @lines << @line
    put string
  end
end
timestamp(file) click to toggle source
# File lib/ruby2js/serializer.rb, line 63
def timestamp(file)
  if file
    @timestamps[file] = File.mtime(file) if File.exist?(file)
  end
end
to_s() click to toggle source

return the output as a string

# File lib/ruby2js/serializer.rb, line 286
def to_s
  return @str if (@str ||= nil)
  respace
  @lines.map(&:to_s).join(@nl)
end
to_str() click to toggle source
# File lib/ruby2js/serializer.rb, line 292
def to_str
  @str ||= to_s
end
uptodate?() click to toggle source
# File lib/ruby2js/serializer.rb, line 69
def uptodate?
  return false if @timestamps.empty?
  return @timestamps.all? {|file, mtime| File.mtime(file) == mtime}
end
vlq(*mark) click to toggle source

docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit sokra.github.io/source-map-visualization/

# File lib/ruby2js/serializer.rb, line 304
def vlq(*mark)
  if !@mark
    diffs = mark
    @mark = [0, 0, 0, 0, 0, 0]
  else
    if @mark[0] == mark[0]
      return if @mark[4] == mark[4] and @mark[3] == mark[3]
      @mappings += ',' unless @mappings == ''
    end

    diffs = mark.zip(@mark).map {|a,b| a-b}
  end

  while @mark[0] < mark[0]
    @mappings += ';'
    @mark[0] += 1
    diffs[1] = mark[1] 
  end

  @mark[0...mark.length] = mark

  diffs[1..-1].each do |diff|
    if diff < 0
      data = (-diff << 1) + 1
    else
      data = diff << 1
    end

    if data <= 0b11111
      # workaround https://github.com/opal/opal/issues/575
      encoded = BASE64[data]
    else
      encoded = ''

      begin
        digit = data & 0b11111
        data >>= 5
        digit |= 0b100000 if data > 0
        encoded += BASE64[digit]
      end while data > 0
    end

    @mappings += encoded
  end
end
wrap(open = '{', close = '}') { || ... } click to toggle source

wrap long statements in curly braces

# File lib/ruby2js/serializer.rb, line 222
def wrap(open = '{', close = '}')
  puts open
  mark = output_location
  yield

  if \
    @lines.length > mark.first+1 or
    @lines[mark.first-1].join.length + @line.join.length >= @width
  then
    sput close
  else
    @line = @lines[mark.first-1]
    @line[-1..-1] = @lines.pop
  end
end