Skip to main content

microcad_lang/syntax/identifier/
qualified_name.rs

1// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::syntax::*;
5use derive_more::{Deref, DerefMut};
6use microcad_lang_base::{Refer, SrcRef, SrcReferrer, TreeDisplay, TreeState};
7use miette::SourceSpan;
8
9/// A *qualified name* consists of a list of *identifiers*, separated by `::`,
10/// e.g. `a::b::c`
11#[derive(Default, Clone, PartialEq, Hash, Eq, Ord, PartialOrd, DerefMut, Deref)]
12pub struct QualifiedName(Refer<Vec<Identifier>>);
13
14impl QualifiedName {
15    /// If the qualified name is a single identifier, return it
16    pub fn as_identifier(&self) -> Option<&Identifier> {
17        self.0.first().filter(|_| self.0.len() == 1)
18    }
19}
20
21/// List of *qualified names* which can be displayed.
22#[derive(Deref)]
23pub struct QualifiedNames(Vec<QualifiedName>);
24
25impl std::fmt::Display for QualifiedNames {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        write!(
28            f,
29            "{}",
30            self.0
31                .iter()
32                .map(|name| name.to_string())
33                .collect::<Vec<_>>()
34                .join(", ")
35        )
36    }
37}
38
39impl std::fmt::Debug for QualifiedNames {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(
42            f,
43            "{}",
44            self.0
45                .iter()
46                .map(|name| format!("{:?}", name.to_string()))
47                .collect::<Vec<_>>()
48                .join(", ")
49        )
50    }
51}
52
53impl FromIterator<QualifiedName> for QualifiedNames {
54    fn from_iter<T: IntoIterator<Item = QualifiedName>>(iter: T) -> Self {
55        Self(iter.into_iter().collect())
56    }
57}
58
59impl QualifiedName {
60    /// Create [`QualifiedName`] from [`Identifier`]s.
61    ///
62    /// - `ids`: *Identifiers* that concatenate to the *qualified name*.
63    /// - `src_ref`: Reference for the whole name.
64    pub fn new(ids: Vec<Identifier>, src_ref: SrcRef) -> Self {
65        Self(Refer::new(ids, src_ref))
66    }
67
68    /// Create [`QualifiedName`] from single [`Identifier`].
69    pub fn from_id(id: Identifier) -> Self {
70        let src_ref = id.src_ref();
71        Self(Refer::new(vec![id], src_ref))
72    }
73
74    /// Create *qualified name* from [`identifier`]s without source code reference.
75    ///
76    /// - `ids`: *Identifiers* that concatenate to the *qualified name*.
77    pub fn no_ref(ids: Vec<Identifier>) -> Self {
78        Self(Refer::none(ids))
79    }
80
81    /// Returns true if self is a qualified name with multiple ids in it
82    pub fn is_qualified(&self) -> bool {
83        self.0.len() > 1
84    }
85
86    /// Returns true if name contains exactly one id
87    pub fn is_id(&self) -> bool {
88        self.0.len() == 1
89    }
90
91    /// Tells if self is in a specified module
92    pub fn is_within(&self, module: &QualifiedName) -> bool {
93        self.starts_with(module)
94    }
95
96    /// Returns `true` if this name is in builtin module
97    pub fn is_builtin(&self) -> bool {
98        if let Some(first) = self.first() {
99            first == "__builtin"
100        } else {
101            false
102        }
103    }
104
105    /// remove the first name from path
106    pub fn remove_first(&self) -> Self {
107        Self(Refer::new(self.0[1..].to_vec(), self.0.src_ref.clone()))
108    }
109
110    /// remove the first name from path
111    pub fn remove_last(self) -> Self {
112        Self(Refer::new(
113            self.0[..self.0.len() - 1].to_vec(),
114            self.0.src_ref.clone(),
115        ))
116    }
117
118    /// Append identifier to name
119    pub fn push(&mut self, id: Identifier) {
120        self.0.push(id)
121    }
122
123    /// Split name into first id and the rest
124    pub fn split_first(&self) -> (Identifier, QualifiedName) {
125        match self.len() {
126            0 => todo!("return None or error?"),
127            1 => (self.0[0].clone(), Self::default()),
128            _ => (self.0[0].clone(), Self(Refer::none(self.0[1..].into()))),
129        }
130    }
131
132    /// return basename, `std::geo2d` returns `std`
133    pub fn basename(&self) -> Option<Self> {
134        let mut s = self.clone();
135        if s.len() >= 2 {
136            s.pop();
137            Some(s)
138        } else {
139            None
140        }
141    }
142
143    /// Return the base of the given relative name.
144    pub fn base(&self, relative: &Self) -> Self {
145        if self == relative {
146            QualifiedName::default()
147        } else {
148            assert!(!relative.is_empty());
149            assert!(self.len() > relative.len());
150            assert!(self.ends_with(relative));
151            let (base, _) = self.split_at(self.len() - relative.len());
152            base.iter().cloned().collect()
153        }
154    }
155
156    /// Add given prefix to name
157    pub fn with_prefix(&self, prefix: &QualifiedName) -> Self {
158        let mut full_name = prefix.clone();
159        full_name.append(&mut self.clone());
160        full_name
161    }
162
163    /// Add a given identifier as suffix.
164    pub fn with_suffix(&self, suffix: &Identifier) -> Self {
165        let mut name = self.clone();
166        name.push(suffix.clone());
167        name
168    }
169
170    pub(crate) fn count_super(&self) -> usize {
171        self.iter().take_while(|id| id.is_super()).count()
172    }
173}
174
175impl SingleIdentifier for QualifiedName {
176    fn single_identifier(&self) -> Option<&Identifier> {
177        if self.is_single_identifier() {
178            self.0.first()
179        } else {
180            None
181        }
182    }
183
184    fn is_single_identifier(&self) -> bool {
185        self.0.len() == 1
186    }
187}
188
189impl From<QualifiedName> for SourceSpan {
190    fn from(value: QualifiedName) -> Self {
191        value.src_ref().into()
192    }
193}
194
195#[test]
196fn test_base() {
197    let d: QualifiedName = "a::b::c::d".into();
198    assert_eq!(d.base(&"b::c::d".into()), "a".into());
199    assert_eq!(d.base(&"c::d".into()), "a::b".into());
200    assert_eq!(d.base(&"d".into()), "a::b::c".into());
201}
202
203#[test]
204#[should_panic]
205fn test_base_panic() {
206    let d: QualifiedName = "a::b::c::d".into();
207    assert_eq!(d.base(&"a::b::c::d".into()), "".into());
208}
209
210#[test]
211fn dissolve_super() {
212    let what: QualifiedName = "super::super::c::x".into();
213    assert_eq!(what.count_super(), 2);
214}
215
216impl std::fmt::Display for QualifiedName {
217    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
218        if self.is_empty() {
219            write!(f, microcad_lang_base::invalid_no_ansi!(NAME))
220        } else {
221            write!(
222                f,
223                "{}",
224                self.iter()
225                    .map(|id| format!("{id}"))
226                    .collect::<Vec<_>>()
227                    .join("::")
228            )
229        }
230    }
231}
232
233impl std::fmt::Debug for QualifiedName {
234    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
235        if self.is_empty() {
236            write!(f, microcad_lang_base::invalid!(NAME))
237        } else {
238            write!(
239                f,
240                "{}",
241                self.iter()
242                    .map(|id| format!("{id:?}"))
243                    .collect::<Vec<_>>()
244                    .join("::")
245            )
246        }
247    }
248}
249
250impl SrcReferrer for QualifiedName {
251    fn src_ref(&self) -> SrcRef {
252        self.0.src_ref()
253    }
254}
255
256impl From<Refer<Vec<Identifier>>> for QualifiedName {
257    fn from(value: Refer<Vec<Identifier>>) -> Self {
258        Self(value)
259    }
260}
261
262impl FromIterator<Identifier> for QualifiedName {
263    fn from_iter<T: IntoIterator<Item = Identifier>>(iter: T) -> Self {
264        Self(Refer::none(iter.into_iter().collect()))
265    }
266}
267
268impl From<&Identifier> for QualifiedName {
269    fn from(id: &Identifier) -> Self {
270        Self(Refer::none(vec![id.clone()]))
271    }
272}
273
274impl From<&std::path::Path> for QualifiedName {
275    fn from(path: &std::path::Path) -> Self {
276        // check if this is a module file and remove doublet module generation
277        let path = if path.file_stem() == Some(std::ffi::OsStr::new("mod")) {
278            path.parent().expect("mod file in root path is not allowed")
279        } else {
280            path
281        };
282
283        QualifiedName::no_ref(
284            path.iter()
285                .map(|id| {
286                    Identifier(Refer {
287                        value: id.to_string_lossy().into_owned().into(),
288                        src_ref: SrcRef(None),
289                    })
290                })
291                .collect(),
292        )
293    }
294}
295
296#[cfg(test)]
297impl From<&str> for QualifiedName {
298    fn from(value: &str) -> Self {
299        Self(Refer::none(
300            value.split("::").map(Identifier::from).collect(),
301        ))
302    }
303}
304
305#[cfg(not(test))]
306impl TryFrom<&str> for QualifiedName {
307    type Error = ();
308
309    fn try_from(value: &str) -> Result<Self, Self::Error> {
310        let mut name = Vec::new();
311        for id in value.split("::").map(Identifier::try_from) {
312            if id.is_err() {
313                return Err(());
314            }
315            name.push(id.expect("unexpected error"));
316        }
317
318        Ok(Self(Refer::none(name)))
319    }
320}
321
322impl From<Identifier> for QualifiedName {
323    fn from(id: Identifier) -> Self {
324        let src_ref = id.src_ref();
325        QualifiedName(Refer::new(vec![id], src_ref))
326    }
327}
328
329impl From<QualifiedName> for String {
330    fn from(value: QualifiedName) -> Self {
331        value
332            .iter()
333            .map(|id| format!("{id}"))
334            .collect::<Vec<_>>()
335            .join("::")
336    }
337}
338
339impl TreeDisplay for QualifiedName {
340    fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
341        writeln!(
342            f,
343            "{:depth$}QualifiedName: '{}'",
344            "",
345            self.iter()
346                .map(|id| format!("{id:?}"))
347                .collect::<Vec<_>>()
348                .join("::")
349        )
350    }
351}