use std::str;
use std::cmp::PartialEq;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::fmt::Debug;
use nom::{space, multispace, IResult, ErrorKind};
use dtree::{Tree, Section, Mapping};
#[derive(Debug)]
struct PartialDTreeOption {
parent: String,
description: String,
opt_name: String,
dest: String,
}
impl PartialEq for PartialDTreeOption {
fn eq(&self, other: &PartialDTreeOption) -> bool {
return self.parent == other.parent && self.description == other.description &&
self.opt_name == other.opt_name && self.dest == other.dest;
}
}
#[derive(Debug)]
struct PartialDTree {
name: String,
description: String,
}
impl PartialEq for PartialDTree {
fn eq(&self, other: &PartialDTree) -> bool {
return self.description == other.description && self.name == other.name;
}
}
fn identifier(input_u8: &[u8]) -> IResult<&[u8], &str> {
let input = match str::from_utf8(input_u8) {
Err(_) => return IResult::Error(ErrorKind::Custom(13)),
Ok(t) => t
};
let mut bytes_i: usize = 0;
let mut last_size: usize = 0;
for c in input.chars() {
if (!c.is_alphabetic() && !c.is_numeric() && c != '_' && c != '-') {
break;
}
bytes_i += c.len_utf8();
last_size = c.len_utf8();
}
return IResult::Done(&input_u8[bytes_i..], &input[0..bytes_i - last_size + 1])
}
named!(desc_text<String>,
call!(escaped_until, '\n')
);
named!(mapping_name<String>,
call!(escaped_until, ')')
);
named!(section_desc<PartialDTree>,
do_parse!(
opt!(multispace) >>
tag!("[") >>
opt!(space) >>
n: identifier >>
opt!(space) >>
tag!("]") >>
opt!(space) >>
m: desc_text >>
(PartialDTree{name: String::from(n), description: m})
)
);
fn escaped_until(input_u8: &[u8], until: char) -> IResult<&[u8], String> {
let input = match str::from_utf8(input_u8) {
Ok(t) => t,
Err(_) => return IResult::Error(ErrorKind::Custom(31)),
};
let mut s = String::new();
let mut iter = input.chars().peekable();
let mut bytes_i: usize = 0;
loop {
let c = match iter.next() {
Some(n) => n,
None => break,
};
match iter.peek() {
Some(&a) if c == '\\' && a == until => {
s.push(until);
iter.next();
bytes_i += 1 + until.len_utf8();
continue;
},
_ => {}
}
if c == until {
return IResult::Done(&input_u8[bytes_i..], s);
}
bytes_i += c.len_utf8();
s.push(c);
}
return IResult::Done(&b""[..], s);
}
named!(mapping<PartialDTreeOption>,
do_parse!(
opt!(multispace) >>
tag!("[") >>
opt!(space) >>
n: identifier >>
opt!(space) >>
tag!("(") >>
name: mapping_name >>
tag!(")") >>
opt!(space) >>
tag!("->") >>
opt!(space) >>
to: identifier >>
opt!(space) >>
tag!("]") >>
opt!(space) >>
m: desc_text >>
(PartialDTreeOption{parent: String::from(n), description: m,
opt_name: name, dest: String::from(to)})
)
);
fn dtree_parse_impl(input: &[u8]) -> IResult<&[u8], Tree, String> {
let mut in_mut = input;
let mut sects_partial: Vec<PartialDTree> = Vec::new();
let mut maps_partial: Vec<PartialDTreeOption> = Vec::new();
loop {
match mapping(in_mut) {
IResult::Done(i, o) => {
in_mut = i;
maps_partial.push(o);
continue;
}
IResult::Error(_) => {}
IResult::Incomplete(_) => {
break;
}
}
match section_desc(in_mut) {
IResult::Done(i, o) => {
in_mut = i;
sects_partial.push(o);
continue;
}
IResult::Error(_) => {
return IResult::Error(ErrorKind::Tag);
}
IResult::Incomplete(_) => {
break;
}
}
}
let mut sections = HashMap::<String, Section>::new();
for s in sects_partial {
match sections.entry(s.name.clone()) {
Occupied(_) => {
return IResult::Error(ErrorKind::Custom(
format!("Section '{}' already has a description", s.name),
))
}
Vacant(e) => {
e.insert(Section {
name: s.name,
description: s.description,
mappings: HashMap::new(),
})
}
};
}
for m in maps_partial {
if !sections.contains_key(&m.dest) {
return IResult::Error(ErrorKind::Custom(
format!("Destinaton '{}' for mapping ({})->{} in section '{}' does not exist",
m.dest, m.opt_name, m.dest, m.parent),
));
}
match sections.entry(m.parent.clone()) {
Vacant(_) => {
return IResult::Error(ErrorKind::Custom(
format!("Section '{}' does not exist, and a mapping ({})->{} was created for it",
m.parent, m.opt_name, m.dest),
))
}
Occupied(e) => {
e.into_mut().mappings.insert(
m.opt_name,
Mapping {
description: m.description,
to: m.dest,
},
)
}
};
}
return IResult::Done(in_mut, Tree { sections });
}
pub fn parse_dtree (input: &str) -> Result<Tree, Box<Debug>> {
match dtree_parse_impl(input.as_bytes()) {
IResult::Done(_, t) => return Result::Ok(t),
IResult::Error(e) => return Result::Err(Box::new(e)),
IResult::Incomplete(i) => return Result::Err(Box::new(i))
}
}
#[test]
fn parse_identifier_test() {
assert_eq!(identifier(b"asdf"), IResult::Done(&b""[..], "asdf"));
assert_eq!(identifier(b"asdf123"), IResult::Done(&b""[..], "asdf123"));
assert_eq!(identifier(b"asd 12f"), IResult::Done(&b" 12f"[..], "asd"));
assert_eq!(identifier(b"asd^f"), IResult::Done(&b"^f"[..], "asd"));
assert_eq!(identifier(b"a_d^f"), IResult::Done(&b"^f"[..], "a_d"));
assert_eq!(identifier("a京d^f".as_bytes()), IResult::Done(&b"^f"[..], "a京d"));
}
#[test]
fn parse_desc_test() {
assert_eq!(desc_text("💝hiboi\\\na".as_bytes()), IResult::Done(&b""[..], String::from("💝hiboi\na")));
assert_eq!(desc_text(b"hiboi\na"), IResult::Done(&b"\na"[..], String::from("hiboi")));
assert_eq!(desc_text(b"\\hiboi\\\na"), IResult::Done(&b""[..], String::from("\\hiboi\na")));
assert_eq!(desc_text(b"hello \nasdf"), IResult::Done(&b"\nasdf"[..], String::from("hello ")));
}
#[test]
fn section_test() {
assert_eq!(section_desc(b"[ a ] hello \nasdf"), IResult::Done(&b"\nasdf"[..], PartialDTree{
name: String::from("a"), description: String::from("hello ")}));
assert_eq!(section_desc(b"[a] hello\\\naaaa"), IResult::Done(&b""[..], PartialDTree{
name: String::from("a"), description: String::from("hello\naaaa")}));
}
#[test]
fn mapping_test() {
assert_eq!(mapping(b" [ a (b) -> c ] adf"), IResult::Done(&b""[..], PartialDTreeOption{
parent: String::from("a"), description: String::from("adf"),
opt_name: String::from("b"), dest: String::from("c")}));
assert_eq!(mapping(b"[ a123 (b\\)a\\d)->c] adf \\\nhello\na"),
IResult::Done(&b"\na"[..], PartialDTreeOption{parent: String::from("a123"),
description: String::from("adf \nhello"), opt_name: String::from("b)a\\d"),
dest: String::from("c")}));
}