scpi 1.0.1

SCPI/IEEE488.2 parser library
Documentation
//! # 8.3.3 Numeric Lists
//!
//! A numeric list is a an expression format for compactly expressing numbers and ranges of
//! numbers in a single parameter.

use crate::error::{Error, ErrorCode};

type Number<'a> = crate::parser::tokenizer::Token<'a>;

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Token<'a> {
    Numeric(Number<'a>),
    NumericRange(Number<'a>, Number<'a>),
}

/// Numeric list expression tokenizer
#[derive(Clone)]
pub struct NumericList<'a> {
    pub tokenizer: crate::parser::tokenizer::Tokenizer<'a>,
    pub first: bool,
}

impl<'a> NumericList<'a> {
    pub fn new(s: &'a [u8]) -> NumericList<'a> {
        NumericList {
            tokenizer: crate::parser::tokenizer::Tokenizer::new(s),
            first: true,
        }
    }

    fn read_numeric_data(&mut self) -> Result<Token<'a>, ErrorCode> {
        let begin: Number = self.tokenizer.read_nrf()?;
        if let Some(c) = self.tokenizer.chars.clone().next() {
            //&& *c == b':' {
            if *c == b':' {
                self.tokenizer.chars.next();
                let end = self.tokenizer.read_nrf()?;
                return Ok(Token::NumericRange(begin, end));
            }
        }

        Ok(Token::Numeric(begin))
    }
}

impl<'a> Iterator for NumericList<'a> {
    type Item = Result<Token<'a>, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        //TODO: This has to be tokenizer abuse or something...
        let char = self.tokenizer.chars.clone().next()?;

        Some(match char {
            b',' if !self.first => {
                self.tokenizer.chars.next().unwrap();
                self.read_numeric_data().map_err(|err| {
                    Error::new(ErrorCode::InvalidExpression).extended(err.get_message())
                })
            }
            x if x.is_ascii_digit() || *x == b'-' || *x == b'+' && self.first => {
                self.first = false;
                self.read_numeric_data().map_err(|err| {
                    Error::new(ErrorCode::InvalidExpression).extended(err.get_message())
                })
            }
            _ => Err(Error::new(ErrorCode::InvalidExpression).extended(b"Invalid character")),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    extern crate std;

    #[test]
    fn test_numeric_data() {
        let spec = NumericList::new(b"2").read_numeric_data();
        assert_eq!(
            spec,
            Ok(Token::Numeric(Number::DecimalNumericProgramData(b"2")))
        );
        let range = NumericList::new(b"2:5").read_numeric_data();
        assert_eq!(
            range,
            Ok(Token::NumericRange(
                Number::DecimalNumericProgramData(b"2"),
                Number::DecimalNumericProgramData(b"5")
            ))
        );
        let specfail = NumericList::new(b"2::5").read_numeric_data();
        assert_eq!(specfail, Err(ErrorCode::NumericDataError));
    }

    #[test]
    fn test_numeric_list() {
        let mut expr = NumericList::new(b"3.1415,1.1:3.9e6");
        assert_eq!(
            expr.next().unwrap(),
            Ok(Token::Numeric(Number::DecimalNumericProgramData(b"3.1415")))
        );
        assert_eq!(
            expr.next().unwrap(),
            Ok(Token::NumericRange(
                Number::DecimalNumericProgramData(b"1.1"),
                Number::DecimalNumericProgramData(b"3.9e6")
            ))
        );
        assert_eq!(expr.next(), None);
    }

    #[test]
    fn test_numeric_leading() {
        let mut expr = NumericList::new(b",1,2:5");
        assert_eq!(
            expr.next().unwrap(),
            Err(Error::new(ErrorCode::InvalidExpression).extended(b"Invalid character"))
        );
    }

    #[test]
    fn test_numeric_repeated() {
        let mut expr = NumericList::new(b"1,,2:5");
        assert_eq!(
            expr.next().unwrap(),
            Ok(Token::Numeric(Number::DecimalNumericProgramData(b"1")))
        );
        assert_eq!(
            expr.next().unwrap(),
            Err(Error::new(ErrorCode::InvalidExpression)
                .extended(ErrorCode::NumericDataError.get_message()))
        );
    }
}