donet_core/parser/
error.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024 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
30/// Convert `self` type to an error code.
31/// Used with DC parser pipeline error types.
32pub trait ToErrorCode
33where
34    Self: std::error::Error,
35{
36    fn error_code(&self) -> &str;
37}
38
39#[derive(Debug, Error)]
40#[error(transparent)]
41pub enum DCReadError {
42    #[error("parser error")]
43    Syntax,
44    #[error("semantics error")]
45    Semantic,
46    IO(#[from] std::io::Error),
47}
48
49#[derive(Debug, Error)]
50#[error(transparent)]
51pub enum PipelineError {
52    Parser(#[from] ParseError),
53    Semantics(#[from] SemanticError),
54}
55
56impl ToErrorCode for PipelineError {
57    fn error_code(&self) -> &str {
58        // Get the error code from the underlying error type.
59        match self {
60            Self::Parser(err) => err.error_code(),
61            Self::Semantics(err) => err.error_code(),
62        }
63    }
64}
65
66/// Error type for the semantic analysis stage of the pipeline.
67#[derive(Debug, Error)]
68pub enum SemanticError {
69    // generic
70    #[error("`{0}` is already defined")]
71    AlreadyDefined(String),
72    #[error("`{0}` is not defined")]
73    NotDefined(String),
74
75    // dc file
76    #[error("multiple inheritance is not allowed")]
77    MultipleInheritanceDisabled,
78    #[error("maximum number of dclasses declared")]
79    DClassOverflow,
80    #[error("maximum number of fields declared")]
81    FieldOverflow,
82
83    // python-style imports
84    #[error("redundant view suffix `{0}`")]
85    RedundantViewSuffix(String),
86
87    // keywords
88    #[error("redundant keyword `{0}`")]
89    RedundantKeyword(String),
90
91    // structs
92    #[error("dc keywords are not allowed in struct fields")]
93    KeywordsInStructField,
94
95    // switches
96    #[error("duplicate case value")]
97    RedundantCase,
98    #[error("default case already defined")]
99    RedundantDefault,
100    #[error("case value type does not match key value type")]
101    InvalidCaseValueType,
102
103    // molecular fields
104    #[error("`mismatched dc keywords in molecule between `{atom1}` and `{atom2}`")]
105    MismatchedKeywords { atom1: String, atom2: String },
106    #[error("`{0}` is not an atomic field")]
107    ExpectedAtomic(String),
108
109    // numeric ranges
110    #[error("invalid range for type")]
111    InvalidRange,
112    #[error("overlapping range")]
113    OverlappingRange,
114    #[error("value out of range")]
115    ValueOutOfRange,
116
117    // transforms
118    #[error("invalid divisor")]
119    InvalidDivisor,
120    #[error("invalid modulus")]
121    InvalidModulus,
122
123    // default
124    #[error("invalid default value for type")]
125    InvalidDefault,
126
127    // struct type
128    #[error("`{0}` is not a struct")]
129    ExpectedStruct(String),
130}
131
132impl ToErrorCode for SemanticError {
133    fn error_code(&self) -> &str {
134        match self {
135            // generic
136            Self::AlreadyDefined(_) => "E0200",
137            Self::NotDefined(_) => "E0201",
138            // dc file
139            Self::MultipleInheritanceDisabled => "E0210",
140            Self::DClassOverflow => "E0211",
141            Self::FieldOverflow => "E0212",
142            // python-style imports
143            Self::RedundantViewSuffix(_) => "E0220",
144            // keywords
145            Self::RedundantKeyword(_) => "E0230",
146            // structs
147            Self::KeywordsInStructField => "E0240",
148            // switches
149            Self::RedundantCase => "E0250",
150            Self::RedundantDefault => "E0251",
151            Self::InvalidCaseValueType => "E0252",
152            // molecular fields
153            Self::MismatchedKeywords { atom1: _, atom2: _ } => "E0260",
154            Self::ExpectedAtomic(_) => "E0261",
155            // numeric ranges
156            Self::InvalidRange => "E0270",
157            Self::OverlappingRange => "E0271",
158            Self::ValueOutOfRange => "E0272",
159            // transforms
160            Self::InvalidDivisor => "E0280",
161            Self::InvalidModulus => "E0281",
162            // default
163            Self::InvalidDefault => "E0290",
164            // struct type
165            Self::ExpectedStruct(_) => "E0300",
166        }
167    }
168}
169
170/// Error type for the parser stage of the pipeline.
171/// Currently, it only stores one error type, which is
172/// the standard error type for the parser. Due to a
173/// plex limitation, I am unable to propagate custom
174/// errors from grammar productions. See Donet issue #19.
175#[derive(Debug, Error)]
176pub enum ParseError {
177    #[error("syntax error; {1}, found `{0:?}`")]
178    Error(DCToken, String),
179}
180
181impl ToErrorCode for ParseError {
182    fn error_code(&self) -> &str {
183        match self {
184            Self::Error(_, _) => "E0100",
185        }
186    }
187}
188
189pub(crate) struct Diagnostic {
190    span: Span,
191    stage: PipelineStage,
192    file_id: usize,
193    severity: codespan_diag::Severity,
194    error: PipelineError,
195}
196
197impl Diagnostic {
198    pub fn error(span: Span, pipeline: &mut PipelineData, err: impl Into<PipelineError>) -> Self {
199        Self {
200            span,
201            stage: pipeline.current_stage(),
202            file_id: pipeline.current_file(),
203            severity: codespan_diag::Severity::Error,
204            error: err.into(),
205        }
206    }
207}
208
209/// Allows converting our Diagnostic type into a codespan Diagnostic type.
210impl From<Diagnostic> for codespan_diag::Diagnostic<usize> {
211    fn from(val: Diagnostic) -> codespan_diag::Diagnostic<usize> {
212        codespan_diag::Diagnostic::new(val.severity)
213            .with_message(val.error.to_string())
214            .with_code(val.error.error_code())
215            .with_labels(vec![Label::new(
216                LabelStyle::Primary,
217                val.file_id,
218                val.span.min..val.span.max,
219            )])
220            .with_notes({
221                // If error type is from the Plex parser stage, emit the following notice.
222                if discriminant(&val.stage) == discriminant(&PipelineStage::Parser) {
223                    vec![
224                        "Syntax errors are limited. Please see issue #19.".into(),
225                        "https://gitlab.com/donet-server/donet/-/issues/19".into(),
226                    ]
227                } else {
228                    vec![]
229                }
230            })
231    }
232}