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/// List of valid µcad extensions.
37pub const MICROCAD_EXTENSIONS: &[&str] = &["µcad", "mcad", "ucad"];
38
39/// Parse a rule from given string into a syntax element.
40/// - `ty`: Type of the output syntax element
41/// - `rule`: Parsing rule to use.
42/// - `code`: String slice of the code to parse
43#[macro_export]
44macro_rules! parse {
45    ($ty:path, $rule:path, $code:expr) => {
46        $crate::parser::Parser::parse_rule::<$ty>($rule, $code, 0).expect("bad inline code")
47    };
48}
49
50#[test]
51fn parse_macro() {
52    let y3 = 3;
53    let p = parse!(
54        syntax::ParameterList,
55        parser::Rule::parameter_list,
56        &format!("(x=0,y=[1,2,{y3},4],z=2)")
57    );
58    assert_eq!(p.to_string(), "x = 0, y = [1, 2, 3, 4], z = 2");
59}
60
61/// Shortens given string to it's first line and to `max_chars` characters.
62pub fn shorten(what: &str, max_chars: usize) -> String {
63    let short: String = what
64        .chars()
65        .enumerate()
66        .filter_map(|(p, ch)| {
67            if p == max_chars {
68                Some('…')
69            } else if p < max_chars {
70                if ch == '\n' {
71                    Some('⏎')
72                } else {
73                    Some(ch)
74                }
75            } else {
76                None
77            }
78        })
79        .collect();
80
81    if cfg!(feature = "ansi-color") && short.contains('\x1b') {
82        short + "\x1b[0m"
83    } else {
84        short
85    }
86}
87
88/// Shortens given string to it's first line and to maximum characters.
89#[macro_export]
90macro_rules! shorten {
91    ($what:expr) => {
92        $crate::shorten(&format!("{}", $what), 140)
93    };
94    ($what:expr,$shorten:expr) => {
95        if $shorten {
96            $crate::shorten!($what)
97        } else {
98            $what
99        }
100    };
101    ($what:expr, $max_chars:literal) => {
102        shorten(format!("{}", $what).lines(), max_chars)
103    };
104}
105
106/// Create a marker string which is colored with ANSI.
107#[cfg(feature = "ansi-color")]
108#[macro_export]
109macro_rules! mark {
110    (FOUND) => {
111        color_print::cformat!("<G!,k,s> FOUND </>")
112    };
113    (FOUND_INTERIM) => {
114        color_print::cformat!("<W!,k,s> FOUND </>")
115    };
116    (MATCH) => {
117        color_print::cformat!("<Y!,k,s> MATCH </>")
118    };
119    (CALL) => {
120        color_print::cformat!("<B,k,s> CALL </>")
121    };
122    (LOOKUP) => {
123        color_print::cformat!("<c,s>LOOKUP</>")
124    };
125    (LOAD) => {
126        color_print::cformat!("<Y,k,s> LOADING </>")
127    };
128    (RESOLVE) => {
129        color_print::cformat!("<M,k,s> RESOLVE </>")
130    };
131    (AMBIGUOUS) => {
132        color_print::cformat!("<R,k,s> AMBIGUOUS </>")
133    };
134    (NOT_FOUND) => {
135        color_print::cformat!("<R,k,s> NOT FOUND </>")
136    };
137    (NOT_FOUND_INTERIM) => {
138        color_print::cformat!("<Y,k,s> NOT FOUND </>")
139    };
140}
141
142#[cfg(not(feature = "ansi-color"))]
143#[macro_export]
144macro_rules! found {
145    (FOUND) => {
146        "Found"
147    };
148    (FINAL) => {
149        "Found"
150    };
151    (INTERMEDIATE) => {
152        "Found"
153    };
154    (MATCH) => {
155        "Match"
156    };
157    (CALL) => {
158        "Call"
159    };
160    (LOOKUP) => {
161        "Lookup"
162    };
163    (LOAD) => {
164        "Loading"
165    };
166    (RESOLVE) => {
167        "Resolve"
168    };
169    (AMBIGUOUS) => {
170        "Ambiguous"
171    };
172    (NOT_FOUND) => {
173        "Not found"
174    };
175    (NOT_FOUND_INTERIM) => {
176        "Not found"
177    };
178}
179
180/// Generate string literal ` INVALID `*XXX*` ` with ANSI color.
181#[cfg(feature = "ansi-color")]
182#[macro_export]
183macro_rules! invalid {
184    (VALUE) => {
185        color_print::cstr!("<R!,k,s> NO VALUE </>")
186    };
187    (TYPE) => {
188        color_print::cstr!("<R!,k,s> NO TYPE </>")
189    };
190    (OUTPUT) => {
191        color_print::cstr!("<R!,k,s> NO OUTPUT </>")
192    };
193    (STACK) => {
194        color_print::cstr!("<W,k,s> EMPTY STACK </>")
195    };
196    (REF) => {
197        color_print::cstr!("<Y!,k,s> NO REF </>")
198    };
199    (FILE) => {
200        color_print::cstr!("<Y!,k,s> NO FILE </>")
201    };
202    (RESULT) => {
203        color_print::cstr!("<Y!,k,s> NO RESULT </>")
204    };
205    (LINE) => {
206        color_print::cstr!("<Y!,k,s> NO LINE </>")
207    };
208    (SOURCE) => {
209        color_print::cstr!("<C!,k,s> FROM STR </>")
210    };
211    (UNKNOWN) => {
212        color_print::cstr!("<M!,k,s> UNKNOWN </>")
213    };
214    (ID) => {
215        color_print::cstr!("<M!,k,s> NO ID </>")
216    };
217    (NAME) => {
218        color_print::cstr!("<M!,k,s> NO NAME </>")
219    };
220    (EXPRESSION) => {
221        color_print::cstr!("<R!,k,s> INVALID EXPRESSION </>")
222    };
223}
224
225/// Generate string literal `<INVALID `*XXX*`>`.
226#[macro_export]
227macro_rules! invalid_no_ansi {
228    (VALUE) => {
229        "<NO VALUE>"
230    };
231    (TYPE) => {
232        "<NO TYPE>"
233    };
234    (OUTPUT) => {
235        "<NO OUTPUT>"
236    };
237    (STACK) => {
238        "<EMPTY STACK>"
239    };
240    (REF) => {
241        "<NO REF>"
242    };
243    (FILE) => {
244        "<NO FILE>"
245    };
246    (RESULT) => {
247        "<NO RESULT>"
248    };
249    (LINE) => {
250        "<NO LINE>"
251    };
252    (SOURCE) => {
253        "<FROM STR>"
254    };
255    (UNKNOWN) => {
256        "<UNKNOWN>"
257    };
258    (ID) => {
259        "<NO ID>"
260    };
261    (NAME) => {
262        "<INVALID NAME>"
263    };
264    (EXPRESSION) => {
265        "<INVALID EXPRESSION>"
266    };
267}
268
269#[cfg(not(feature = "ansi-color"))]
270macro_rules! invalid {
271    ($x:literal) => {
272        invalid_no_ansi!($x)
273    };
274}