Skip to main content

opy_rs/
diag.rs

1//! Frontend diagnostics: structured, source-located failures.
2//!
3//! Every frontend failure is a [`OpyError`] with a stable `code`, a
4//! human message, and an optional source span. The `code` is the machine
5//! contract; wording is not.
6//!
7//! [`Position`] is serializable so tooling surfaces ([`crate::tooling`]) can
8//! emit resolved source locations as JSON without introducing a parallel
9//! position type.
10
11/// A structured frontend error.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct OpyError {
14    /// A stable machine-readable code, e.g. `parse-error`.
15    pub code: String,
16    /// Human-readable message (not part of the machine contract).
17    pub message: String,
18    /// The offending source region, when known.
19    pub span: Option<Span>,
20}
21
22/// A source span in the frontend's file registry.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Span {
25    pub file: u32,
26    pub start: Position,
27    pub end: Position,
28}
29
30/// A 1-based line/column position.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
32pub struct Position {
33    pub line: u32,
34    pub col: u32,
35}
36
37impl Position {
38    pub const fn new(line: u32, col: u32) -> Position {
39        Position { line, col }
40    }
41}
42
43pub(crate) fn shift_position(position: Position, origin: Position) -> Position {
44    Position::new(
45        origin.line + position.line.saturating_sub(1),
46        if position.line == 1 {
47            origin.col + position.col.saturating_sub(1)
48        } else {
49            position.col
50        },
51    )
52}
53
54impl Span {
55    pub fn new(file: u32, start: Position, end: Position) -> Span {
56        Span { file, start, end }
57    }
58}
59
60/// A crate-wide result alias.
61pub type OpyResult<T> = Result<T, OpyError>;
62
63impl OpyError {
64    /// An error without a source span.
65    pub fn new(code: impl Into<String>, message: impl Into<String>) -> OpyError {
66        OpyError {
67            code: code.into(),
68            message: message.into(),
69            span: None,
70        }
71    }
72
73    /// An error at a source position.
74    pub fn at(code: impl Into<String>, message: impl Into<String>, span: Span) -> OpyError {
75        OpyError {
76            code: code.into(),
77            message: message.into(),
78            span: Some(span),
79        }
80    }
81}
82
83impl std::fmt::Display for OpyError {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "{}: {}", self.code, self.message)
86    }
87}
88
89impl std::error::Error for OpyError {}