From 0b22d9060a4c84cd7b418cbb15d95eacce352615 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 14 Dec 2025 15:07:21 +0100 Subject: [PATCH 1/3] Fix docs of opening_loc/closing_loc of BlockNode --- config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config.yml b/config.yml index 5e29d6fa18..4e5b077a35 100644 --- a/config.yml +++ b/config.yml @@ -1269,17 +1269,17 @@ nodes: - name: opening_loc type: location comment: | - Represents the location of the opening `|`. + Represents the location of the opening `{` or `do`. [1, 2, 3].each { |i| puts x } - ^ + ^ - name: closing_loc type: location comment: | - Represents the location of the closing `|`. + Represents the location of the closing `}` or `end`. [1, 2, 3].each { |i| puts x } - ^ + ^ comment: | Represents a block of ruby code. From 71fcb891e0198e97a712148f2f9613ca8d5ed74d Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 14 Dec 2025 21:36:45 +0100 Subject: [PATCH 2/3] Simplify and optimize Prism::Node#tunnel * By comparing byte offsets which folds 3 branches into 1. * Also avoids allocation of Location objects. --- templates/lib/prism/node.rb.erb | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/templates/lib/prism/node.rb.erb b/templates/lib/prism/node.rb.erb index 066a0cea1b..181842e230 100644 --- a/templates/lib/prism/node.rb.erb +++ b/templates/lib/prism/node.rb.erb @@ -184,24 +184,14 @@ module Prism queue = [self] #: Array[Prism::node] result = [] #: Array[Prism::node] + line_offset = source.offsets[line - 1] or raise + search_offset = line_offset + column + while (node = queue.shift) result << node node.each_child_node do |child_node| - child_location = child_node.location - - start_line = child_location.start_line - end_line = child_location.end_line - - if start_line == end_line - if line == start_line && column >= child_location.start_column && column < child_location.end_column - queue << child_node - break - end - elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column) - queue << child_node - break - elsif line > start_line && line < end_line + if child_node.start_offset <= search_offset && search_offset < child_node.end_offset queue << child_node break end From ff81a29ba5e1421da7c61e777a444ef3caa6704b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 15 Dec 2025 10:46:24 +0100 Subject: [PATCH 3/3] Add Prism::Source#line_to_byte_offset and replace direct accesses to offsets --- lib/prism/parse_result.rb | 9 +++++++ sig/prism/parse_result.rbs | 1 + templates/lib/prism/node.rb.erb | 3 +-- test/prism/ruby/source_test.rb | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 test/prism/ruby/source_test.rb diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 3570af136a..12d19da562 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -76,6 +76,15 @@ def slice(byte_offset, length) source.byteslice(byte_offset, length) or raise end + # Converts the line number to a byte offset corresponding to the start of that line + def line_to_byte_offset(line) + l = line - @start_line + if l < 0 || l >= offsets.size + raise ArgumentError, "line #{line} is out of range" + end + offsets[l] + end + # Binary search through the offsets to find the line number for the given # byte offset. def line(byte_offset) diff --git a/sig/prism/parse_result.rbs b/sig/prism/parse_result.rbs index e88e5f0664..d878ca2edd 100644 --- a/sig/prism/parse_result.rbs +++ b/sig/prism/parse_result.rbs @@ -14,6 +14,7 @@ module Prism def encoding: () -> Encoding def lines: () -> Array[String] def slice: (Integer byte_offset, Integer length) -> String + def line_to_byte_offset: (Integer line) -> Integer def line: (Integer byte_offset) -> Integer def line_start: (Integer byte_offset) -> Integer def line_end: (Integer byte_offset) -> Integer diff --git a/templates/lib/prism/node.rb.erb b/templates/lib/prism/node.rb.erb index 181842e230..8225bfb328 100644 --- a/templates/lib/prism/node.rb.erb +++ b/templates/lib/prism/node.rb.erb @@ -184,8 +184,7 @@ module Prism queue = [self] #: Array[Prism::node] result = [] #: Array[Prism::node] - line_offset = source.offsets[line - 1] or raise - search_offset = line_offset + column + search_offset = source.line_to_byte_offset(line) + column while (node = queue.shift) result << node diff --git a/test/prism/ruby/source_test.rb b/test/prism/ruby/source_test.rb new file mode 100644 index 0000000000..afd2825765 --- /dev/null +++ b/test/prism/ruby/source_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Prism + class SourceTest < TestCase + def test_line_to_byte_offset + parse_result = Prism.parse(<<~SRC) + abcd + efgh + ijkl + SRC + source = parse_result.source + + assert_equal 0, source.line_to_byte_offset(1) + assert_equal 5, source.line_to_byte_offset(2) + assert_equal 10, source.line_to_byte_offset(3) + assert_equal 15, source.line_to_byte_offset(4) + e = assert_raise(ArgumentError) { source.line_to_byte_offset(5) } + assert_equal "line 5 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(0) } + assert_equal "line 0 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(-1) } + assert_equal "line -1 is out of range", e.message + end + + def test_line_to_byte_offset_with_start_line + parse_result = Prism.parse(<<~SRC, line: 11) + abcd + efgh + ijkl + SRC + source = parse_result.source + + assert_equal 0, source.line_to_byte_offset(11) + assert_equal 5, source.line_to_byte_offset(12) + assert_equal 10, source.line_to_byte_offset(13) + assert_equal 15, source.line_to_byte_offset(14) + e = assert_raise(ArgumentError) { source.line_to_byte_offset(15) } + assert_equal "line 15 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(10) } + assert_equal "line 10 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(9) } + assert_equal "line 9 is out of range", e.message + end + end +end