class Parser::Source::Buffer


@api public
@return [Integer] first line
First line of the buffer, 1 by default.
@!attribute [r] first_line
@return [String] buffer name
to relative path to the file.
Buffer name. If the buffer was created from a file, the name corresponds
@!attribute [r] name
A source buffer is immutable once populated.
of encoding.
associated location information (name and first line), and takes care
A buffer with source code. {Buffer} contains the source code itself,
#

def self.recognize_encoding(string)

Returns:
  • (String, nil) - encoding name, if recognized

Parameters:
  • string (String) --
def self.recognize_encoding(string)
  return if string.empty?
  # extract the first two lines in an efficient way
  string =~ /\A(.*)\n?(.*\n)?/
  first_line, second_line = $1, $2
  if first_line.start_with?("\xef\xbb\xbf".freeze) # BOM
    return Encoding::UTF_8
  elsif first_line[0, 2] == '#!'.freeze
    encoding_line = second_line
  else
    encoding_line = first_line
  end
  return nil if encoding_line.nil? || encoding_line[0] != '#'
  if (result = ENCODING_RE.match(encoding_line))
    Encoding.find(result[3] || result[4] || result[6])
  else
    nil
  end
end

def self.reencode_string(input)

Raises:
  • (EncodingError) -

Returns:
  • (String) -

Parameters:
  • input (String) --
def self.reencode_string(input)
  original_encoding = input.encoding
  detected_encoding = recognize_encoding(input.force_encoding(Encoding::BINARY))
  if detected_encoding.nil?
    input.force_encoding(original_encoding)
  elsif detected_encoding == Encoding::BINARY
    input
  else
    input.
      force_encoding(detected_encoding).
      encode(Encoding::UTF_8)
  end
end

def bsearch(line_begins, position)

RUBY_VERSION >= 2.3
def bsearch(line_begins, position)
  line_begins.bsearch_index do |line_begin|
    position < line_begin
  end || line_begins.size - 1 # || only for out of bound values
end

def bsearch(line_begins, position)

def bsearch(line_begins, position)
  @line_range ||= 0...line_begins.size
  @line_range.bsearch do |i|
    position < line_begins[i]
  end || line_begins.size - 1 # || only for out of bound values
end

def column_for_position(position)

Other tags:
    Api: - private

Returns:
  • (Integer) - column

Parameters:
  • position (Integer) --
def column_for_position(position)
  line_index = line_index_for_position(position)
  position - line_begins[line_index]
end

def decompose_position(position)

Returns:
  • ([Integer, Integer]) - `[line, column]`

Parameters:
  • position (Integer) --
def decompose_position(position)
  line_index = line_index_for_position(position)
  line_begin = line_begins[line_index]
  [ @first_line + line_index , position - line_begin ]
end

def freeze

:nodoc:
def freeze
  source_lines; line_begins; source_range # build cache
  super
end

def initialize(name, first_line = 1, source: nil)

def initialize(name, first_line = 1, source: nil)
  @name        = name.to_s
  @source      = nil
  @first_line  = first_line
  @lines       = nil
  @line_begins = nil
  # UTF-32-reencoded source for O(1) slicing
  @slice_source = nil
  # Cache for fast lookup
  @line_index_for_position = {}
  self.source = source if source
end

def inspect

:nodoc:
def inspect
  "#<#{self.class} #{name}>"
end

def last_line

Returns:
  • (Integer) -
def last_line
  line_begins.size + @first_line - 2
end

def line_begins

@returns [0, line_begin_of_line_1, ..., source.size + 1]
def line_begins
  @line_begins ||= begin
    begins = [0]
    index = 0
    while index = @source.index("\n".freeze, index)
      index += 1
      begins << index
    end
    begins << @source.size + 1
    begins
  end
end

def line_for_position(position)

Other tags:
    Api: - private

Returns:
  • (Integer) - line

Parameters:
  • position (Integer) --
def line_for_position(position)
  line_index_for_position(position) + @first_line
end

def line_index_for_position(position)

@returns 0-based line index of position
def line_index_for_position(position)
  @line_index_for_position[position] || begin
    index = bsearch(line_begins, position) - 1
    @line_index_for_position[position] = index unless @line_index_for_position.frozen?
    index
  end
end

def line_range(lineno)

Raises:
  • (IndexError) - if `lineno` is out of bounds

Returns:
  • (Range) -

Parameters:
  • lineno (Integer) --
def line_range(lineno)
  index = lineno - @first_line
  if index < 0 || index + 1 >= line_begins.size
    raise IndexError, 'Parser::Source::Buffer: range for line ' \
      "#{lineno} requested, valid line numbers are #{@first_line}.." \
      "#{@first_line + line_begins.size - 2}"
  else
    Range.new(self, line_begins[index], line_begins[index + 1] - 1)
  end
end

def raw_source=(input)

Returns:
  • (String) -

Raises:
  • (ArgumentError) - if already populated

Parameters:
  • input (String) --
def raw_source=(input)
  if @source
    raise ArgumentError, 'Source::Buffer is immutable'
  end
  @source = input.gsub("\r\n".freeze, "\n".freeze).freeze
  if !@source.ascii_only? &&
     @source.encoding != Encoding::UTF_32LE &&
     @source.encoding != Encoding::BINARY
    @slice_source = @source.encode(Encoding::UTF_32LE)
  end
end

def read

Raises:
  • (ArgumentError) - if already populated

Returns:
  • (Buffer) - self
def read
  File.open(@name, 'rb') do |io|
    self.source = io.read
  end
  self
end

def slice(range)

def slice(range)
  if @slice_source.nil?
    @source[range]
  else
    @slice_source[range].encode(@source.encoding)
  end
end

def source

Raises:
  • (RuntimeError) - if buffer is not populated yet

Returns:
  • (String) - source code
def source
  if @source.nil?
    raise RuntimeError, 'Cannot extract source from uninitialized Source::Buffer'
  end
  @source
end

def source=(input)

Returns:
  • (String) -

Raises:
  • (EncodingError) - if `input` includes invalid byte sequence for the encoding
  • (ArgumentError) - if already populated

Parameters:
  • input (String) --
def source=(input)
  input = input.dup if input.frozen?
  input = self.class.reencode_string(input)
  unless input.valid_encoding?
    raise EncodingError, "invalid byte sequence in #{input.encoding.name}"
  end
  self.raw_source = input
end

def source_line(lineno)

Raises:
  • (IndexError) - if `lineno` is out of bounds

Returns:
  • (String) -

Parameters:
  • lineno (Integer) --
def source_line(lineno)
  source_lines.fetch(lineno - @first_line).dup
end

def source_lines

Returns:
  • (Array) -
def source_lines
  @lines ||= begin
    lines = @source.lines.to_a
    lines << ''.dup if @source.end_with?("\n".freeze)
    lines.each do |line|
      line.chomp!("\n".freeze)
      line.freeze
    end
    lines.freeze
  end
end

def source_range

Returns:
  • (Range) - A range covering the whole source
def source_range
  @source_range ||= Range.new(self, 0, source.size)
end