swamp_script_error_report/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/script
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod analyze;
6mod dep;
7pub mod loader;
8pub mod parse;
9pub mod prelude;
10pub mod runtime;
11pub mod script_resolve;
12pub mod semantic;
13
14use eira::{Color, Kind, Pos, PosSpan, SourceLines};
15use std::fmt::Display;
16use std::io;
17use std::io::{Write, stderr};
18use std::path::Path;
19use swamp_script_analyzer::prelude::Error;
20use swamp_script_dep_loader::{DepLoaderError, DependencyError};
21
22use swamp_script_eval_loader::LoaderErr;
23use swamp_script_node::Span;
24use swamp_script_source_map::{FileId, SourceMap};
25
26#[derive(Debug)]
27pub enum ScriptResolveError {
28    AnalyzerError(Error),
29    DepLoaderError(DepLoaderError),
30    DependencyError(DependencyError),
31    LoaderError(LoaderErr),
32}
33
34impl From<DependencyError> for ScriptResolveError {
35    fn from(err: DependencyError) -> Self {
36        Self::DependencyError(err)
37    }
38}
39
40impl From<Error> for ScriptResolveError {
41    fn from(err: Error) -> Self {
42        Self::AnalyzerError(err)
43    }
44}
45
46impl From<DepLoaderError> for ScriptResolveError {
47    fn from(err: DepLoaderError) -> Self {
48        Self::DepLoaderError(err)
49    }
50}
51
52impl From<LoaderErr> for ScriptResolveError {
53    fn from(err: LoaderErr) -> Self {
54        Self::LoaderError(err)
55    }
56}
57
58// -------------------------------------------
59
60pub struct SourceLinesWrap<'a> {
61    pub file_id: FileId,
62    pub source_map: &'a SourceMap,
63}
64
65impl<'a> SourceLines for SourceLinesWrap<'a> {
66    fn get_line(&self, line_number: usize) -> Option<&str> {
67        self.source_map.get_source_line(self.file_id, line_number)
68    }
69}
70
71pub struct Report<C> {
72    config: Builder<C>,
73}
74
75impl<C: Display + Clone> Report<C> {
76    pub fn build(kind: Kind, code: C, error_name: &str, primary_span: &Span) -> Builder<C> {
77        Builder {
78            primary_span: primary_span.clone(),
79            kind,
80            error_code: code,
81            error_name: error_name.to_string(),
82            labels: vec![],
83            note: None,
84            error_module: String::new(),
85        }
86    }
87
88    pub const fn new(config: Builder<C>) -> Self {
89        Self { config }
90    }
91
92    /// # Errors
93    ///
94    pub fn print(
95        &self,
96        source_map: &SourceMap,
97        current_dir: &Path,
98        mut writer: impl Write,
99    ) -> io::Result<()> {
100        let header = eira::Header {
101            header_kind: self.config.kind,
102            code: self.config.error_code.clone(),
103            code_prefix: self.config.error_module.clone(),
104            message: self.config.error_name.clone(),
105        };
106        header.write(&mut writer)?;
107        let primary_span = &self.config.primary_span;
108        if primary_span.file_id == 0 {
109            eprintln!("{}", format!("header {} {}", header.message, header.code));
110        }
111        let (row, col) =
112            source_map.get_span_location_utf8(primary_span.file_id, primary_span.offset as usize);
113        let filename = source_map.get_relative_path_to(primary_span.file_id, current_dir)?;
114
115        eira::FileSpanMessage::write(
116            filename.to_str().unwrap(),
117            &PosSpan {
118                pos: Pos { x: col, y: row },
119                length: primary_span.length as usize,
120            },
121            &mut writer,
122        )?;
123
124        let mut source_file_section = eira::SourceFileSection::new();
125        for label in &self.config.labels {
126            let (row, col) =
127                source_map.get_span_location_utf8(label.span.file_id, label.span.offset as usize);
128
129            source_file_section.labels.push(eira::Label {
130                start: Pos { x: col, y: row },
131                character_count: label.span.length as usize,
132                text: label.description.clone(),
133                color: Color::default(),
134            });
135        }
136
137        if self.config.labels.is_empty() {
138            source_file_section.labels.push(eira::Label {
139                start: Pos { x: col, y: row },
140                character_count: primary_span.length as usize,
141                text: self.config.error_name.clone(),
142                color: Color::default(),
143            });
144        }
145
146        source_file_section.layout();
147
148        let source_line_wrap = SourceLinesWrap {
149            file_id: primary_span.file_id,
150            source_map,
151        };
152        source_file_section.draw(&source_line_wrap, &mut writer)?;
153
154        if let Some(found_note) = &self.config.note {
155            let header = eira::Header {
156                header_kind: Kind::Note,
157                code: 100,
158                code_prefix: String::new(),
159                message: found_note.to_string(),
160            };
161            header.write(&mut writer)?;
162        }
163
164        Ok(())
165    }
166}
167
168pub struct Label {
169    pub span: Span,
170    pub description: String,
171}
172
173pub struct Builder<C> {
174    pub primary_span: Span,
175    pub kind: Kind,
176    pub error_code: C,
177    pub error_name: String,
178    pub error_module: String,
179    pub labels: Vec<Label>,
180    pub note: Option<String>,
181}
182
183impl<C: Display + Clone> Builder<C> {
184    #[must_use]
185    pub fn with_label(mut self, label: &str, span: Span) -> Self {
186        let l = Label {
187            span,
188            description: label.to_string(),
189        };
190
191        self.labels.push(l);
192        self
193    }
194
195    #[must_use]
196    pub fn with_note(mut self, note: &str) -> Self {
197        self.note = Some(note.to_string());
198        self
199    }
200
201    pub const fn build(self) -> Report<C> {
202        Report::new(self)
203    }
204}
205
206pub fn build_and_print(builder: Builder<usize>, source_map: &SourceMap, current_dir: &Path) {
207    let report = builder.build();
208    report.print(source_map, current_dir, stderr()).unwrap();
209}