fosk 0.2.0

In-memory SQL-like query engine and lightweight data store for testing and prototyping.
Documentation
use crate::parser::{ParseError, QueryParser, WordComparer, ast::Literal};

pub struct NumberParser;

impl NumberParser {
    pub fn is_number(parser: &QueryParser) -> bool {
        let current = parser.current();
        current.is_ascii_digit() || current == '+' || current == '-'
    }

    pub fn parse(parser: &mut QueryParser) -> Result<Literal, ParseError> {
        let pivot = parser.position;
        let mut is_float = false;

        if !NumberParser::is_number(parser) {
            return Err(ParseError::new("Invalid number value", pivot, parser));
        }

        while !parser.eof() && (NumberParser::is_number(parser) || parser.current() == '.') {
            if parser.current() == '.' {
                is_float = true;
            }
            parser.next();
        }

        if !parser.eof() && !WordComparer::is_any_delimiter(parser.current()) {
            return Err(ParseError::new("Invalid number value", pivot, parser));
        }

        let number = parser.text_from_pivot(pivot);
        let number = match is_float {
            true => Literal::Float(
                ordered_float::NotNan::new(
                    number
                        .parse::<f64>()
                        .map_err(|_| ParseError::new("Invalid number", pivot, parser))?,
                )
                .map_err(|_| ParseError::new("Invalid number (NaN)", pivot, parser))?,
            ),
            false => Literal::Int(
                number
                    .parse::<i64>()
                    .map_err(|_| ParseError::new("Invalid number", pivot, parser))?,
            ),
        };

        Ok(number)
    }
}

#[cfg(test)]
pub mod tests {
    use crate::parser::{
        QueryParser,
        ast::{Literal, NumberParser},
    };

    fn parse_number(text: &str) -> Literal {
        let mut parser = QueryParser::new(text);
        NumberParser::parse(&mut parser)
            .unwrap_or_else(|error| panic!("expected number literal, got {error}"))
    }

    #[test]
    pub fn test_number_parser_int() {
        assert_eq!(parse_number("32"), Literal::Int(32));
    }

    #[test]
    pub fn test_number_parser_int_positive() {
        assert_eq!(parse_number("+32"), Literal::Int(32));
    }

    #[test]
    pub fn test_number_parser_int_negative() {
        assert_eq!(parse_number("-32"), Literal::Int(-32));
    }

    #[test]
    pub fn test_number_parser_float() {
        assert_eq!(
            parse_number("32."),
            Literal::Float(ordered_float::NotNan::new(32.0).unwrap())
        );
    }

    #[test]
    pub fn test_number_parser_float_digit() {
        assert_eq!(
            parse_number("32.5"),
            Literal::Float(ordered_float::NotNan::new(32.5).unwrap())
        );
    }

    #[test]
    pub fn test_number_parser_float_positive() {
        assert_eq!(
            parse_number("+32.5"),
            Literal::Float(ordered_float::NotNan::new(32.5).unwrap())
        );
    }

    #[test]
    pub fn test_number_parser_float_negative() {
        assert_eq!(
            parse_number("-32.5"),
            Literal::Float(ordered_float::NotNan::new(-32.5).unwrap())
        );
    }

    #[test]
    pub fn test_number_parser_comma_delimiter() {
        assert_eq!(parse_number("32,"), Literal::Int(32));
    }

    #[test]
    pub fn test_number_parser_space_delimiter() {
        assert_eq!(parse_number("32 "), Literal::Int(32));
    }

    #[test]
    pub fn test_number_parser_break_line() {
        assert_eq!(parse_number("32\r"), Literal::Int(32));
    }

    #[test]
    pub fn test_number_parser_wrong_value() {
        let text = "32a";

        let mut parser = QueryParser::new(text);

        let result = NumberParser::parse(&mut parser);

        let err = result.unwrap_err();
        assert_eq!(err.text, "32a");
        assert_eq!(err.start, 0);
        assert_eq!(err.end, 2);
    }

    #[test]
    pub fn test_number_parser_invalid_start() {
        let text = "abc";
        let mut parser = QueryParser::new(text);

        let err = NumberParser::parse(&mut parser).unwrap_err();

        assert_eq!(err.text, "a");
        assert_eq!(err.start, 0);
        assert_eq!(err.end, 0);
    }

    #[test]
    pub fn test_number_parser_rejects_sign_without_digits() {
        let text = "+";
        let mut parser = QueryParser::new(text);

        let err = NumberParser::parse(&mut parser).unwrap_err();

        assert_eq!(err.text, "+");
        assert_eq!(err.start, 0);
        assert_eq!(err.end, 1);
    }

    #[test]
    pub fn test_number_parser_rejects_invalid_float() {
        let text = "-.";
        let mut parser = QueryParser::new(text);

        let err = NumberParser::parse(&mut parser).unwrap_err();

        assert_eq!(err.text, "-.");
        assert_eq!(err.start, 0);
        assert_eq!(err.end, 2);
    }
}