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"];
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' { Some('⏎') } else { Some(ch) }
77            } else {
78                None
79            }
80        })
81        .collect();
82
83    if cfg!(feature = "ansi-color") && short.contains('\x1b') {
84        short + "\x1b[0m"
85    } else {
86        short
87    }
88}
89
90/// Shortens given string to it's first line and to maximum characters.
91#[macro_export]
92macro_rules! shorten {
93    ($what:expr) => {
94        $crate::shorten(&format!("{}", $what), 140)
95    };
96    ($what:expr,$shorten:expr) => {
97        if $shorten {
98            $crate::shorten!($what)
99        } else {
100            $what
101        }
102    };
103    ($what:expr, $max_chars:literal) => {
104        shorten(format!("{}", $what).lines(), max_chars)
105    };
106}
107
108/// Create a marker string which is colored with ANSI.
109#[cfg(feature = "ansi-color")]
110#[macro_export]
111macro_rules! mark {
112    (FOUND) => {
113        color_print::cformat!("<W!,k,s> FOUND </>")
114    };
115    (FOUND_INTERIM) => {
116        color_print::cformat!("<Y!,k,s> FOUND </>")
117    };
118    (FOUND_FINAL) => {
119        color_print::cformat!("<G!,k,s> FOUND </>")
120    };
121    (MATCH) => {
122        color_print::cformat!("<Y!,k,s> MATCH </>")
123    };
124    (CALL) => {
125        color_print::cformat!("<B,k,s> CALL </>")
126    };
127    (LOAD) => {
128        color_print::cformat!("<Y,k,s> LOADING </>")
129    };
130    (RESOLVE) => {
131        color_print::cformat!("<M,k,s> RESOLVE </>")
132    };
133    (AMBIGUOUS) => {
134        color_print::cformat!("<R,k,s> AMBIGUOUS </>")
135    };
136    (NOT_FOUND) => {
137        color_print::cformat!("<R,k,s> NOT FOUND </>")
138    };
139    (NOT_FOUND_INTERIM) => {
140        color_print::cformat!("<Y,k,s> NOT FOUND </>")
141    };
142}
143
144#[cfg(not(feature = "ansi-color"))]
145#[macro_export]
146macro_rules! found {
147    (FOUND) => {
148        "Found"
149    };
150    (FINAL) => {
151        "Found"
152    };
153    (INTERMEDIATE) => {
154        "Found"
155    };
156    (MATCH) => {
157        "Match"
158    };
159    (CALL) => {
160        "Call"
161    };
162    (LOAD) => {
163        "Loading"
164    };
165    (RESOLVE) => {
166        "Resolve"
167    };
168    (AMBIGUOUS) => {
169        "Ambiguous"
170    };
171    (NOT_FOUND) => {
172        "Not found"
173    };
174    (NOT_FOUND_INTERIMEDIATE) => {
175        "Not found"
176    };
177}
178
179/// Generate string literal ` INVALID `*XXX*` ` with ANSI color.
180#[cfg(feature = "ansi-color")]
181#[macro_export]
182macro_rules! invalid {
183    (VALUE) => {
184        color_print::cstr!("<R!,k,s> INVALID VALUE </>")
185    };
186    (TYPE) => {
187        color_print::cstr!("<R!,k,s> INVALID TYPE </>")
188    };
189    (OUTPUT) => {
190        color_print::cstr!("<R!,k,s> INVALID OUTPUT </>")
191    };
192    (STACK) => {
193        color_print::cstr!("<W,k,s> EMPTY STACK </>")
194    };
195    (REF) => {
196        color_print::cstr!("<Y!,k,s> NO REF </>")
197    };
198    (FILE) => {
199        color_print::cstr!("<Y!,k,s> NO FILE </>")
200    };
201    (RESULT) => {
202        color_print::cstr!("<Y!,k,s> NO RESULT </>")
203    };
204    (LINE) => {
205        color_print::cstr!("<Y!,k,s> NO LINE </>")
206    };
207    (SOURCE) => {
208        color_print::cstr!("<C!,k,s> FROM STR </>")
209    };
210    (UNKNOWN) => {
211        color_print::cstr!("<M!,k,s> UNKNOWN </>")
212    };
213    (ID) => {
214        color_print::cstr!("<M!,k,s> NO ID </>")
215    };
216    (NAME) => {
217        color_print::cstr!("<M!,k,s> NO NAME </>")
218    };
219    (EXPRESSION) => {
220        color_print::cstr!("<R!,k,s> INVALID EXPRESSION </>")
221    };
222}
223
224/// Generate string literal `<INVALID `*XXX*`>`.
225#[macro_export]
226macro_rules! invalid_no_ansi {
227    (VALUE) => {
228        "<INVALID VALUE>"
229    };
230    (TYPE) => {
231        "<INVALID TYPE>"
232    };
233    (OUTPUT) => {
234        "<INVALID OUTPUT>"
235    };
236    (STACK) => {
237        "<INVALID STACK>"
238    };
239    (REF) => {
240        "<INVALID REF>"
241    };
242    (FILE) => {
243        "<INVALID FILE>"
244    };
245    (RESULT) => {
246        "<INVALID RESULT>"
247    };
248    (LINE) => {
249        "<INVALID LINE>"
250    };
251    (SOURCE) => {
252        "<FROM STR>"
253    };
254    (UNKNOWN) => {
255        "<INVALID UNKNOWN>"
256    };
257    (ID) => {
258        "<NO ID>"
259    };
260    (NAME) => {
261        "<INVALID NAME>"
262    };
263    (EXPRESSION) => {
264        "<INVALID EXPRESSION>"
265    };
266}
267
268#[cfg(not(feature = "ansi-color"))]
269macro_rules! invalid {
270    ($x:literal) => {
271        invalid_no_ansi!($x)
272    };
273}