1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-19 09:29:36 +01:00
adf-obdd/lib/src/parser.rs
Stefan Ellmauthaler d7e71e5da7
milestone/nogoods (#74)
* Add NoGood and NoGoodStore (#65)
* Update Flake to nix 22.05
* Add nogood-algorithm to the ADF
* Add public api for the nogood-learner
* Add direnv to gitignore
* Avoid a Box, support custom heuristics functions
* Add ng option with heu to binary
* Introduce a new flag to handle big instances (modelcount vs adhoccount)
Note that adhoccount without modelcount will not produce correct modelcounts if memoization is used
* Add new heuristic
* Add crossbeam-channel to represent an output-stream of stable models
Uses a crossbeam-channel to fill a Queue, which can be used safely
from outside the function.
This rework is done to also allow ad-hoc output of results in a
potentially multi-threaded setup.
* Added documentation on this new feature on the module-page
* Fix broken links in rust-doc
* Update Readme for lib to reflect the new NoGood API
* Add metadata to bin/Cargo.toml, add features
* added a benchmark feature, to easily compile benchmark-releases
* Fix facet count tests
* Add multithread-safe functionality for the dictionary/ordering
* Streamline a couple of API calls
* Expose more structs and methods to the public API
* Breaking some API (though nothing which is currently used in the binary)
* Simple version of gh pages
* Added more links and information to the landing page
* Fix badges in the app-doc
* Add two valued interpretation
Parameterised the stable-nogood algorithm to allow a variable
stability check function.
* Refactor nogood-algorithm name
* Update README.md and documentation (`docu` folder)
`README.md` on the `/` level is now presenting the same information
which is provided in `docs/index.md`
* Update main
- Update main functionality
- Update naming
* Fix cli-test
* Update Version to 0.3.0
Due to braking API changes and reaching a milestone, the version is
incremented to
0.3.0 (beta)
* Update Documentation navigation (#81)
* flake.lock: Update
Flake lock file updates:
• Updated input 'flake-utils':
    'github:numtide/flake-utils/1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1' (2022-05-30)
  → 'github:numtide/flake-utils/7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249' (2022-07-04)
• Updated input 'gitignoresrc':
    'github:hercules-ci/gitignore.nix/bff2832ec341cf30acb3a4d3e2e7f1f7b590116a' (2022-03-05)
  → 'github:hercules-ci/gitignore.nix/f2ea0f8ff1bce948ccb6b893d15d5ea3efaf1364' (2022-07-21)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/8b538fcb329a7bc3d153962f17c509ee49166973' (2022-06-15)
  → 'github:NixOS/nixpkgs/e43cf1748462c81202a32b26294e9f8eefcc3462' (2022-08-01)
• Updated input 'nixpkgs-unstable':
    'github:NixOS/nixpkgs/b1957596ff1c7aa8c55c4512b7ad1c9672502e8e' (2022-06-15)
  → 'github:NixOS/nixpkgs/7b9be38c7250b22d829ab6effdee90d5e40c6e5c' (2022-07-30)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/9eea93067eff400846c36f57b7499df9ef428ba0' (2022-06-17)
  → 'github:oxalica/rust-overlay/9055cb4f33f062c0dd33aa7e3c89140da8f70057' (2022-08-02)
* Add type alias for NoGood
Add a type alias `Interpretation` for NoGood to reflect the duality
where an Interpretation might become a NoGood.
* Add documentation information about later revisions on VarContainer

Co-authored-by: Maximilian Marx <mmarx@wh2.tu-dresden.de>
2022-08-02 14:02:00 +02:00

581 lines
20 KiB
Rust

//! Parser for ADFs with all needed helper-methods.
//! It utilises the [nom-crate](https://crates.io/crates/nom).
use lexical_sort::{natural_lexical_cmp, StringSort};
use nom::{
branch::alt,
bytes::complete::{tag, take_until},
character::complete::{alphanumeric1, multispace0},
combinator::{all_consuming, value},
multi::many1,
sequence::{delimited, preceded, separated_pair, terminated},
IResult,
};
use std::collections::HashMap;
use std::{
cell::RefCell,
sync::{Arc, RwLock},
};
use crate::datatypes::adf::VarContainer;
/// A representation of a formula, still using the strings from the input.
#[derive(Clone, PartialEq, Eq)]
pub enum Formula<'a> {
/// `c(f)` in the input format.
Bot,
/// `c(v)` in the input format.
Top,
/// Some atomic variable in the input format.
Atom(&'a str),
/// Negation of a subformula.
Not(Box<Formula<'a>>),
/// Conjunction of two subformulae.
And(Box<Formula<'a>>, Box<Formula<'a>>),
/// Disjunction of two subformulae.
Or(Box<Formula<'a>>, Box<Formula<'a>>),
/// Implication of two subformulae.
Imp(Box<Formula<'a>>, Box<Formula<'a>>),
/// Exclusive-Or of two subformulae.
Xor(Box<Formula<'a>>, Box<Formula<'a>>),
/// If and only if connective between two formulae.
Iff(Box<Formula<'a>>, Box<Formula<'a>>),
}
impl Formula<'_> {
pub(crate) fn to_boolean_expr(
&self,
) -> biodivine_lib_bdd::boolean_expression::BooleanExpression {
match self {
Formula::Top => biodivine_lib_bdd::boolean_expression::BooleanExpression::Const(true),
Formula::Bot => biodivine_lib_bdd::boolean_expression::BooleanExpression::Const(false),
Formula::Atom(name) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::Variable(name.to_string())
}
Formula::Not(subformula) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::Not(Box::new(
subformula.to_boolean_expr(),
))
}
Formula::And(sub_a, sub_b) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::And(
Box::new(sub_a.to_boolean_expr()),
Box::new(sub_b.to_boolean_expr()),
)
}
Formula::Or(sub_a, sub_b) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::Or(
Box::new(sub_a.to_boolean_expr()),
Box::new(sub_b.to_boolean_expr()),
)
}
Formula::Iff(sub_a, sub_b) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::Iff(
Box::new(sub_a.to_boolean_expr()),
Box::new(sub_b.to_boolean_expr()),
)
}
Formula::Imp(sub_a, sub_b) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::Imp(
Box::new(sub_a.to_boolean_expr()),
Box::new(sub_b.to_boolean_expr()),
)
}
Formula::Xor(sub_a, sub_b) => {
biodivine_lib_bdd::boolean_expression::BooleanExpression::Xor(
Box::new(sub_a.to_boolean_expr()),
Box::new(sub_b.to_boolean_expr()),
)
}
}
}
}
impl std::fmt::Debug for Formula<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Formula::Atom(a) => {
write!(f, "{}", a)?;
}
Formula::Not(n) => {
write!(f, "not({:?})", n)?;
}
Formula::And(f1, f2) => {
write!(f, "and({:?},{:?})", f1, f2)?;
}
Formula::Or(f1, f2) => {
write!(f, "or({:?},{:?})", f1, f2)?;
}
Formula::Imp(f1, f2) => {
write!(f, "imp({:?},{:?})", f1, f2)?;
}
Formula::Xor(f1, f2) => {
write!(f, "xor({:?},{:?})", f1, f2)?;
}
Formula::Iff(f1, f2) => {
write!(f, "iff({:?},{:?})", f1, f2)?;
}
Formula::Bot => {
write!(f, "Const(B)")?;
}
Formula::Top => {
write!(f, "Const(T)")?;
}
}
write!(f, "")
}
}
/// A parse structure to hold all the information given by the input file in one place.
///
/// Due to an internal representation with [RefCell][std::cell::RefCell] and [Rc][std::rc::Rc] the values can be
/// handed over to other structures without further memory needs.
///
/// Note that the parser can be utilised by an [ADF][`crate::adf::Adf`] to initialise it with minimal overhead.
#[derive(Debug)]
pub struct AdfParser<'a> {
namelist: Arc<RwLock<Vec<String>>>,
dict: Arc<RwLock<HashMap<String, usize>>>,
formulae: RefCell<Vec<Formula<'a>>>,
formulaname: RefCell<Vec<String>>,
}
impl Default for AdfParser<'_> {
fn default() -> Self {
AdfParser {
namelist: Arc::new(RwLock::new(Vec::new())),
dict: Arc::new(RwLock::new(HashMap::new())),
formulae: RefCell::new(Vec::new()),
formulaname: RefCell::new(Vec::new()),
}
}
}
impl<'a, 'b> AdfParser<'b>
where
'a: 'b,
{
#[allow(dead_code)]
fn parse_statements(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
move |input| {
let (rem, _) = many1(self.parse_statement())(input)?;
Ok((rem, ()))
}
}
/// Parses a full input file and creates internal structures.
/// Note that this method returns a closure (see the following example for the correct usage).
/// # Example
/// ```
/// let parser = adf_bdd::parser::AdfParser::default();
/// parser.parse()("s(a).ac(a,c(v)).s(b).ac(b,a).s(c).ac(c,neg(b)).");
/// let adf = adf_bdd::adf::Adf::from_parser(&parser);
/// ```
pub fn parse(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
log::info!("[Start] parsing");
|input| {
value(
(),
all_consuming(many1(alt((self.parse_statement(), self.parse_ac())))),
)(input)
}
}
fn parse_statement(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
|input| {
let mut dict = self
.dict
.write()
.expect("RwLock of dict could not get write access");
let mut namelist = self
.namelist
.write()
.expect("RwLock of namelist could not get write access");
let (remain, statement) =
terminated(AdfParser::statement, terminated(tag("."), multispace0))(input)?;
if !dict.contains_key(statement) {
let pos = namelist.len();
namelist.push(String::from(statement));
dict.insert(namelist[pos].clone(), pos);
}
Ok((remain, ()))
}
}
fn parse_ac(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
|input| {
let (remain, (name, formula)) =
terminated(AdfParser::ac, terminated(tag("."), multispace0))(input)?;
self.formulae.borrow_mut().push(formula);
self.formulaname.borrow_mut().push(String::from(name));
Ok((remain, ()))
}
}
}
impl<'a> AdfParser<'a> {
/// Creates a new parser, utilising the already existing [VarContainer]
pub fn with_var_container(var_container: VarContainer) -> AdfParser<'a> {
AdfParser {
namelist: var_container.names(),
dict: var_container.mappings(),
formulae: RefCell::new(Vec::new()),
formulaname: RefCell::new(Vec::new()),
}
}
}
impl AdfParser<'_> {
/// after an update to the namelist, all indizes are updated
fn regenerate_indizes(&self) {
self.namelist
.read()
.expect("ReadLock on namelist failed")
.iter()
.enumerate()
.for_each(|(i, elem)| {
self.dict
.write()
.expect("WriteLock on dict failed")
.insert(elem.clone(), i);
});
}
/// Sort the variables in lexicographical order.
/// Results, which got used before might become corrupted.
/// Ensure that all used data is physically copied.
pub fn varsort_lexi(&self) -> &Self {
self.namelist
.write()
.expect("WriteLock on namelist failed")
.sort_unstable();
self.regenerate_indizes();
self
}
/// Sort the variables in alphanumerical order.
/// Results, which got used before might become corrupted.
/// Ensure that all used data is physically copied.
pub fn varsort_alphanum(&self) -> &Self {
self.namelist
.write()
.expect("WriteLock on namelist failed")
.string_sort_unstable(natural_lexical_cmp);
self.regenerate_indizes();
self
}
fn statement(input: &str) -> IResult<&str, &str> {
preceded(tag("s"), delimited(tag("("), AdfParser::atomic, tag(")")))(input)
}
fn ac(input: &str) -> IResult<&str, (&str, Formula)> {
preceded(
tag("ac"),
delimited(
tag("("),
separated_pair(
AdfParser::atomic,
delimited(multispace0, tag(","), multispace0),
AdfParser::formula,
),
tag(")"),
),
)(input)
}
fn atomic_term(input: &str) -> IResult<&str, Formula> {
AdfParser::atomic(input).map(|(input, result)| (input, Formula::Atom(result)))
}
fn formula(input: &str) -> IResult<&str, Formula> {
alt((
AdfParser::constant,
AdfParser::binary_op,
AdfParser::unary_op,
AdfParser::atomic_term,
))(input)
}
fn unary_op(input: &str) -> IResult<&str, Formula> {
preceded(
tag("neg"),
delimited(tag("("), AdfParser::formula, tag(")")),
)(input)
.map(|(input, result)| (input, Formula::Not(Box::new(result))))
}
fn constant(input: &str) -> IResult<&str, Formula> {
alt((
preceded(tag("c"), delimited(tag("("), tag("v"), tag(")"))),
preceded(tag("c"), delimited(tag("("), tag("f"), tag(")"))),
))(input)
.map(|(input, result)| {
(
input,
match result {
"v" => Formula::Top,
"f" => Formula::Bot,
_ => unreachable!(),
},
)
})
}
fn formula_pair(input: &str) -> IResult<&str, (Formula, Formula)> {
separated_pair(
preceded(tag("("), AdfParser::formula),
delimited(multispace0, tag(","), multispace0),
terminated(AdfParser::formula, tag(")")),
)(input)
}
fn and(input: &str) -> IResult<&str, Formula> {
preceded(tag("and"), AdfParser::formula_pair)(input)
.map(|(input, (f1, f2))| (input, Formula::And(Box::new(f1), Box::new(f2))))
}
fn or(input: &str) -> IResult<&str, Formula> {
preceded(tag("or"), AdfParser::formula_pair)(input)
.map(|(input, (f1, f2))| (input, Formula::Or(Box::new(f1), Box::new(f2))))
}
fn imp(input: &str) -> IResult<&str, Formula> {
preceded(tag("imp"), AdfParser::formula_pair)(input)
.map(|(input, (f1, f2))| (input, Formula::Imp(Box::new(f1), Box::new(f2))))
}
fn xor(input: &str) -> IResult<&str, Formula> {
preceded(tag("xor"), AdfParser::formula_pair)(input)
.map(|(input, (f1, f2))| (input, Formula::Xor(Box::new(f1), Box::new(f2))))
}
fn iff(input: &str) -> IResult<&str, Formula> {
preceded(tag("iff"), AdfParser::formula_pair)(input)
.map(|(input, (f1, f2))| (input, Formula::Iff(Box::new(f1), Box::new(f2))))
}
fn binary_op(input: &str) -> IResult<&str, Formula> {
alt((
AdfParser::and,
AdfParser::or,
AdfParser::imp,
AdfParser::xor,
AdfParser::iff,
))(input)
}
fn atomic(input: &str) -> IResult<&str, &str> {
alt((
delimited(tag("\""), take_until("\""), tag("\"")),
alphanumeric1,
))(input)
}
/// Allows insight of the number of parsed statements.
pub fn dict_size(&self) -> usize {
//self.dict.borrow().len()
self.dict.read().expect("ReadLock on dict failed").len()
}
/// Returns the number-representation and position of a given statement in string-representation.
///
/// Will return [None] if the string does no occur in the dictionary.
pub fn dict_value(&self, value: &str) -> Option<usize> {
self.dict
.read()
.expect("ReadLock on dict failed")
.get(value)
.copied()
}
/// Returns the acceptance condition of a statement at the given position.
///
/// Will return [None] if the position does not map to a formula.
pub fn ac_at(&self, idx: usize) -> Option<Formula> {
self.formulae.borrow().get(idx).cloned()
}
pub(crate) fn dict(&self) -> Arc<RwLock<HashMap<String, usize>>> {
Arc::clone(&self.dict)
}
pub(crate) fn namelist(&self) -> Arc<RwLock<Vec<String>>> {
Arc::clone(&self.namelist)
}
/// Returns a [`VarContainer`][crate::datatypes::adf::VarContainer] which allows to access the variable information gathered by the parser
pub fn var_container(&self) -> VarContainer {
VarContainer::from_parser(self.namelist(), self.dict())
}
pub(crate) fn formula_count(&self) -> usize {
self.formulae.borrow().len()
}
pub(crate) fn formula_order(&self) -> Vec<usize> {
self.formulaname
.borrow()
.iter()
.map(|name| {
*self
.dict
.read()
.expect("ReadLock on dict failed")
.get(name)
.expect("Dictionary should contain all the used formulanames")
})
.collect()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn atomic_parse() {
assert_eq!(
AdfParser::atomic("\" 123 21 ())) ((( {}||| asfjklj fsajfj039409u902 jfi a \""),
Ok((
"",
" 123 21 ())) ((( {}||| asfjklj fsajfj039409u902 jfi a "
))
);
assert_eq!(AdfParser::atomic("foo"), Ok(("", "foo")));
assert_eq!(AdfParser::atomic("foo()"), Ok(("()", "foo")));
assert_eq!(
AdfParser::atomic("()foo"),
Err(nom::Err::Error(nom::error::Error::new(
"()foo",
nom::error::ErrorKind::AlphaNumeric
)))
);
assert!(AdfParser::atomic(" adf").is_err());
}
#[test]
fn statement_parse() {
assert_eq!(AdfParser::statement("s(ab)"), Ok(("", "ab")));
assert_eq!(AdfParser::statement("s(\"a b\")"), Ok(("", "a b")));
assert!(AdfParser::statement("s(a_b)").is_err());
}
#[test]
fn parse_statement() {
let parser: AdfParser = AdfParser::default();
let input = "s(a). s(b). s(c).s(d).s(b).s(c).";
// many1(parser.parse_statement())(input).unwrap();
let (_remain, _) = parser.parse_statement()(input).unwrap();
assert_eq!(parser.dict_size(), 1);
assert_eq!(parser.dict_value("c"), None);
let (_remain, _) = parser.parse_statements()(input).unwrap();
assert_eq!(parser.dict_size(), 4);
assert_eq!(parser.dict_value("c"), Some(2usize));
}
#[test]
fn parse_formula() {
let input = "and(or(neg(a),iff(\" iff left \",b)),xor(imp(c,d),e))";
let (_remain, result) = AdfParser::formula(input).unwrap();
assert_eq!(
format!("{:?}", result),
"and(or(not(a),iff( iff left ,b)),xor(imp(c,d),e))"
);
assert_eq!(
AdfParser::formula("and(c(v),c(f))").unwrap(),
(
"",
Formula::And(Box::new(Formula::Top), Box::new(Formula::Bot))
)
);
}
#[test]
fn parse() {
let parser = AdfParser::default();
let input = "s(a).s(c).ac(a,b).ac(b,neg(a)).s(b).ac(c,and(c(v),or(c(f),a))).";
let (remain, _) = parser.parse()(input).unwrap();
assert_eq!(remain, "");
assert_eq!(parser.dict_size(), 3);
assert_eq!(parser.dict_value("b"), Some(2usize));
assert_eq!(
format!("{:?}", parser.ac_at(1).unwrap()),
format!("{:?}", Formula::Not(Box::new(Formula::Atom("a"))))
);
assert_eq!(parser.formula_count(), 3);
assert_eq!(parser.formula_order(), vec![0, 2, 1]);
}
#[test]
fn non_consuming_parse() {
let parser = AdfParser::default();
let input = "s(a).s(c).ac(a,b).ac(b,neg(a)).s(b).ac(c,and(c(v),or(c(f),a))). wee";
let x = parser.parse()(input);
assert!(x.is_err());
assert_eq!(
x.err().unwrap(),
nom::Err::Error(nom::error::Error::new("wee", nom::error::ErrorKind::Eof))
);
}
#[test]
fn constant() {
assert_eq!(AdfParser::constant("c(v)").unwrap().1, Formula::Top);
assert_eq!(AdfParser::constant("c(f)").unwrap().1, Formula::Bot);
assert_eq!(format!("{:?}", Formula::Top), "Const(T)");
assert_eq!(format!("{:?}", Formula::Bot), "Const(B)");
}
#[test]
fn sort_updates() {
let parser = AdfParser::default();
let input = "s(a).s(c).ac(a,b).ac(b,neg(a)).s(b).ac(c,and(c(v),or(c(f),a))).";
parser.parse()(input).unwrap();
assert_eq!(parser.dict_value("a"), Some(0));
assert_eq!(parser.dict_value("b"), Some(2));
assert_eq!(parser.dict_value("c"), Some(1));
parser.varsort_lexi();
assert_eq!(parser.dict_value("a"), Some(0));
assert_eq!(parser.dict_value("b"), Some(1));
assert_eq!(parser.dict_value("c"), Some(2));
let parser = AdfParser::default();
let input = "s(a2).s(0).s(1).s(2).s(10).s(11).s(20).ac(0,c(v)).ac(1,c(v)).ac(2,c(v)).ac(10,c(v)).ac(20,c(v)).ac(11,c(v)).ac(a2,c(f)).";
parser.parse()(input).unwrap();
assert_eq!(parser.dict_value("a2"), Some(0));
assert_eq!(parser.dict_value("0"), Some(1));
assert_eq!(parser.dict_value("1"), Some(2));
assert_eq!(parser.dict_value("2"), Some(3));
assert_eq!(parser.dict_value("10"), Some(4));
assert_eq!(parser.dict_value("11"), Some(5));
assert_eq!(parser.dict_value("20"), Some(6));
parser.varsort_lexi();
assert_eq!(parser.dict_value("0"), Some(0));
assert_eq!(parser.dict_value("1"), Some(1));
assert_eq!(parser.dict_value("2"), Some(4));
assert_eq!(parser.dict_value("10"), Some(2));
assert_eq!(parser.dict_value("11"), Some(3));
assert_eq!(parser.dict_value("20"), Some(5));
assert_eq!(parser.dict_value("a2"), Some(6));
parser.varsort_alphanum();
assert_eq!(parser.dict_value("0"), Some(0));
assert_eq!(parser.dict_value("1"), Some(1));
assert_eq!(parser.dict_value("2"), Some(2));
assert_eq!(parser.dict_value("10"), Some(3));
assert_eq!(parser.dict_value("11"), Some(4));
assert_eq!(parser.dict_value("20"), Some(5));
assert_eq!(parser.dict_value("a2"), Some(6));
}
}