Skip to content

Commit

Permalink
Add a helper for getting the location of a span in some textual source.
Browse files Browse the repository at this point in the history
  • Loading branch information
nical committed May 23, 2022
1 parent 62da3bf commit b523f3d
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 13 deletions.
18 changes: 6 additions & 12 deletions src/front/wgsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{
proc::{
ensure_block_returns, Alignment, Layouter, ResolveContext, ResolveError, TypeResolution,
},
span::SourceLocation,
span::Span as NagaSpan,
Bytes, ConstantInner, FastHashMap, ScalarValue,
};
Expand All @@ -29,7 +30,7 @@ use self::{
};
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::{Files, SimpleFile},
files::SimpleFile,
term::{
self,
termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor},
Expand Down Expand Up @@ -1367,17 +1368,10 @@ impl ParseError {

/// Returns the 1-based line number and column of the first label in the
/// error message.
pub fn location(&self, source: &str) -> (usize, usize) {
let files = SimpleFile::new("wgsl", source);
match self.labels.get(0) {
Some(label) => {
let location = files
.location((), label.0.start)
.expect("invalid span location");
(location.line_number, location.column_number)
}
None => (1, 1),
}
pub fn location(&self, source: &str) -> Option<SourceLocation> {
self.labels
.get(0)
.map(|label| NagaSpan::new(label.0.start as u32, label.0.end as u32).location(source))
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ pub mod valid;

pub use crate::arena::{Arena, Handle, Range, UniqueArena};

pub use crate::span::{Span, SpanContext, WithSpan};
pub use crate::span::{Span, SpanContext, WithSpan, SourceLocation};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[cfg(feature = "deserialize")]
Expand Down
150 changes: 150 additions & 0 deletions src/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ impl Span {
pub fn is_defined(&self) -> bool {
*self != Self::default()
}

/// Returns the 1-based line number and column of the this span in
/// the provided source.
pub fn location(&self, source: &str) -> SourceLocation {
let prefix = &source[..self.start as usize];
let line_number = prefix.matches('\n').count() as u32 + 1;
let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1;

SourceLocation {
line_number,
line_position,
offset: self.start,
length: self.end - self.start,
}
}
}

impl From<Range<usize>> for Span {
Expand All @@ -70,6 +86,22 @@ impl From<Range<usize>> for Span {
}
}

/// A human-readable representation for span, tailored for text source.
///
/// Corresponds to the positional members of `GPUCompilationMessage` from the WebGPU specification,
/// using utf8 instead of utf16 as reference encoding.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SourceLocation {
/// 1-based line number.
pub line_number: u32,
/// 1-based column of the start of this span
pub line_position: u32,
/// 0-based Offset in code units (in bytes) of the start of the span.
pub offset: u32,
/// Length in code units (in bytes) of the span.
pub length: u32,
}

/// A source code span together with "context", a user-readable description of what part of the error it refers to.
pub type SpanContext = (Span, String);

Expand Down Expand Up @@ -186,6 +218,20 @@ impl<E> WithSpan<E> {
res.spans.extend(self.spans);
res
}

#[cfg(feature = "span")]
pub fn location(&self, source: &str) -> Option<SourceLocation> {
if self.spans.is_empty() {
return None;
}

Some(self.spans[0].0.location(source))
}

#[cfg(not(feature = "span"))]
pub fn location(&self, _source: &str) -> Option<SourceLocation> {
None
}
}

/// Convenience trait for [`Error`] to be able to apply spans to anything.
Expand Down Expand Up @@ -273,3 +319,107 @@ impl<T, E, E2> MapErrWithSpan<E, E2> for Result<T, WithSpan<E>> {
self.map_err(|e| e.and_then(func).into_other::<E2>())
}
}

#[test]
fn span_location() {
let source = "12\n45\n\n89\n";
assert_eq!(
Span { start: 0, end: 1 }.location(source),
SourceLocation {
line_number: 1,
line_position: 1,
offset: 0,
length: 1
}
);
assert_eq!(
Span { start: 1, end: 2 }.location(source),
SourceLocation {
line_number: 1,
line_position: 2,
offset: 1,
length: 1
}
);
assert_eq!(
Span { start: 2, end: 3 }.location(source),
SourceLocation {
line_number: 1,
line_position: 3,
offset: 2,
length: 1
}
);
assert_eq!(
Span { start: 3, end: 5 }.location(source),
SourceLocation {
line_number: 2,
line_position: 1,
offset: 3,
length: 2
}
);
assert_eq!(
Span { start: 4, end: 6 }.location(source),
SourceLocation {
line_number: 2,
line_position: 2,
offset: 4,
length: 2
}
);
assert_eq!(
Span { start: 5, end: 6 }.location(source),
SourceLocation {
line_number: 2,
line_position: 3,
offset: 5,
length: 1
}
);
assert_eq!(
Span { start: 6, end: 7 }.location(source),
SourceLocation {
line_number: 3,
line_position: 1,
offset: 6,
length: 1
}
);
assert_eq!(
Span { start: 7, end: 8 }.location(source),
SourceLocation {
line_number: 4,
line_position: 1,
offset: 7,
length: 1
}
);
assert_eq!(
Span { start: 8, end: 9 }.location(source),
SourceLocation {
line_number: 4,
line_position: 2,
offset: 8,
length: 1
}
);
assert_eq!(
Span { start: 9, end: 10 }.location(source),
SourceLocation {
line_number: 4,
line_position: 3,
offset: 9,
length: 1
}
);
assert_eq!(
Span { start: 10, end: 11 }.location(source),
SourceLocation {
line_number: 5,
line_position: 1,
offset: 10,
length: 1
}
);
}

0 comments on commit b523f3d

Please sign in to comment.