diff --git a/rust/ruby-prism-sys/build/main.rs b/rust/ruby-prism-sys/build/main.rs index b403747ed8..2bddc9cd16 100644 --- a/rust/ruby-prism-sys/build/main.rs +++ b/rust/ruby-prism-sys/build/main.rs @@ -9,6 +9,7 @@ fn main() { let ruby_build_path = prism_lib_path(); let ruby_include_path = prism_include_path(); + emit_rerun_hints(&ruby_include_path); // Tell cargo/rustc that we want to link against `libprism.a`. println!("cargo:rustc-link-lib=static=prism"); @@ -23,6 +24,19 @@ fn main() { write_bindings(&bindings); } +fn emit_rerun_hints(ruby_include_path: &Path) { + println!("cargo:rerun-if-env-changed=PRISM_INCLUDE_DIR"); + println!("cargo:rerun-if-env-changed=PRISM_LIB_DIR"); + println!("cargo:rerun-if-changed={}", ruby_include_path.display()); + + if let Some(project_root) = ruby_include_path.parent() { + let src_path = project_root.join("src"); + if src_path.exists() { + println!("cargo:rerun-if-changed={}", src_path.display()); + } + } +} + /// Gets the path to project files (`libprism*`) at `[root]/build/`. /// fn prism_lib_path() -> PathBuf { diff --git a/rust/ruby-prism/src/lib.rs b/rust/ruby-prism/src/lib.rs index f628056b3e..ac2ee4aa58 100644 --- a/rust/ruby-prism/src/lib.rs +++ b/rust/ruby-prism/src/lib.rs @@ -127,7 +127,7 @@ mod tests { } #[test] - fn location_test() { + fn location_slice_test() { let source = "111 + 222 + 333"; let result = parse(source.as_ref()); @@ -181,6 +181,48 @@ mod tests { assert_eq!(slice, "222"); } + #[test] + fn location_line_column_test() { + let source = "first\nsecond\nthird"; + let result = parse(source.as_ref()); + + let node = result.node(); + let program = node.as_program_node().unwrap(); + let statements = program.statements().body(); + let mut iter = statements.iter(); + + let _first = iter.next().unwrap(); + let second = iter.next().unwrap(); + let third = iter.next().unwrap(); + + let second_loc = second.location(); + assert_eq!(second_loc.start_line(), 2); + assert_eq!(second_loc.end_line(), 2); + assert_eq!(second_loc.start_column(), 0); + assert_eq!(second_loc.end_column(), 6); + + let third_loc = third.location(); + assert_eq!(third_loc.start_line(), 3); + assert_eq!(third_loc.end_line(), 3); + assert_eq!(third_loc.start_column(), 0); + assert_eq!(third_loc.end_column(), 5); + } + + #[test] + fn location_chop_test() { + let result = parse(b"foo"); + let mut location = result.node().as_program_node().unwrap().location(); + + assert_eq!(location.chop().as_slice(), b"fo"); + assert_eq!(location.chop().chop().chop().as_slice(), b""); + + // Check that we don't go negative. + for _ in 0..10 { + location = location.chop(); + } + assert_eq!(location.as_slice(), b""); + } + #[test] fn visitor_test() { use super::{visit_interpolated_regular_expression_node, visit_regular_expression_node, InterpolatedRegularExpressionNode, RegularExpressionNode, Visit}; diff --git a/rust/ruby-prism/src/parse_result/mod.rs b/rust/ruby-prism/src/parse_result/mod.rs index 99fad68241..3268e78340 100644 --- a/rust/ruby-prism/src/parse_result/mod.rs +++ b/rust/ruby-prism/src/parse_result/mod.rs @@ -66,6 +66,69 @@ impl<'pr> Location<'pr> { }) } } + + /// Returns a new location that is the result of chopping off the last byte. + #[must_use] + pub const fn chop(&self) -> Self { + Location { + parser: self.parser, + start: self.start, + length: if self.length == 0 { 0 } else { self.length - 1 }, + marker: std::marker::PhantomData, + } + } +} + +impl Location<'_> { + /// Returns the line number where this location starts. + #[must_use] + pub fn start_line(&self) -> i32 { + self.line_column(self.start).0 + } + + /// Returns the column number in bytes where this location starts from the + /// start of the line. + #[must_use] + pub fn start_column(&self) -> u32 { + self.line_column(self.start).1 + } + + /// Returns the line number where this location ends. + #[must_use] + pub fn end_line(&self) -> i32 { + self.line_column(self.end()).0 + } + + /// Returns the column number in bytes where this location ends from the + /// start of the line. + #[must_use] + pub fn end_column(&self) -> u32 { + self.line_column(self.end()).1 + } + + /// Returns the line and column number for the given byte offset, using + /// binary search on the line offsets array. + fn line_column(&self, cursor: u32) -> (i32, u32) { + // SAFETY: We read the line_offsets and start_line from the parser, + // which is valid for the lifetime of this Location. + unsafe { + let parser = self.parser.as_ptr(); + let list = &(*parser).line_offsets; + let start_line = (*parser).start_line; + let offsets = std::slice::from_raw_parts(list.offsets, list.size); + + let index = offsets.partition_point(|&offset| offset <= cursor); + + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + if index > 0 && offsets[index - 1] == cursor { + ((index - 1) as i32 + start_line, 0) + } else { + let line = (index - 1) as i32 + start_line; + let column = cursor - offsets[index - 1]; + (line, column) + } + } + } } impl std::fmt::Debug for Location<'_> {