diff --git a/Cargo.lock b/Cargo.lock index 2a9fe8c6..1ca281ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,7 +245,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -951,6 +951,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1253,7 +1262,7 @@ dependencies = [ "cpu-time", "flate2", "itertools 0.14.0", - "nom", + "nom 8.0.0", "rand", "rustc-hash", "rustsat-minisat", @@ -1388,7 +1397,7 @@ dependencies = [ "clap", "concolor-clap", "itertools 0.14.0", - "nom", + "nom 8.0.0", "rand", "rand_chacha", "rustsat", diff --git a/Cargo.toml b/Cargo.toml index 34cd7876..0d8c1fba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ flate2 = { version = "1.0.35", features = [ git2 = "0.20.0" glob = "0.3.2" itertools = "0.14.0" -nom = "7.1.3" +nom = "8.0.0" termcolor = "1.4.1" thiserror = "2.0.11" rand = "0.9.0" diff --git a/src/instances/fio/dimacs.rs b/src/instances/fio/dimacs.rs index 3fa90cdd..302adf8e 100644 --- a/src/instances/fio/dimacs.rs +++ b/src/instances/fio/dimacs.rs @@ -22,8 +22,8 @@ use nom::{ combinator::{all_consuming, map, map_res, recognize, success}, error::{context, Error as NomError}, multi::separated_list0, - sequence::{pair, terminated, tuple}, - IResult, + sequence::terminated, + IResult, Parser, }; use std::{ convert::TryFrom, @@ -294,22 +294,24 @@ where fn parse_p_line(input: &str) -> IResult<&str, Preamble> { let (input, _) = context( "p line does not start with 'p'", - terminated::<_, _, _, NomError<_>, _, _>(tag("p"), multispace1), - )(input)?; + terminated::<_, _, NomError<_>, _, _>(tag("p"), multispace1), + ) + .parse(input)?; let (input, id_token) = context( "invalid id token in p line", alt(( - terminated::<_, _, _, NomError<_>, _, _>(tag("cnf"), multispace1), + terminated::<_, _, NomError<_>, _, _>(tag("cnf"), multispace1), #[cfg(feature = "optimization")] terminated(tag("wcnf"), multispace1), )), - )(input)?; + ) + .parse(input)?; //.with_context(|| format!("invalid id token in '{}'", input))?; if id_token == "cnf" { // Is CNF file let (input, (n_vars, _, n_clauses)) = context( "failed to parse number of variables and clauses", - tuple::<_, _, NomError<_>, _>(( + ( context( "number of vars does not fit usize", map_res(u64, usize::try_from), @@ -319,8 +321,9 @@ fn parse_p_line(input: &str) -> IResult<&str, Preamble> { "number of clauses does not fit usize", map_res(u64, usize::try_from), ), - )), - )(input)?; + ), + ) + .parse(input)?; return Ok((input, Preamble::Cnf { n_vars, n_clauses })); } #[cfg(feature = "optimization")] @@ -328,7 +331,7 @@ fn parse_p_line(input: &str) -> IResult<&str, Preamble> { // Is WCNF file let (input, (n_vars, _, n_clauses, _, top)) = context( "failed to parse number of variables, clauses, and top", - tuple::<_, _, NomError<_>, _>(( + ( context( "number of vars does not fit usize", map_res(u64, usize::try_from), @@ -340,8 +343,9 @@ fn parse_p_line(input: &str) -> IResult<&str, Preamble> { ), multispace1, context("top does not fit usize", map_res(u64, usize::try_from)), - )), - )(input)?; + ), + ) + .parse(input)?; return Ok(( input, Preamble::WcnfPre22 { @@ -383,7 +387,7 @@ fn parse_wcnf_pre22_line(input: &str) -> IResult<&str, Option<(usize, Clause)>> } else { // Line is not a comment let (input, (weight, clause)) = - separated_pair(parse_weight, multispace1, parse_clause)(input)?; + separated_pair(parse_weight, multispace1, parse_clause).parse(input)?; Ok((input, Some((weight, clause)))) } } @@ -406,7 +410,7 @@ fn parse_mcnf_line(input: &str) -> IResult<&str, McnfDataLine> { Err(_) => // Line is not a comment { - match terminated(tag::<_, _, NomError<_>>("h"), multispace1)(input) { + match terminated(tag::<_, _, NomError<_>>("h"), multispace1).parse(input) { Ok((input, _)) => { // Hard clause let (input, clause) = parse_clause(input)?; @@ -416,19 +420,19 @@ fn parse_mcnf_line(input: &str) -> IResult<&str, McnfDataLine> { // Soft clause if let Ok((input, _)) = tag::<_, _, NomError<_>>("o")(input) { // MCNF soft (explicit obj index) - let (input, (idx, _, weight, _, clause)) = - tuple(( - parse_idx, - multispace1, - parse_weight, - multispace1, - parse_clause, - ))(input)?; + let (input, (idx, _, weight, _, clause)) = ( + parse_idx, + multispace1, + parse_weight, + multispace1, + parse_clause, + ) + .parse(input)?; Ok((input, Some((Some((idx, weight)), clause)))) } else { // WCNF soft (implicit obj index of 1) let (input, (weight, clause)) = - separated_pair(parse_weight, multispace1, parse_clause)(input)?; + separated_pair(parse_weight, multispace1, parse_clause).parse(input)?; Ok((input, Some((Some((1, weight)), clause)))) } } @@ -445,7 +449,8 @@ fn parse_clause(input: &str) -> IResult<&str, Clause> { terminated(separated_list0(multispace1, parse_lit), parse_clause_ending), Clause::from_iter, ), - )(input) + ) + .parse(input) } #[cfg(feature = "optimization")] @@ -457,7 +462,8 @@ fn parse_weight(input: &str) -> IResult<&str, usize> { context("expected number for weight", u64), TryInto::try_into, ), - )(input) + ) + .parse(input) } #[cfg(feature = "optimization")] @@ -471,12 +477,13 @@ fn parse_idx(input: &str) -> IResult<&str, usize> { } w.try_into().map_err(|_| ()) }), - )(input) + ) + .parse(input) } /// Nom-like parser for literal fn parse_lit(input: &str) -> IResult<&str, Lit> { - context("invalid ipasir literal", map_res(i32, Lit::from_ipasir))(input) + context("invalid ipasir literal", map_res(i32, Lit::from_ipasir)).parse(input) } /// Parses the end of a clause @@ -484,16 +491,17 @@ fn parse_lit(input: &str) -> IResult<&str, Lit> { /// white-space or only a line-break are treated as valid clause endings. /// This is more lean than the file format spec. fn parse_clause_ending(input: &str) -> IResult<&str, &str> { - recognize(pair( + recognize(( multispace0, alt(( - recognize(all_consuming(success(""))), - recognize(all_consuming(tag("0"))), - recognize(terminated(tag("0"), line_ending)), - recognize(terminated(tag("0"), multispace1)), - recognize(line_ending), + all_consuming(success("")), + all_consuming(tag("0")), + terminated(tag("0"), line_ending), + terminated(tag("0"), multispace1), + line_ending, )), - ))(input) + )) + .parse(input) } /// Writes a CNF to a DIMACS CNF file diff --git a/src/instances/fio/opb.rs b/src/instances/fio/opb.rs index 43b1862f..15beaaae 100644 --- a/src/instances/fio/opb.rs +++ b/src/instances/fio/opb.rs @@ -23,8 +23,7 @@ use nom::{ combinator::{cut, eof, map, map_res, recognize}, error::Error as NomError, multi::{many0, many1, many_till}, - sequence::{pair, tuple}, - IResult, + IResult, Parser, }; use std::{ io::{self, BufRead, Write}, @@ -179,7 +178,8 @@ fn parse_opb_data(reader: &mut R, opts: Options) -> anyhow::Result 0 { - let (rem, new_data) = many0(|i| opb_data(i, opts))(&buf) + let (rem, new_data) = many0(|i| opb_data(i, opts)) + .parse(&buf) .map_err(nom::Err::>::to_owned) .with_context(|| format!("failed to parse opb line '{buf}'"))?; data.extend(new_data); @@ -196,13 +196,14 @@ fn parse_opb_data(reader: &mut R, opts: Options) -> anyhow::Result IResult<&str, &str> { - recognize(pair( + recognize(( tag("*"), alt(( recognize(many_till(anychar, line_ending)), recognize(many0(anychar)), )), - ))(input) + )) + .parse(input) } /// Parses an OPB variable @@ -211,22 +212,23 @@ fn variable(input: &str, opts: Options) -> IResult<&str, Var> { map_res(u64, |idx| { let idx = (TryInto::::try_into(idx)?) - opts.first_var_idx; Ok::(Var::new(idx)) - })(input) + }) + .parse(input) } /// Parses a literal. The spec for linear OPB instances only allows for /// variables but we allow negated literals with `~` as in non-linear OPB /// instances. pub(crate) fn literal(input: &str, opts: Options) -> IResult<&str, Lit> { - match alt::<_, _, NomError<_>, _>((tag("~"), tag("-")))(input) { - Ok((input, _)) => map_res(|i| variable(i, opts), |v| Ok::<_, ()>(v.neg_lit()))(input), - Err(_) => map_res(|i| variable(i, opts), |v| Ok::<_, ()>(v.pos_lit()))(input), + match alt((tag::<_, _, NomError<&str>>("~"), tag("-"))).parse(input) { + Ok((input, _)) => map_res(|i| variable(i, opts), |v| Ok::<_, ()>(v.neg_lit())).parse(input), + Err(_) => map_res(|i| variable(i, opts), |v| Ok::<_, ()>(v.pos_lit())).parse(input), } } /// Parses an OPB relational operator. We admit more operators than the spec. fn operator(input: &str) -> IResult<&str, OpbOperator> { - let (input, op_str) = alt((tag("<="), tag(">="), tag("<"), tag(">"), tag("=")))(input)?; + let (input, op_str) = alt((tag("<="), tag(">="), tag("<"), tag(">"), tag("="))).parse(input)?; Ok(( input, if op_str == "<=" { @@ -245,37 +247,38 @@ fn operator(input: &str) -> IResult<&str, OpbOperator> { /// Parses an OPB weight fn weight(input: &str) -> IResult<&str, isize> { - map_res(i64, TryInto::try_into)(input) + map_res(i64, TryInto::try_into).parse(input) } /// Parses an OPB weighted term fn weighted_literal(input: &str, opts: Options) -> IResult<&str, (Lit, isize)> { map( - tuple((weight, cut(space1), cut(|i| literal(i, opts)), space0)), + (weight, cut(space1), cut(|i| literal(i, opts)), space0), |(w, _, l, _)| (l, w), - )(input) + ) + .parse(input) } /// Parses an OPB sum fn weighted_lit_sum(input: &str, opts: Options) -> IResult<&str, Vec<(Lit, isize)>> { - many1(|i| weighted_literal(i, opts))(input) + many1(|i| weighted_literal(i, opts)).parse(input) } #[cfg(feature = "optimization")] /// Parses a (potentially empty) OPB sum fn weighted_lit_sum0(input: &str, opts: Options) -> IResult<&str, Vec<(Lit, isize)>> { - many0(|i| weighted_literal(i, opts))(input) + many0(|i| weighted_literal(i, opts)).parse(input) } /// Leniently parses OPB constraint or objective ending as ';' or a line ending fn opb_ending(input: &str) -> IResult<&str, &str> { // TODO: potentially simplify with `cut`? - recognize(pair( + recognize(( space0, alt(( - recognize(pair( + recognize(( alt(( - recognize(tuple((tag(";"), space0, line_ending))), + recognize((tag(";"), space0, line_ending)), line_ending, tag(";"), )), @@ -283,19 +286,20 @@ fn opb_ending(input: &str) -> IResult<&str, &str> { )), eof, )), - ))(input) + )) + .parse(input) } /// Parses an OPB constraint fn constraint(input: &str, opts: Options) -> IResult<&str, PbConstraint> { map_res( - tuple(( + ( |i| weighted_lit_sum(i, opts), cut(operator), space0, cut(weight), cut(opb_ending), - )), + ), |(wls, op, _, b, _)| { let lits = wls.into_iter(); Ok::<_, ()>(match op { @@ -306,37 +310,40 @@ fn constraint(input: &str, opts: Options) -> IResult<&str, PbConstraint> { OpbOperator::EQ => PbConstraint::new_eq(lits, b), }) }, - )(input) + ) + .parse(input) } #[cfg(feature = "optimization")] /// Parses an OPB objective fn objective(input: &str, opts: Options) -> IResult<&str, Objective> { map_res( - tuple(( + ( tag("min:"), space0, |i| weighted_lit_sum0(i, opts), cut(opb_ending), - )), + ), |(_, _, wsl, _)| { let mut obj = Objective::new(); wsl.into_iter() .for_each(|(l, w)| obj.increase_soft_lit_int(w, l)); Ok::<_, ()>(obj) }, - )(input) + ) + .parse(input) } #[cfg(not(feature = "optimization"))] /// Matches an OPB objective fn objective(input: &str, opts: Options) -> IResult<&str, &str> { - recognize(tuple(( + recognize(( tag("min:"), space0, |i| weighted_lit_sum(i, opts), opb_ending, - )))(input) + )) + .parse(input) } /// Top level string parser applied to lines @@ -353,7 +360,8 @@ fn opb_data(input: &str, opts: Options) -> IResult<&str, OpbData> { |i| objective(i, opts), |obj| OpbData::Obj(String::from(obj)), ), - ))(input) + )) + .parse(input) } /// Possible lines that can be written to OPB diff --git a/tools/src/encodings/assignment.rs b/tools/src/encodings/assignment.rs index 5f83dc28..cfbdbf36 100644 --- a/tools/src/encodings/assignment.rs +++ b/tools/src/encodings/assignment.rs @@ -47,7 +47,7 @@ mod parsing { use std::io; use anyhow::Context; - use nom::character::complete::u32; + use nom::{character::complete::u32, Parser}; use crate::parsing::{callback_list, single_value}; @@ -69,12 +69,14 @@ mod parsing { pub fn parse_moolib(mut reader: impl io::BufRead) -> anyhow::Result { let line = next_non_comment_line!(reader) .context("file ended before number of objectives line")?; - let (_, n_objs) = single_value(u32, "#")(&line) + let (_, n_objs) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of objectives line '{line}'"))?; let line = next_non_comment_line!(reader).context("file ended before number of tasks line")?; - let (_, n_tasks) = single_value(u32, "#")(&line) + let (_, n_tasks) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of tasks line '{line}'"))?; let mut inst = super::Assignment::empty(n_tasks as usize, n_objs as usize); diff --git a/tools/src/encodings/cnf/clustering.rs b/tools/src/encodings/cnf/clustering.rs index 5a007435..157a4891 100644 --- a/tools/src/encodings/cnf/clustering.rs +++ b/tools/src/encodings/cnf/clustering.rs @@ -17,7 +17,8 @@ use nom::{ combinator::{map, recognize}, multi::many1, number::complete::double, - sequence::{terminated, tuple}, + sequence::terminated, + Parser, }; use rustsat::{ clause, @@ -175,12 +176,13 @@ impl Encoding { if line.starts_with('%') { return None; } - let (_, tup) = tuple(( + let (_, tup) = ( terminated(ident, multispace1), terminated(ident, multispace1), double, - ))(line) - .ok()?; + ) + .parse(line) + .ok()?; if !ident_map.contains_key(&tup.0) { ident_map.insert(tup.0.clone(), next_idx); next_idx += 1; @@ -427,7 +429,8 @@ fn ident(input: &str) -> nom::IResult<&str, String> { map( recognize(many1(alt((alphanumeric1, recognize(char('_')))))), String::from, - )(input) + ) + .parse(input) } pub fn scaling_map(sim: f64, multiplier: u32) -> isize { diff --git a/tools/src/encodings/facilitylocation.rs b/tools/src/encodings/facilitylocation.rs index 0bf32abc..ed667230 100644 --- a/tools/src/encodings/facilitylocation.rs +++ b/tools/src/encodings/facilitylocation.rs @@ -58,7 +58,7 @@ mod parsing { use std::io; use anyhow::Context; - use nom::character::complete::u32; + use nom::{character::complete::u32, Parser}; use crate::parsing::{callback_separated, single_value}; @@ -75,12 +75,14 @@ mod parsing { pub fn parse_voptlib(mut reader: impl io::BufRead) -> anyhow::Result { let line = next_line!(reader).context("file ended before number of users line")?; - let (_, n_users) = single_value(u32, "#")(&line) + let (_, n_users) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of users line '{line}'"))?; let n_users = usize::try_from(n_users).context("u32 does not fit in usize")?; let line = next_line!(reader).context("file ended before number of services line")?; - let (_, n_services) = single_value(u32, "#")(&line) + let (_, n_services) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of services line '{line}'"))?; let n_services = usize::try_from(n_services).context("u32 does not fit in usize")?; diff --git a/tools/src/encodings/knapsack.rs b/tools/src/encodings/knapsack.rs index 73ccf879..b47b86ed 100644 --- a/tools/src/encodings/knapsack.rs +++ b/tools/src/encodings/knapsack.rs @@ -83,7 +83,7 @@ mod parsing { bytes::complete::tag, character::complete::{space0, u32}, error::Error as NomErr, - sequence::tuple, + Parser, }; use crate::parsing::{callback_list, single_value}; @@ -106,16 +106,19 @@ mod parsing { pub fn parse_moolib(mut reader: impl io::BufRead) -> anyhow::Result { let line = next_non_comment_line!(reader) .context("file ended before number of objectives line")?; - let (_, n_obj) = single_value(u32, "#")(&line) + let (_, n_obj) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of objectives line '{line}'"))?; let line = next_non_comment_line!(reader).context("file ended before number of items line")?; - let (_, n_items) = single_value(u32, "#")(&line) + let (_, n_items) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of items line '{line}'"))?; let line = next_non_comment_line!(reader).context("file ended before capacity line")?; - let (_, capacity) = single_value(u32, "#")(&line) + let (_, capacity) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse capacity line '{line}'"))?; let mut inst = super::Knapsack { @@ -151,10 +154,11 @@ mod parsing { item_idx += 1; Ok(()) })?; - match tuple::<_, _, NomErr<_>, _>((space0, tag(","), space0))(remain) { + match (space0::<_, NomErr<_>>, tag(","), space0).parse(remain) { Ok(_) => (), Err(_) => { - tuple::<_, _, NomErr<_>, _>((space0, tag("]")))(remain) + (space0::<_, NomErr<_>>, tag("]")) + .parse(remain) .map_err(|e| e.to_owned()) .context("failed to find closing delimiter for value list")?; ended = true; @@ -187,15 +191,18 @@ mod parsing { pub fn parse_voptlib(mut reader: impl io::BufRead) -> anyhow::Result { let line = next_non_comment_line!(reader).context("file ended before number of items line")?; - let (_, n_items) = single_value(u32, "#")(&line) + let (_, n_items) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of items line '{line}'"))?; let line = next_non_comment_line!(reader) .context("file ended before number of objectives line")?; - let (_, n_obj) = single_value(u32, "#")(&line) + let (_, n_obj) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of objectives line '{line}'"))?; - let _ = single_value(u32, "#")(&line) + let _ = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse number of constraints line '{line}'"))?; let mut inst = super::Knapsack { @@ -213,7 +220,8 @@ mod parsing { let line = next_non_comment_line!(reader).with_context(|| { format!("file ended before {item_idx} value of objective {obj_idx}") })?; - let (_, value) = single_value(u32, "#")(&line) + let (_, value) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| { format!("failed to parse {item_idx} value of objective {obj_idx} '{line}'") @@ -224,7 +232,8 @@ mod parsing { for item_idx in 0..n_items { let line = next_non_comment_line!(reader) .with_context(|| format!("file ended before weight of item {item_idx}"))?; - let (_, weight) = single_value(u32, "#")(&line) + let (_, weight) = single_value(u32, "#") + .parse(&line) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse weight of item {item_idx} '{line}'"))?; inst.items[item_idx as usize].weight = weight as usize; diff --git a/tools/src/parsing.rs b/tools/src/parsing.rs index 62063693..a25efbdd 100644 --- a/tools/src/parsing.rs +++ b/tools/src/parsing.rs @@ -7,20 +7,20 @@ use nom::{ character::complete::{line_ending, multispace1, not_line_ending, space0}, combinator::{map, recognize}, error::{context, Error as NomErr}, - sequence::{pair, tuple}, + Parser, }; pub fn single_value<'input, T, P>( parser: P, comment_tag: &'static str, -) -> impl FnMut(&'input str) -> nom::IResult<&'input str, T> +) -> impl Parser<&'input str, Output = T, Error = NomErr<&'input str>> where - P: Fn(&'input str) -> nom::IResult<&'input str, T>, + P: nom::Parser<&'input str, Output = T, Error = NomErr<&'input str>>, { context( "expected a single u32 number", map( - tuple((space0, parser, space0, comment_or_end(comment_tag))), + (space0, parser, space0, comment_or_end(comment_tag)), |tup| tup.1, ), ) @@ -28,10 +28,10 @@ where pub fn comment_or_end<'input>( comment_tag: &'static str, -) -> impl FnMut(&'input str) -> nom::IResult<&'input str, &'input str> { - recognize(pair( +) -> impl nom::Parser<&'input str, Output = &'input str, Error = NomErr<&'input str>> { + recognize(( alt(( - recognize(tuple((tag(comment_tag), not_line_ending))), + recognize((tag::<_, _, NomErr<_>>(comment_tag), not_line_ending)), not_line_ending, )), line_ending, @@ -46,7 +46,8 @@ pub fn callback_list<'input, T, P>( where P: FnMut(&'input str) -> nom::IResult<&'input str, T>, { - let (mut buf, _) = tuple::<_, _, NomErr<_>, _>((tag("["), space0))(input) + let (mut buf, _) = (tag::<_, _, NomErr<_>>("["), space0) + .parse(input) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse list start '{input}'"))?; loop { @@ -58,10 +59,11 @@ where !remain.trim().is_empty(), "line ended before list was closed" ); - buf = match tuple::<_, _, NomErr<_>, _>((space0, tag(","), space0))(remain) { + buf = match (space0::<_, NomErr<_>>, tag(","), space0).parse(remain) { Ok((remain, _)) => remain, Err(_) => { - let (remain, _) = tuple::<_, _, NomErr<_>, _>((space0, tag("]")))(remain) + let (remain, _) = (space0::<_, NomErr<_>>, tag("]")) + .parse(remain) .map_err(|e| e.to_owned()) .with_context(|| format!("failed to parse list end/separator '{input}'"))?; return Ok(remain);