fosk 0.2.0

In-memory SQL-like query engine and lightweight data store for testing and prototyping.
Documentation
use crate::parser::QueryParser;

#[derive(Debug, Default)]
pub struct WordComparer {
    pub length: usize,
    pub word: Vec<char>,
    whitespace_postfix: bool,
    break_line_postfix: bool,
    full_block_delimiter_postfix: bool,
    eof: bool,
    delimiter: Option<char>,
    optional_postfix: Vec<char>,
}

impl WordComparer {
    pub fn new(word: &str) -> Self {
        Self {
            length: word.len(),
            word: word.to_uppercase().chars().collect(),
            whitespace_postfix: false,
            break_line_postfix: false,
            full_block_delimiter_postfix: false,
            eof: false,
            delimiter: None,
            optional_postfix: vec![],
        }
    }

    pub fn reach_eof(&self, parser: &QueryParser) -> bool {
        parser.position + self.length >= parser.length
    }

    pub fn is_block_delimiter(ch: char) -> bool {
        ch.is_ascii_whitespace()
    }

    pub fn is_any_delimiter(ch: char) -> bool {
        ch == ',' || ch == '(' || ch == ')' || ch == '.' || Self::is_block_delimiter(ch)
    }

    pub fn is_break_line(ch: char) -> bool {
        ch == '\r' || ch == '\n'
    }

    pub fn is_current_block_delimiter(parser: &QueryParser) -> bool {
        Self::is_block_delimiter(parser.current())
    }

    pub fn is_current_break_line(parser: &QueryParser) -> bool {
        Self::is_break_line(parser.current())
    }

    pub fn compare(&self, parser: &QueryParser) -> bool {
        let mut position = 0;
        while position < self.length {
            if (parser.position + position) >= parser.length
                || self.word[position]
                    != parser.text_v[parser.position + position].to_ascii_uppercase()
            {
                return false;
            }
            position += 1;
        }

        if self.reach_eof(parser) {
            return self.eof;
        }

        if self.delimiter.is_none()
            && !self.full_block_delimiter_postfix
            && !self.whitespace_postfix
            && !self.break_line_postfix
            && self.optional_postfix.is_empty()
        {
            return true;
        }

        let next = parser.text_v[parser.position + position];

        if let Some(delimiter) = self.delimiter
            && next == delimiter
        {
            return true;
        }

        if self.full_block_delimiter_postfix && Self::is_any_delimiter(next) {
            return true;
        }

        if self.whitespace_postfix && Self::is_block_delimiter(next) {
            return true;
        }

        if self.break_line_postfix && Self::is_break_line(next) {
            return true;
        }

        for value in self.optional_postfix.iter() {
            if *value == next {
                return true;
            }
        }

        false
    }

    pub fn with_eof(mut self) -> Self {
        self.eof = true;
        self
    }
    pub fn with_whitespace_postfix(mut self) -> Self {
        self.whitespace_postfix = true;
        self
    }
    pub fn with_break_line_postfix(mut self) -> Self {
        self.break_line_postfix = true;
        self
    }
    pub fn with_any_delimiter_postfix(mut self) -> Self {
        self.full_block_delimiter_postfix = true;
        self
    }
    pub fn with_delimiter(mut self, delimiter: char) -> Self {
        self.delimiter = Some(delimiter);
        self
    }
    pub fn with_optional_postfix(mut self, value: char) -> Self {
        self.optional_postfix.push(value);
        self
    }

    pub fn compare_with_block_delimiter(&self, parser: &QueryParser) -> bool {
        self.compare(parser)
            && (self.reach_eof(parser) || Self::is_any_delimiter(parser.peek(self.length)))
    }
}

#[cfg(test)]
mod tests {
    use super::WordComparer;
    use crate::parser::QueryParser;

    #[test]
    fn compare_is_case_insensitive_without_postfix_rules() {
        let parser = QueryParser::new("select ");

        assert!(WordComparer::new("SELECT").compare(&parser));
        assert!(!WordComparer::new("FROM").compare(&parser));
    }

    #[test]
    fn compare_honors_eof_and_postfix_rules() {
        assert!(
            WordComparer::new("x")
                .with_eof()
                .compare(&QueryParser::new("x"))
        );
        assert!(!WordComparer::new("x").compare(&QueryParser::new("x")));
        assert!(
            WordComparer::new("x")
                .with_whitespace_postfix()
                .compare(&QueryParser::new("x "))
        );
        assert!(
            WordComparer::new("x")
                .with_break_line_postfix()
                .compare(&QueryParser::new("x\n"))
        );
        assert!(
            WordComparer::new("x")
                .with_any_delimiter_postfix()
                .compare(&QueryParser::new("x,"))
        );
        assert!(
            WordComparer::new("x")
                .with_delimiter('(')
                .compare(&QueryParser::new("x("))
        );
        assert!(
            WordComparer::new("x")
                .with_optional_postfix(',')
                .compare(&QueryParser::new("x,"))
        );
        assert!(
            !WordComparer::new("x")
                .with_whitespace_postfix()
                .compare(&QueryParser::new("x,"))
        );
    }

    #[test]
    fn delimiter_helpers_classify_current_character() {
        let space = QueryParser::new(" ");
        let newline = QueryParser::new("\n");

        assert!(WordComparer::is_block_delimiter(' '));
        assert!(WordComparer::is_any_delimiter('.'));
        assert!(WordComparer::is_break_line('\n'));
        assert!(WordComparer::is_current_block_delimiter(&space));
        assert!(WordComparer::is_current_break_line(&newline));
    }

    #[test]
    fn compare_with_block_delimiter_requires_delimited_match() {
        assert!(WordComparer::new("id").compare_with_block_delimiter(&QueryParser::new("id,")));
        assert!(!WordComparer::new("id").compare_with_block_delimiter(&QueryParser::new("idea")));
    }
}