use crate::dot::{Attribute, IdPair};
use crate::parser::symbols;
use pom::char_class::{alphanum, digit};
use pom::parser::{is_a, one_of, sym, Parser};
use std::str::FromStr;
#[allow(clippy::absurd_extreme_comparisons)]
pub fn acceptable_char(term: char) -> bool {
let term = term as u8;
alphanum(term) || (term >= 0x80 && term <= 0xFF) || term == 0x5F
}
pub fn id_pair<'a>() -> Parser<'a, char, IdPair> {
let p = ident() - symbols::space() - sym('=') - symbols::space() + ident();
p.map(|(key, value)| Attribute { key, value })
}
fn digit_char(c: char) -> bool {
digit(c as u8)
}
pub fn number<'a>() -> Parser<'a, char, f64> {
let integer = (one_of("123456789") - one_of("0123456789").repeat(0..)) | sym('0');
let frac = sym('.') + one_of("0123456789").repeat(1..);
let number = sym('-').opt() + integer + frac.opt();
number.collect().convert(|s| {
let st: String = s.iter().collect();
f64::from_str(&st)
})
}
pub fn naked_ident<'a>() -> Parser<'a, char, String> {
let alpha_num = !is_a(digit_char)
* (is_a(acceptable_char))
.repeat(1..)
.collect()
.map(|c| c.iter().collect());
let num = number().map(|e| e.to_string());
num | alpha_num
}
pub fn quoted_ident<'a>() -> Parser<'a, char, String> {
sym('"') * naked_ident() - sym('"')
}
pub fn ident<'a>() -> Parser<'a, char, String> {
quoted_ident() | naked_ident()
}
#[cfg(test)]
mod test {
use super::*;
use assert_approx_eq::assert_approx_eq;
use pretty_assertions::assert_eq;
use pom::parser::end;
#[test]
fn ident_with_correct_input() {
let inputs = vec![
(r#"aa"#, "aa"),
(r#"ab"#, "ab"),
(r#""a""#, "a"),
(r#""a"b"#, "a"),
(r#""ab""#, "ab"),
(r#"_b"#, "_b"),
(r#"çåt"#, "çåt"),
(r#"_çåt"#, "_çåt"),
(r#"cool_çåt"#, "cool_çåt"),
];
for (input, res) in inputs {
let input: Vec<char> = input.chars().collect();
match ident().parse(&input) {
Ok(a) => assert_eq!(a, res.to_string()),
Err(e) => panic!(format!("Should have passed. Failed instead with: {:?}", e)),
};
}
}
#[test]
fn number_with_correct_input() {
let inputs: Vec<(&str, f64)> = vec![
(r#"90"#, 90.0),
(r#"0.9"#, 0.9),
(r#"0"#, 0.0),
(r#"-98.9"#, -98.9),
];
for (input, res) in inputs {
let input: Vec<char> = input.chars().collect();
match number().parse(&input) {
Ok(a) => assert_approx_eq!(a, res),
Err(e) => panic!(format!("Should have passed. Failed instead with: {:?}", e)),
};
}
}
#[test]
fn number_with_incorrect_input() {
let inputs = vec![r#"a"#, r#"a.9"#, r#"-b"#];
for input in inputs {
let i: Vec<char> = input.chars().collect();
match number().parse(&i) {
Err(_) => (),
Ok(r) => panic!(format!("Should have failed. Passed instead with: {:?}", r)),
};
}
}
#[test]
fn ident_with_incorrect_input() {
let inputs = vec![
r#""aa"#, r#" b a"#, r#"9ab"#,
];
for input in inputs {
let input: Vec<char> = input.chars().collect();
match (ident() - end()).parse(&input) {
Err(_) => (),
Ok(r) => panic!(format!("Should have failed. Passed instead with: {:?}", r)),
};
}
}
#[test]
fn id_pair_with_correct_input() {
let inputs = vec![
r#"a=b"#,
r#""a"="b"",
r#""a"=b"#,
r#"a="b""#,
];
for input in inputs {
let input: Vec<char> = input.chars().collect();
match id_pair().parse(&input) {
Ok(a) => assert_eq!(
a,
Attribute {
key: "a".to_string(),
value: "b".to_string()
}
),
Err(e) => panic!(format!("Should have passed. Failed instead with: {:?}", e)),
};
}
}
#[test]
fn id_pair_with_incorrect_input() {
let inputs = vec![r#"a:b"#, r#""c"~"d""#, r#""e"f"#, r#""g=h"#];
for input in inputs {
let input: Vec<char> = input.chars().collect();
match id_pair().parse(&input) {
Err(_) => (),
Ok(r) => panic!(format!("Should have failed. Passed instead with: {:?}", r)),
};
}
}
}