use arrayvec::ArrayVec;
use chrono::NaiveTime;
use std::str::{self, FromStr};
use std::{io, iter};
use err::{LexError, ParseError};
use lexer;
const LAT_SPLIT: usize = 2;
const ABS_MAX_LAT: f64 = 90.0;
const LONG_SPLIT: usize = 3;
const ABS_MAX_LONG: f64 = 180.0;
#[derive(Debug)]
enum CardDir {
North,
South,
East,
West,
}
impl CardDir {
fn get_sign(&self) -> isize {
match self {
CardDir::North | CardDir::East => 1,
CardDir::South | CardDir::West => -1,
}
}
}
#[derive(Debug, PartialEq)]
pub enum GpsQualityInd {
FixNotAvailable,
GpsFix,
DifferentialGpsFix,
PpsFix,
RealTimeKinematic,
FloatRtk,
Estimated,
ManualInputMode,
SimulationMode,
}
impl GpsQualityInd {
fn try_from_isize(int: isize) -> Result<Self, ParseError> {
match int {
0 => Ok(GpsQualityInd::FixNotAvailable),
1 => Ok(GpsQualityInd::GpsFix),
2 => Ok(GpsQualityInd::DifferentialGpsFix),
3 => Ok(GpsQualityInd::PpsFix),
4 => Ok(GpsQualityInd::RealTimeKinematic),
5 => Ok(GpsQualityInd::FloatRtk),
6 => Ok(GpsQualityInd::Estimated),
7 => Ok(GpsQualityInd::ManualInputMode),
8 => Ok(GpsQualityInd::SimulationMode),
_ => Err(ParseError::InvalidValue(
"quality indicator has to be between 0 and 8 inclusive",
)),
}
}
}
#[derive(Debug, PartialEq)]
pub struct GgaSentence {
pub talker_id: [u8; lexer::HEADER_LENGTH],
pub utc: NaiveTime,
pub lat: Option<f64>,
pub long: Option<f64>,
pub gps_qlty: GpsQualityInd,
pub sat_view: u64,
pub hdop: Option<f64>,
pub altitude: Option<f64>,
pub geo_sep: Option<f64>,
pub age: Option<f64>,
pub station_id: Option<u32>,
}
#[derive(Debug)]
pub struct GgaParser<R: io::Read> {
lexer: iter::Peekable<lexer::Tokenizer<R>>,
}
impl<R: io::Read> GgaParser<R> {
pub fn new(input: R) -> Result<Self, io::Error> {
Ok(GgaParser {
lexer: lexer::Tokenizer::new(input)?.peekable(),
})
}
pub fn read_sentence(&mut self) -> Result<GgaSentence, ParseError> {
let talker_id = self.expect_header()?;
let sen_type = self.expect_sen_type()?;
match sen_type.as_slice() {
b"GGA" => {
expect!(self, CommaSeparator)?;
self.parse_gga(talker_id)
}
_ => Err(ParseError::UnexpectedSentenceType),
}
}
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
fn parse_gga(
&mut self,
talker_id: [u8; lexer::HEADER_LENGTH],
) -> Result<GgaSentence, ParseError> {
let utc = self.expect_utc()?;
expect!(self, CommaSeparator)?;
let lat = self.accept_lat()?;
expect!(self, CommaSeparator)?;
let long = self.accept_long()?;
expect!(self, CommaSeparator)?;
let gps_qlty = self.expect_qual_ind()?;
expect!(self, CommaSeparator)?;
let sat_view = self.expect_sat_in_view()?;
expect!(self, CommaSeparator)?;
let hdop = self.accept_hdop()?;
expect!(self, CommaSeparator)?;
let altitude = self.accept_altitude()?;
expect!(self, CommaSeparator)?;
self.expect_meters()?;
expect!(self, CommaSeparator)?;
let geo_sep = self.accept_geo_sep()?;
expect!(self, CommaSeparator)?;
self.expect_meters()?;
expect!(self, CommaSeparator)?;
let age = self.accept_age()?;
expect!(self, CommaSeparator)?;
let station_id = self.accept_station_id()?;
expect!(self, Checksum, c)?;
accept!(self, LineEnding)?;
Ok(GgaSentence {
talker_id,
utc,
lat,
long,
gps_qlty,
sat_view,
hdop,
altitude,
geo_sep,
age,
station_id,
})
}
pub fn jump_to_header(&mut self) -> Option<Result<(), io::Error>> {
loop {
match self.lexer.peek() {
Some(Ok(v)) if v.is_header() => return Some(Ok(())),
Some(_) => (),
None => return None,
}
if let Some(Err(LexError::Io(e))) = self.lexer.next() {
return Some(Err(e));
}
}
}
fn expect_header(&mut self) -> Result<[u8; 2], ParseError> {
expect!(self, Header, h)
}
fn expect_sen_type(&mut self) -> Result<ArrayVec<[u8; 64]>, ParseError> {
expect!(self, StringLiteral, s)
}
fn expect_utc(&mut self) -> Result<NaiveTime, ParseError> {
Ok(NaiveTime::parse_from_str(
str::from_utf8(&expect!(self, FloatLiteral, f)?)?,
"%H%M%S%.f",
)?)
}
fn accept_lat(&mut self) -> Result<Option<f64>, ParseError> {
let raw_lat = match accept!(self, FloatLiteral, f)? {
Some(f) => Some(f),
None => None,
};
expect!(self, CommaSeparator)?;
let lat_dir = match accept!(self, StringLiteral, s)? {
Some(ref s) if s.as_slice() == b"N" => Some(CardDir::North),
Some(ref s) if s.as_slice() == b"S" => Some(CardDir::South),
Some(s) => return Err(ParseError::UnexpectedDir(s)),
None => None,
};
match (raw_lat, lat_dir) {
(Some(lat), Some(d)) => Ok(Some(parse_coord(&lat, &d, LAT_SPLIT, ABS_MAX_LAT)?)),
(_, _) => Ok(None),
}
}
fn accept_long(&mut self) -> Result<Option<f64>, ParseError> {
let raw_long = match accept!(self, FloatLiteral, f)? {
Some(f) => Some(f),
None => None,
};
expect!(self, CommaSeparator)?;
let long_dir = match accept!(self, StringLiteral, s)? {
Some(ref s) if s.as_slice() == b"E" => Some(CardDir::East),
Some(ref s) if s.as_slice() == b"W" => Some(CardDir::West),
Some(s) => return Err(ParseError::UnexpectedDir(s)),
None => None,
};
match (raw_long, long_dir) {
(Some(long), Some(d)) => Ok(Some(parse_coord(&long, &d, LONG_SPLIT, ABS_MAX_LONG)?)),
(_, _) => Ok(None),
}
}
fn expect_qual_ind(&mut self) -> Result<GpsQualityInd, ParseError> {
GpsQualityInd::try_from_isize(expect!(self, IntLiteral, i)?)
}
fn expect_sat_in_view(&mut self) -> Result<u64, ParseError> {
match expect!(self, IntLiteral, i)? {
i if i < 0 => Err(ParseError::InvalidValue(
"number of satellites in view has to be larger than or equal to 0",
)),
i => Ok(i as u64),
}
}
fn accept_hdop(&mut self) -> Result<Option<f64>, ParseError> {
match accept!(self, FloatLiteral, f)? {
Some(f) => Ok(Some(fl_as_f64(f.as_slice())?)),
None => Ok(None),
}
}
fn accept_altitude(&mut self) -> Result<Option<f64>, ParseError> {
match accept!(self, FloatLiteral, f)? {
Some(f) => Ok(Some(fl_as_f64(f.as_slice())?)),
None => Ok(None),
}
}
fn accept_geo_sep(&mut self) -> Result<Option<f64>, ParseError> {
match accept!(self, FloatLiteral, f)? {
Some(f) => Ok(Some(fl_as_f64(f.as_slice())?)),
None => Ok(None),
}
}
fn accept_age(&mut self) -> Result<Option<f64>, ParseError> {
match accept!(self, FloatLiteral, f)? {
Some(f) => {
let f = fl_as_f64(f.as_slice())?;
if f < 0.0 {
return Err(ParseError::InvalidValue(
"age of the data cannot be negative",
));
}
Ok(Some(f))
}
None => Ok(None),
}
}
fn accept_station_id(&mut self) -> Result<Option<u32>, ParseError> {
match accept!(self, IntLiteral, i)? {
Some(i) if 0 <= i && i <= 1023 => Ok(Some(i as u32)),
Some(_) => Err(ParseError::InvalidValue(
"station_id must be between 0 and 1023",
)),
None => Ok(None),
}
}
fn expect_meters(&mut self) -> Result<(), ParseError> {
if expect!(self, StringLiteral, s)?.as_slice() != b"M" {
return Err(ParseError::InvalidUnit);
}
Ok(())
}
}
impl<R: io::Read> iter::Iterator for GgaParser<R> {
type Item = Result<GgaSentence, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
let _ = self.jump_to_header()?;
match self.read_sentence() {
Ok(gga) => Some(Ok(gga)),
Err(ParseError::Lexer(LexError::UnexpectedEof(_))) => None,
Err(e) => Some(Err(e)),
}
}
}
fn parse_coord(
coord: &[u8],
dir: &CardDir,
deg_split: usize,
abs_max: f64,
) -> Result<f64, ParseError> {
if deg_split > coord.len() {
return Err(ParseError::InvalidValue(
"the float is too short for a coordinate",
));
}
let (deg, min) = coord.split_at(deg_split);
let degrees = f64::from(u8::from_str(str::from_utf8(deg)?)?);
let min = fl_as_f64(min)?;
if min >= 60.0 {
return Err(ParseError::InvalidValue("minutes have to be less than 60"));
}
let decimal_min = min * 10.0 / 6.0 / 100.0;
let dec_deg = degrees + decimal_min;
if dec_deg.abs() > abs_max {
return Err(ParseError::InvalidCoord(dec_deg, abs_max));
}
Ok(dec_deg * dir.get_sign() as f64)
}
fn fl_as_f64(fl: &[u8]) -> Result<f64, ParseError> {
Ok(f64::from_str(str::from_utf8(fl)?)?)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn t_parser(arg: &str) -> GgaParser<Cursor<&str>> {
GgaParser::new(Cursor::new(arg)).unwrap()
}
fn str_array_vec(vec: Vec<u8>) -> ::arrayvec::ArrayVec<[u8; ::lexer::STRING_LENGTH]> {
let mut av = ::arrayvec::ArrayVec::<[u8; ::lexer::STRING_LENGTH]>::new();
for v in vec {
av.push(v);
}
av
}
#[test]
fn expect_methods_on_empty() {
let mut parser = t_parser("");
assert_matches!(parser.expect_header(), Err(ParseError::UnexpectedToken));
assert_matches!(parser.expect_sen_type(), Err(ParseError::UnexpectedToken));
assert_matches!(parser.expect_utc(), Err(ParseError::UnexpectedToken));
assert_matches!(parser.expect_qual_ind(), Err(ParseError::UnexpectedToken));
assert_matches!(
parser.expect_sat_in_view(),
Err(ParseError::UnexpectedToken)
);
assert_matches!(parser.expect_meters(), Err(ParseError::UnexpectedToken));
}
#[test]
fn accept_methods_on_empty() {
let mut parser = t_parser("");
assert_matches!(parser.accept_age(), Ok(None));
assert_matches!(parser.accept_altitude(), Ok(None));
assert_matches!(parser.accept_geo_sep(), Ok(None));
assert_matches!(parser.accept_hdop(), Ok(None));
assert_matches!(parser.accept_lat(), Err(ParseError::UnexpectedToken));
assert_matches!(parser.accept_long(), Err(ParseError::UnexpectedToken));
assert_matches!(parser.accept_station_id(), Ok(None));
}
#[test]
fn direction_sign() {
assert_eq!(CardDir::North.get_sign(), 1);
assert_eq!(CardDir::East.get_sign(), 1);
assert_eq!(CardDir::South.get_sign(), -1);
assert_eq!(CardDir::West.get_sign(), -1);
}
#[test]
fn gps_quality_from_isize() {
assert_matches!(
GpsQualityInd::try_from_isize(-1),
Err(ParseError::InvalidValue(_))
);
assert_matches!(
GpsQualityInd::try_from_isize(0),
Ok(GpsQualityInd::FixNotAvailable)
);
assert_matches!(GpsQualityInd::try_from_isize(1), Ok(GpsQualityInd::GpsFix));
assert_matches!(
GpsQualityInd::try_from_isize(2),
Ok(GpsQualityInd::DifferentialGpsFix)
);
assert_matches!(GpsQualityInd::try_from_isize(3), Ok(GpsQualityInd::PpsFix));
assert_matches!(
GpsQualityInd::try_from_isize(4),
Ok(GpsQualityInd::RealTimeKinematic)
);
assert_matches!(
GpsQualityInd::try_from_isize(5),
Ok(GpsQualityInd::FloatRtk)
);
assert_matches!(
GpsQualityInd::try_from_isize(6),
Ok(GpsQualityInd::Estimated)
);
assert_matches!(
GpsQualityInd::try_from_isize(7),
Ok(GpsQualityInd::ManualInputMode)
);
assert_matches!(
GpsQualityInd::try_from_isize(8),
Ok(GpsQualityInd::SimulationMode)
);
assert_matches!(
GpsQualityInd::try_from_isize(9),
Err(ParseError::InvalidValue(_))
);
}
mod header {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("$GP");
assert_matches!(parser.expect_header(), Ok([b'G', b'P']));
assert!(parser.lexer.next().is_none());
}
#[test]
fn too_short() {
let mut parser = t_parser("$a");
assert_matches!(parser.expect_header(), Err(ParseError::Lexer(_)));
assert!(parser.lexer.next().is_none());
}
}
mod sentence_type {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("GGA");
let exp = str_array_vec(vec![b'G', b'G', b'A']);
assert_matches!(parser.expect_sen_type(), Ok(ref s) if s == &exp);
}
#[test]
fn ok_long() {
let mut parser = t_parser("aaaaa");
let exp = str_array_vec(vec![b'a', b'a', b'a', b'a', b'a']);
assert_matches!(parser.expect_sen_type(), Ok(ref s) if s == &exp);
}
#[test]
fn ok_short() {
let mut parser = t_parser("a");
let exp = str_array_vec(vec![b'a']);
assert_matches!(parser.expect_sen_type(), Ok(ref s) if s == &exp);
}
}
mod utc {
use super::*;
#[test]
fn max() {
let mut parser = t_parser("235959.999999999");
let left = parser.expect_utc();
let expected = NaiveTime::from_hms_nano(23, 59, 59, 999999999);
assert!(left.is_ok());
assert_eq!(left.unwrap(), expected);
}
#[test]
fn min() {
let mut parser = t_parser("000000.0");
let left = parser.expect_utc();
assert!(left.is_ok());
assert_eq!(left.unwrap(), NaiveTime::from_hms(0, 0, 0));
}
#[test]
fn negative() {
let mut parser = t_parser("-00000.0");
let left = parser.expect_utc();
assert_matches!(left, Err(ParseError::Time(_)));
}
#[test]
fn no_dot() {
let mut parser = t_parser("111111");
assert_matches!(parser.expect_utc(), Err(ParseError::UnexpectedToken));
}
#[test]
fn wrong_fmt() {
let mut parser = t_parser("1111111.11");
assert_matches!(parser.expect_utc(), Err(ParseError::Time(_)));
}
}
mod parse_coord {
use super::*;
#[test]
fn coord_ok() {
let left = parse_coord(b"18000.000", &CardDir::East, 3, 180.0);
assert_matches!(left, Ok(coord) if coord == 180.0);
}
#[test]
fn short_coord() {
let left = parse_coord(b"111.0", &CardDir::East, 3, 180.0);
assert_matches!(left, Ok(coord) if coord == 111.0);
}
#[test]
fn higher_than_max() {
let left = parse_coord(b"18000.3", &CardDir::East, 3, 180.0);
assert_matches!(left,
Err(ParseError::InvalidCoord(c, max))
if c == 180.005 && max == 180.0);
}
#[test]
fn negative_coord() {
let left = parse_coord(b"-0000.001", &CardDir::East, 3, 180.0);
assert_matches!(left, Err(ParseError::Int(_)));
}
#[test]
fn minutes_too_high() {
let left = parse_coord(b"00060.0", &CardDir::East, 3, 180.0);
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
#[test]
fn direction_handling() {
let coord = b"01612.369";
let north = parse_coord(coord, &CardDir::North, 3, 180.0);
let east = parse_coord(coord, &CardDir::East, 3, 80.0);
let south = parse_coord(coord, &CardDir::South, 3, 180.0);
let west = parse_coord(coord, &CardDir::West, 3, 80.0);
assert!(north.is_ok() && east.is_ok() && south.is_ok() && west.is_ok());
let north = north.unwrap();
let east = east.unwrap();
let south = south.unwrap();
let west = west.unwrap();
assert_eq!(north, east);
assert_eq!(south, west);
assert_eq!(north, south * -1.0);
}
}
mod accept_lat {
use super::*;
#[test]
fn some_lat() {
let mut parser = t_parser("1612.369,N");
let left = parser.accept_lat();
assert_matches!(left, Ok(Some(lat)) if lat == 16.20615);
assert!(parser.lexer.next().is_none());
}
#[test]
fn negative_lat() {
let mut parser = t_parser("1612.369,S");
let left = parser.accept_lat();
assert_matches!(left, Ok(Some(lat)) if lat == -16.20615);
assert!(parser.lexer.next().is_none());
}
#[test]
fn no_coordinate() {
let mut parser = t_parser(",N");
assert_matches!(parser.accept_lat(), Ok(None));
assert!(parser.lexer.next().is_none());
}
#[test]
fn no_direction() {
let mut parser = t_parser("1612.369,");
assert_matches!(parser.accept_lat(), Ok(None));
assert!(parser.lexer.next().is_none());
}
#[test]
fn no_coord_or_dir() {
let mut parser = t_parser(",");
assert_matches!(parser.accept_lat(), Ok(None));
assert!(parser.lexer.next().is_none());
}
#[test]
fn wrong_direction() {
let mut parser = t_parser("01612.369,W");
let left = parser.accept_lat();
assert_matches!(left, Err(ParseError::UnexpectedDir(_)));
assert!(parser.lexer.next().is_none());
}
}
mod accept_long {
use super::*;
#[test]
fn some_long() {
let mut parser = t_parser("01612.369,E");
let left = parser.accept_long();
assert_matches!(left, Ok(Some(long)) if long == 16.20615);
assert!(parser.lexer.next().is_none());
}
#[test]
fn negative_long() {
let mut parser = t_parser("01612.369,W");
let left = parser.accept_long();
assert_matches!(left, Ok(Some(long)) if long == -16.20615);
assert!(parser.lexer.next().is_none());
}
#[test]
fn no_coordinate() {
let mut parser = t_parser(",E");
assert_matches!(parser.accept_long(), Ok(None));
assert!(parser.lexer.next().is_none());
}
#[test]
fn no_direction() {
let mut parser = t_parser("01612.369,");
assert_matches!(parser.accept_long(), Ok(None));
assert!(parser.lexer.next().is_none());
}
#[test]
fn no_coord_or_dir() {
let mut parser = t_parser(",");
assert_matches!(parser.accept_long(), Ok(None));
assert!(parser.lexer.next().is_none());
}
#[test]
fn wrong_direction() {
let mut parser = t_parser("01612.369,N");
let left = parser.accept_long();
assert_matches!(left, Err(ParseError::UnexpectedDir(_)));
assert!(parser.lexer.next().is_none());
}
}
mod quality_indicator {
use super::*;
#[test]
fn lowest_value() {
let mut parser = t_parser("0");
let left = parser.expect_qual_ind();
assert_matches!(left, Ok(GpsQualityInd::FixNotAvailable));
}
#[test]
fn highest_value() {
let mut parser = t_parser("8");
let left = parser.expect_qual_ind();
assert_matches!(left, Ok(GpsQualityInd::SimulationMode));
}
#[test]
fn too_low() {
let mut parser = t_parser("-1");
let left = parser.expect_qual_ind();
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
#[test]
fn too_high() {
let mut parser = t_parser("9");
let left = parser.expect_qual_ind();
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
}
mod sattelites {
use super::*;
#[test]
fn lowest_value() {
let mut parser = t_parser("0");
assert_matches!(parser.expect_sat_in_view(), Ok(0));
}
#[test]
fn too_low() {
let mut parser = t_parser("-1");
let left = parser.expect_sat_in_view();
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
}
mod hdop {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("12345.6789");
let left = parser.accept_hdop();
assert_matches!(left, Ok(Some(hdop)) if hdop == 12345.6789);
}
}
mod altitude {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("12345.6789");
let left = parser.accept_altitude();
assert_matches!(left, Ok(Some(f)) if f == 12345.6789);
}
}
mod geoidal_separation {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("12345.6789");
let left = parser.accept_geo_sep();
assert_matches!(left, Ok(Some(f)) if f == 12345.6789);
}
}
mod age {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("12345.6789");
let left = parser.accept_age();
assert_matches!(left, Ok(Some(age)) if age == 12345.6789);
}
#[test]
fn lowest_value() {
let mut parser = t_parser("0.0");
let left = parser.accept_age();
assert_matches!(left, Ok(Some(age)) if age == 0.0);
}
#[test]
fn negative() {
let mut parser = t_parser("-0.0001");
let left = parser.accept_age();
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
}
mod station_id {
use super::*;
#[test]
fn lowest() {
let mut parser = t_parser("0");
let left = parser.accept_station_id();
assert_matches!(left, Ok(Some(id)) if id == 0);
}
#[test]
fn highest() {
let mut parser = t_parser("1023");
let left = parser.accept_station_id();
assert_matches!(left, Ok(Some(id)) if id == 1023);
}
#[test]
fn too_low() {
let mut parser = t_parser("-1");
let left = parser.accept_station_id();
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
#[test]
fn too_high() {
let mut parser = t_parser("1024");
let left = parser.accept_station_id();
assert_matches!(left, Err(ParseError::InvalidValue(_)));
}
}
mod units {
use super::*;
#[test]
fn meters() {
let mut parser = t_parser("M");
let left = parser.expect_meters();
assert!(left.is_ok());
}
}
mod parse_gga {
use super::*;
#[test]
fn with_location() {
let gga = "$GPGGA,142212.000,1956.9418,S,06938.0163,W,1,3,5.74,102.1,M,47.9,M,,*57";
let talker_id = [b'G', b'P'];
let mut parser = t_parser(gga);
parser.expect_header().expect("header");
parser.expect_sen_type().expect("sentence type");
expect!(parser, CommaSeparator).expect("comma");
let left = parser.parse_gga(talker_id);
let expected = GgaSentence {
talker_id,
utc: NaiveTime::from_hms(14, 22, 12),
lat: Some(
parse_coord(b"1956.9418", &CardDir::South, 2, 90.0).expect("lat: -19.949030"),
),
long: Some(
parse_coord(b"06938.0163", &CardDir::West, 3, 180.0).expect("long: -69.633605"),
),
gps_qlty: GpsQualityInd::try_from_isize(1).expect("GpsFix"),
sat_view: 3,
hdop: Some(5.74),
altitude: Some(102.1),
geo_sep: Some(47.9),
age: None,
station_id: None,
};
assert!(left.is_ok());
assert_eq!(left.unwrap(), expected);
}
#[test]
fn without_location() {
let talker_id = [b'G', b'P'];
let mut parser = t_parser("$GPGGA,142054.304,,,,,0,0,,,M,,M,,*49");
parser.expect_header().expect("header");
parser.expect_sen_type().expect("sentence type");
expect!(parser, CommaSeparator).expect("comma");
let left = parser.parse_gga(talker_id);
let expected = GgaSentence {
talker_id,
utc: NaiveTime::from_hms_milli(14, 20, 54, 304),
lat: None,
long: None,
gps_qlty: GpsQualityInd::try_from_isize(0).expect("FixNotAvailable"),
sat_view: 0,
hdop: None,
altitude: None,
geo_sep: None,
age: None,
station_id: None,
};
assert!(left.is_ok());
assert_eq!(left.unwrap(), expected);
}
#[test]
fn comma_not_consumed() {
let talker_id = [b'G', b'P'];
let mut parser = t_parser("$GPGGA,142054.304,,,,,0,0,,,M,,M,,*49");
parser.expect_header().expect("header");
parser.expect_sen_type().expect("sentence type");
let left = parser.parse_gga(talker_id);
assert_matches!(left, Err(ParseError::UnexpectedToken));
}
}
mod read_sentence {
use super::*;
#[test]
fn with_location() {
let gga = "$GPGGA,142212.000,1956.9418,S,06938.0163,W,1,3,5.74,102.1,M,47.9,M,,*57";
let mut parser = t_parser(gga);
let left = parser.read_sentence();
let expected = GgaSentence {
talker_id: [b'G', b'P'],
utc: NaiveTime::from_hms(14, 22, 12),
lat: Some(
parse_coord(b"1956.9418", &CardDir::South, 2, 90.0).expect("lat: -19.949030"),
),
long: Some(
parse_coord(b"06938.0163", &CardDir::West, 3, 180.0).expect("long: -69.633605"),
),
gps_qlty: GpsQualityInd::try_from_isize(1).expect("GpsFix"),
sat_view: 3,
hdop: Some(5.74),
altitude: Some(102.1),
geo_sep: Some(47.9),
age: None,
station_id: None,
};
assert!(left.is_ok());
assert_eq!(left.unwrap(), expected);
}
#[test]
fn without_location() {
let mut parser = t_parser("$GPGGA,142054.304,,,,,0,0,,,M,,M,,*49");
let left = parser.read_sentence();
let expected = GgaSentence {
talker_id: [b'G', b'P'],
utc: NaiveTime::from_hms_milli(14, 20, 54, 304),
lat: None,
long: None,
gps_qlty: GpsQualityInd::try_from_isize(0).expect("FixNotAvailable"),
sat_view: 0,
hdop: None,
altitude: None,
geo_sep: None,
age: None,
station_id: None,
};
assert!(left.is_ok());
assert_eq!(left.unwrap(), expected);
}
#[test]
fn wrong_sentence_type() {
let mut parser = t_parser("$GPGLL");
let left = parser.read_sentence();
assert_matches!(left, Err(ParseError::UnexpectedSentenceType));
}
}
mod jump_to_header {
use super::*;
#[test]
fn ok() {
let mut parser = t_parser("abc123$aa");
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(parser.expect_header(), Ok([b'a', b'a']));
}
#[test]
fn empty() {
let mut parser = t_parser("");
assert_matches!(parser.jump_to_header(), None);
}
#[test]
fn input_but_no_header() {
let mut parser = t_parser(" asdfasd o/))) 2f'#");
assert_matches!(parser.jump_to_header(), None);
}
#[test]
fn stay_at_header() {
let mut parser = t_parser("$aa123$bb");
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(parser.expect_header(), Ok([b'a', b'a']));
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(parser.expect_header(), Ok([b'b', b'b']));
}
#[test]
fn no_header() {
let mut parser = t_parser("$aa");
assert_matches!(parser.expect_header(), Ok([b'a', b'a']));
assert_matches!(parser.jump_to_header(), None);
assert_matches!(parser.jump_to_header(), None);
}
#[test]
fn invalid_ascii() {
let sentences =
"$GPGGA,142130.220,4900.7350,N,00825.5268,E,1,3,5.53,102.1,M,47.9,M,,*5B\n\n\
-�OAz�\n\
�\"AAliCGRA�B؇=&J]���b����?$GPRMC,142132.000,A,\
4900.7350,N,00825.5269,E,0.00,182.46,150518,,,A*63\n\n\
$GPVTG,182.46,T,,M,0.00,N,0.00,K,A*34\n\n\
$GPGGA,142132.000,4900.7350,N,00825.5269,E,1,3,5.53,102.1,M,47.9,M,,*58";
let mut parser = t_parser(sentences);
assert!(parser.read_sentence().is_ok());
assert_matches!(
parser.read_sentence(),
Err(ParseError::Lexer(::err::LexError::IncompleteToken(_)))
);
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(
parser.read_sentence(),
Err(ParseError::UnexpectedSentenceType)
);
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert_matches!(
parser.read_sentence(),
Err(ParseError::UnexpectedSentenceType)
);
assert_matches!(parser.jump_to_header(), Some(Ok(())));
assert!(parser.read_sentence().is_ok());
}
}
mod iter {
use super::*;
#[test]
fn correct_sentences() {
let sentences =
"$GPGGA,142130.220,4900.7350,N,00825.5268,E,1,3,5.53,102.1,M,47.9,M,,*5B\n\n\
$GPGGA,142132.000,4900.7350,N,00825.5269,E,1,3,5.53,102.1,M,47.9,M,,*58";
let mut parser = t_parser(sentences);
assert_matches!(parser.next(), Some(Ok(GgaSentence { .. })));
assert_matches!(parser.next(), Some(Ok(GgaSentence { .. })));
assert!(parser.next().is_none());
}
#[test]
fn with_invalid_ascii() {
let sentences =
"$GPGGA,142130.220,4900.7350,N,00825.5268,E,1,3,5.53,102.1,M,47.9,M,,*5B\n\n\
-�OAz�\n\
�\"AAliCGRA�B؇=&J]���b����?$GPRMC,142132.000,A,\
4900.7350,N,00825.5269,E,0.00,182.46,150518,,,A*63\n\n\
$GPVTG,182.46,T,,M,0.00,N,0.00,K,A*34\n\n\
$GPGGA,142132.000,4900.7350,N,00825.5269,E,1,3,5.53,102.1,M,47.9,M,,*58";
let mut parser = t_parser(sentences);
assert_matches!(parser.next(), Some(Ok(GgaSentence { .. })));
assert_matches!(parser.next(), Some(Err(_)));
assert_matches!(parser.next(), Some(Err(_)));
assert_matches!(parser.next(), Some(Ok(GgaSentence { .. })));
assert!(parser.next().is_none());
}
#[test]
fn filter_gga_with_invalid_ascii() {
let sentences =
"$GPGGA,142130.220,4900.7350,N,00825.5268,E,1,3,5.53,102.1,M,47.9,M,,*5B\n\n\
-�OAz�\n\
�\"AAliCGRA�B؇=&J]���b����?$GPRMC,142132.000,A,\
4900.7350,N,00825.5269,E,0.00,182.46,150518,,,A*63\n\n\
$GPVTG,182.46,T,,M,0.00,N,0.00,K,A*34\n\n\
$GPGGA,142132.000,4900.7350,N,00825.5269,E,1,3,5.53,102.1,M,47.9,M,,*58";
let mut parser = t_parser(sentences).filter_map(|v| v.ok());
assert_matches!(parser.next(), Some(GgaSentence { .. }));
assert_matches!(parser.next(), Some(GgaSentence { .. }));
assert!(parser.next().is_none());
}
#[test]
fn empty_input() {
let mut parser = t_parser("");
assert!(parser.next().is_none());
}
#[test]
fn without_gga() {
let input = "iCGRA�B؇=&J]���b����?$GPRMC,142132.000,A,\
4900.7350,N,00825.5269,E,0.00,182.46,150518,,,A*63\n\n\
$GPVTG,182.46,T,,M,0.00,N,0.00";
let mut parser = t_parser(input);
assert_matches!(parser.next(), Some(Err(_)));
assert_matches!(parser.next(), Some(Err(_)));
assert!(parser.next().is_none());
}
#[test]
fn filter_gga_without_gga() {
let input = "iCGRA�B؇=&J]���b����?$GPRMC,142132.000,A,\
4900.7350,N,00825.5269,E,0.00,182.46,150518,,,A*63\n\n\
$GPVTG,182.46,T,,M,0.00,N,0.00";
let mut parser = t_parser(input).filter_map(|v| v.ok());
assert!(parser.next().is_none());
}
}
}