Skip to content

Commit ddbf846

Browse files
Merge pull request #35 from axodotdev/subspan
add SourceFile::span_for_substr
2 parents 701388f + 071a28e commit ddbf846

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed

src/source.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,34 @@ impl SourceFile {
141141
}
142142
Some(SourceSpan::from(start..end))
143143
}
144+
145+
/// Creates a span for an item using a substring of `contents`
146+
///
147+
/// Note that substr must be a literal substring, as in it must be
148+
/// a pointer into the same string! If it's not we'll return None.
149+
pub fn span_for_substr(&self, substr: &str) -> Option<SourceSpan> {
150+
// Get the bounds of the full string
151+
let base_addr = self.inner.contents.as_ptr() as usize;
152+
let base_len = self.inner.contents.len();
153+
154+
// Get the bounds of the substring
155+
let substr_addr = substr.as_ptr() as usize;
156+
let substr_len = substr.len();
157+
158+
// The index of the substring is just the number of bytes it is from the start
159+
// (This will bail out if the """substring""" has an address *before* the full string)
160+
let start = substr_addr.checked_sub(base_addr)?;
161+
// The end index (exclusive) is just the start index + sublen
162+
// (This will bail out if this overflows)
163+
let end = start.checked_add(substr_len)?;
164+
// Finally, make sure the substr endpoint isn't past the end of the full string
165+
if end > base_len {
166+
return None;
167+
}
168+
169+
// At this point it's definitely a substring, nice!
170+
Some(SourceSpan::from(start..end))
171+
}
144172
}
145173

146174
impl SourceCode for SourceFile {

tests/source.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use miette::SourceCode;
2+
3+
#[test]
4+
fn substr_span() {
5+
// Make the file
6+
let contents = String::from("hello !there!");
7+
let source = axoasset::SourceFile::new("file.md", contents).unwrap();
8+
9+
// Do some random parsing operation
10+
let mut parse = source.contents().split('!');
11+
let _ = parse.next();
12+
let there = parse.next().unwrap();
13+
14+
// Get the span
15+
let there_span = source.span_for_substr(there).unwrap();
16+
17+
// Assert the span is correct
18+
let span_bytes = source.read_span(&there_span, 0, 0).unwrap().data();
19+
assert_eq!(std::str::from_utf8(span_bytes).unwrap(), "there");
20+
}
21+
22+
#[test]
23+
fn substr_span_invalid() {
24+
// Make the file
25+
let contents = String::from("hello !there!");
26+
let source = axoasset::SourceFile::new("file.md", contents).unwrap();
27+
28+
// Get the span for a non-substring (string literal isn't pointing into the String)
29+
let there_span = source.span_for_substr("there");
30+
assert_eq!(there_span, None);
31+
}

0 commit comments

Comments
 (0)