Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a helper for getting the location of a span in some textual source #1937

Merged
merged 2 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 7 additions & 14 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 @@ -1365,19 +1366,11 @@ impl ParseError {
writer.into_string()
}

/// 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),
}
/// Returns a [`SourceLocation`] for the first label in the error message.
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::{SourceLocation, Span, SpanContext, WithSpan};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[cfg(feature = "deserialize")]
Expand Down
154 changes: 154 additions & 0 deletions src/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ impl Span {
pub fn is_defined(&self) -> bool {
*self != Self::default()
}

/// Return a [`SourceLocation`] for 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 +85,25 @@ impl From<Range<usize>> for Span {
}
}

/// A human-readable representation for a span, tailored for text source.
///
/// Corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
/// the WebGPU specification, except that `offset` and `length` are in bytes
/// (UTF-8 code units), instead of UTF-16 code units.
///
/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage
#[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 +220,22 @@ impl<E> WithSpan<E> {
res.spans.extend(self.spans);
res
}

#[cfg(feature = "span")]
/// Return a [`SourceLocation`] for our first span, if we have one.
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"))]
/// Return a [`SourceLocation`] for our first span, if we have one.
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 +323,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
}
);
}