Skip to content

Commit 5a7b428

Browse files
committed
Add Prism::Source#line_to_byte_offset and replace direct accesses to offsets
1 parent f8c80e6 commit 5a7b428

4 files changed

Lines changed: 57 additions & 7 deletions

File tree

lib/prism/parse_result.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ def slice(byte_offset, length)
7676
source.byteslice(byte_offset, length) or raise
7777
end
7878

79+
# Converts the line number to a byte offset corresponding to the start of that line
80+
def line_to_byte_offset(line)
81+
l = line - @start_line
82+
if l < 0 || l >= offsets.size
83+
raise ArgumentError, "line #{line} is out of range"
84+
end
85+
offsets[l]
86+
end
87+
7988
# Binary search through the offsets to find the line number for the given
8089
# byte offset.
8190
def line(byte_offset)
@@ -145,9 +154,10 @@ def code_units_column(byte_offset, encoding)
145154

146155
# Returns the byte offset for a given line number and column number
147156
def line_and_character_column_to_byte_offset(line, column)
148-
line_start = offsets[line - 1]
149-
line_end = offsets[line]
150-
byte_column = (@source.byteslice(line_start, line_end) or raise)[0...column]&.bytesize #: Integer
157+
line_start = line_to_byte_offset(line)
158+
line_end = offsets[line + 1 - @start_line] || source.bytesize
159+
byteslice = @source.byteslice(line_start, line_end) or raise ArgumentError, "line #{line} is out of range"
160+
byte_column = (byteslice[0...column] or raise).bytesize
151161
line_start + byte_column
152162
end
153163

@@ -284,8 +294,7 @@ def code_units_column(byte_offset, encoding)
284294
# Specialized version of `line_and_character_column_to_byte_offset`
285295
# which does not need to access the source String
286296
def line_and_character_column_to_byte_offset(line, column)
287-
line_start = offsets[line - 1]
288-
line_start + column
297+
line_to_byte_offset(line) + column
289298
end
290299
end
291300

sig/prism/parse_result.rbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Prism
1414
def encoding: () -> Encoding
1515
def lines: () -> Array[String]
1616
def slice: (Integer byte_offset, Integer length) -> String
17+
def line_to_byte_offset: (Integer line) -> Integer
1718
def line: (Integer byte_offset) -> Integer
1819
def line_start: (Integer byte_offset) -> Integer
1920
def line_end: (Integer byte_offset) -> Integer

templates/lib/prism/node.rb.erb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,7 @@ module Prism
184184
queue = [self] #: Array[Prism::node]
185185
result = [] #: Array[Prism::node]
186186

187-
line_offset = source.offsets[line - 1] or raise
188-
search_offset = line_offset + column
187+
search_offset = source.line_to_byte_offset(line) + column
189188

190189
while (node = queue.shift)
191190
result << node

test/prism/ruby/source_test.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../test_helper"
4+
5+
module Prism
6+
class SourceTest < TestCase
7+
def test_line_to_byte_offset
8+
parse_result = Prism.parse(<<~SRC)
9+
abcd
10+
efgh
11+
ijkl
12+
SRC
13+
source = parse_result.source
14+
15+
assert_equal 0, source.line_to_byte_offset(1)
16+
assert_equal 5, source.line_to_byte_offset(2)
17+
assert_equal 10, source.line_to_byte_offset(3)
18+
assert_equal 15, source.line_to_byte_offset(4)
19+
assert_raise(ArgumentError.new("line 5 is out of range")) { source.line_to_byte_offset(5) }
20+
assert_raise(ArgumentError.new("line 0 is out of range")) { source.line_to_byte_offset(0) }
21+
assert_raise(ArgumentError.new("line -1 is out of range")) { source.line_to_byte_offset(-1) }
22+
end
23+
24+
def test_line_to_byte_offset_with_start_line
25+
parse_result = Prism.parse(<<~SRC, line: 11)
26+
abcd
27+
efgh
28+
ijkl
29+
SRC
30+
source = parse_result.source
31+
32+
assert_equal 0, source.line_to_byte_offset(11)
33+
assert_equal 5, source.line_to_byte_offset(12)
34+
assert_equal 10, source.line_to_byte_offset(13)
35+
assert_equal 15, source.line_to_byte_offset(14)
36+
assert_raise(ArgumentError.new("line 15 is out of range")) { source.line_to_byte_offset(15) }
37+
assert_raise(ArgumentError.new("line 10 is out of range")) { source.line_to_byte_offset(10) }
38+
assert_raise(ArgumentError.new("line 9 is out of range")) { source.line_to_byte_offset(9) }
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)