sdml_core/model/
identifiers.rs

1/*!
2Provide the Rust types that implement *identifier*-related components of the SDML Grammar.
3*/
4use crate::load::ModuleLoader;
5use crate::model::modules::Module;
6use crate::model::{HasSourceSpan, Span};
7use convert_case::{Case, Casing};
8use lazy_static::lazy_static;
9use regex::Regex;
10use sdml_errors::diagnostics::functions::{
11    identifier_not_preferred_case, invalid_identifier, IdentifierCaseConvention,
12};
13use std::{
14    fmt::{Debug, Display},
15    hash::Hash,
16    str::FromStr,
17};
18use tracing::error;
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23// ------------------------------------------------------------------------------------------------
24// Public Types
25// ------------------------------------------------------------------------------------------------
26
27///
28/// Corresponds the grammar rule `identifier`.
29///
30#[derive(Clone, Debug)]
31#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
32pub struct Identifier {
33    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
34    span: Option<Box<Span>>,
35    value: String,
36}
37
38///
39/// Corresponds the grammar rule `qualified_identifier`.
40///
41#[derive(Clone, Debug)]
42#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
43pub struct QualifiedIdentifier {
44    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
45    span: Option<Box<Span>>,
46    module: Identifier,
47    member: Identifier,
48}
49
50///
51/// Corresponds the grammar rule `identifier_reference`.
52///
53#[derive(Clone, Debug)]
54#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
55pub enum IdentifierReference {
56    Identifier(Identifier),
57    QualifiedIdentifier(QualifiedIdentifier),
58}
59
60// ------------------------------------------------------------------------------------------------
61// Implementations
62// ------------------------------------------------------------------------------------------------
63
64lazy_static! {
65    static ref IDENTIFIER: Regex =
66        Regex::new(r"^[\p{Lu}\p{Ll}][\p{Lu}\p{Ll}\p{Nd}]*(?:_+[\p{Lu}\p{Ll}\p{Nd}]+)*$").unwrap();
67}
68
69const RESERVED_KEYWORDS: [&str; 19] = [
70    "as",
71    "base",
72    "datatype",
73    "end",
74    "entity",
75    "enum",
76    "event",
77    "group",
78    "identity",
79    "import",
80    "is",
81    "module",
82    "of",
83    "property",
84    "ref",
85    "source",
86    "structure",
87    "union",
88    "unknown",
89];
90const RESERVED_TYPES: [&str; 6] = ["string", "double", "decimal", "integer", "boolean", "iri"];
91const RESERVED_MODULES: [&str; 12] = [
92    "dc", "dc_am", "dc_terms", "dc_types", "iso_3166", "iso_4217", "owl", "rdf", "rdfs", "sdml",
93    "skos", "xsd",
94];
95
96// ------------------------------------------------------------------------------------------------
97
98impl Display for Identifier {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(f, "{}", self.value)
101    }
102}
103
104impl FromStr for Identifier {
105    type Err = crate::error::Error;
106
107    fn from_str(s: &str) -> Result<Self, Self::Err> {
108        if Self::is_valid(s) {
109            Ok(Self {
110                span: None,
111                value: s.to_string(),
112            })
113        } else {
114            error!("Identifier::from_str({s}) is invalid");
115            Err(invalid_identifier(0, None, s).into())
116        }
117    }
118}
119
120impl From<Identifier> for String {
121    fn from(value: Identifier) -> Self {
122        value.value
123    }
124}
125
126impl From<&Identifier> for String {
127    fn from(value: &Identifier) -> Self {
128        value.value.clone()
129    }
130}
131
132impl AsRef<str> for Identifier {
133    fn as_ref(&self) -> &str {
134        self.value.as_str()
135    }
136}
137
138impl PartialEq<str> for Identifier {
139    fn eq(&self, other: &str) -> bool {
140        self.value.as_str() == other
141    }
142}
143
144impl PartialEq for Identifier {
145    fn eq(&self, other: &Self) -> bool {
146        self.value == other.value
147    }
148}
149
150impl Eq for Identifier {}
151
152impl PartialOrd for Identifier {
153    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
154        Some(self.cmp(other))
155    }
156}
157
158impl Ord for Identifier {
159    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
160        self.value.cmp(&other.value)
161    }
162}
163
164impl Hash for Identifier {
165    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
166        // ignore: self.span.hash(state);
167        self.value.hash(state);
168    }
169}
170
171impl_has_source_span_for!(Identifier);
172
173impl Identifier {
174    // --------------------------------------------------------------------------------------------
175    // Identifier :: Constructors
176    // --------------------------------------------------------------------------------------------
177
178    pub fn new_unchecked(s: &str) -> Self {
179        Self {
180            span: None,
181            value: s.to_string(),
182        }
183    }
184
185    #[inline(always)]
186    pub fn with_module(&self, module: Identifier) -> QualifiedIdentifier {
187        QualifiedIdentifier::new(module, self.clone())
188    }
189
190    #[inline(always)]
191    pub fn with_member(&self, member: Identifier) -> QualifiedIdentifier {
192        QualifiedIdentifier::new(self.clone(), member)
193    }
194
195    // --------------------------------------------------------------------------------------------
196    // Identifier :: Helpers
197    // --------------------------------------------------------------------------------------------
198
199    pub fn validate(
200        &self,
201        top: &Module,
202        loader: &impl ModuleLoader,
203        as_case: Option<IdentifierCaseConvention>,
204    ) {
205        if !Self::is_valid(&self.value) {
206            loader
207                .report(&invalid_identifier(
208                    top.file_id().copied().unwrap_or_default(),
209                    self.span.as_ref().map(|s| s.as_ref().into()),
210                    &self.value,
211                ))
212                .unwrap();
213        }
214        if let Some(case) = as_case {
215            if !case.is_valid(self) {
216                loader
217                    .report(&identifier_not_preferred_case(
218                        top.file_id().copied().unwrap_or_default(),
219                        self.source_span().map(|span| span.byte_range()),
220                        self,
221                        case,
222                    ))
223                    .unwrap();
224            }
225        }
226    }
227
228    #[inline(always)]
229    pub fn is_valid<S>(s: S) -> bool
230    where
231        S: AsRef<str>,
232    {
233        let s = s.as_ref();
234        IDENTIFIER.is_match(s) && !Self::is_keyword(s)
235    }
236
237    #[inline(always)]
238    pub fn is_keyword<S>(s: S) -> bool
239    where
240        S: AsRef<str>,
241    {
242        RESERVED_KEYWORDS.contains(&s.as_ref())
243    }
244
245    #[inline(always)]
246    pub fn is_type_name<S>(s: S) -> bool
247    where
248        S: AsRef<str>,
249    {
250        RESERVED_TYPES.contains(&s.as_ref())
251    }
252
253    #[inline(always)]
254    pub fn is_library_module_name<S>(s: S) -> bool
255    where
256        S: AsRef<str>,
257    {
258        RESERVED_MODULES.contains(&s.as_ref())
259    }
260
261    #[inline(always)]
262    pub fn eq_with_span(&self, other: &Self) -> bool {
263        self.span == other.span && self.value == other.value
264    }
265
266    #[inline(always)]
267    pub fn to_type_label(&self) -> String {
268        self.value.to_case(Case::Title)
269    }
270
271    #[inline(always)]
272    pub fn to_variant_label(&self) -> String {
273        self.to_type_label()
274    }
275
276    #[inline(always)]
277    pub fn to_member_label(&self) -> String {
278        self.value.to_case(Case::Lower)
279    }
280
281    #[inline(always)]
282    pub fn to_module_label(&self) -> String {
283        self.to_member_label()
284    }
285}
286
287// ------------------------------------------------------------------------------------------------
288
289impl Display for QualifiedIdentifier {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        write!(f, "{}:{}", self.module, self.member)
292    }
293}
294
295impl From<QualifiedIdentifier> for String {
296    fn from(value: QualifiedIdentifier) -> Self {
297        String::from(&value)
298    }
299}
300
301impl From<&QualifiedIdentifier> for String {
302    fn from(value: &QualifiedIdentifier) -> Self {
303        value.to_string()
304    }
305}
306
307impl From<(Identifier, Identifier)> for QualifiedIdentifier {
308    fn from(value: (Identifier, Identifier)) -> Self {
309        Self::new(value.0, value.1)
310    }
311}
312
313impl FromStr for QualifiedIdentifier {
314    type Err = crate::error::Error;
315
316    fn from_str(s: &str) -> Result<Self, Self::Err> {
317        let parts = s.split(Self::SEPARATOR_STR).collect::<Vec<&str>>();
318        if parts.len() == 2 {
319            Ok(Self::new(
320                Identifier::from_str(parts[0])?,
321                Identifier::from_str(parts[1])?,
322            ))
323        } else {
324            error!("QualifiedIdentifier::from_str({s:?}) is invalid");
325            Err(invalid_identifier(0, None, s).into())
326        }
327    }
328}
329
330impl PartialEq<str> for QualifiedIdentifier {
331    fn eq(&self, other: &str) -> bool {
332        self.to_string().as_str() == other
333    }
334}
335
336impl PartialEq for QualifiedIdentifier {
337    fn eq(&self, other: &Self) -> bool {
338        self.module == other.module && self.member == other.member
339    }
340}
341
342impl Eq for QualifiedIdentifier {}
343
344impl Hash for QualifiedIdentifier {
345    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
346        // ignore: self.span.hash(state);
347        self.module.hash(state);
348        self.member.hash(state);
349    }
350}
351
352impl_has_source_span_for!(QualifiedIdentifier, span);
353
354impl QualifiedIdentifier {
355    const SEPARATOR_STR: &'static str = ":";
356
357    // --------------------------------------------------------------------------------------------
358    // QualifiedIdentifier :: Constructors
359    // --------------------------------------------------------------------------------------------
360
361    pub const fn new(module: Identifier, member: Identifier) -> Self {
362        Self {
363            span: None,
364            module,
365            member,
366        }
367    }
368
369    // --------------------------------------------------------------------------------------------
370    // QualifiedIdentifier :: Fields
371    // --------------------------------------------------------------------------------------------
372
373    getter!(pub module => Identifier);
374
375    getter!(pub member => Identifier);
376
377    // --------------------------------------------------------------------------------------------
378    // QualifiedIdentifier :: Helpers
379    // --------------------------------------------------------------------------------------------
380
381    pub fn validate(&self, top: &Module, loader: &impl ModuleLoader) {
382        self.module
383            .validate(top, loader, Some(IdentifierCaseConvention::Module));
384        self.member
385            .validate(top, loader, Some(IdentifierCaseConvention::ImportedMember));
386    }
387
388    pub fn eq_with_span(&self, other: &Self) -> bool {
389        self.span == other.span && self.module == other.module && self.member == other.member
390    }
391}
392
393// ------------------------------------------------------------------------------------------------
394
395impl From<IdentifierReference> for String {
396    fn from(value: IdentifierReference) -> Self {
397        String::from(&value)
398    }
399}
400
401impl From<&IdentifierReference> for String {
402    fn from(value: &IdentifierReference) -> Self {
403        match value {
404            IdentifierReference::Identifier(v) => v.to_string(),
405            IdentifierReference::QualifiedIdentifier(v) => v.to_string(),
406        }
407    }
408}
409
410impl FromStr for IdentifierReference {
411    type Err = crate::error::Error;
412
413    fn from_str(s: &str) -> Result<Self, Self::Err> {
414        let parts = s
415            .split(QualifiedIdentifier::SEPARATOR_STR)
416            .collect::<Vec<&str>>();
417        if parts.len() == 1 {
418            Ok(Self::Identifier(Identifier::from_str(parts[0])?))
419        } else if parts.len() == 2 {
420            Ok(Self::QualifiedIdentifier(QualifiedIdentifier::new(
421                Identifier::from_str(parts[0])?,
422                Identifier::from_str(parts[1])?,
423            )))
424        } else {
425            error!("QualifiedIdentifier::from_str({s:?}) is invalid");
426            Err(invalid_identifier(0, None, s).into())
427        }
428    }
429}
430
431impl PartialEq<str> for IdentifierReference {
432    fn eq(&self, other: &str) -> bool {
433        self.to_string().as_str() == other
434    }
435}
436
437impl PartialEq for IdentifierReference {
438    fn eq(&self, other: &Self) -> bool {
439        match (self, other) {
440            (Self::Identifier(l0), Self::Identifier(r0)) => l0.eq(r0),
441            (Self::QualifiedIdentifier(l0), Self::QualifiedIdentifier(r0)) => l0.eq(r0),
442            _ => false,
443        }
444    }
445}
446
447impl Eq for IdentifierReference {}
448
449impl Hash for IdentifierReference {
450    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
451        core::mem::discriminant(self).hash(state);
452    }
453}
454
455enum_display_impl!(IdentifierReference => Identifier, QualifiedIdentifier);
456
457impl_from_for_variant!(IdentifierReference, Identifier, Identifier);
458impl_from_for_variant!(
459    IdentifierReference,
460    QualifiedIdentifier,
461    QualifiedIdentifier
462);
463
464impl_has_source_span_for!(IdentifierReference => variants Identifier, QualifiedIdentifier);
465
466impl IdentifierReference {
467    // --------------------------------------------------------------------------------------------
468    // Variants
469    // --------------------------------------------------------------------------------------------
470
471    is_as_variant!(Identifier (Identifier) => is_identifier, as_identifier);
472
473    is_as_variant!(QualifiedIdentifier (QualifiedIdentifier) => is_qualified_identifier, as_qualified_identifier);
474
475    // --------------------------------------------------------------------------------------------
476    // IdentifierReference :: Variants
477    // --------------------------------------------------------------------------------------------
478
479    pub const fn module(&self) -> Option<&Identifier> {
480        match self {
481            IdentifierReference::Identifier(_) => None,
482            IdentifierReference::QualifiedIdentifier(v) => Some(v.module()),
483        }
484    }
485
486    pub const fn member(&self) -> &Identifier {
487        match self {
488            IdentifierReference::Identifier(v) => v,
489            IdentifierReference::QualifiedIdentifier(v) => v.member(),
490        }
491    }
492
493    // --------------------------------------------------------------------------------------------
494    // IdentifierReference :: Helpers
495    // --------------------------------------------------------------------------------------------
496
497    pub fn validate(&self, top: &Module, loader: &impl ModuleLoader) {
498        match self {
499            IdentifierReference::Identifier(v) => v.validate(top, loader, None),
500            IdentifierReference::QualifiedIdentifier(v) => v.validate(top, loader),
501        };
502    }
503
504    pub fn eq_with_span(&self, other: &Self) -> bool {
505        match (self, other) {
506            (Self::Identifier(l0), Self::Identifier(r0)) => l0.eq_with_span(r0),
507            (Self::QualifiedIdentifier(l0), Self::QualifiedIdentifier(r0)) => l0.eq_with_span(r0),
508            _ => false,
509        }
510    }
511}
512
513// ------------------------------------------------------------------------------------------------
514// Modules
515// ------------------------------------------------------------------------------------------------
516
517#[cfg(test)]
518mod tests {
519    use super::Identifier;
520    use pretty_assertions::assert_eq;
521
522    #[test]
523    fn test_type_label() {
524        assert_eq!("Foo", Identifier::new_unchecked("Foo").to_type_label());
525        assert_eq!(
526            "Foo Bar",
527            Identifier::new_unchecked("FooBar").to_type_label()
528        );
529        assert_eq!(
530            "Foo Bar Baz",
531            Identifier::new_unchecked("FooBarBaz").to_type_label()
532        );
533        assert_eq!(
534            "Foo Bar Baz",
535            Identifier::new_unchecked("Foo_Bar_Baz").to_type_label()
536        );
537    }
538
539    #[test]
540    fn test_member_label() {
541        assert_eq!("foo", Identifier::new_unchecked("Foo").to_member_label());
542        assert_eq!(
543            "foo bar",
544            Identifier::new_unchecked("FooBar").to_member_label()
545        );
546        assert_eq!(
547            "foo bar baz",
548            Identifier::new_unchecked("FooBarBaz").to_member_label()
549        );
550        assert_eq!(
551            "foo bar baz",
552            Identifier::new_unchecked("Foo_Bar_Baz").to_member_label()
553        );
554    }
555}