class Opal::SourceMap::File

def absolute_mappings

each segment is made up of 1,4 or 5 variable length fields.
each segment is separated by a “,”
each group representing a line in the generated file is separated by a ”;”

The “mappings” data is broken down as follows:
def absolute_mappings
  @absolute_mappings ||= begin
    mappings = []
    fragments_by_line.each do |raw_segments|
      generated_column = 0
      segments = []
      raw_segments.each do |(generated_code, fragment)|
        unless fragment.is_a?(Opal::Fragment) && fragment.skip_source_map?
          segments << segment_from_fragment(fragment, generated_column)
        end
        generated_column += generated_code.size
      end
      mappings << segments
    end
    mappings
  end
end

def fragments_by_line

def fragments_by_line
  raw_mappings = [[]]
  fragments.flat_map do |fragment|
    fragment_code = fragment.code
    splitter = /\r/.match?(fragment_code) ? /\r?\n/ : "\n"
    fragment_lines = fragment_code.split(splitter, -1) # a negative limit won't suppress trailing null values
    fragment_lines.each.with_index do |fragment_line, index|
      raw_segment = [fragment_line, fragment]
      if index.zero? && !fragment_line.size.zero?
        raw_mappings.last << raw_segment
      elsif index.zero? && fragment_line.size.zero?
        # noop
      elsif fragment_line.size.zero?
        raw_mappings << []
      else
        raw_mappings << [raw_segment]
      end
    end
  end
  raw_mappings
end

def generated_code

def generated_code
  @generated_code ||= @fragments.map(&:code).join
end

def initialize(fragments, file, source, generated_code = nil)

def initialize(fragments, file, source, generated_code = nil)
  @fragments = fragments
  @file = file
  @source = source
  @names_map = Hash.new { |hash, name| hash[name] = hash.size }
  @generated_code = generated_code
  @absolute_mappings = nil
end

def map(source_root: '')

Line 8: A string with the encoded mapping data.
Line 7: A list of symbol names used by the “mappings” entry.
“null” may be used if some original sources should be retrieved by name.
hosted. The contents are listed in the same order as the sources in line 5.
Line 6: An optional list of source content, useful when the “source” can’t be
Line 5: A list of original sources used by the “mappings” entry.
the individual entries in the “source” field.
or removing repeated values in the “sources” entry. This value is prepended to
Line 4: An optional source root, useful for relocating source files on a server
associated with.
Line 3: An optional name of the generated code that this source map is
positive integer.
Line 2: File version (always the first entry in the object) and must be a
Line 1: The entire file is a single JSON object

9: }
8: "mappings": "A,AAAB;;ABCDE;"
7: "names": ["src", "maps", "are", "fun"],
6: "sourcesContent": [null, null],
5: "sources": ["foo.js", "bar.js"],
4: "sourceRoot": "",
3: "file": "out.js",
2: "version" : 3,
1: {
Proposed Format
def map(source_root: '')
  {
    version: 3,
    # file: "out.js", # This is optional
    sourceRoot: source_root,
    sources: [file],
    sourcesContent: [source.encoding == Encoding::UTF_8 ? source : source.encode('UTF-8', undef: :replace)],
    names: names,
    mappings: Opal::SourceMap::VLQ.encode_mappings(relative_mappings),
    # x_com_opalrb_original_lines: source.count("\n"),
    # x_com_opalrb_generated_lines: generated_code.count("\n"),
  }
end

def names

def names
  @names ||= begin
    absolute_mappings # let the processing happen
    @names_map.to_a.sort_by { |_, index| index }.map { |name, _| name }
  end
end

def relative_mappings

def relative_mappings
  @relative_mappings ||= begin
    reference_segment = [0, 0, 0, 0, 0]
    reference_name_index = 0
    absolute_mappings.map do |absolute_mapping|
      # [generated_column, source_index, original_line, original_column, map_name_index]
      reference_segment[0] = 0 # reset the generated_column at each new line
      absolute_mapping.map do |absolute_segment|
        segment = []
        segment[0] = absolute_segment[0] - reference_segment[0]
        segment[1] = absolute_segment[1] - (reference_segment[1] || 0)
        segment[2] = absolute_segment[2] - (reference_segment[2] || 0)
        segment[3] = absolute_segment[3] - (reference_segment[3] || 0)
        # Since [4] can be nil we need to keep track of it in the reference_segment even if it's nil in absolute_segment
        if absolute_segment[4]
          segment[4] = absolute_segment[4].to_int - (reference_segment[4] || reference_name_index).to_int
          reference_name_index = absolute_segment[4]
        end
        reference_segment = absolute_segment
        segment
      end
    end
  end
end

def segment_from_fragment(fragment, generated_column)

field, in which case the whole value is represented.
occurrence of this field, unless this is the first occurrence of this
this segment. This field is a base 64 VLQ relative to the previous
5. If present, the zero-based index into the “names” list associated with

there is a source field.
field, in which case the whole value is represented. Always present if
occurrence of this field, unless this is the first occurrence of this
represented. This field is a base 64 VLQ relative to the previous
4. If present, the zero-based starting column of the line in the source

there is a source field.
field, in which case the whole value is represented. Always present if
occurrence of this field, unless this is the first occurrence of this
represented. This field is a base 64 VLQ relative to the previous
3. If present, the zero-based starting line in the original source

is represented.
this is the first occurrence of this field, in which case the whole value
a base 64 VLQ relative to the previous occurrence of this field, unless
2. If present, an zero-based index into the “sources” list. This field is

after every generated line.
is different than the fields below because the previous value is reset
that is relative to the previous occurrence of this field. Note that this
holds the whole base 64 VLQ. Otherwise, this field contains a base 64 VLQ
the first segment following a new generated line (“;”), then this field
the segment represents. If this is the first field of the first segment, or
1. The zero-based starting column of the line in the generated code that

The fields in each segment are:
def segment_from_fragment(fragment, generated_column)
  source_index     = 0                          # always 0, we're dealing with a single file
  original_line    = (fragment.line || 0) - 1   # fragments have 1-based lines
  original_line    = 0 if original_line < 0     # line 0 (-1) for fragments in source maps will crash
                                                # browsers devtools and the webpack build process
  original_column  = fragment.column || 0       # fragments have 0-based columns
  if fragment.source_map_name
    map_name_index = (@names_map[fragment.source_map_name.to_s] ||= @names_map.size)
    [
      generated_column,
      source_index,
      original_line,
      original_column,
      map_name_index,
    ]
  else
    [
      generated_column,
      source_index,
      original_line,
      original_column,
    ]
  end
end