use std::collections::BTreeMap;
use std::fmt;
use thiserror::Error;
use component::Component;
use property::Property;
#[derive(Debug, Clone, Error)]
pub enum ParseErrorReason {
#[error("trailing data: {}", _0)]
TrailingData(String),
#[error("expected {}, found EOL", _0)]
UnexpectedEol(char),
#[error("expected {}, found {}", _0, _1)]
UnexpectedChar(char, char),
#[error("expected EOL")]
ExpectedEol,
#[error("no property name found")]
NoPropertyName,
#[error("no parameter name found")]
NoParameterName,
#[error("expected BEGIN tag")]
ExpectedBegin,
#[error("mismatched tags: BEGIN:{} vs END:{}", _0, _1)]
MismatchedTag(String, String),
}
type ParseResult<T> = Result<T, ParseErrorReason>;
pub struct Parser<'s> {
pub input: &'s str,
pub pos: usize,
}
impl<'s> Parser<'s> {
pub fn new(input: &'s str) -> Self {
Parser {
input: input,
pos: 0,
}
}
fn peek_at(&self, at: usize) -> Option<(char, usize)> {
match self.input[self.pos+at..].chars().next() {
None => None,
Some('\r') => self.peek_at(at + 1),
Some('\n') => {
match self.peek_at(at + 1) {
Some((' ', offset)) |
Some(('\t', offset)) => self.peek_at(offset),
_ => Some(('\n', at + 1)),
}
}
Some(x) => Some((x, at + x.len_utf8()))
}
}
#[inline]
fn peek(&self) -> Option<(char, usize)> {
self.peek_at(0)
}
pub fn eof(&self) -> bool {
self.pos >= self.input.len()
}
fn assert_char(&self, c: char) -> ParseResult<()> {
let real_c = match self.peek() {
Some((x, _)) => x,
None => {
return Err(ParseErrorReason::UnexpectedEol(c))
}
};
if real_c != c {
return Err(ParseErrorReason::UnexpectedChar(c, real_c))
};
Ok(())
}
pub fn consume_char(&mut self) -> Option<char> {
match self.peek() {
Some((c, offset)) => { self.pos += offset; Some(c) },
None => None
}
}
pub fn consume_only_char(&mut self, c: char) -> bool {
match self.peek() {
Some((d, offset)) if d == c => { self.pos += offset; true },
_ => false
}
}
fn consume_eol(&mut self) -> ParseResult<()> {
let start_pos = self.pos;
let consumed = match self.consume_char() {
Some('\n') => true,
Some('\r') => match self.consume_char() {
Some('\n') => true,
_ => false,
},
_ => false,
};
if consumed {
Ok(())
} else {
self.pos = start_pos;
return Err(ParseErrorReason::ExpectedEol)
}
}
fn sloppy_terminate_line(&mut self) -> ParseResult<()> {
if !self.eof() {
self.consume_eol()?;
while let Ok(_) = self.consume_eol() {}
};
Ok(())
}
pub fn consume_while<F: Fn(char) -> bool>(&mut self, test: F) -> String {
let mut sl_start_pos = self.pos;
let mut res = String::new();
while !self.eof() {
match self.peek() {
Some((c, offset)) => {
if !test(c) {
break
} else {
if offset > c.len_utf8() {
res.push_str(&self.input[sl_start_pos..self.pos]);
res.push(c);
sl_start_pos = self.pos + offset;
}
self.pos += offset;
}
},
_ => break
}
}
if sl_start_pos < self.pos {
res.push_str(&self.input[sl_start_pos..self.pos])
}
res
}
pub fn consume_property(&mut self) -> ParseResult<Property> {
let group = self.consume_property_group().ok();
let name = self.consume_property_name()?;
let params = self.consume_params();
self.assert_char(':')?;
self.consume_char();
let value = self.consume_property_value()?;
Ok(Property {
name: name,
params: params,
raw_value: value,
prop_group: group,
})
}
fn consume_property_name(&mut self) -> ParseResult<String> {
let rv = self.consume_while(|x| x == '-' || x.is_alphanumeric());
if rv.is_empty() {
Err(ParseErrorReason::NoPropertyName)
} else {
Ok(rv)
}
}
fn consume_property_group(&mut self) -> ParseResult<String> {
let start_pos = self.pos;
let name = self.consume_property_name();
let e = match name {
Ok(name) => match self.assert_char('.') {
Ok(_) => {
self.consume_char();
return Ok(name);
},
Err(e) => Err(e),
},
Err(e) => Err(e),
};
self.pos = start_pos;
e
}
fn consume_property_value(&mut self) -> ParseResult<String> {
let rv = self.consume_while(|x| x != '\r' && x != '\n');
self.sloppy_terminate_line()?;
Ok(rv)
}
fn consume_param_name(&mut self) -> ParseResult<String> {
self.consume_property_name()
.map_err(|_| ParseErrorReason::NoParameterName)
}
fn consume_param_value(&mut self) -> ParseResult<String> {
let qsafe = |x| {
x != '"' &&
x != '\r' &&
x != '\n' &&
x != '\u{7F}' &&
x > '\u{1F}'
};
if self.consume_only_char('"') {
let rv = self.consume_while(qsafe);
self.assert_char('"')?;
self.consume_char();
Ok(rv)
} else {
Ok(self.consume_while(|x| qsafe(x) && x != ';' && x != ':'))
}
}
fn consume_param(&mut self) -> ParseResult<(String, String)> {
let name = self.consume_param_name()?;
let start_pos = self.pos;
let value = if self.consume_only_char('=') {
match self.consume_param_value() {
Ok(x) => x,
Err(e) => { self.pos = start_pos; return Err(e); }
}
} else {
String::new()
};
Ok((name, value))
}
fn consume_params(&mut self) -> BTreeMap<String, String> {
let mut rv: BTreeMap<String, String> = BTreeMap::new();
while self.consume_only_char(';') {
match self.consume_param() {
Ok((name, value)) => { rv.insert(name.to_owned(), value.to_owned()); },
Err(_) => break,
}
}
rv
}
pub fn consume_component(&mut self) -> ParseResult<Component> {
let start_pos = self.pos;
let mut property = self.consume_property()?;
if property.name != "BEGIN" {
self.pos = start_pos;
return Err(ParseErrorReason::ExpectedBegin);
};
let mut component = Component::new(property.raw_value);
loop {
let previous_pos = self.pos;
property = self.consume_property()?;
if property.name == "BEGIN" {
self.pos = previous_pos;
component.subcomponents.push(self.consume_component()?);
} else if property.name == "END" {
if property.raw_value != component.name {
self.pos = start_pos;
return Err(ParseErrorReason::MismatchedTag(component.name, property.raw_value));
}
break;
} else {
component.push(property);
}
}
Ok(component)
}
}
#[cfg(test)]
mod tests {
use super::Parser;
#[test]
fn test_unfold1() {
let mut p = Parser{input: "ab\r\n c", pos: 2};
assert_eq!(p.consume_char(), Some('c'));
assert_eq!(p.pos, 6);
}
#[test]
fn test_unfold2() {
let mut p = Parser{input: "ab\n\tc\nx", pos: 2};
assert_eq!(p.consume_char(), Some('c'));
assert_eq!(p.consume_char(), Some('\n'));
assert_eq!(p.consume_char(), Some('x'));
}
#[test]
fn test_consume_while() {
let mut p = Parser{input:"af\n oo:bar", pos: 1};
assert_eq!(p.consume_while(|x| x != ':'), "foo");
assert_eq!(p.consume_char(), Some(':'));
assert_eq!(p.consume_while(|x| x != '\n'), "bar");
}
#[test]
fn test_consume_while2() {
let mut p = Parser{input:"af\n oo\n\t:bar", pos: 1};
assert_eq!(p.consume_while(|x| x != ':'), "foo");
assert_eq!(p.consume_char(), Some(':'));
assert_eq!(p.consume_while(|x| x != '\n'), "bar");
}
#[test]
fn test_consume_while3() {
let mut p = Parser{input:"af\n oo:\n bar", pos: 1};
assert_eq!(p.consume_while(|x| x != ':'), "foo");
assert_eq!(p.consume_char(), Some(':'));
assert_eq!(p.consume_while(|x| x != '\n'), "bar");
}
#[test]
fn test_consume_only_char() {
let mut p = Parser{input:"\n \"bar", pos: 0};
assert!(p.consume_only_char('"'));
assert_eq!(p.pos, 3);
assert!(!p.consume_only_char('"'));
assert_eq!(p.pos, 3);
assert!(p.consume_only_char('b'));
assert_eq!(p.pos, 4);
}
#[test]
fn mismatched_begin_end_tags_returns_error() {
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::time::Duration;
use super::ParseErrorReason;
let mut p = Parser {input: "BEGIN:a\nBEGIN:b\nEND:a", pos: 0};
let (tx, rx) = channel();
::std::thread::spawn(move|| { tx.send(p.consume_component()) });
match rx.recv_timeout(Duration::from_millis(50)) {
Err(RecvTimeoutError::Timeout) => assert!(false),
Ok(Err(e)) => {
match e {
ParseErrorReason::MismatchedTag(begin, end) => {
assert_eq!(begin, "b");
assert_eq!(end, "a");
},
_ => assert!(false),
}
},
_ => assert!(false),
}
}
}