microcad_lang_base/
lib.rs1use std::str::FromStr;
7
8use miette::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
9
10mod code_display;
11mod diag;
12mod identifier;
13mod ord_map;
14mod output;
15mod rc;
16mod source;
17mod src_ref;
18mod tree_display;
19
20pub use compact_str::{CompactString, ToCompactString};
21
22pub type Id = CompactString;
24
25pub use url::Url;
27
28pub fn virtual_url(name: &str) -> Url {
29 Url::from_str(&format!("virtual://{name}")).unwrap()
30}
31
32pub trait ResourceLocation {
33 fn url(&self) -> &Url;
35
36 fn to_file_path(&self) -> Option<std::path::PathBuf> {
39 if self.url().scheme() == "file" {
40 self.url().to_file_path().ok()
41 } else {
42 None
43 }
44 }
45
46 fn is_local(&self) -> bool {
48 self.url().scheme() == "file"
49 }
50
51 fn relative_path(&self) -> Option<std::path::PathBuf> {
53 self.to_file_path().map(|path| {
54 let current_dir = std::env::current_dir().expect("current dir");
55 if let Ok(path) = path.canonicalize() {
56 pathdiff::diff_paths(path, current_dir).unwrap_or_default()
57 } else {
58 path.to_path_buf()
59 }
60 })
61 }
62
63 fn source_name(&self) -> String {
65 self.relative_path()
66 .map(|s| s.to_string_lossy().to_string())
67 .unwrap_or(self.url().path().to_string())
68 }
69}
70
71pub const MICROCAD_EXTENSIONS: &[&str] = &["µcad", "mcad", "ucad"];
73
74pub use code_display::*;
75pub use diag::{
76 Diag, DiagError, DiagHandler, DiagRenderOptions, DiagResult, Diagnostic, Diagnostics, Level,
77 PushDiag,
78};
79pub use identifier::Identifier;
80pub use ord_map::{OrdMap, OrdMapValue};
81pub use output::{Capture, Output, Stdout};
82pub use rc::{Rc, RcMut};
83pub use src_ref::{LineCol, LineIndex, Refer, Span, SrcRef, SrcReferrer};
84pub use tree_display::{FormatTree, TreeDisplay, TreeState};
85
86pub use microcad_core::hash::{ComputedHash, HashId, HashMap, HashSet, Hashed};
87pub use source::Source;
88
89pub struct SourceLocInfo<'a> {
91 pub code: &'a str,
93 pub url: Url,
95 pub line_offset: u32,
97}
98
99impl SourceLocInfo<'static> {
100 pub fn invalid() -> Self {
102 SourceLocInfo {
103 code: "NO FILE",
104 url: virtual_url("invalid"),
105 line_offset: 0,
106 }
107 }
108}
109
110impl<'a> ResourceLocation for SourceLocInfo<'a> {
111 fn url(&self) -> &Url {
112 &self.url
113 }
114}
115
116impl SourceCode for SourceLocInfo<'_> {
117 fn read_span<'a>(
118 &'a self,
119 span: &SourceSpan,
120 context_lines_before: usize,
121 context_lines_after: usize,
122 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
123 let inner_contents =
124 self.code
125 .read_span(span, context_lines_before, context_lines_after)?;
126 let contents = MietteSpanContents::new_named(
127 self.source_name(),
128 inner_contents.data(),
129 *inner_contents.span(),
130 inner_contents.line() + self.line_offset as usize,
131 inner_contents.column(),
132 inner_contents.line_count(),
133 )
134 .with_language("µcad");
135 Ok(Box::new(contents))
136 }
137}
138
139pub trait GetSourceLocInfoByHash {
141 fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>>;
143}
144
145pub fn shorten(what: &str, max_chars: usize) -> String {
147 let short: String = what
148 .chars()
149 .enumerate()
150 .filter_map(|(p, ch)| {
151 if p == max_chars {
152 Some('…')
153 } else if p < max_chars {
154 if ch == '\n' { Some('⏎') } else { Some(ch) }
155 } else {
156 None
157 }
158 })
159 .collect();
160
161 if cfg!(feature = "ansi-color") && short.contains('\x1b') {
162 short + "\x1b[0m"
163 } else {
164 short
165 }
166}
167
168#[cfg(feature = "ansi-color")]
170#[macro_export]
171macro_rules! mark {
172 (FOUND!) => {
173 color_print::cformat!("<G!,k,s> FOUND </>")
174 };
175 (FOUND) => {
176 color_print::cformat!("<W!,k,s> FOUND </>")
177 };
178 (MATCH) => {
179 color_print::cformat!("<Y!,k,s> MATCH </>")
180 };
181 (NO_MATCH) => {
182 color_print::cformat!("<Y,k,s> NO MATCH </>")
183 };
184 (MATCH!) => {
185 color_print::cformat!("<G!,k,s> MATCH </>")
186 };
187 (NO_MATCH!) => {
188 color_print::cformat!("<R,k,s> NO MATCH </>")
189 };
190 (CALL) => {
191 color_print::cformat!("<B,k,s> CALL </>")
192 };
193 (LOOKUP) => {
194 color_print::cformat!("<c,s>LOOKUP</>")
195 };
196 (LOAD) => {
197 color_print::cformat!("<Y,k,s> LOADING </>")
198 };
199 (RESOLVE) => {
200 color_print::cformat!("<M,k,s> RESOLVE </>")
201 };
202 (AMBIGUOUS) => {
203 color_print::cformat!("<R,k,s> AMBIGUOUS </>")
204 };
205 (NOT_FOUND!) => {
206 color_print::cformat!("<R,k,s> NOT FOUND </>")
207 };
208 (NOT_FOUND) => {
209 color_print::cformat!("<Y,k,s> NOT FOUND </>")
210 };
211}
212
213pub trait WriteToFile: std::fmt::Display {
215 fn write_to_file(&self, filename: &impl AsRef<std::path::Path>) -> std::io::Result<()> {
217 use std::io::Write;
218 let file = std::fs::File::create(filename)?;
219 let mut writer = std::io::BufWriter::new(file);
220 write!(writer, "{self}")
221 }
222}