Skip to main content

intuicio_core/
meta.rs

1//! Free-form annotations attached to types, functions and fields.
2//!
3//! Metadata is how a frontend or a tool marks up definitions without the
4//! platform having to know what the marks mean: a serialization hint, a
5//! documentation string, an editor category. Queries can filter on it, so a
6//! script or tool can ask the registry for "everything tagged like this".
7//!
8//! A [`Meta`] is a small tree of identifiers, literals, arrays, maps and named
9//! entries. Build one with the `meta!` macro, or parse one from text with
10//! [`Meta::parse`]:
11//!
12//! ```
13//! # use intuicio_core::meta::Meta;
14//! let meta = Meta::parse("{name: 'foo', size: 42, flags: [a, b]}").unwrap();
15//! assert!(meta.has_id("size"));
16//! ```
17use pest::{Parser, iterators::Pair};
18use pest_derive::Parser;
19use serde::{Deserialize, Serialize};
20use std::{collections::HashMap, fmt::Write};
21
22/// Grammar-driven parser for the metadata syntax. See `meta.pest`.
23#[derive(Parser)]
24#[grammar = "meta.pest"]
25struct MetaParser;
26
27impl MetaParser {
28    fn parse_main(content: &str) -> Result<Meta, String> {
29        match Self::parse(Rule::main, content) {
30            Ok(mut pairs) => {
31                let pair = pairs.next().unwrap().into_inner().next().unwrap();
32                match pair.as_rule() {
33                    Rule::meta => Ok(Self::parse_meta(pair)),
34                    rule => unreachable!("{:?}", rule),
35                }
36            }
37            Err(error) => Err(format!("{error}")),
38        }
39    }
40
41    fn parse_meta(pair: Pair<Rule>) -> Meta {
42        let pair = pair.into_inner().next().unwrap();
43        match pair.as_rule() {
44            Rule::identifier => Meta::Identifier(Self::parse_identifier(pair)),
45            Rule::value => Meta::Value(Self::parse_value(pair)),
46            Rule::array => Meta::Array(Self::parse_array(pair)),
47            Rule::map => Meta::Map(Self::parse_map(pair)),
48            Rule::named => {
49                let (id, meta) = Self::parse_named(pair);
50                Meta::Named(id, Box::new(meta))
51            }
52            rule => unreachable!("{:?}", rule),
53        }
54    }
55
56    fn parse_identifier(pair: Pair<Rule>) -> String {
57        pair.as_str().to_owned()
58    }
59
60    fn parse_value(pair: Pair<Rule>) -> MetaValue {
61        let pair = pair.into_inner().next().unwrap();
62        match pair.as_rule() {
63            Rule::literal_bool => MetaValue::Bool(pair.as_str().parse::<bool>().unwrap()),
64            Rule::literal_integer => MetaValue::Integer(pair.as_str().parse::<i64>().unwrap()),
65            Rule::literal_float => MetaValue::Float(pair.as_str().parse::<f64>().unwrap()),
66            Rule::literal_string => {
67                MetaValue::String(pair.into_inner().next().unwrap().as_str().to_owned())
68            }
69            rule => unreachable!("{:?}", rule),
70        }
71    }
72
73    fn parse_array(pair: Pair<Rule>) -> Vec<Meta> {
74        pair.into_inner().map(Self::parse_meta).collect()
75    }
76
77    fn parse_map(pair: Pair<Rule>) -> HashMap<String, Meta> {
78        pair.into_inner()
79            .map(|pair| {
80                let mut pairs = pair.into_inner();
81                (
82                    Self::parse_identifier(pairs.next().unwrap()),
83                    Self::parse_meta(pairs.next().unwrap()),
84                )
85            })
86            .collect()
87    }
88
89    fn parse_named(pair: Pair<Rule>) -> (String, Meta) {
90        let mut pairs = pair.into_inner();
91        (
92            Self::parse_identifier(pairs.next().unwrap()),
93            Self::parse_meta(pairs.next().unwrap()),
94        )
95    }
96}
97
98/// A literal inside a [`Meta`] tree.
99#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
100pub enum MetaValue {
101    /// `true` or `false`.
102    Bool(bool),
103    /// A whole number.
104    Integer(i64),
105    /// A fractional number.
106    Float(f64),
107    /// Text, written in single quotes.
108    String(String),
109}
110
111impl MetaValue {
112    /// Returns the boolean, or [`None`] for any other kind.
113    pub fn as_bool(&self) -> Option<bool> {
114        match self {
115            Self::Bool(value) => Some(*value),
116            _ => None,
117        }
118    }
119
120    /// Returns the integer, or [`None`] for any other kind.
121    pub fn as_integer(&self) -> Option<i64> {
122        match self {
123            Self::Integer(value) => Some(*value),
124            _ => None,
125        }
126    }
127
128    /// Returns the float, or [`None`] for any other kind.
129    pub fn as_float(&self) -> Option<f64> {
130        match self {
131            Self::Float(value) => Some(*value),
132            _ => None,
133        }
134    }
135
136    /// Borrows the string, or [`None`] for any other kind.
137    pub fn as_str(&self) -> Option<&str> {
138        match self {
139            Self::String(value) => Some(value.as_str()),
140            _ => None,
141        }
142    }
143
144    /// Copies the string, or [`None`] for any other kind.
145    pub fn as_string(&self) -> Option<String> {
146        match self {
147            Self::String(value) => Some(value.to_owned()),
148            _ => None,
149        }
150    }
151}
152
153impl std::fmt::Display for MetaValue {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        match self {
156            Self::Bool(value) => value.fmt(f),
157            Self::Integer(value) => value.fmt(f),
158            Self::Float(value) => value.fmt(f),
159            Self::String(value) => f.write_fmt(format_args!("{value:?}")),
160        }
161    }
162}
163
164impl From<bool> for MetaValue {
165    fn from(value: bool) -> Self {
166        Self::Bool(value)
167    }
168}
169
170impl From<i64> for MetaValue {
171    fn from(value: i64) -> Self {
172        Self::Integer(value)
173    }
174}
175
176impl From<f64> for MetaValue {
177    fn from(value: f64) -> Self {
178        Self::Float(value)
179    }
180}
181
182impl From<&str> for MetaValue {
183    fn from(value: &str) -> Self {
184        Self::String(value.to_owned())
185    }
186}
187
188/// A tree of annotations. See the [module docs](self).
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub enum Meta {
191    /// A bare name, such as `serialize`.
192    Identifier(String),
193    /// A literal, such as `42` or `'foo'`.
194    Value(MetaValue),
195    /// An ordered list, written `[a, b]`.
196    Array(Vec<Meta>),
197    /// Named entries, written `{a: 1, b: 2}`.
198    Map(HashMap<String, Meta>),
199    /// A single named entry, written `a = 1`.
200    Named(String, Box<Meta>),
201}
202
203impl Meta {
204    /// Parses metadata from text, returning the parser error as a message.
205    pub fn parse(content: &str) -> Result<Self, String> {
206        MetaParser::parse_main(content)
207    }
208
209    /// Returns the identifier, or [`None`] for any other kind.
210    pub fn as_identifier(&self) -> Option<&str> {
211        match self {
212            Self::Identifier(value) => Some(value.as_str()),
213            _ => None,
214        }
215    }
216
217    /// Returns the literal, or [`None`] for any other kind.
218    pub fn as_value(&self) -> Option<&MetaValue> {
219        match self {
220            Self::Value(value) => Some(value),
221            _ => None,
222        }
223    }
224
225    /// Returns the list, or [`None`] for any other kind.
226    pub fn as_array(&self) -> Option<&Vec<Meta>> {
227        match self {
228            Self::Array(value) => Some(value),
229            _ => None,
230        }
231    }
232
233    /// Returns the map, or [`None`] for any other kind.
234    pub fn as_map(&self) -> Option<&HashMap<String, Meta>> {
235        match self {
236            Self::Map(value) => Some(value),
237            _ => None,
238        }
239    }
240
241    /// Returns the name and its value, or [`None`] for any other kind.
242    pub fn as_named(&self) -> Option<(&str, &Meta)> {
243        match self {
244            Self::Named(name, value) => Some((name.as_str(), value)),
245            _ => None,
246        }
247    }
248
249    /// Returns `true` when this tree mentions `name`, as an identifier, a string,
250    /// a map key or a named entry.
251    ///
252    /// Searches one level into arrays. This is the usual predicate for a
253    /// metadata query.
254    pub fn has_id(&self, name: &str) -> bool {
255        match self {
256            Self::Identifier(value) => value == name,
257            Self::Value(value) => value.as_str() == Some(name),
258            Self::Array(values) => values.iter().any(|meta| meta.has_id(name)),
259            Self::Map(values) => values.iter().any(|(key, _)| key == name),
260            Self::Named(key, _) => key == name,
261        }
262    }
263
264    /// Returns what is stored under `name`, or [`None`] when it is absent.
265    pub fn extract_by_id(&'_ self, name: &str) -> Option<MetaExtract<'_>> {
266        match self {
267            Self::Identifier(value) => {
268                if value == name {
269                    Some(MetaExtract::Identifier(value.as_str()))
270                } else {
271                    None
272                }
273            }
274            Self::Value(value) => {
275                if value.as_str() == Some(name) {
276                    Some(MetaExtract::Value(value))
277                } else {
278                    None
279                }
280            }
281            Self::Array(values) => values
282                .iter()
283                .filter_map(|meta| meta.extract_by_id(name))
284                .next(),
285            Self::Map(values) => values
286                .iter()
287                .filter_map(|(key, value)| {
288                    if key == name {
289                        Some(MetaExtract::Meta(value))
290                    } else {
291                        None
292                    }
293                })
294                .next(),
295            Self::Named(key, value) => {
296                if key == name {
297                    Some(MetaExtract::Meta(value))
298                } else {
299                    None
300                }
301            }
302        }
303    }
304
305    /// Iterates the immediate children, treating a leaf as a single item.
306    pub fn items_iter(&'_ self) -> MetaExtractIter<'_> {
307        match self {
308            Self::Identifier(name) => {
309                MetaExtractIter::new(std::iter::once(MetaExtract::Identifier(name.as_str())))
310            }
311            Self::Value(value) => MetaExtractIter::new(std::iter::once(MetaExtract::Value(value))),
312            Self::Array(values) => MetaExtractIter::new(values.iter().map(MetaExtract::Meta)),
313            Self::Map(values) => MetaExtractIter::new(values.values().map(MetaExtract::Meta)),
314            Self::Named(_, value) => {
315                MetaExtractIter::new(std::iter::once(MetaExtract::Meta(value)))
316            }
317        }
318    }
319}
320
321impl std::fmt::Display for Meta {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        match self {
324            Self::Identifier(value) => value.fmt(f),
325            Self::Value(value) => value.fmt(f),
326            Self::Array(value) => {
327                f.write_char('[')?;
328                for (index, value) in value.iter().enumerate() {
329                    if index > 0 {
330                        f.write_str(", ")?;
331                    }
332                    value.fmt(f)?;
333                }
334                f.write_char(']')
335            }
336            Self::Map(value) => {
337                f.write_char('{')?;
338                for (index, (key, value)) in value.iter().enumerate() {
339                    if index > 0 {
340                        f.write_str(", ")?;
341                    }
342                    key.fmt(f)?;
343                    f.write_str(": ")?;
344                    value.fmt(f)?;
345                }
346                f.write_char('}')
347            }
348            Self::Named(name, value) => {
349                f.write_str(name)?;
350                f.write_str(" = ")?;
351                value.fmt(f)
352            }
353        }
354    }
355}
356
357/// What a lookup into a [`Meta`] tree found.
358#[derive(Debug, PartialEq)]
359pub enum MetaExtract<'a> {
360    /// Nothing was found.
361    Undefined,
362    /// The name itself, present as a bare identifier.
363    Identifier(&'a str),
364    /// A subtree.
365    Meta(&'a Meta),
366    /// A literal.
367    Value(&'a MetaValue),
368}
369
370impl MetaExtract<'_> {
371    /// Returns `true` when nothing was found.
372    pub fn is_undefined(&self) -> bool {
373        matches!(self, Self::Undefined)
374    }
375
376    /// Returns the identifier, or [`None`] for any other kind.
377    pub fn as_identifier(&self) -> Option<&str> {
378        match self {
379            Self::Identifier(value) => Some(*value),
380            _ => None,
381        }
382    }
383
384    /// Returns the literal, or [`None`] for any other kind.
385    pub fn as_value(&self) -> Option<&MetaValue> {
386        match self {
387            Self::Value(value) => Some(*value),
388            _ => None,
389        }
390    }
391
392    /// Returns the subtree, or [`None`] for any other kind.
393    pub fn as_meta(&self) -> Option<&Meta> {
394        match self {
395            Self::Meta(value) => Some(*value),
396            _ => None,
397        }
398    }
399}
400
401/// Iterator over the children of a [`Meta`], returned by [`Meta::items_iter`].
402pub struct MetaExtractIter<'a>(Box<dyn Iterator<Item = MetaExtract<'a>> + 'a>);
403
404impl<'a> MetaExtractIter<'a> {
405    fn new(iter: impl Iterator<Item = MetaExtract<'a>> + 'a) -> Self {
406        Self(Box::new(iter))
407    }
408}
409
410impl<'a> Iterator for MetaExtractIter<'a> {
411    type Item = MetaExtract<'a>;
412
413    fn next(&mut self) -> Option<Self::Item> {
414        self.0.next()
415    }
416}
417
418/// Builds a [`Meta`] tree from literal syntax.
419///
420/// ```
421/// # use intuicio_core::meta;
422/// let meta = meta!({name: "foo", size: 42, flags: [a, b], mode: (speed = fast)});
423/// assert!(meta.has_id("size"));
424/// ```
425#[macro_export]
426macro_rules! meta {
427    (@item { $( $key:ident : $item:tt ),* }) => {{
428        #[allow(unused_mut)]
429        let mut result = std::collections::HashMap::default();
430        $(
431            result.insert(
432                stringify!($key).to_owned(),
433                $crate::meta!(@item $item),
434            );
435        )*
436        $crate::meta::Meta::Map(result)
437    }};
438    (@item [ $( $item:tt ),* ]) => {
439        $crate::meta::Meta::Array(vec![ $( $crate::meta!(@item $item) ),* ])
440    };
441    (@item ( $name:ident = $item:tt )) => {
442        $crate::meta::Meta::Named(
443            stringify!($name).to_owned(),
444            Box::new($crate::meta!(@item $item))
445        )
446    };
447    (@item $value:literal) => {
448        $crate::meta::Meta::Value($crate::meta::MetaValue::from($value))
449    };
450    (@item $value:ident) => {
451        $crate::meta::Meta::Identifier(stringify!($value).to_owned())
452    };
453    ($tree:tt) => {
454        $crate::meta!(@item $tree)
455    };
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    #[test]
463    fn test_parser() {
464        println!("{}", Meta::parse("foo").unwrap());
465        println!("{}", Meta::parse("true").unwrap());
466        println!("{}", Meta::parse("42").unwrap());
467        println!("{}", Meta::parse("4.2").unwrap());
468        println!("{}", Meta::parse("'foo'").unwrap());
469        println!("{}", Meta::parse("foo = true").unwrap());
470        println!(
471            "{}",
472            Meta::parse("[true, 42, 4.2, 'foo', foo = true]").unwrap()
473        );
474        println!(
475            "{}",
476            Meta::parse("{bool: true, integer: 42, float: 4.2, string: 'foo', named: foo = true}")
477                .unwrap()
478        );
479    }
480
481    #[test]
482    fn test_macro() {
483        let meta = crate::meta!(foo);
484        assert!(matches!(meta, Meta::Identifier(_)));
485        assert_eq!(meta.as_identifier().unwrap(), "foo");
486
487        let meta = crate::meta!(true);
488        assert!(matches!(meta, Meta::Value(MetaValue::Bool(_))));
489        assert!(meta.as_value().unwrap().as_bool().unwrap());
490
491        let meta = crate::meta!(42);
492        assert!(matches!(meta, Meta::Value(MetaValue::Integer(_))));
493        assert_eq!(meta.as_value().unwrap().as_integer().unwrap(), 42);
494
495        let meta = crate::meta!(4.2);
496        assert!(matches!(meta, Meta::Value(MetaValue::Float(_))));
497        assert_eq!(meta.as_value().unwrap().as_float().unwrap(), 4.2);
498
499        let meta = crate::meta!("foo");
500        assert!(matches!(meta, Meta::Value(MetaValue::String(_))));
501        assert_eq!(meta.as_value().unwrap().as_str().unwrap(), "foo");
502
503        let meta = crate::meta!([]);
504        assert!(matches!(meta, Meta::Array(_)));
505
506        let meta = crate::meta!([true, 42, 4.2, "foo"]);
507        assert!(
508            meta.as_array().unwrap()[0]
509                .as_value()
510                .unwrap()
511                .as_bool()
512                .unwrap()
513        );
514        assert_eq!(
515            meta.as_array().unwrap()[1]
516                .as_value()
517                .unwrap()
518                .as_integer()
519                .unwrap(),
520            42
521        );
522        assert_eq!(
523            meta.as_array().unwrap()[2]
524                .as_value()
525                .unwrap()
526                .as_float()
527                .unwrap(),
528            4.2
529        );
530        assert_eq!(
531            meta.as_array().unwrap()[3]
532                .as_value()
533                .unwrap()
534                .as_str()
535                .unwrap(),
536            "foo"
537        );
538
539        let meta = crate::meta!({});
540        assert!(matches!(meta, Meta::Map(_)));
541
542        let meta = crate::meta!({bool: true, integer: 42, float: 4.2, string: "foo"});
543        assert!(
544            meta.as_map().unwrap()["bool"]
545                .as_value()
546                .unwrap()
547                .as_bool()
548                .unwrap()
549        );
550        assert_eq!(
551            meta.as_map().unwrap()["integer"]
552                .as_value()
553                .unwrap()
554                .as_integer()
555                .unwrap(),
556            42
557        );
558        assert_eq!(
559            meta.as_map().unwrap()["float"]
560                .as_value()
561                .unwrap()
562                .as_float()
563                .unwrap(),
564            4.2
565        );
566        assert_eq!(
567            meta.as_map().unwrap()["string"]
568                .as_value()
569                .unwrap()
570                .as_str()
571                .unwrap(),
572            "foo"
573        );
574
575        let meta = crate::meta!((foo = true));
576        assert!(matches!(meta, Meta::Named(_, _)));
577        assert_eq!(meta.as_named().unwrap().0, "foo");
578        assert!(
579            meta.as_named()
580                .unwrap()
581                .1
582                .as_value()
583                .unwrap()
584                .as_bool()
585                .unwrap()
586        );
587    }
588
589    #[test]
590    fn test_meta_extract() {
591        let meta = crate::meta!(foo);
592        assert!(meta.has_id("foo"));
593        assert_eq!(
594            meta.extract_by_id("foo").unwrap(),
595            MetaExtract::Identifier("foo")
596        );
597        assert_eq!(
598            meta.items_iter().collect::<Vec<_>>(),
599            vec![MetaExtract::Identifier("foo")]
600        );
601
602        let meta = crate::meta!("foo");
603        assert!(meta.has_id("foo"));
604        assert_eq!(
605            meta.extract_by_id("foo").unwrap(),
606            MetaExtract::Value(&MetaValue::String("foo".to_owned()))
607        );
608        assert_eq!(
609            meta.items_iter().collect::<Vec<_>>(),
610            vec![MetaExtract::Value(&MetaValue::String("foo".to_owned()))]
611        );
612
613        let meta = crate::meta!([true, 42, 4.2, "foo"]);
614        assert!(meta.has_id("foo"));
615        assert_eq!(
616            meta.extract_by_id("foo").unwrap(),
617            MetaExtract::Value(&MetaValue::String("foo".to_owned()))
618        );
619        assert_eq!(
620            meta.items_iter().collect::<Vec<_>>(),
621            vec![
622                MetaExtract::Meta(&Meta::Value(MetaValue::Bool(true))),
623                MetaExtract::Meta(&Meta::Value(MetaValue::Integer(42))),
624                MetaExtract::Meta(&Meta::Value(MetaValue::Float(4.2))),
625                MetaExtract::Meta(&Meta::Value(MetaValue::String("foo".to_owned()))),
626            ]
627        );
628
629        let meta = crate::meta!({bool: true, integer: 42, float: 4.2, string: "foo"});
630        assert!(meta.has_id("bool"));
631        assert!(meta.has_id("integer"));
632        assert!(meta.has_id("float"));
633        assert!(meta.has_id("string"));
634        assert_eq!(
635            meta.extract_by_id("bool").unwrap(),
636            MetaExtract::Meta(&Meta::Value(MetaValue::Bool(true)))
637        );
638        assert_eq!(
639            meta.extract_by_id("integer").unwrap(),
640            MetaExtract::Meta(&Meta::Value(MetaValue::Integer(42)))
641        );
642        assert_eq!(
643            meta.extract_by_id("float").unwrap(),
644            MetaExtract::Meta(&Meta::Value(MetaValue::Float(4.2)))
645        );
646        assert_eq!(
647            meta.extract_by_id("string").unwrap(),
648            MetaExtract::Meta(&Meta::Value(MetaValue::String("foo".to_owned())))
649        );
650        let mut result = meta.items_iter().collect::<Vec<_>>();
651        result.sort_by(|a, b| {
652            let a = match a {
653                MetaExtract::Meta(Meta::Value(MetaValue::Bool(_))) => 0,
654                MetaExtract::Meta(Meta::Value(MetaValue::Integer(_))) => 1,
655                MetaExtract::Meta(Meta::Value(MetaValue::Float(_))) => 2,
656                MetaExtract::Meta(Meta::Value(MetaValue::String(_))) => 3,
657                _ => 4,
658            };
659            let b = match b {
660                MetaExtract::Meta(Meta::Value(MetaValue::Bool(_))) => 0,
661                MetaExtract::Meta(Meta::Value(MetaValue::Integer(_))) => 1,
662                MetaExtract::Meta(Meta::Value(MetaValue::Float(_))) => 2,
663                MetaExtract::Meta(Meta::Value(MetaValue::String(_))) => 3,
664                _ => 4,
665            };
666            a.cmp(&b)
667        });
668        assert_eq!(
669            result,
670            vec![
671                MetaExtract::Meta(&Meta::Value(MetaValue::Bool(true))),
672                MetaExtract::Meta(&Meta::Value(MetaValue::Integer(42))),
673                MetaExtract::Meta(&Meta::Value(MetaValue::Float(4.2))),
674                MetaExtract::Meta(&Meta::Value(MetaValue::String("foo".to_owned()))),
675            ]
676        );
677
678        let meta = crate::meta!((foo = true));
679        assert!(meta.has_id("foo"));
680        assert_eq!(
681            meta.extract_by_id("foo").unwrap(),
682            MetaExtract::Meta(&Meta::Value(MetaValue::Bool(true)))
683        );
684        assert_eq!(
685            meta.items_iter().collect::<Vec<_>>(),
686            vec![MetaExtract::Meta(&Meta::Value(MetaValue::Bool(true)))]
687        );
688    }
689}