microcad_lang/
lib.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Processing of µcad source code.
5//!
6//! This module includes all components to parse, resolve and evaluate µcad code and diagnose errors.
7//!
8//! - Load and parse source files in [`mod@parse`] and [`syntax`]
9//! - Resolve parsed sources in [`resolve`]
10//! - Evaluate resolved sources in [`eval`]
11//! - Diagnose any evaluation errors in [`diag`]
12//!
13//! The grammar of µcad can be found [here](../../../lang/grammar.pest).
14//!
15//! Good starting point to understand how µcad syntax works: [`syntax::SourceFile::load()`] loads a µcad source file.
16
17pub mod builtin;
18pub mod diag;
19pub mod eval;
20pub mod model;
21pub mod ord_map;
22pub mod parse;
23pub mod parser;
24pub mod rc;
25pub mod render;
26pub mod resolve;
27pub mod src_ref;
28pub mod syntax;
29pub mod tree_display;
30pub mod ty;
31pub mod value;
32
33/// Id type (base of all identifiers)
34pub type Id = compact_str::CompactString;
35
36/// Global test initialization.
37#[cfg(test)]
38#[ctor::ctor]
39fn init() {
40    env_logger::init();
41}
42
43const MICROCAD_EXTENSIONS: &[&str] = &["µcad", "mcad", "ucad"];
44
45/// Parse a rule from given string into a syntax element.
46/// - `ty`: Type of the output syntax element
47/// - `rule`: Parsing rule to use.
48/// - `code`: String slice of the code to parse
49#[macro_export]
50macro_rules! parse {
51    ($ty:path, $rule:path, $code:expr) => {
52        $crate::parser::Parser::parse_rule::<$ty>($rule, $code, 0).expect("bad inline code")
53    };
54}
55
56#[test]
57fn parse_macro() {
58    let y3 = 3;
59    let p = parse!(
60        syntax::ParameterList,
61        parser::Rule::parameter_list,
62        &format!("(x=0,y=[1,2,{y3},4],z=2)")
63    );
64    assert_eq!(p.to_string(), "x = 0, y = [1, 2, 3, 4], z = 2");
65}
66
67/// Shortens given string to it's first line and to `max_chars` characters.
68pub fn shorten(what: &str, max_chars: usize) -> String {
69    let short: String = what
70        .chars()
71        .enumerate()
72        .filter_map(|(p, ch)| {
73            if p == max_chars {
74                Some('…')
75            } else if p < max_chars {
76                if ch == '\n' {
77                    Some('⏎')
78                } else {
79                    Some(ch)
80                }
81            } else {
82                None
83            }
84        })
85        .collect();
86
87    if cfg!(feature = "ansi-color") && short.contains('\x1b') {
88        short + "\x1b[0m"
89    } else {
90        short
91    }
92}
93
94/// Shortens given string to it's first line and to maximum characters.
95#[macro_export]
96macro_rules! shorten {
97    ($what:expr) => {
98        $crate::shorten(&format!("{}", $what), 140)
99    };
100    ($what:expr,$shorten:expr) => {
101        if $shorten {
102            $crate::shorten!($what)
103        } else {
104            $what
105        }
106    };
107    ($what:expr, $max_chars:literal) => {
108        shorten(format!("{}", $what).lines(), max_chars)
109    };
110}
111
112/// Create a marker string which is colored with ANSI.
113#[cfg(feature = "ansi-color")]
114#[macro_export]
115macro_rules! mark {
116    (FOUND) => {
117        color_print::cformat!("<G!,k,s> FOUND </>")
118    };
119    (FOUND_INTERIM) => {
120        color_print::cformat!("<W!,k,s> FOUND </>")
121    };
122    (MATCH) => {
123        color_print::cformat!("<Y!,k,s> MATCH </>")
124    };
125    (CALL) => {
126        color_print::cformat!("<B,k,s> CALL </>")
127    };
128    (LOOKUP) => {
129        color_print::cformat!("<c,s>LOOKUP</>")
130    };
131    (LOAD) => {
132        color_print::cformat!("<Y,k,s> LOADING </>")
133    };
134    (RESOLVE) => {
135        color_print::cformat!("<M,k,s> RESOLVE </>")
136    };
137    (AMBIGUOUS) => {
138        color_print::cformat!("<R,k,s> AMBIGUOUS </>")
139    };
140    (NOT_FOUND) => {
141        color_print::cformat!("<R,k,s> NOT FOUND </>")
142    };
143    (NOT_FOUND_INTERIM) => {
144        color_print::cformat!("<Y,k,s> NOT FOUND </>")
145    };
146}
147
148#[cfg(not(feature = "ansi-color"))]
149#[macro_export]
150macro_rules! found {
151    (FOUND) => {
152        "Found"
153    };
154    (FINAL) => {
155        "Found"
156    };
157    (INTERMEDIATE) => {
158        "Found"
159    };
160    (MATCH) => {
161        "Match"
162    };
163    (CALL) => {
164        "Call"
165    };
166    (LOOKUP) => {
167        "Lookup"
168    };
169    (LOAD) => {
170        "Loading"
171    };
172    (RESOLVE) => {
173        "Resolve"
174    };
175    (AMBIGUOUS) => {
176        "Ambiguous"
177    };
178    (NOT_FOUND) => {
179        "Not found"
180    };
181    (NOT_FOUND_INTERIM) => {
182        "Not found"
183    };
184}
185
186/// Generate string literal ` INVALID `*XXX*` ` with ANSI color.
187#[cfg(feature = "ansi-color")]
188#[macro_export]
189macro_rules! invalid {
190    (VALUE) => {
191        color_print::cstr!("<R!,k,s> NO VALUE </>")
192    };
193    (TYPE) => {
194        color_print::cstr!("<R!,k,s> INVALID TYPE </>")
195    };
196    (OUTPUT) => {
197        color_print::cstr!("<R!,k,s> INVALID OUTPUT </>")
198    };
199    (STACK) => {
200        color_print::cstr!("<W,k,s> EMPTY STACK </>")
201    };
202    (REF) => {
203        color_print::cstr!("<Y!,k,s> NO REF </>")
204    };
205    (FILE) => {
206        color_print::cstr!("<Y!,k,s> NO FILE </>")
207    };
208    (RESULT) => {
209        color_print::cstr!("<Y!,k,s> NO RESULT </>")
210    };
211    (LINE) => {
212        color_print::cstr!("<Y!,k,s> NO LINE </>")
213    };
214    (SOURCE) => {
215        color_print::cstr!("<C!,k,s> FROM STR </>")
216    };
217    (UNKNOWN) => {
218        color_print::cstr!("<M!,k,s> UNKNOWN </>")
219    };
220    (ID) => {
221        color_print::cstr!("<M!,k,s> NO ID </>")
222    };
223    (NAME) => {
224        color_print::cstr!("<M!,k,s> NO NAME </>")
225    };
226    (EXPRESSION) => {
227        color_print::cstr!("<R!,k,s> INVALID EXPRESSION </>")
228    };
229}
230
231/// Generate string literal `<INVALID `*XXX*`>`.
232#[macro_export]
233macro_rules! invalid_no_ansi {
234    (VALUE) => {
235        "<NO VALUE>"
236    };
237    (TYPE) => {
238        "<INVALID TYPE>"
239    };
240    (OUTPUT) => {
241        "<INVALID OUTPUT>"
242    };
243    (STACK) => {
244        "<INVALID STACK>"
245    };
246    (REF) => {
247        "<INVALID REF>"
248    };
249    (FILE) => {
250        "<INVALID FILE>"
251    };
252    (RESULT) => {
253        "<INVALID RESULT>"
254    };
255    (LINE) => {
256        "<INVALID LINE>"
257    };
258    (SOURCE) => {
259        "<FROM STR>"
260    };
261    (UNKNOWN) => {
262        "<INVALID UNKNOWN>"
263    };
264    (ID) => {
265        "<NO ID>"
266    };
267    (NAME) => {
268        "<INVALID NAME>"
269    };
270    (EXPRESSION) => {
271        "<INVALID EXPRESSION>"
272    };
273}
274
275#[cfg(not(feature = "ansi-color"))]
276macro_rules! invalid {
277    ($x:literal) => {
278        invalid_no_ansi!($x)
279    };
280}