donet_core/parser/
pipeline.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024 Max Rodriguez <[email protected]>
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//! Defines the [`PipelineStage`] structure, which manages
21//! data stored in memory throughout the DC parser pipeline.
22
23use super::ast;
24use crate::dconfig::*;
25use codespan_reporting::diagnostic::Diagnostic;
26use codespan_reporting::diagnostic::Severity;
27use codespan_reporting::files::{self, SimpleFiles};
28use codespan_reporting::term;
29use term::termcolor::{ColorChoice, StandardStream};
30
31/// Used by the [`PipelineData`] structure to keep track
32/// of the current pipeline stage to properly store its state.
33#[derive(Debug, Default, Clone, PartialEq, Eq)]
34pub(crate) enum PipelineStage {
35    #[default]
36    Parser, // includes lexical and syntax analysis
37    SemanticAnalyzer,
38}
39
40impl PipelineStage {
41    pub(crate) fn next(&self) -> Self {
42        match self {
43            PipelineStage::Parser => PipelineStage::SemanticAnalyzer,
44            PipelineStage::SemanticAnalyzer => panic!("No next stage in pipeline."),
45        }
46    }
47}
48
49/// Data stored in memory throughout the DC parser pipeline.
50///
51/// Sets up writer and codespan config for rendering diagnostics
52/// to stderr & storing DC files that implement codespan's File trait.
53pub(crate) struct PipelineData<'a> {
54    dc_parser_config: DCFileConfig,
55    stage: PipelineStage,
56    _writer: StandardStream,
57    _config: term::Config,
58    diagnostics_enabled: bool,
59    errors_emitted: usize,
60    pub files: SimpleFiles<&'a str, &'a str>,
61    current_file: usize,
62    pub syntax_trees: Vec<ast::Root>,
63}
64
65/// If the [`PipelineData`] structure is dropped, this means the
66/// pipeline finished, either with success or error.
67///
68/// Upon drop, emit a final diagnostic with the finish status of the pipeline.
69impl Drop for PipelineData<'_> {
70    fn drop(&mut self) {
71        if self.errors_emitted > 0 {
72            let diag = Diagnostic::error().with_message(format!(
73                "Failed to read DC files due to {} previous errors.",
74                self.errors_emitted
75            ));
76
77            self.emit_diagnostic(diag).expect("Failed to emit diagnostic.");
78        }
79    }
80}
81
82impl From<DCFileConfig> for PipelineData<'_> {
83    fn from(value: DCFileConfig) -> Self {
84        Self {
85            dc_parser_config: value,
86            stage: PipelineStage::default(),
87            _writer: StandardStream::stderr(ColorChoice::Always),
88            _config: term::Config::default(),
89            diagnostics_enabled: {
90                // Disable diagnostics in unit tests
91                cfg_if! {
92                    if #[cfg(test)] {
93                        false
94                    } else {
95                        true
96                    }
97                }
98            },
99            errors_emitted: 0,
100            files: SimpleFiles::new(),
101            current_file: 0,
102            syntax_trees: vec![],
103        }
104    }
105}
106
107impl DCFileConfigAccessor for PipelineData<'_> {
108    fn get_dc_config(&self) -> &DCFileConfig {
109        &self.dc_parser_config
110    }
111}
112
113impl PipelineData<'_> {
114    /// Thin wrapper for emitting a codespan diagnostic using `PipelineData` properties.
115    pub(crate) fn emit_diagnostic(&mut self, diag: Diagnostic<usize>) -> Result<(), files::Error> {
116        if diag.severity == Severity::Error {
117            self.errors_emitted += 1;
118        }
119        if !self.diagnostics_enabled {
120            return Ok(());
121        }
122        term::emit(&mut self._writer.lock(), &self._config, &self.files, &diag)
123    }
124
125    #[inline(always)]
126    pub(crate) fn current_stage(&self) -> PipelineStage {
127        self.stage.clone()
128    }
129
130    pub(crate) fn next_stage(&mut self) {
131        self.stage = self.stage.next();
132        self.current_file = 0;
133    }
134
135    #[inline(always)]
136    pub(crate) fn current_file(&self) -> usize {
137        self.current_file
138    }
139
140    pub(crate) fn next_file(&mut self) {
141        self.current_file += 1
142    }
143
144    #[inline(always)]
145    pub(crate) fn failing(&self) -> bool {
146        self.errors_emitted > 0
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn next_stage_state() {
156        let mut pipeline: PipelineData = DCFileConfig::default().into();
157
158        pipeline.next_file(); // increase file counter to 1
159
160        pipeline.next_stage(); // should reset state for next stage
161
162        assert_eq!(pipeline.stage, PipelineStage::SemanticAnalyzer);
163        assert_eq!(pipeline.current_file, 0);
164    }
165}