Class: Ibex::Runtime::CST::SourceText

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/runtime/cst/source_text.rb

Overview

Immutable byte-oriented source with lazy line/column conversion.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(text, file: nil) ⇒ SourceText

Returns a new instance of SourceText.



15
16
17
18
19
20
# File 'lib/ibex/runtime/cst/source_text.rb', line 15

def initialize(text, file: nil)
  @text = text.encoding == Encoding::BINARY && text.frozen? ? text : text.b.freeze
  @file = file&.then { |value| value.frozen? ? value : value.dup.freeze }
  @line_starts = build_line_starts.freeze #: Array[Integer]
  freeze
end

Instance Attribute Details

#fileObject (readonly)

: String?



12
13
14
# File 'lib/ibex/runtime/cst/source_text.rb', line 12

def file
  @file
end

#textObject (readonly)

@rbs! type position = [Integer, Integer]



11
12
13
# File 'lib/ibex/runtime/cst/source_text.rb', line 11

def text
  @text
end

Instance Method Details

#apply(edits) ⇒ Object

Apply non-overlapping byte edits expressed against this source.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/ibex/runtime/cst/source_text.rb', line 27

def apply(edits)
  ordered = TextEdit.normalize(edits)
  output = String.new(encoding: Encoding::BINARY)
  cursor = 0
  ordered.each do |edit|
    raise ArgumentError, "text edits overlap" if edit.start < cursor
    raise RangeError, "text edit exceeds source" if edit.range.end > @text.bytesize

    output << (@text.byteslice(cursor, edit.start - cursor) || "".b)
    output << edit.insert_text
    cursor = edit.range.end
  end
  output << (@text.byteslice(cursor, @text.bytesize - cursor) || "".b)
  SourceText.new(output, file: @file)
end

#bytesizeObject



23
# File 'lib/ibex/runtime/cst/source_text.rb', line 23

def bytesize = @text.bytesize

#line_text(line) ⇒ Object

Raises:

  • (ArgumentError)


68
69
70
71
72
73
74
75
# File 'lib/ibex/runtime/cst/source_text.rb', line 68

def line_text(line)
  raise ArgumentError, "line must be positive" unless line.positive?

  start_offset = @line_starts.fetch(line - 1)
  end_offset = @line_starts.fetch(line, @text.bytesize)
  value = @text.byteslice(start_offset, end_offset - start_offset) || "".b
  value.delete_suffix("\n".b).delete_suffix("\r".b)
end

#location(range) ⇒ Object



57
58
59
60
61
62
63
64
65
# File 'lib/ibex/runtime/cst/source_text.rb', line 57

def location(range)
  start_offset, end_offset = normalized_range(range)
  line, column = position(start_offset)
  end_line, end_column = position(end_offset)
  Ibex::Location.new(
    file: @file, line: line, column: column, end_line: end_line, end_column: end_column,
    start_byte: start_offset, end_byte: end_offset, source_line: line_text(line)
  )
end

#position(offset) ⇒ Object

Return one-based line and Unicode-scalar column for a byte offset. Invalid UTF-8 bytes count as one replacement scalar each.



46
47
48
49
50
51
52
53
54
# File 'lib/ibex/runtime/cst/source_text.rb', line 46

def position(offset)
  validate_offset(offset)
  line_index = @line_starts.bsearch_index { |start| start > offset }
  line_index = line_index ? line_index - 1 : @line_starts.length - 1
  line_start = @line_starts.fetch(line_index)
  prefix = @text.byteslice(line_start, offset - line_start) || "".b
  value = [line_index + 1, unicode_scalar_length(prefix) + 1] #: position
  value.freeze
end