Skip to content

Commit a690c9d

Browse files
authored
Merge pull request #3890 from sei40kr/feat/rust-location-methods
[rust] add `Location` line/column and chop methods
2 parents d9e359f + 4258b2a commit a690c9d

4 files changed

Lines changed: 110 additions & 3 deletions

File tree

include/prism/util/pm_line_offset_list.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ int32_t pm_line_offset_list_line(const pm_line_offset_list_t *list, uint32_t cur
9696
* @param start_line The line to start counting from.
9797
* @return The line and column of the given offset.
9898
*/
99-
pm_line_column_t pm_line_offset_list_line_column(const pm_line_offset_list_t *list, uint32_t cursor, int32_t start_line);
99+
PRISM_EXPORTED_FUNCTION pm_line_column_t pm_line_offset_list_line_column(const pm_line_offset_list_t *list, uint32_t cursor, int32_t start_line);
100100

101101
/**
102102
* Free the internal memory allocated for the list.

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

Lines changed: 15 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 {
@@ -134,6 +148,7 @@ fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings {
134148
.rustified_non_exhaustive_enum("pm_options_version_t")
135149
// Functions
136150
.allowlist_function("pm_arena_free")
151+
.allowlist_function("pm_line_offset_list_line_column")
137152
.allowlist_function("pm_list_empty_p")
138153
.allowlist_function("pm_list_free")
139154
.allowlist_function("pm_options_command_line_set")

rust/ruby-prism/src/lib.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ mod tests {
448448
}
449449

450450
#[test]
451-
fn location_test() {
451+
fn location_slice_test() {
452452
let source = "111 + 222 + 333";
453453
let result = parse(source.as_ref());
454454

@@ -502,6 +502,48 @@ mod tests {
502502
assert_eq!(slice, "222");
503503
}
504504

505+
#[test]
506+
fn location_line_column_test() {
507+
let source = "first\nsecond\nthird";
508+
let result = parse(source.as_ref());
509+
510+
let node = result.node();
511+
let program = node.as_program_node().unwrap();
512+
let statements = program.statements().body();
513+
let mut iter = statements.iter();
514+
515+
let _first = iter.next().unwrap();
516+
let second = iter.next().unwrap();
517+
let third = iter.next().unwrap();
518+
519+
let second_loc = second.location();
520+
assert_eq!(second_loc.start_line(), 2);
521+
assert_eq!(second_loc.end_line(), 2);
522+
assert_eq!(second_loc.start_column(), 0);
523+
assert_eq!(second_loc.end_column(), 6);
524+
525+
let third_loc = third.location();
526+
assert_eq!(third_loc.start_line(), 3);
527+
assert_eq!(third_loc.end_line(), 3);
528+
assert_eq!(third_loc.start_column(), 0);
529+
assert_eq!(third_loc.end_column(), 5);
530+
}
531+
532+
#[test]
533+
fn location_chop_test() {
534+
let result = parse(b"foo");
535+
let mut location = result.node().as_program_node().unwrap().location();
536+
537+
assert_eq!(location.chop().as_slice(), b"fo");
538+
assert_eq!(location.chop().chop().chop().as_slice(), b"");
539+
540+
// Check that we don't go negative.
541+
for _ in 0..10 {
542+
location = location.chop();
543+
}
544+
assert_eq!(location.as_slice(), b"");
545+
}
546+
505547
#[test]
506548
fn visitor_test() {
507549
use super::{visit_interpolated_regular_expression_node, visit_regular_expression_node, InterpolatedRegularExpressionNode, RegularExpressionNode, Visit};

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

Lines changed: 51 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_arena_free, pm_arena_t, pm_comment_t, pm_diagnostic_t, pm_location_t, pm_magic_comment_t, pm_node_t, pm_parser_free, pm_parser_t};
11+
use ruby_prism_sys::{pm_arena_free, pm_arena_t, pm_comment_t, pm_diagnostic_t, pm_line_offset_list_line_column, pm_location_t, pm_magic_comment_t, 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,56 @@ impl<'pr> Location<'pr> {
6666
})
6767
}
6868
}
69+
70+
/// Returns a new location that is the result of chopping off the last byte.
71+
#[must_use]
72+
pub const fn chop(&self) -> Self {
73+
Location {
74+
parser: self.parser,
75+
start: self.start,
76+
length: if self.length == 0 { 0 } else { self.length - 1 },
77+
marker: std::marker::PhantomData,
78+
}
79+
}
80+
}
81+
82+
impl Location<'_> {
83+
/// Returns the line number where this location starts.
84+
#[must_use]
85+
pub fn start_line(&self) -> i32 {
86+
self.line_column(self.start).0
87+
}
88+
89+
/// Returns the column number in bytes where this location starts from the
90+
/// start of the line.
91+
#[must_use]
92+
pub fn start_column(&self) -> u32 {
93+
self.line_column(self.start).1
94+
}
95+
96+
/// Returns the line number where this location ends.
97+
#[must_use]
98+
pub fn end_line(&self) -> i32 {
99+
self.line_column(self.end()).0
100+
}
101+
102+
/// Returns the column number in bytes where this location ends from the
103+
/// start of the line.
104+
#[must_use]
105+
pub fn end_column(&self) -> u32 {
106+
self.line_column(self.end()).1
107+
}
108+
109+
/// Returns the line and column number for the given byte offset.
110+
fn line_column(&self, cursor: u32) -> (i32, u32) {
111+
// SAFETY: We read the line_offsets and start_line from the parser,
112+
// which is valid for the lifetime of this Location.
113+
unsafe {
114+
let parser = self.parser.as_ptr();
115+
let result = pm_line_offset_list_line_column(&raw const (*parser).line_offsets, cursor, (*parser).start_line);
116+
(result.line, result.column)
117+
}
118+
}
69119
}
70120

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

0 commit comments

Comments
 (0)