Skip to main content

microcad_lang_base/
lib.rs

1// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! µcad language base components for error handling etc.
5
6use miette::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
7
8mod diag;
9mod rc;
10mod src_ref;
11mod tree_display;
12
13/// Id type (base of all identifiers)
14pub type Id = compact_str::CompactString;
15
16/// List of valid µcad extensions.
17pub const MICROCAD_EXTENSIONS: &[&str] = &["µcad", "mcad", "ucad"];
18
19pub use diag::{
20    Diag, DiagError, DiagHandler, DiagRenderOptions, DiagResult, Diagnostic, Level, PushDiag,
21};
22pub use rc::{Rc, RcMut};
23pub use src_ref::{Refer, SrcRef, SrcRefInner, SrcReferrer};
24pub use tree_display::{FormatTree, TreeDisplay, TreeState};
25
26/// A compatibility layer for using SourceFile with miette
27pub struct MietteSourceFile<'a> {
28    source: &'a str,
29    name: String,
30    line_offset: usize,
31}
32
33impl MietteSourceFile<'static> {
34    /// Create an invalid source file for when we can't load the source
35    pub fn invalid() -> Self {
36        MietteSourceFile {
37            source: crate::invalid_no_ansi!(FILE),
38            name: crate::invalid_no_ansi!(FILE).into(),
39            line_offset: 0,
40        }
41    }
42}
43
44impl SourceCode for MietteSourceFile<'_> {
45    fn read_span<'a>(
46        &'a self,
47        span: &SourceSpan,
48        context_lines_before: usize,
49        context_lines_after: usize,
50    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
51        let inner_contents =
52            self.source
53                .read_span(span, context_lines_before, context_lines_after)?;
54        let contents = MietteSpanContents::new_named(
55            self.name.clone(),
56            inner_contents.data(),
57            *inner_contents.span(),
58            inner_contents.line() + self.line_offset,
59            inner_contents.column(),
60            inner_contents.line_count(),
61        )
62        .with_language("µcad");
63        Ok(Box::new(contents))
64    }
65}
66
67/// Trait that can fetch for a file by it's hash value.
68pub trait GetSourceStrByHash {
69    /// Get a source string by it's hash value.
70    fn get_str_by_hash(&self, hash: u64) -> Option<&str>;
71
72    /// Get filename by hash
73    fn get_filename_by_hash(&self, hash: u64) -> Option<std::path::PathBuf>;
74}
75
76/// Shortens given string to it's first line and to `max_chars` characters.
77pub fn shorten(what: &str, max_chars: usize) -> String {
78    let short: String = what
79        .chars()
80        .enumerate()
81        .filter_map(|(p, ch)| {
82            if p == max_chars {
83                Some('…')
84            } else if p < max_chars {
85                if ch == '\n' { Some('⏎') } else { Some(ch) }
86            } else {
87                None
88            }
89        })
90        .collect();
91
92    if cfg!(feature = "ansi-color") && short.contains('\x1b') {
93        short + "\x1b[0m"
94    } else {
95        short
96    }
97}
98
99/// Shortens given string to it's first line and to maximum characters.
100#[macro_export]
101macro_rules! shorten {
102    ($what:expr) => {
103        $crate::shorten(&format!("{}", $what), 140)
104    };
105    ($what:expr,$shorten:expr) => {
106        if $shorten {
107            $crate::shorten!($what)
108        } else {
109            $what
110        }
111    };
112    ($what:expr, $max_chars:literal) => {
113        shorten(format!("{}", $what).lines(), max_chars)
114    };
115}
116
117/// Create a marker string which is colored with ANSI.
118#[cfg(feature = "ansi-color")]
119#[macro_export]
120macro_rules! mark {
121    (FOUND!) => {
122        color_print::cformat!("<G!,k,s> FOUND </>")
123    };
124    (FOUND) => {
125        color_print::cformat!("<W!,k,s> FOUND </>")
126    };
127    (MATCH) => {
128        color_print::cformat!("<Y!,k,s> MATCH </>")
129    };
130    (NO_MATCH) => {
131        color_print::cformat!("<Y,k,s> NO MATCH </>")
132    };
133    (MATCH!) => {
134        color_print::cformat!("<G!,k,s> MATCH </>")
135    };
136    (NO_MATCH!) => {
137        color_print::cformat!("<R,k,s> NO MATCH </>")
138    };
139    (CALL) => {
140        color_print::cformat!("<B,k,s> CALL </>")
141    };
142    (LOOKUP) => {
143        color_print::cformat!("<c,s>LOOKUP</>")
144    };
145    (LOAD) => {
146        color_print::cformat!("<Y,k,s> LOADING </>")
147    };
148    (RESOLVE) => {
149        color_print::cformat!("<M,k,s> RESOLVE </>")
150    };
151    (AMBIGUOUS) => {
152        color_print::cformat!("<R,k,s> AMBIGUOUS </>")
153    };
154    (NOT_FOUND!) => {
155        color_print::cformat!("<R,k,s> NOT FOUND </>")
156    };
157    (NOT_FOUND) => {
158        color_print::cformat!("<Y,k,s> NOT FOUND </>")
159    };
160}
161
162#[cfg(not(feature = "ansi-color"))]
163#[macro_export]
164macro_rules! found {
165    (FOUND) => {
166        "Found"
167    };
168    (FINAL) => {
169        "Found"
170    };
171    (INTERMEDIATE) => {
172        "Found"
173    };
174    (MATCH) => {
175        "Match"
176    };
177    (NO_MATCH) => {
178        "No Match"
179    };
180    (CALL) => {
181        "Call"
182    };
183    (LOOKUP) => {
184        "Lookup"
185    };
186    (LOAD) => {
187        "Loading"
188    };
189    (RESOLVE) => {
190        "Resolve"
191    };
192    (AMBIGUOUS) => {
193        "Ambiguous"
194    };
195    (NOT_FOUND) => {
196        "Not found"
197    };
198    (NOT_FOUND) => {
199        "Not found"
200    };
201}
202
203/// Generate string literal ` INVALID `*XXX*` ` with ANSI color.
204#[cfg(feature = "ansi-color")]
205#[macro_export]
206macro_rules! invalid {
207    (VALUE) => {
208        color_print::cstr!("<R!,k,s> NO VALUE </>")
209    };
210    (TYPE) => {
211        color_print::cstr!("<R!,k,s> NO TYPE </>")
212    };
213    (OUTPUT) => {
214        color_print::cstr!("<R!,k,s> NO OUTPUT </>")
215    };
216    (STACK) => {
217        color_print::cstr!("<W,k,s> EMPTY STACK </>")
218    };
219    (REF) => {
220        color_print::cstr!("<Y!,k,s> NO REF </>")
221    };
222    (FILE) => {
223        color_print::cstr!("<Y!,k,s> NO FILE </>")
224    };
225    (RESULT) => {
226        color_print::cstr!("<Y!,k,s> NO RESULT </>")
227    };
228    (LINE) => {
229        color_print::cstr!("<Y!,k,s> NO LINE </>")
230    };
231    (SOURCE) => {
232        color_print::cstr!("<C!,k,s> FROM STR </>")
233    };
234    (UNKNOWN) => {
235        color_print::cstr!("<M!,k,s> UNKNOWN </>")
236    };
237    (ID) => {
238        color_print::cstr!("<M!,k,s> NO ID </>")
239    };
240    (NAME) => {
241        color_print::cstr!("<M!,k,s> NO NAME </>")
242    };
243    (EXPRESSION) => {
244        color_print::cstr!("<R!,k,s> INVALID EXPRESSION </>")
245    };
246}
247
248/// Generate string literal `<INVALID `*XXX*`>`.
249#[macro_export]
250macro_rules! invalid_no_ansi {
251    (VALUE) => {
252        "<NO VALUE>"
253    };
254    (TYPE) => {
255        "<NO TYPE>"
256    };
257    (OUTPUT) => {
258        "<NO OUTPUT>"
259    };
260    (STACK) => {
261        "<EMPTY STACK>"
262    };
263    (REF) => {
264        "<NO REF>"
265    };
266    (FILE) => {
267        "<NO FILE>"
268    };
269    (RESULT) => {
270        "<NO RESULT>"
271    };
272    (LINE) => {
273        "<NO LINE>"
274    };
275    (SOURCE) => {
276        "<FROM STR>"
277    };
278    (UNKNOWN) => {
279        "<UNKNOWN>"
280    };
281    (ID) => {
282        "<NO ID>"
283    };
284    (NAME) => {
285        "<INVALID NAME>"
286    };
287    (EXPRESSION) => {
288        "<INVALID EXPRESSION>"
289    };
290}
291
292#[macro_export]
293#[cfg(not(feature = "ansi-color"))]
294macro_rules! invalid {
295    ($x:literal) => {
296        invalid_no_ansi!($x)
297    };
298}
299
300/// Trait to write something with Display trait into a file.
301pub trait WriteToFile: std::fmt::Display {
302    /// Write something to a file.
303    fn write_to_file(&self, filename: &impl AsRef<std::path::Path>) -> std::io::Result<()> {
304        use std::io::Write;
305        let file = std::fs::File::create(filename)?;
306        let mut writer = std::io::BufWriter::new(file);
307        write!(writer, "{self}")
308    }
309}