From 28d026fb69a3d11a14b842f5cc0f2d6ec9a4b10b Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Fri, 20 May 2022 19:01:16 +0200 Subject: [PATCH] Add a helper for getting the location of a span in some textual source. --- src/front/wgsl/mod.rs | 18 ++--- src/lib.rs | 2 +- src/span.rs | 150 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 13 deletions(-) diff --git a/src/front/wgsl/mod.rs b/src/front/wgsl/mod.rs index 94c3510d7f..76a61e98ea 100644 --- a/src/front/wgsl/mod.rs +++ b/src/front/wgsl/mod.rs @@ -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, }; @@ -29,7 +30,7 @@ use self::{ }; use codespan_reporting::{ diagnostic::{Diagnostic, Label}, - files::{Files, SimpleFile}, + files::SimpleFile, term::{ self, termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}, @@ -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 { + self.labels + .get(0) + .map(|label| NagaSpan::new(label.0.start as u32, label.0.end as u32).location(source)) } } diff --git a/src/lib.rs b/src/lib.rs index 80e7e6f789..60bbe0e28a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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")] diff --git a/src/span.rs b/src/span.rs index 0c60ee005c..1c2249c4a0 100644 --- a/src/span.rs +++ b/src/span.rs @@ -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> for Span { @@ -70,6 +86,22 @@ impl From> 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); @@ -186,6 +218,20 @@ impl WithSpan { res.spans.extend(self.spans); res } + + #[cfg(feature = "span")] + pub fn location(&self, source: &str) -> Option { + 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 { + None + } } /// Convenience trait for [`Error`] to be able to apply spans to anything. @@ -273,3 +319,107 @@ impl MapErrWithSpan for Result> { self.map_err(|e| e.and_then(func).into_other::()) } } + +#[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 + } + ); +}