Skip to main content

ogma_libs/
matcher.rs

1//! Function matching utilities
2
3use crate::vm::{Callable, Func};
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use core::fmt;
7use object_query::Query;
8use serde::Deserialize;
9
10/// An error which can occur during matchng
11#[derive(Debug)]
12pub enum MatchError {
13    /// NLSD parse error
14    Nlsd(nlsd::Error),
15    /// Token mismatch
16    MismatchedStaticToken,
17    /// Missing query
18    EmptyQuery,
19    /// Mismatched query Name
20    UnknownQueryVar,
21    /// Mismatched data Name
22    UnknownDataVar,
23    /// Var left empty
24    UnfilledVar,
25    /// Expected more tokens to match against
26    UnexpectedEof,
27    /// Matching has finished but there is still more string
28    ExpectedEof,
29    /// Invalid matching context
30    InvalidCtx,
31}
32
33/// Reads tokens, queries and data from a string
34pub struct Matcher<'a> {
35    src: &'a str,
36}
37
38impl<'a> Matcher<'a> {
39    /// Create a new Matcher instance
40    pub fn new(src: &'a str) -> Self {
41        Self { src }
42    }
43
44    /// Get the next static token from the string
45    pub fn next_static(&mut self) -> Result<&'a str, MatchError> {
46        if let Ok((_, tok, rest)) = nl_parser::parse_token(self.src) {
47            self.src = rest;
48            Ok(tok)
49        } else {
50            Err(MatchError::UnexpectedEof)
51        }
52    }
53
54    /// Get the next NLOQ query from the string
55    pub fn next_query(&mut self) -> Result<Vec<Query<'a>>, MatchError> {
56        let mut nloq_de = nloq::Deserializer::from_str(self.src);
57        let query = nloq_de.query();
58        if query.is_empty() {
59            Err(MatchError::EmptyQuery)
60        } else {
61            self.src = nloq_de.rest();
62            Ok(query)
63        }
64    }
65
66    /// Get the next NLOQ query from the string
67    pub fn next_query_owned(&mut self) -> Result<Vec<Query<'static>>, MatchError> {
68        Ok(self
69            .next_query()?
70            .into_iter()
71            .map(|q| q.to_owned())
72            .collect())
73    }
74
75    /// Get the next NLSD object from the string and deserialize into `T`
76    pub fn next_data<T>(&mut self) -> Result<T, MatchError>
77    where
78        T: Deserialize<'a>,
79    {
80        let mut nlsd_de = nlsd::Deserializer::from_str(self.src);
81        let out = T::deserialize(&mut nlsd_de)?;
82        self.src = nlsd_de.rest();
83        Ok(out)
84    }
85
86    /// Check if the matcher contains more tokens
87    pub fn is_empty(&self) -> bool {
88        self.src.trim_start().is_empty()
89    }
90}
91
92/// Create `Self` from a string slice given a context. This should be implemented by functions that
93/// wish to be compileable and read their arguments from an English string
94pub trait Match<'a, C>: Sized {
95    fn match_str(ctx: &mut C, string: &'a str) -> Result<Self, MatchError>;
96}
97
98/// A function pointer which matches a string given a context to a callable Func
99pub type FuncMatcher<'a, C> = fn(&mut C, &'a str) -> Result<Func<'a>, MatchError>;
100
101/// Auto implemented trait for types which both implement `Match` and `Callable`
102pub trait MatchFunc<'a, C>: 'a + Match<'a, C> + Callable {
103    fn match_func(ctx: &mut C, string: &'a str) -> Result<Func<'a>, MatchError> {
104        Self::match_str(ctx, string).map(|this| Box::new(this) as Box<dyn Callable>)
105    }
106}
107
108impl<'a, C, T> MatchFunc<'a, C> for T where T: 'a + Match<'a, C> + Callable {}
109
110impl From<nlsd::Error> for MatchError {
111    fn from(err: nlsd::Error) -> Self {
112        Self::Nlsd(err)
113    }
114}
115
116impl fmt::Display for MatchError {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            Self::Nlsd(err) => f.write_fmt(format_args!("NLSD err: {}", err)),
120            Self::MismatchedStaticToken => f.write_str("mismatched static token"),
121            Self::EmptyQuery => f.write_str("empty NLOQ query"),
122            Self::UnknownQueryVar => f.write_str("mismatched query variable name"),
123            Self::UnknownDataVar => f.write_str("mismatched data variable name"),
124            Self::UnfilledVar => f.write_str("variable not set"),
125            Self::UnexpectedEof => f.write_str("unexpected end of file"),
126            Self::ExpectedEof => f.write_str("clause has extra tokens"),
127            Self::InvalidCtx => f.write_str("context error when parsing"),
128        }
129    }
130}
131
132#[cfg(feature = "std")]
133impl std::error::Error for MatchError {}