use nom::{
IResult, Parser,
branch::alt,
bytes::complete::tag,
character::complete::{char, digit1},
combinator::{map, recognize, value},
multi::many1,
sequence::{pair, preceded},
};
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TokenIdentifier {
Header,
And,
Or,
Literal {
feature: i32,
},
True,
False,
}
pub type TId = TokenIdentifier;
use C2DToken::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum C2DToken {
Header {
nodes: usize,
edges: usize,
variables: usize,
},
And { children: Vec<usize> },
Or { children: Vec<usize>, decision: u32 },
Literal { feature: i32 },
True,
False,
}
#[inline]
pub fn lex_line_c2d(line: &str) -> IResult<&str, C2DToken> {
alt((
lex_header,
lex_true,
lex_false,
lex_and,
lex_or,
lex_positive_literal,
lex_negative_literal,
))
.parse(line)
}
fn lex_header(line: &str) -> IResult<&str, C2DToken> {
map(
preceded(tag("nnf"), parse_alt_space1_number1),
|out: &str| {
let nums: Vec<usize> = split_numbers(out);
Header {
nodes: nums[0],
edges: nums[1],
variables: nums[2],
}
},
)
.parse(line)
}
fn lex_and(line: &str) -> IResult<&str, C2DToken> {
map(
preceded(char('A'), parse_alt_space1_number1),
|out: &str| {
let mut nums: Vec<usize> = split_numbers(out);
nums.remove(0); And { children: nums }
},
)
.parse(line)
}
fn lex_or(line: &str) -> IResult<&str, C2DToken> {
map(
preceded(char('O'), parse_alt_space1_number1),
|out: &str| {
let mut nums: Vec<usize> = split_numbers(out);
let dec = nums.remove(0) as u32;
nums.remove(0); Or {
decision: dec,
children: nums,
}
},
)
.parse(line)
}
fn lex_positive_literal(line: &str) -> IResult<&str, C2DToken> {
map(preceded(tag("L "), recognize(digit1)), |s: &str| Literal {
feature: s.parse::<i32>().unwrap(),
})
.parse(line)
}
fn lex_negative_literal(line: &str) -> IResult<&str, C2DToken> {
map(
preceded(tag("L "), recognize(pair(char('-'), digit1))),
|s: &str| Literal {
feature: s.parse::<i32>().unwrap(),
},
)
.parse(line)
}
fn lex_true(line: &str) -> IResult<&str, C2DToken> {
value(True, tag("A 0")).parse(line)
}
fn lex_false(line: &str) -> IResult<&str, C2DToken> {
value(False, tag("O 0 0")).parse(line)
}
pub(super) fn split_numbers<T: std::str::FromStr>(out: &str) -> Vec<T> {
out.split_whitespace()
.map(|num: &str| {
num.parse::<T>().unwrap_or_else(|_| {
panic!(
"Was not able to parse {} for node. String was {}",
std::any::type_name::<T>(),
out
)
})
})
.collect::<Vec<T>>()
}
pub(super) fn parse_alt_space1_number1(input: &str) -> IResult<&str, &str> {
recognize(many1(pair(char(' '), digit1))).parse(input)
}
#[allow(non_snake_case)]
pub fn deconstruct_C2DToken(C2DToken: C2DToken) -> String {
match C2DToken {
Header {
nodes,
edges,
variables,
} => format!("nnf {nodes} {edges} {variables}\n"),
And { children: c } => {
let mut s = String::from("A ");
s.push_str(&c.len().to_string());
s.push(' ');
for n in 0..c.len() {
s.push_str(&c[n].to_string());
if n != c.len() - 1 {
s.push(' ');
} else {
s.push('\n');
}
}
s
}
Or {
decision,
children: c,
} => format!("O {decision} 2 {} {}\n", c[0], c[1]),
Literal { feature } => format!("L {feature}\n"),
False => String::from("O 0 0\n"),
True => String::from("A 0\n"),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn lex_lines() {
let and_str = "A 3 11 12 13";
let or_str = "O 10 2 40 44";
let positive_literal_str = "L 3";
let failed_and_str = "A THREE 11 TWELVE 13";
assert_eq!(
lex_line_c2d(and_str).unwrap().1,
And {
children: vec![11, 12, 13]
}
);
assert_eq!(
lex_line_c2d(or_str).unwrap().1,
Or {
decision: 10,
children: vec![40, 44]
}
);
assert_eq!(
lex_line_c2d(positive_literal_str).unwrap().1,
Literal { feature: 3 }
);
let result = std::panic::catch_unwind(|| lex_line_c2d(failed_and_str)).unwrap();
assert!(result.is_err());
}
#[test]
fn individual_lexer() {
let header_str = "nnf 32 13 23";
assert_eq!(
lex_header(header_str).unwrap().1,
Header {
nodes: 32,
edges: 13,
variables: 23
}
);
let and_str = "A 1 1";
assert_eq!(lex_and(and_str).unwrap().1, And { children: vec![1] });
let or_str = "O 10 2 20 24";
assert_eq!(
lex_or(or_str).unwrap().1,
Or {
decision: 10,
children: vec![20, 24]
}
);
let positive_literal_str = "L 13";
assert_eq!(
lex_positive_literal(positive_literal_str).unwrap().1,
Literal { feature: 13 }
);
let negative_literal_str = "L -113";
assert_eq!(
lex_negative_literal(negative_literal_str).unwrap().1,
Literal { feature: -113 }
);
let true_str = "A 0";
assert_eq!(lex_true(true_str).unwrap().1, True);
let false_str = "O 0 0";
assert_eq!(lex_false(false_str).unwrap().1, False);
}
#[test]
fn serialization() {
let header: C2DToken = Header {
nodes: 10,
edges: 20,
variables: 30,
};
let header_s: String = String::from("nnf 10 20 30\n");
assert_eq!(deconstruct_C2DToken(header), header_s);
let and: C2DToken = And {
children: vec![1, 2, 3, 4, 5],
};
let and_s: String = String::from("A 5 1 2 3 4 5\n");
assert_eq!(deconstruct_C2DToken(and), and_s);
let or: C2DToken = Or {
decision: 42,
children: vec![1, 2],
};
let or_s: String = String::from("O 42 2 1 2\n");
assert_eq!(deconstruct_C2DToken(or), or_s);
let p_literal: C2DToken = Literal { feature: 4269 };
let p_literal_s: String = String::from("L 4269\n");
assert_eq!(deconstruct_C2DToken(p_literal), p_literal_s);
let n_literal: C2DToken = Literal { feature: -4269 };
let n_literal_s: String = String::from("L -4269\n");
assert_eq!(deconstruct_C2DToken(n_literal), n_literal_s);
let ttrue: C2DToken = True;
let ttrue_s: String = String::from("A 0\n");
assert_eq!(deconstruct_C2DToken(ttrue), ttrue_s);
let tfalse: C2DToken = False;
let tfalse_s: String = String::from("O 0 0\n");
assert_eq!(deconstruct_C2DToken(tfalse), tfalse_s);
}
}