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