donet_core/parser/
error.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024-2025 Max Rodriguez
5
6    Donet is free software; you can redistribute it and/or modify
7    it under the terms of the GNU Affero General Public License,
8    as published by the Free Software Foundation, either version 3
9    of the License, or (at your option) any later version.
10
11    Donet is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14    GNU Affero General Public License for more details.
15
16    You should have received a copy of the GNU Affero General Public
17    License along with Donet. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20//! Errors that can be returned by the DC parser pipeline.
21
22use super::lexer::{DCToken, Span};
23use super::pipeline::{PipelineData, PipelineStage};
24use codespan_diag::Label;
25use codespan_diag::LabelStyle;
26use codespan_reporting::diagnostic as codespan_diag;
27use std::mem::discriminant;
28use thiserror::Error;
29
30const ERR_DOCS: &str = "https://docs.donet-server.org/master/dclanguage/error-codes";
31
32/// Convert `self` type to an error code.
33/// Used with DC parser pipeline error types.
34pub trait ToErrorCode
35where
36    Self: std::error::Error,
37{
38    fn error_code(&self) -> &str;
39}
40
41#[derive(Debug, Error)]
42#[error(transparent)]
43pub enum DCReadError {
44    #[error("parser error")]
45    Syntax,
46    #[error("semantics error")]
47    Semantic,
48    IO(#[from] std::io::Error),
49}
50
51#[derive(Debug, Error)]
52#[error(transparent)]
53pub enum PipelineError {
54    Parser(#[from] ParseError),
55    Semantics(#[from] SemanticError),
56}
57
58impl ToErrorCode for PipelineError {
59    fn error_code(&self) -> &str {
60        // Get the error code from the underlying error type.
61        match self {
62            Self::Parser(err) => err.error_code(),
63            Self::Semantics(err) => err.error_code(),
64        }
65    }
66}
67
68/// Error type for the semantic analysis stage of the pipeline.
69#[derive(Debug, Error)]
70pub enum SemanticError {
71    // generic
72    #[error("`{0}` is already defined")]
73    AlreadyDefined(String),
74    #[error("`{0}` is not defined")]
75    NotDefined(String),
76
77    // dc file
78    #[error("multiple inheritance is not allowed")]
79    MultipleInheritanceDisabled,
80    #[error("maximum number of dclasses declared")]
81    DClassOverflow,
82    #[error("maximum number of fields declared")]
83    FieldOverflow,
84
85    // python-style imports
86    #[error("redundant view suffix `{0}`")]
87    RedundantViewSuffix(String),
88
89    // keywords
90    #[error("redundant keyword `{0}`")]
91    RedundantKeyword(String),
92
93    // structs
94    #[error("dc keywords are not allowed in struct fields")]
95    KeywordsInStructField,
96
97    // switches
98    #[error("duplicate case value")]
99    RedundantCase,
100    #[error("default case already defined")]
101    RedundantDefault,
102    #[error("case value type does not match key value type")]
103    InvalidCaseValueType,
104
105    // molecular fields
106    #[error("`mismatched dc keywords in molecule between `{atom1}` and `{atom2}`")]
107    MismatchedKeywords { atom1: String, atom2: String },
108    #[error("`{0}` is not an atomic field")]
109    ExpectedAtomic(String),
110
111    // numeric ranges
112    #[error("invalid range for type")]
113    InvalidRange,
114    #[error("overlapping range")]
115    OverlappingRange,
116    #[error("value out of range")]
117    ValueOutOfRange,
118
119    // transforms
120    #[error("invalid divisor")]
121    InvalidDivisor,
122    #[error("invalid modulus")]
123    InvalidModulus,
124
125    // default
126    #[error("invalid default value for type")]
127    InvalidDefault,
128
129    // struct type
130    #[error("`{0}` is not a struct")]
131    ExpectedStruct(String),
132}
133
134impl ToErrorCode for SemanticError {
135    fn error_code(&self) -> &str {
136        match self {
137            // generic
138            Self::AlreadyDefined(_) => "E0200",
139            Self::NotDefined(_) => "E0201",
140            // dc file
141            Self::MultipleInheritanceDisabled => "E0210",
142            Self::DClassOverflow => "E0211",
143            Self::FieldOverflow => "E0212",
144            // python-style imports
145            Self::RedundantViewSuffix(_) => "E0220",
146            // keywords
147            Self::RedundantKeyword(_) => "E0230",
148            // structs
149            Self::KeywordsInStructField => "E0240",
150            // switches
151            Self::RedundantCase => "E0250",
152            Self::RedundantDefault => "E0251",
153            Self::InvalidCaseValueType => "E0252",
154            // molecular fields
155            Self::MismatchedKeywords { atom1: _, atom2: _ } => "E0260",
156            Self::ExpectedAtomic(_) => "E0261",
157            // numeric ranges
158            Self::InvalidRange => "E0270",
159            Self::OverlappingRange => "E0271",
160            Self::ValueOutOfRange => "E0272",
161            // transforms
162            Self::InvalidDivisor => "E0280",
163            Self::InvalidModulus => "E0281",
164            // default
165            Self::InvalidDefault => "E0290",
166            // struct type
167            Self::ExpectedStruct(_) => "E0300",
168        }
169    }
170}
171
172/// Error type for the parser stage of the pipeline.
173/// Currently, it only stores one error type, which is
174/// the standard error type for the parser. Due to a
175/// plex limitation, I am unable to propagate custom
176/// errors from grammar productions. See Donet issue #19.
177#[derive(Debug, Error)]
178pub enum ParseError {
179    #[error("syntax error; {1}, found `{0:?}`")]
180    Error(DCToken, String),
181}
182
183impl ToErrorCode for ParseError {
184    fn error_code(&self) -> &str {
185        match self {
186            Self::Error(_, _) => "E0100",
187        }
188    }
189}
190
191pub(crate) struct Diagnostic {
192    span: Span,
193    stage: PipelineStage,
194    file_id: usize,
195    severity: codespan_diag::Severity,
196    error: PipelineError,
197}
198
199impl Diagnostic {
200    pub fn error(span: Span, pipeline: &mut PipelineData, err: impl Into<PipelineError>) -> Self {
201        Self {
202            span,
203            stage: pipeline.current_stage(),
204            file_id: pipeline.current_file(),
205            severity: codespan_diag::Severity::Error,
206            error: err.into(),
207        }
208    }
209}
210
211/// Allows converting our Diagnostic type into a codespan Diagnostic type.
212impl From<Diagnostic> for codespan_diag::Diagnostic<usize> {
213    fn from(val: Diagnostic) -> codespan_diag::Diagnostic<usize> {
214        codespan_diag::Diagnostic::new(val.severity)
215            .with_message(val.error.to_string())
216            .with_code(val.error.error_code())
217            .with_labels(vec![Label::new(
218                LabelStyle::Primary,
219                val.file_id,
220                val.span.min..val.span.max,
221            )])
222            .with_notes({
223                // If error type is from the Plex parser stage, emit the following notice.
224                if discriminant(&val.stage) == discriminant(&PipelineStage::Parser) {
225                    vec![
226                        "Syntax errors are limited. Please see issue #19.".into(),
227                        "https://gitlab.com/donet-server/donet/-/issues/19".into(),
228                    ]
229                // for all other error codes, simply add a note that has a link to its doc page.
230                } else {
231                    vec![
232                        "For a more detailed explanation see:".into(),
233                        format!("{}/#{}", ERR_DOCS, val.error.error_code().to_lowercase()),
234                    ]
235                }
236            })
237    }
238}