Skip to content

Commit 18ee95e

Browse files
sei40krclaude
authored 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 c5882ce commit 18ee95e

5 files changed

Lines changed: 129 additions & 6 deletions

File tree

.github/workflows/rust-bindings.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ jobs:
4040
path: |
4141
~/.cargo/registry
4242
~/.cargo/git
43-
target
44-
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
43+
rust/target
44+
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml', 'rust/Cargo.lock', 'rust/ruby-prism-sys/build/**/*.rs', 'include/**/*.h', 'src/**/*.c') }}
4545
- name: Run tests
4646
run: bundle exec rake cargo:test
4747

@@ -69,8 +69,8 @@ jobs:
6969
path: |
7070
~/.cargo/registry
7171
~/.cargo/git
72-
target
73-
key: ${{ runner.os }}-cargo--${{ hashFiles('**/Cargo.toml') }}
72+
rust/target
73+
key: ${{ runner.os }}-cargo--${{ hashFiles('**/Cargo.toml', 'rust/Cargo.lock', 'rust/ruby-prism-sys/build/**/*.rs', 'include/**/*.h', 'src/**/*.c') }}
7474
- name: rake cargo:build
7575
run: bundle exec rake cargo:build
7676
- name: rake cargo:lint

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ fn main() {
99

1010
let ruby_build_path = prism_lib_path();
1111
let ruby_include_path = prism_include_path();
12+
emit_rerun_hints(&ruby_include_path);
1213

1314
// Tell cargo/rustc that we want to link against `libprism.a`.
1415
println!("cargo:rustc-link-lib=static=prism");
@@ -23,6 +24,19 @@ fn main() {
2324
write_bindings(&bindings);
2425
}
2526

27+
fn emit_rerun_hints(ruby_include_path: &Path) {
28+
println!("cargo:rerun-if-env-changed=PRISM_INCLUDE_DIR");
29+
println!("cargo:rerun-if-env-changed=PRISM_LIB_DIR");
30+
println!("cargo:rerun-if-changed={}", ruby_include_path.display());
31+
32+
if let Some(project_root) = ruby_include_path.parent() {
33+
let src_path = project_root.join("src");
34+
if src_path.exists() {
35+
println!("cargo:rerun-if-changed={}", src_path.display());
36+
}
37+
}
38+
}
39+
2640
/// Gets the path to project files (`libprism*`) at `[root]/build/`.
2741
///
2842
fn prism_lib_path() -> PathBuf {
@@ -118,6 +132,8 @@ fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings {
118132
.allowlist_type("pm_diagnostic_t")
119133
.allowlist_type("pm_list_t")
120134
.allowlist_type("pm_magic_comment_t")
135+
.allowlist_type("pm_line_column_t")
136+
.allowlist_type("pm_line_offset_list_t")
121137
.allowlist_type("pm_node_t")
122138
.allowlist_type("pm_node_type")
123139
.allowlist_type("pm_parser_t")
@@ -140,6 +156,7 @@ fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings {
140156
.allowlist_function("pm_string_length")
141157
.allowlist_function("pm_string_source")
142158
.allowlist_function("pm_version")
159+
.allowlist_function("pm_line_offset_list_line_column")
143160
// Vars
144161
.allowlist_var(r"^pm_encoding\S+")
145162
.generate()

rust/ruby-prism-sys/tests/utils_tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ fn list_test() {
3030
}
3131
}
3232

33+
#[test]
34+
fn line_column_test() {
35+
use ruby_prism_sys::{pm_line_offset_list_line_column, pm_line_offset_list_t};
36+
37+
let mut offsets: Vec<u32> = vec![0, 10, 20, 30, 40];
38+
let line_offsets = pm_line_offset_list_t {
39+
size: 5,
40+
capacity: 8,
41+
offsets: offsets.as_mut_ptr(),
42+
};
43+
44+
unsafe {
45+
let result = pm_line_offset_list_line_column(&raw const line_offsets, 32, 1);
46+
assert_eq!(result.line, 4);
47+
assert_eq!(result.column, 2);
48+
}
49+
}
50+
3351
mod string {
3452
use ruby_prism_sys::{
3553
pm_string_free, pm_string_length, pm_string_source, pm_string_t, pm_string_t__bindgen_ty_1, PM_STRING_CONSTANT,

rust/ruby-prism/src/lib.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ mod tests {
127127
}
128128

129129
#[test]
130-
fn location_test() {
130+
fn location_slice_test() {
131131
let source = "111 + 222 + 333";
132132
let result = parse(source.as_ref());
133133

@@ -181,6 +181,48 @@ mod tests {
181181
assert_eq!(slice, "222");
182182
}
183183

184+
#[test]
185+
fn location_line_column_test() {
186+
let source = "first\nsecond\nthird";
187+
let result = parse(source.as_ref());
188+
189+
let node = result.node();
190+
let program = node.as_program_node().unwrap();
191+
let statements = program.statements().body();
192+
let mut iter = statements.iter();
193+
194+
let _first = iter.next().unwrap();
195+
let second = iter.next().unwrap();
196+
let third = iter.next().unwrap();
197+
198+
let second_loc = second.location();
199+
assert_eq!(second_loc.start_line(), 2);
200+
assert_eq!(second_loc.end_line(), 2);
201+
assert_eq!(second_loc.start_column(), 0);
202+
assert_eq!(second_loc.end_column(), 6);
203+
204+
let third_loc = third.location();
205+
assert_eq!(third_loc.start_line(), 3);
206+
assert_eq!(third_loc.end_line(), 3);
207+
assert_eq!(third_loc.start_column(), 0);
208+
assert_eq!(third_loc.end_column(), 5);
209+
}
210+
211+
#[test]
212+
fn location_chop_test() {
213+
let result = parse(b"foo");
214+
let mut location = result.node().as_program_node().unwrap().location();
215+
216+
assert_eq!(location.chop().as_slice(), b"fo");
217+
assert_eq!(location.chop().chop().chop().as_slice(), b"");
218+
219+
// Check that we don't go negative.
220+
for _ in 0..10 {
221+
location = location.chop();
222+
}
223+
assert_eq!(location.as_slice(), b"");
224+
}
225+
184226
#[test]
185227
fn visitor_test() {
186228
use super::{visit_interpolated_regular_expression_node, visit_regular_expression_node, InterpolatedRegularExpressionNode, RegularExpressionNode, Visit};

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

Lines changed: 47 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_line_column_t, pm_line_offset_list_line_column, pm_location_t, pm_magic_comment_t, 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,52 @@ 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(self.start).line
74+
}
75+
76+
/// Returns the column number in bytes where this location starts from the
77+
/// start of the line.
78+
#[must_use]
79+
pub fn start_column(&self) -> u32 {
80+
self.line_column(self.start).column
81+
}
82+
83+
/// Returns the line number where this location ends.
84+
#[must_use]
85+
pub fn end_line(&self) -> i32 {
86+
self.line_column(self.end()).line
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(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(&self, offset: u32) -> pm_line_column_t {
108+
unsafe {
109+
let parser = self.parser.as_ptr();
110+
let line_offsets = &(*parser).line_offsets;
111+
let start_line = (*parser).start_line;
112+
pm_line_offset_list_line_column(line_offsets, offset, start_line)
113+
}
114+
}
69115
}
70116

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

0 commit comments

Comments
 (0)