microcad_lang/syntax/identifier/
qualified_name.rs

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