1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Function matching utilities

use crate::vm::{Callable, Func};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use object_query::Query;
use serde::Deserialize;

/// An error which can occur during matchng
#[derive(Debug)]
pub enum MatchError {
    /// NLSD parse error
    Nlsd(nlsd::Error),
    /// Token mismatch
    MismatchedStaticToken,
    /// Missing query
    EmptyQuery,
    /// Mismatched query Name
    UnknownQueryVar,
    /// Mismatched data Name
    UnknownDataVar,
    /// Var left empty
    UnfilledVar,
    /// Expected more tokens to match against
    UnexpectedEof,
    /// Matching has finished but there is still more string
    ExpectedEof,
    /// Invalid matching context
    InvalidCtx,
}

/// Reads tokens, queries and data from a string
pub struct Matcher<'a> {
    src: &'a str,
}

impl<'a> Matcher<'a> {
    /// Create a new Matcher instance
    pub fn new(src: &'a str) -> Self {
        Self { src }
    }

    /// Get the next static token from the string
    pub fn next_static(&mut self) -> Result<&'a str, MatchError> {
        if let Ok((_, tok, rest)) = nl_parser::parse_token(self.src) {
            self.src = rest;
            Ok(tok)
        } else {
            Err(MatchError::UnexpectedEof)
        }
    }

    /// Get the next NLOQ query from the string
    pub fn next_query(&mut self) -> Result<Vec<Query<'a>>, MatchError> {
        let mut nloq_de = nloq::Deserializer::from_str(self.src);
        let query = nloq_de.query();
        if query.is_empty() {
            Err(MatchError::EmptyQuery)
        } else {
            self.src = nloq_de.rest();
            Ok(query)
        }
    }

    /// Get the next NLOQ query from the string
    pub fn next_query_owned(&mut self) -> Result<Vec<Query<'static>>, MatchError> {
        Ok(self
            .next_query()?
            .into_iter()
            .map(|q| q.to_owned())
            .collect())
    }

    /// Get the next NLSD object from the string and deserialize into `T`
    pub fn next_data<T>(&mut self) -> Result<T, MatchError>
    where
        T: Deserialize<'a>,
    {
        let mut nlsd_de = nlsd::Deserializer::from_str(self.src);
        let out = T::deserialize(&mut nlsd_de)?;
        self.src = nlsd_de.rest();
        Ok(out)
    }

    /// Check if the matcher contains more tokens
    pub fn is_empty(&self) -> bool {
        self.src.trim_start().is_empty()
    }
}

/// Create `Self` from a string slice given a context. This should be implemented by functions that
/// wish to be compileable and read their arguments from an English string
pub trait Match<'a, C>: Sized {
    fn match_str(ctx: &mut C, string: &'a str) -> Result<Self, MatchError>;
}

/// A function pointer which matches a string given a context to a callable Func
pub type FuncMatcher<'a, C> = fn(&mut C, &'a str) -> Result<Func<'a>, MatchError>;

/// Auto implemented trait for types which both implement `Match` and `Callable`
pub trait MatchFunc<'a, C>: 'a + Match<'a, C> + Callable {
    fn match_func(ctx: &mut C, string: &'a str) -> Result<Func<'a>, MatchError> {
        Self::match_str(ctx, string).map(|this| Box::new(this) as Box<dyn Callable>)
    }
}

impl<'a, C, T> MatchFunc<'a, C> for T where T: 'a + Match<'a, C> + Callable {}

impl From<nlsd::Error> for MatchError {
    fn from(err: nlsd::Error) -> Self {
        Self::Nlsd(err)
    }
}

impl fmt::Display for MatchError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Nlsd(err) => f.write_fmt(format_args!("NLSD err: {}", err)),
            Self::MismatchedStaticToken => f.write_str("mismatched static token"),
            Self::EmptyQuery => f.write_str("empty NLOQ query"),
            Self::UnknownQueryVar => f.write_str("mismatched query variable name"),
            Self::UnknownDataVar => f.write_str("mismatched data variable name"),
            Self::UnfilledVar => f.write_str("variable not set"),
            Self::UnexpectedEof => f.write_str("unexpected end of file"),
            Self::ExpectedEof => f.write_str("clause has extra tokens"),
            Self::InvalidCtx => f.write_str("context error when parsing"),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for MatchError {}