#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SourcePosition {
pub byte_offset: usize,
pub line: usize,
pub column: usize,
}
impl SourcePosition {
pub const fn start() -> Self {
Self {
byte_offset: 0,
line: 1,
column: 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Span {
pub start: SourcePosition,
pub end: SourcePosition,
}
impl Span {
pub const fn point(position: SourcePosition) -> Self {
Self {
start: position,
end: position,
}
}
pub fn covering(start: Self, end: Self) -> Self {
Self {
start: start.start,
end: end.end,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Spanned<T> {
pub value: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub const fn new(value: T, span: Span) -> Self {
Self { value, span }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelatedInformation {
pub message: String,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub code: &'static str,
pub severity: Severity,
pub message: String,
pub span: Span,
pub expected: Vec<String>,
pub help: Option<String>,
pub related: Vec<RelatedInformation>,
}
impl Diagnostic {
pub(crate) fn error(code: &'static str, message: impl Into<String>, span: Span) -> Self {
Self {
code,
severity: Severity::Error,
message: message.into(),
span,
expected: Vec::new(),
help: None,
related: Vec::new(),
}
}
pub(crate) fn warning(code: &'static str, message: impl Into<String>, span: Span) -> Self {
Self {
code,
severity: Severity::Warning,
message: message.into(),
span,
expected: Vec::new(),
help: None,
related: Vec::new(),
}
}
pub(crate) fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
pub(crate) fn with_expected<I, S>(mut self, expected: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.expected = expected.into_iter().map(Into::into).collect();
self
}
pub(crate) fn with_related(mut self, message: impl Into<String>, span: Span) -> Self {
self.related.push(RelatedInformation {
message: message.into(),
span,
});
self
}
}
#[cfg(test)]
mod tests {
use super::{Diagnostic, SourcePosition, Span};
#[test]
fn diagnostics_default_to_no_expected_values() {
let diagnostic = Diagnostic::error(
"STK2002",
"Unexpected value.",
Span::point(SourcePosition::start()),
);
assert!(diagnostic.expected.is_empty());
}
#[test]
fn diagnostics_preserve_expected_value_order() {
let diagnostic = Diagnostic::error(
"STK2002",
"Unknown direction.",
Span::point(SourcePosition::start()),
)
.with_expected(["right", "down"]);
assert_eq!(diagnostic.expected, ["right", "down"]);
}
}