Skip to content

Commit 50686bc

Browse files
sei40krclaude
andcommitted
feat: add start_line, end_line, start_column, end_column, and chop methods for Location
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f3f9590 commit 50686bc

3 files changed

Lines changed: 98 additions & 1 deletion

File tree

rust/ruby-prism-sys/build/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings {
116116
// Structs
117117
.allowlist_type("pm_comment_t")
118118
.allowlist_type("pm_diagnostic_t")
119+
.allowlist_type("pm_line_column_t")
119120
.allowlist_type("pm_list_t")
120121
.allowlist_type("pm_magic_comment_t")
121122
.allowlist_type("pm_node_t")
@@ -140,6 +141,8 @@ fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings {
140141
// Functions
141142
.allowlist_function("pm_list_empty_p")
142143
.allowlist_function("pm_list_free")
144+
.allowlist_function("pm_newline_list_line")
145+
.allowlist_function("pm_newline_list_line_column")
143146
.allowlist_function("pm_node_destroy")
144147
.allowlist_function("pm_pack_parse")
145148
.allowlist_function("pm_parse")

rust/ruby-prism/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,48 @@ mod tests {
162162
assert_eq!(slice, "222");
163163
}
164164

165+
#[test]
166+
#[allow(clippy::similar_names)]
167+
fn location_line_column_test() {
168+
let source = "foo\nbar\nbaz";
169+
let result = parse(source.as_ref());
170+
171+
let node = result.node();
172+
let program = node.as_program_node().unwrap();
173+
let statements = program.statements().body();
174+
let mut iter = statements.iter();
175+
let _foo = iter.next().unwrap();
176+
let bar = iter.next().unwrap();
177+
let baz = iter.next().unwrap();
178+
179+
let bar_loc = bar.location();
180+
assert_eq!(bar_loc.start_line(), 2);
181+
assert_eq!(bar_loc.end_line(), 2);
182+
assert_eq!(bar_loc.start_column(), 0);
183+
assert_eq!(bar_loc.end_column(), 3);
184+
185+
let baz_loc = baz.location();
186+
assert_eq!(baz_loc.start_line(), 3);
187+
assert_eq!(baz_loc.end_line(), 3);
188+
assert_eq!(baz_loc.start_column(), 0);
189+
assert_eq!(baz_loc.end_column(), 3);
190+
}
191+
192+
#[test]
193+
fn test_chop() {
194+
let result = parse(b"foo");
195+
let mut location = result.node().as_program_node().unwrap().location();
196+
197+
assert_eq!(location.chop().as_slice(), b"fo");
198+
assert_eq!(location.chop().chop().chop().as_slice(), b"");
199+
200+
// Check that we don't go negative.
201+
for _ in 0..10 {
202+
location = location.chop();
203+
}
204+
assert_eq!(location.as_slice(), b"");
205+
}
206+
165207
#[test]
166208
fn visitor_test() {
167209
use super::{visit_interpolated_regular_expression_node, visit_regular_expression_node, InterpolatedRegularExpressionNode, RegularExpressionNode, Visit};

rust/ruby-prism/src/parse_result/mod.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod diagnostics;
88

99
use std::ptr::NonNull;
1010

11-
use ruby_prism_sys::{pm_comment_t, pm_diagnostic_t, pm_location_t, pm_magic_comment_t, pm_node_destroy, pm_node_t, pm_parser_free, pm_parser_t};
11+
use ruby_prism_sys::{pm_comment_t, pm_diagnostic_t, pm_location_t, pm_magic_comment_t, pm_newline_list_line_column, pm_node_destroy, pm_node_t, pm_parser_free, pm_parser_t};
1212

1313
pub use self::comments::{Comment, CommentType, Comments, MagicComment, MagicComments};
1414
pub use self::diagnostics::{Diagnostic, Diagnostics};
@@ -66,6 +66,58 @@ impl<'pr> Location<'pr> {
6666
})
6767
}
6868
}
69+
70+
/// Returns the line number where this location starts.
71+
#[must_use]
72+
pub fn start_line(&self) -> i32 {
73+
self.line_column_at(self.start).line
74+
}
75+
76+
/// Returns the line number where this location ends.
77+
#[must_use]
78+
pub fn end_line(&self) -> i32 {
79+
self.line_column_at(self.end()).line
80+
}
81+
82+
/// Returns the column number in bytes where this location starts from the
83+
/// start of the line.
84+
#[must_use]
85+
pub fn start_column(&self) -> u32 {
86+
self.line_column_at(self.start).column
87+
}
88+
89+
/// Returns the column number in bytes where this location ends from the
90+
/// start of the line.
91+
#[must_use]
92+
pub fn end_column(&self) -> u32 {
93+
self.line_column_at(self.end()).column
94+
}
95+
96+
/// Returns a new location that is the result of chopping off the last byte.
97+
#[must_use]
98+
pub const fn chop(&self) -> Self {
99+
Location {
100+
parser: self.parser,
101+
start: self.start,
102+
length: if self.length == 0 { 0 } else { self.length - 1 },
103+
marker: std::marker::PhantomData,
104+
}
105+
}
106+
107+
fn line_column_at(&self, offset: u32) -> LineColumn {
108+
unsafe {
109+
let parser = self.parser.as_ptr();
110+
let newline_list = &(*parser).newline_list;
111+
let start_line = (*parser).start_line;
112+
let result = pm_newline_list_line_column(newline_list, offset, start_line);
113+
LineColumn { line: result.line, column: result.column }
114+
}
115+
}
116+
}
117+
118+
struct LineColumn {
119+
line: i32,
120+
column: u32,
69121
}
70122

71123
impl std::fmt::Debug for Location<'_> {

0 commit comments

Comments
 (0)