oxidd_dump/
dddmp.rs

1//! Im- and export to the [DDDMP] format used by CUDD
2//!
3//! [DDDMP]: https://github.com/ivmai/cudd/tree/release/dddmp
4
5use std::fmt;
6use std::io;
7use std::str::FromStr;
8
9use bitvec::prelude::*;
10use is_sorted::IsSorted;
11
12use oxidd_core::function::Function;
13use oxidd_core::util::AllocResult;
14use oxidd_core::util::Borrowed;
15use oxidd_core::util::EdgeDropGuard;
16use oxidd_core::util::EdgeHashMap;
17use oxidd_core::util::EdgeVecDropGuard;
18use oxidd_core::util::OutOfMemory;
19use oxidd_core::DiagramRules;
20use oxidd_core::Edge;
21use oxidd_core::HasLevel;
22use oxidd_core::InnerNode;
23use oxidd_core::LevelNo;
24use oxidd_core::Manager;
25use oxidd_core::Node;
26
27// spell-checker:ignore varinfo,suppvar,suppvars,suppvarnames
28// spell-checker:ignore ordervar,orderedvarnames
29// spell-checker:ignore permid,permids,auxids,rootids,rootnames
30// spell-checker:ignore nnodes,nvars,nsuppvars,nroots
31
32type FxBuildHasher = std::hash::BuildHasherDefault<rustc_hash::FxHasher>;
33
34/// Encoding of the variable ID and then/else edges in binary format
35#[derive(Clone, Copy, PartialEq, Eq, Debug)]
36#[repr(u8)]
37enum Code {
38    Terminal,
39    AbsoluteID,
40    RelativeID,
41    Relative1,
42}
43
44impl From<u8> for Code {
45    fn from(value: u8) -> Self {
46        match value {
47            0 => Code::Terminal,
48            1 => Code::AbsoluteID,
49            2 => Code::RelativeID,
50            3 => Code::Relative1,
51            _ => panic!(),
52        }
53    }
54}
55
56#[derive(Clone, Copy, PartialEq, Eq, Debug)]
57enum VarInfo {
58    VariableID,
59    PermutationID,
60    AuxiliaryID,
61    VariableName,
62    None,
63}
64
65/// Information from the header of a DDDMP file
66#[derive(Debug)]
67pub struct DumpHeader {
68    ascii: bool, // .mode A|B
69    varinfo: VarInfo,
70    dd: String, // optional
71    nnodes: usize,
72    nvars: u32,
73    suppvarnames: Vec<String>,    // optional
74    orderedvarnames: Vec<String>, // optional
75    ids: Vec<u32>,
76    permids: Vec<u32>,
77    auxids: Vec<u32>, // optional
78    rootids: Vec<isize>,
79    rootnames: Vec<String>, // optional
80
81    lines: usize, // number of header lines (including `.nodes`)
82}
83
84impl DumpHeader {
85    /// Load the DDDMP header of the given input
86    ///
87    /// This always needs to be called before [`import()`] to retrieve some
88    /// necessary information and to position the `input`'s cursor.
89    pub fn load(mut input: impl io::BufRead) -> io::Result<Self> {
90        let mut header = DumpHeader {
91            ascii: true,
92            varinfo: VarInfo::None,
93            dd: String::new(),
94            nnodes: 0,
95            nvars: 0,
96            suppvarnames: Vec::new(),
97            orderedvarnames: Vec::new(),
98            ids: Vec::new(),
99            permids: Vec::new(),
100            auxids: Vec::new(),
101            rootids: Vec::new(),
102            rootnames: Vec::new(),
103            lines: 1,
104        };
105        let mut nsuppvars = 0;
106        let mut nroots = 0;
107
108        let mut line = Vec::new();
109        let mut line_no = 1usize;
110
111        loop {
112            match input.read_until(b'\n', &mut line) {
113                Ok(0) => return err("unexpected end of file"),
114                Ok(_) => {}
115                Err(e) => return Err(e),
116            }
117            while let Some(b'\n' | b'\r') = line.last() {
118                line.pop();
119            }
120
121            let (key, value) = match memchr::memchr2(b' ', b'\t', &line) {
122                Some(p) => (&line[..p], &line[p + 1..]),
123                None => (&line[..], [].as_slice()),
124            };
125            let value = trim(value);
126
127            // we don't check for duplicate entries; the last one counts
128            match key {
129                b".ver" => {
130                    if value != b"DDDMP-2.0" {
131                        return err(format!(
132                            "unsupported version '{}' (line {line_no})",
133                            String::from_utf8_lossy(value)
134                        ));
135                    }
136                }
137                b".mode" => {
138                    header.ascii = match value {
139                        b"A" => true,
140                        b"B" => false,
141                        _ => {
142                            return err(format!(
143                                "unknown value '{}' for key '.mode' (line {line_no})",
144                                String::from_utf8_lossy(value),
145                            ))
146                        }
147                    };
148                }
149                b".varinfo" => {
150                    header.varinfo = match value {
151                        b"0" => VarInfo::VariableID,
152                        b"1" => VarInfo::PermutationID,
153                        b"2" => VarInfo::AuxiliaryID,
154                        b"3" => VarInfo::VariableName,
155                        b"4" => VarInfo::None,
156                        _ => {
157                            return err(format!(
158                                "unknown value '{}' for key '.varinfo' (line {line_no})",
159                                String::from_utf8_lossy(value),
160                            ))
161                        }
162                    };
163                }
164                b".dd" => header.dd = String::from_utf8_lossy(value).to_string(),
165                b".nnodes" => header.nnodes = parse_single_usize(value, line_no)?,
166                b".nvars" => header.nvars = parse_single_u32(value, line_no)?,
167                b".nsuppvars" => nsuppvars = parse_single_u32(value, line_no)?,
168                b".suppvarnames" => {
169                    header.suppvarnames = parse_str_list(value, header.nvars as usize);
170                }
171                b".orderedvarnames" => {
172                    header.orderedvarnames = parse_str_list(value, header.nvars as usize);
173                }
174                b".ids" => {
175                    header.ids = parse_u32_list(value, nsuppvars as usize, line_no)?;
176                }
177                b".permids" => {
178                    header.permids = parse_u32_list(value, nsuppvars as usize, line_no)?;
179                }
180                b".auxids" => {
181                    header.auxids = parse_u32_list(value, nsuppvars as usize, line_no)?;
182                }
183                b".nroots" => nroots = parse_single_usize(value, line_no)?,
184                b".rootids" => {
185                    header.rootids.clear();
186                    parse_edge_list(value, &mut header.rootids, line_no)?;
187                }
188                b".rootnames" => header.rootnames = parse_str_list(value, nroots),
189                b".nodes" => break,
190                _ => {
191                    return err(format!(
192                        "unknown key '{}' (line {line_no})",
193                        String::from_utf8_lossy(key),
194                    ))
195                }
196            }
197
198            line_no += 1;
199            line.clear();
200        }
201
202        header.lines = line_no;
203
204        // validation
205
206        if nsuppvars > header.nvars {
207            return err(format!(
208                ".nsuppvars ({nsuppvars}) must not be greater than .nvars ({})",
209                header.nvars,
210            ));
211        }
212
213        if header.ids.len() != nsuppvars as usize {
214            return err(format!(
215            "number of support variables in .ids entry ({}) does not match .nsuppvars ({nsuppvars})",
216            header.ids.len(),
217        ));
218        }
219        if header.permids.len() != nsuppvars as usize {
220            return err(format!(
221            "number of support variables in .permids entry ({}) does not match .nsuppvars ({nsuppvars})",
222            header.permids.len(),
223        ));
224        }
225        if !header.auxids.is_empty() && header.auxids.len() != nsuppvars as usize {
226            return err(format!(
227            "number of support variables in .auxids entry ({}) does not match .nsuppvars ({nsuppvars})",
228            header.auxids.len(),
229        ));
230        }
231
232        if !header.ids.is_empty() {
233            if !IsSorted::is_sorted_by(&mut header.ids.iter(), cmp_strict) {
234                return err("support variables in .ids must be ascending");
235            }
236            if *header.ids.last().unwrap() >= header.nvars {
237                return err(format!(
238                    "support variables in .ids must be less than .nvars ({})",
239                    header.nvars,
240                ));
241            }
242        }
243
244        let mut permids_present: BitVec = bitvec![0; header.nvars as usize];
245        for &id in &header.permids {
246            if id >= header.nvars {
247                return err(format!(
248                    "support variables in .permids must be less than .nvars ({})",
249                    header.nvars,
250                ));
251            }
252            if permids_present[id as usize] {
253                return err(format!("variable ({id}) occurs twice in .permids"));
254            }
255            permids_present.set(id as usize, true);
256        }
257
258        if !header.orderedvarnames.is_empty()
259            && header.orderedvarnames.len() != header.nvars as usize
260        {
261            return err(format!(
262                "number of variables in .orderedvarnames entry ({}) does not match .nvars ({})",
263                header.orderedvarnames.len(),
264                header.nvars
265            ));
266        }
267        if !header.suppvarnames.is_empty() {
268            if header.suppvarnames.len() != nsuppvars as usize {
269                return err(format!(
270                "number of variables in .suppvarnames entry ({}) does not match .nsuppvars ({nsuppvars})",
271                header.suppvarnames.len(),
272            ));
273            }
274
275            if !header.orderedvarnames.is_empty() {
276                for (i, suppvar) in header.suppvarnames.iter().enumerate() {
277                    let permid = header.permids[i];
278                    let ordervar = &header.orderedvarnames[permid as usize];
279                    if suppvar != ordervar {
280                        return err(format!(
281                            ".suppvarnames and .orderedvarnames do not match \
282                        (entry {i} of .suppvarnames is '{suppvar}' \
283                        but the permuted ID is {permid} with name '{ordervar}')",
284                        ));
285                    }
286                }
287            }
288        }
289
290        if header.rootids.len() != nroots {
291            return err(format!(
292                "number of roots in .rootids entry ({}) does not match .nroots ({nroots})",
293                header.rootids.len(),
294            ));
295        }
296        for &id in &header.rootids {
297            if id == 0 {
298                return err(".rootids must not be 0");
299            }
300            if id.unsigned_abs() > header.nnodes {
301                return err(format!(
302                    "entry in .rootids out of range ({id} > {})",
303                    header.nnodes,
304                ));
305            }
306        }
307        if !header.rootnames.is_empty() && header.rootnames.len() != nroots {
308            return err(format!(
309                "number of roots in .rootnames entry ({}) does not match .nroots ({nroots})",
310                header.rootnames.len(),
311            ));
312        }
313
314        Ok(header)
315    }
316
317    /// Name of the decision diagram
318    ///
319    /// Corresponds to the DDDMP `.dd` field.
320    pub fn diagram_name(&self) -> Option<&str> {
321        if self.dd.is_empty() {
322            None
323        } else {
324            Some(&self.dd)
325        }
326    }
327
328    /// Number of nodes in the dumped decision diagram
329    ///
330    /// Corresponds to the DDDMP `.nnodes` field.
331    pub fn num_nodes(&self) -> usize {
332        self.nnodes
333    }
334
335    /// Number of all variables in the exported decision diagram
336    ///
337    /// Corresponds to the DDDMP `.nvars` field.
338    pub fn num_vars(&self) -> u32 {
339        self.nvars
340    }
341
342    /// Number of variables in the true support of the decision diagram
343    ///
344    /// Corresponds to the DDDMP `.nsuppvars` field.
345    pub fn num_support_vars(&self) -> u32 {
346        self.ids.len() as _
347    }
348
349    /// Variables in the true support of the decision diagram. Concretely, these
350    /// are indices of the original variable numbering. Hence, the returned
351    /// slice contains [`DumpHeader::num_support_vars()`] integers in strictly
352    /// ascending order.
353    ///
354    /// Example: Consider a decision diagram that was created with the variables
355    /// `x`, `y` and `z`, in this order (`x` is the top-most variable). Suppose
356    /// that only `y` and `z` are used by the dumped functions. Then this
357    /// function returns `[1, 2]` regardless of any subsequent reordering.
358    ///
359    /// Corresponds to the DDDMP `.ids` field.
360    pub fn support_vars(&self) -> &[u32] {
361        &self.ids
362    }
363
364    /// Permutation of the variables in the true support of the decision
365    /// diagram. The returned slice is always [`DumpHeader::num_support_vars()`]
366    /// elements long. If the value at the `i`th index is `l`, then the `i`th
367    /// support variable is at level `l` in the dumped decision diagram. By the
368    /// `i`th support variable, we mean the variable `header.support_vars()[i]`
369    /// in the original numbering.
370    ///
371    /// Example: Consider a decision diagram that was created with the variables
372    /// `x`, `y` and `z` (`x` is the top-most variable). The variables were
373    /// re-ordered to `z`, `x`, `y`. Suppose that only `y` and `z` are used by
374    /// the dumped functions. Then this function returns `[2, 0]`.
375    ///
376    /// Corresponds to the DDDMP `.permids` field.
377    pub fn support_var_permutation(&self) -> &[u32] {
378        &self.permids
379    }
380
381    /// Auxiliary variable IDs. The returned slice contains
382    /// [`DumpHeader::num_support_vars()`] elements.
383    ///
384    /// Corresponds to the DDDMP `.auxids` field.
385    pub fn auxiliary_var_ids(&self) -> &[u32] {
386        &self.auxids
387    }
388
389    /// Names of variables in the true support of the decision diagram. If
390    /// present, the returned slice contains [`DumpHeader::num_support_vars()`]
391    /// many elements. The order is the "original" variable order.
392    ///
393    /// Example: Consider a decision diagram that was created with the variables
394    /// `x`, `y` and `z`, in this order (`x` is the top-most variable). Suppose
395    /// that only `y` and `z` are used by the dumped functions. Then this
396    /// function returns `["y", "z"]` regardless of any subsequent reordering.
397    ///
398    /// Corresponds to the DDDMP `.suppvarnames` field.
399    pub fn support_var_names(&self) -> Option<&[String]> {
400        if self.suppvarnames.is_empty() {
401            None
402        } else {
403            Some(&self.suppvarnames[..])
404        }
405    }
406
407    /// Names of all variables in the exported decision diagram. If present, the
408    /// returned slice contains [`DumpHeader::num_vars()`] many elements. The
409    /// order is the "current" variable order, i.e. the first name corresponds
410    /// to the top-most variable in the dumped decision diagram.
411    ///
412    /// Example: Consider a decision diagram that was created with the variables
413    /// `x`, `y` and `z` (`x` is the top-most variable). The variables were
414    /// re-ordered to `z`, `x`, `y`. In this case, the function returns
415    /// `["z", "x", "y"]` (even if some of the variables are unused by the
416    /// dumped functions).
417    ///
418    /// Corresponds to the DDDMP `.nvars` field.
419    pub fn ordered_var_names(&self) -> Option<&[String]> {
420        if self.orderedvarnames.is_empty() {
421            None
422        } else {
423            Some(&self.orderedvarnames[..])
424        }
425    }
426
427    /// Number of roots
428    ///
429    /// [`import()`] returns this number of roots on success.
430    /// Corresponds to the DDDMP `.nroots` field.
431    pub fn num_roots(&self) -> usize {
432        self.rootids.len()
433    }
434
435    /// Names of roots, if present, in the same order as returned by
436    /// [`import()`]
437    ///
438    /// Corresponds to the DDDMP `.rootnames` field.
439    pub fn root_names(&self) -> Option<&[String]> {
440        if self.rootnames.is_empty() {
441            None
442        } else {
443            Some(&self.rootnames[..])
444        }
445    }
446}
447
448/// Helper function to return a parse error
449fn err<T>(msg: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Result<T> {
450    Err(io::Error::new(io::ErrorKind::InvalidData, msg))
451}
452
453/// Compare, mapping `Equal` to `Greater`
454fn cmp_strict<T: Ord>(a: &T, b: &T) -> Option<std::cmp::Ordering> {
455    Some(a.cmp(b).then(std::cmp::Ordering::Greater))
456}
457
458/// Like [`std::fmt::Display`], but the format should use ASCII characters only
459pub trait AsciiDisplay {
460    /// Format the value with the given formatter
461    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error>;
462}
463
464struct Ascii<T>(T);
465impl<T: AsciiDisplay> fmt::Display for Ascii<&T> {
466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467        self.0.fmt(f)
468    }
469}
470
471/// Export the decision diagram in `manager` to `file`
472///
473/// `ascii` indicates whether to use the ASCII or binary format.
474///
475/// `dd_name` is the name that is output to the `.dd` field, unless it is an
476/// empty string.
477///
478/// `vars` are edges representing *all* variables in the decision diagram. The
479/// order does not matter. `var_names` are the names of these variables
480/// (optional). If given, there must be `vars.len()` names in the same order as
481/// in `vars`.
482///
483/// `functions` are edges pointing to the root nodes of functions.
484/// `function_names` are the corresponding names (optional). If given, there
485/// must be `functions.len()` names in the same order as in `function_names`.
486///
487/// `is_complemented` is a function that returns whether an edge is
488/// complemented.
489#[allow(clippy::too_many_arguments)] // FIXME: use a builder pattern
490pub fn export<'id, F: Function>(
491    mut file: impl io::Write,
492    manager: &F::Manager<'id>,
493    ascii: bool,
494    dd_name: &str,
495    vars: &[&F],
496    var_names: Option<&[&str]>,
497    functions: &[&F],
498    function_names: Option<&[&str]>,
499    is_complemented: impl Fn(&<F::Manager<'id> as Manager>::Edge) -> bool,
500) -> io::Result<()>
501where
502    <F::Manager<'id> as Manager>::InnerNode: HasLevel,
503    <F::Manager<'id> as Manager>::Terminal: AsciiDisplay,
504{
505    assert!(var_names.is_none() || var_names.unwrap().len() == vars.len());
506    assert!(function_names.is_none() || function_names.unwrap().len() == functions.len());
507    assert!(
508        ascii || <F::Manager<'id> as Manager>::InnerNode::ARITY == 2,
509        "binary mode is (currently) only supported for binary nodes"
510    );
511
512    writeln!(file, ".ver DDDMP-2.0")?;
513    writeln!(file, ".mode {}", if ascii { 'A' } else { 'B' })?;
514
515    // TODO: other .varinfo modes?
516    writeln!(file, ".varinfo {}", VarInfo::None as u32)?;
517
518    if !dd_name.is_empty() {
519        let invalid = |c: char| !c.is_ascii() || c.is_ascii_control();
520        if dd_name.chars().any(invalid) {
521            return Err(io::Error::new(
522                io::ErrorKind::InvalidInput,
523                "decision diagram name must be ASCII text without control characters",
524            ));
525        }
526        writeln!(file, ".dd {dd_name}")?;
527    }
528
529    let nvars = manager.num_levels();
530    assert!(nvars as usize == vars.len());
531
532    // Map from the current level number to its internal var index (almost the
533    // same numbering, but vars not in the support removed, i.e. range
534    // `0..nsuppvars`), and the nodes (as edges) contained at this level
535    // together with their indexes.
536    let mut node_map: Vec<(LevelNo, EdgeHashMap<F::Manager<'id>, usize, FxBuildHasher>)> =
537        (0..nvars).map(|_| (0, EdgeHashMap::new(manager))).collect();
538    let mut terminal_map: EdgeHashMap<F::Manager<'id>, usize, FxBuildHasher> =
539        EdgeHashMap::new(manager);
540
541    fn rec_add_map<M: Manager>(
542        manager: &M,
543        node_map: &mut Vec<(u32, EdgeHashMap<M, usize, FxBuildHasher>)>,
544        terminal_map: &mut EdgeHashMap<M, usize, FxBuildHasher>,
545        e: Borrowed<M::Edge>,
546    ) where
547        M::InnerNode: HasLevel,
548    {
549        match manager.get_node(&e) {
550            Node::Inner(node) => {
551                let (_, map) = &mut node_map[node.level() as usize];
552                // Map to 0 -> we assign the indexes below
553                let res = map.insert(&e.with_tag(Default::default()), 0);
554                if res.is_none() {
555                    for e in node.children() {
556                        rec_add_map::<M>(manager, node_map, terminal_map, e);
557                    }
558                }
559            }
560            Node::Terminal(_) => {
561                terminal_map.insert(&e.with_tag(Default::default()), 0);
562            }
563        }
564    }
565
566    for &func in functions {
567        rec_add_map::<F::Manager<'id>>(
568            manager,
569            &mut node_map,
570            &mut terminal_map,
571            func.as_edge(manager).borrowed(),
572        );
573    }
574
575    let mut nnodes = 0;
576    for (_, idx) in &mut terminal_map {
577        nnodes += 1; // pre increment -> numbers in range 1..=#nodes
578        *idx = nnodes;
579    }
580    let nsuppvars = node_map.iter().filter(|(_, m)| !m.is_empty()).count() as u32;
581    let mut suppvars: BitVec = bitvec![0; nvars as usize];
582    let mut suppvar_idx = nsuppvars;
583    // rev() -> assign node IDs bottom-up
584    for (i, (var_idx, level)) in node_map.iter_mut().enumerate().rev() {
585        if level.is_empty() {
586            continue;
587        }
588        suppvar_idx -= 1;
589        *var_idx = suppvar_idx;
590        suppvars.set(i, true);
591        for (_, idx) in level.iter_mut() {
592            nnodes += 1; // pre increment -> numbers in range 1..=#nodes
593            *idx = nnodes;
594        }
595    }
596    writeln!(file, ".nnodes {nnodes}")?;
597    writeln!(file, ".nvars {nvars}")?;
598    writeln!(file, ".nsuppvars {nsuppvars}")?;
599
600    if let Some(var_names) = var_names {
601        let mut ordered_var_names = Vec::new();
602        ordered_var_names.resize(var_names.len(), "");
603
604        write!(file, ".suppvarnames")?;
605        for (&f, &name) in vars.iter().zip(var_names) {
606            let invalid =
607                |c: char| !c.is_ascii() || c.is_ascii_control() || c.is_ascii_whitespace();
608            if name.is_empty() || name.chars().any(invalid) {
609                return Err(io::Error::new(
610                    io::ErrorKind::InvalidInput,
611                    "variable name must be ASCII text without control characters and spaces",
612                ));
613            }
614            let node = manager
615                .get_node(f.as_edge(manager))
616                .expect_inner("variables must not be terminals");
617            let level = node.level() as usize;
618            assert!(
619                ordered_var_names[level].is_empty(),
620                "variables must not be given multiple times"
621            );
622            ordered_var_names[level] = name;
623            if suppvars[level] {
624                write!(file, " {name}")?;
625            }
626        }
627        writeln!(file)?;
628
629        write!(file, ".orderedvarnames")?;
630        for name in ordered_var_names {
631            write!(file, " {name}")?;
632        }
633        writeln!(file)?;
634    }
635
636    write!(file, ".ids")?;
637    for (id, &f) in vars.iter().enumerate() {
638        let node = manager
639            .get_node(f.as_edge(manager))
640            .expect_inner("variables must not be terminals");
641        if suppvars[node.level() as usize] {
642            write!(file, " {id}")?;
643        }
644    }
645    writeln!(file)?;
646
647    write!(file, ".permids")?;
648    for &f in vars {
649        let level = manager.get_node(f.as_edge(manager)).unwrap_inner().level();
650        if suppvars[level as usize] {
651            write!(file, " {level}")?;
652        }
653    }
654    writeln!(file)?;
655
656    // TODO: .auxids?
657
658    let idx = |e: &<F::Manager<'id> as Manager>::Edge| {
659        let idx = match manager.get_node(e) {
660            Node::Inner(node) => {
661                let (_, map) = &node_map[node.level() as usize];
662                *map.get(&e.with_tag(Default::default())).unwrap() as isize
663            }
664            Node::Terminal(_) => {
665                *terminal_map.get(&e.with_tag(Default::default())).unwrap() as isize
666            }
667        };
668        if is_complemented(e) {
669            -idx
670        } else {
671            idx
672        }
673    };
674    let bin_idx = |e: &<F::Manager<'id> as Manager>::Edge, node_id: usize| {
675        let idx = match manager.get_node(e) {
676            Node::Inner(node) => {
677                let (_, map) = &node_map[node.level() as usize];
678                *map.get(&e.with_tag(Default::default())).unwrap()
679            }
680            Node::Terminal(_) => {
681                if manager.num_terminals() == 1 {
682                    // TODO: This may be bad in case there could be more
683                    // terminals for the decision diagram type (i.e. the value
684                    // returned by `manager.num_terminals()` is not always 1),
685                    // but currently, there is only a single one.
686                    return (Code::Terminal, 0);
687                }
688                todo!();
689            }
690        };
691        if idx == node_id - 1 {
692            (Code::Relative1, 0)
693        } else if node_id - idx < idx {
694            (Code::RelativeID, node_id - idx)
695        } else {
696            (Code::AbsoluteID, idx)
697        }
698    };
699
700    writeln!(file, ".nroots {}", functions.len())?;
701    write!(file, ".rootids")?;
702    for &func in functions {
703        write!(file, " {}", idx(func.as_edge(manager)))?;
704    }
705    writeln!(file)?;
706    if let Some(function_names) = function_names {
707        write!(file, ".rootnames")?;
708        for &name in function_names {
709            let invalid =
710                |c: char| !c.is_ascii() || c.is_ascii_control() || c.is_ascii_whitespace();
711            if name.is_empty() || name.chars().any(invalid) {
712                return Err(io::Error::new(
713                    io::ErrorKind::InvalidInput,
714                    "function name must be ASCII text without control characters and spaces",
715                ));
716            }
717            write!(file, " {}", name)?;
718        }
719        writeln!(file)?;
720    }
721
722    writeln!(file, ".nodes")?;
723
724    #[inline]
725    const fn node_code(var: Code, t: Code, e_complement: bool, e: Code) -> u8 {
726        (var as u8) << 5 | (t as u8) << 3 | (e_complement as u8) << 2 | e as u8
727    }
728
729    let mut exported_nodes = 0;
730    for (edge, &node_id) in terminal_map.iter() {
731        // This implementation relies on that the iteration order for
732        // `EdgeHashMap`s stays the same as long as no elements are
733        // inserted/removed. To be sure, we assert that this assumption holds.
734        assert_eq!(exported_nodes + 1, node_id);
735        if ascii {
736            // <Node-index> [<Var-extra-info>] <Var-internal-index> <Then-index>
737            // <Else-index>
738            let node = manager.get_node(edge);
739            let desc = Ascii(node.unwrap_terminal());
740            writeln!(file, "{node_id} {desc} 0 0")?;
741        } else {
742            let terminal = node_code(Code::Terminal, Code::Terminal, false, Code::Terminal);
743            write_escaped(&mut file, &[terminal])?;
744        }
745        exported_nodes += 1;
746    }
747    // Work from bottom to top
748    for &(var_idx, ref level) in node_map.iter().rev() {
749        for (e, &node_id) in level.iter() {
750            assert_eq!(exported_nodes + 1, node_id);
751            let node = manager.get_node(e).unwrap_inner();
752            if ascii {
753                // <Node-index> [<Var-extra-info>] <Var-internal-index> <Then-index>
754                // <Else-index>
755                write!(file, "{node_id} {var_idx}")?;
756                for child in node.children() {
757                    write!(file, " {}", idx(&child))?;
758                }
759                writeln!(file)?;
760            } else {
761                let mut iter = node.children();
762                let t = iter.next().unwrap();
763                let t_lvl = manager.get_node(&t).level();
764                let e = iter.next().unwrap();
765                let e_lvl = manager.get_node(&e).level();
766                debug_assert!(iter.next().is_none());
767
768                let mut var_code = Code::AbsoluteID;
769                let mut var_idx = var_idx;
770                let min_lvl = std::cmp::min(t_lvl, e_lvl);
771                if min_lvl != LevelNo::MAX {
772                    let (min_var_idx, _) = node_map[min_lvl as usize];
773                    if var_idx == min_var_idx - 1 {
774                        var_code = Code::Relative1;
775                    } else if min_var_idx - var_idx < var_idx {
776                        var_code = Code::RelativeID;
777                        var_idx = min_var_idx - var_idx;
778                    }
779                }
780
781                let (t_code, t_idx) = bin_idx(&t, node_id);
782                let (e_code, e_idx) = bin_idx(&e, node_id);
783
784                debug_assert!(!is_complemented(&t));
785                write_escaped(
786                    &mut file,
787                    &[node_code(var_code, t_code, is_complemented(&e), e_code)],
788                )?;
789                if var_code == Code::AbsoluteID || var_code == Code::RelativeID {
790                    encode_7bit(&mut file, var_idx as usize)?;
791                }
792                if t_code == Code::AbsoluteID || t_code == Code::RelativeID {
793                    encode_7bit(&mut file, t_idx)?;
794                }
795                if e_code == Code::AbsoluteID || e_code == Code::RelativeID {
796                    encode_7bit(&mut file, e_idx)?;
797                }
798                assert!(u64::BITS >= usize::BITS);
799            }
800            exported_nodes += 1;
801        }
802    }
803
804    writeln!(file, ".end")?;
805
806    Ok(())
807}
808
809/// 7-bit encode an integer and write it (escaped) via `writer`
810fn encode_7bit(writer: impl io::Write, mut value: usize) -> io::Result<()> {
811    let mut buf = [0u8; (usize::BITS as usize + 8 - 1) / 7];
812    let mut idx = buf.len() - 1;
813    buf[idx] = (value as u8).wrapping_shl(1);
814    value >>= 7;
815    while value != 0 {
816        idx -= 1;
817        buf[idx] = (value as u8).wrapping_shl(1) | 1;
818        value >>= 7;
819    }
820    write_escaped(writer, &buf[idx..])
821}
822
823/// Write an DDDMP-style escaped byte
824fn write_escaped(mut writer: impl io::Write, buf: &[u8]) -> io::Result<()> {
825    for &c in buf {
826        match c {
827            0x00 => writer.write_all(&[0x00, 0x00])?,
828            0x0a => writer.write_all(&[0x00, 0x01])?,
829            0x0d => writer.write_all(&[0x00, 0x02])?,
830            0x1a => writer.write_all(&[0x00, 0x03])?,
831            _ => writer.write_all(&[c])?,
832        }
833    }
834    Ok(())
835}
836
837/// Read and unescape a byte. Counterpart of [`write_escaped()`]
838fn read_unescape(input: impl io::Read) -> io::Result<u8> {
839    let mut bytes = input.bytes();
840    let eof_msg = "unexpected end of file";
841    match bytes.next() {
842        None => return err(eof_msg),
843        Some(Ok(0)) => {}
844        Some(res) => return res,
845    };
846    match bytes.next() {
847        None => err(eof_msg),
848        Some(Ok(0x00)) => Ok(0x00),
849        Some(Ok(0x01)) => Ok(0x0a),
850        Some(Ok(0x02)) => Ok(0x0d),
851        Some(Ok(0x03)) => Ok(0x1a),
852        Some(Ok(b)) => Err(io::Error::new(
853            io::ErrorKind::InvalidData,
854            format!("invalid escape sequence 0x00 0x{b:x}"),
855        )),
856        Some(e) => e,
857    }
858}
859
860/// 7-bit decode a number from `input` (unescaping the bytes via
861/// [`read_unescape()`])
862fn decode_7bit(mut input: impl io::Read) -> io::Result<usize> {
863    let mut res = 0usize;
864    loop {
865        let b = read_unescape(&mut input)?;
866        match res.checked_shl(7) {
867            Some(v) => res = v,
868            None => return err("integer too large"),
869        }
870        res |= (b >> 1) as usize;
871        if b & 1 == 0 {
872            return Ok(res);
873        }
874    }
875}
876
877/// Import the decision diagram from `input` into `manager` after loading
878/// `header` from `input`
879///
880/// Important: there must not be any read/seek/... operations on `input` after
881/// reading `header` using [`DumpHeader::load()`].
882///
883/// `support_vars` contains edges representing the variables in the true support
884/// of the decision diagram in `input`. The variables must be ordered by their
885/// current level (lower level numbers first).
886///
887/// `complement` is a function that returns the complemented edge for a given
888/// edge.
889pub fn import<'id, F: Function>(
890    mut input: impl io::BufRead,
891    header: &DumpHeader,
892    manager: &F::Manager<'id>,
893    support_vars: &[F],
894    complement: impl Fn(
895        &F::Manager<'id>,
896        <F::Manager<'id> as Manager>::Edge,
897    ) -> AllocResult<<F::Manager<'id> as Manager>::Edge>,
898) -> io::Result<Vec<F>>
899where
900    <F::Manager<'id> as Manager>::InnerNode: HasLevel,
901    <F::Manager<'id> as Manager>::Terminal: FromStr,
902{
903    assert_eq!(support_vars.len(), header.ids.len());
904    let suppvar_level_map: Vec<LevelNo> = support_vars
905        .iter()
906        .map(|f| {
907            let nw = manager
908                .get_node(f.as_edge(manager))
909                .expect_inner("variables must not be terminals");
910            nw.level()
911        })
912        .collect();
913    assert!(IsSorted::is_sorted_by(
914        &mut suppvar_level_map.iter(),
915        cmp_strict
916    ));
917
918    let nodes = if header.ascii {
919        import_ascii::<F::Manager<'id>>(
920            &mut input,
921            header,
922            manager,
923            &suppvar_level_map,
924            &complement,
925        )
926    } else {
927        let mut level_suppvar_map = vec![u32::MAX; manager.num_levels() as usize];
928        for (i, &level) in suppvar_level_map.iter().enumerate() {
929            level_suppvar_map[level as usize] = i as u32;
930        }
931        import_bin::<F::Manager<'id>>(
932            &mut input,
933            header,
934            manager,
935            &level_suppvar_map,
936            &suppvar_level_map,
937            &complement,
938        )
939    }?;
940    let nodes = EdgeVecDropGuard::new(manager, nodes);
941    debug_assert_eq!(nodes.len(), header.nnodes);
942
943    let mut buf = Vec::with_capacity(8);
944    input.read_to_end(&mut buf)?;
945    if &buf[..4] != b".end" || !buf[4..].iter().all(|b| b.is_ascii_whitespace()) {
946        return err("file must end with '.end'");
947    }
948
949    let mut roots = Vec::with_capacity(header.rootids.len());
950    for &root in &header.rootids {
951        debug_assert_ne!(root, 0);
952        let node_index = root.unsigned_abs() - 1;
953        let e = manager.clone_edge(&nodes[node_index]);
954        roots.push(F::from_edge(
955            manager,
956            if root > 0 {
957                e
958            } else {
959                match complement(manager, e) {
960                    Ok(e) => e,
961                    Err(OutOfMemory) => return Err(io::ErrorKind::OutOfMemory.into()),
962                }
963            },
964        ));
965    }
966
967    Ok(roots)
968}
969
970fn import_ascii<M: Manager>(
971    mut input: impl io::BufRead,
972    header: &DumpHeader,
973    manager: &M,
974    suppvar_level_map: &[LevelNo],
975    complement: impl Fn(&M, M::Edge) -> AllocResult<M::Edge>,
976) -> io::Result<Vec<M::Edge>>
977where
978    M::InnerNode: HasLevel,
979    M::Terminal: FromStr,
980{
981    let mut nodes: Vec<M::Edge> = Vec::with_capacity(header.nnodes);
982    let mut line = Vec::new();
983    let mut line_no = header.lines + 1;
984    let mut children = Vec::with_capacity(M::InnerNode::ARITY);
985    for node_id in 1..=header.nnodes {
986        match input.read_until(b'\n', &mut line) {
987            Ok(0) => return err("unexpected end of file"),
988            Ok(_) => {}
989            Err(e) => return Err(e),
990        }
991        while let Some(b'\n' | b'\r') = line.last() {
992            line.pop();
993        }
994
995        let (rest, node_id_lineno) = parse_usize(&line, line_no)?;
996        if node_id_lineno != node_id {
997            return err(format!("expected node ID {node_id} on line {line_no}"));
998        }
999
1000        let rest = trim_start(rest);
1001        let rest = match header.varinfo {
1002            VarInfo::VariableID
1003            | VarInfo::PermutationID
1004            | VarInfo::AuxiliaryID
1005            | VarInfo::VariableName => match memchr::memchr2(b' ', b'\t', rest) {
1006                Some(pos) => &rest[pos + 1..],
1007                None => {
1008                    return err(format!(
1009                        "expected a space after variable extra info (line {line_no})"
1010                    ))
1011                }
1012            },
1013            VarInfo::None => rest,
1014        };
1015
1016        let rest = trim_start(rest);
1017        let (rest, var_id) = match memchr::memchr2(b' ', b'\t', rest) {
1018            Some(pos) => (&rest[pos + 1..], &rest[..pos]),
1019            None => {
1020                return err(format!(
1021                    "expected a space after variable internal index (line {line_no})"
1022                ))
1023            }
1024        };
1025
1026        parse_edge_list(rest, &mut children, line_no)?;
1027        if children.len() != M::InnerNode::ARITY {
1028            return err(format!(
1029                "expected {} children, got {} (line {line_no})",
1030                M::InnerNode::ARITY,
1031                children.len()
1032            ));
1033        }
1034
1035        let res = if children.iter().any(|&c| c == 0) {
1036            // terminal
1037            let string = match std::str::from_utf8(var_id) {
1038                Ok(s) => s,
1039                Err(_) => {
1040                    return err(format!(
1041                        "terminal description must be valid UTF-8 (line {line_no})"
1042                    ));
1043                }
1044            };
1045            let terminal: M::Terminal = match string.parse() {
1046                Ok(t) => t,
1047                Err(_) => return err(format!("invalid terminal description (line {line_no})")),
1048            };
1049            manager.get_terminal(terminal)
1050        } else {
1051            let (_, var_id) = parse_u32(var_id, line_no)?;
1052            let Some(&level) = suppvar_level_map.get(var_id as usize) else {
1053                return err(format!("variable out of range (line {line_no})"));
1054            };
1055
1056            for &child in &children {
1057                let child = child.unsigned_abs();
1058                if child >= node_id {
1059                    return err(format!(
1060                        "children ids must be less than node ({child} >= {node_id}, line {line_no})",
1061                    ));
1062                }
1063                let child_level = manager.get_node(&nodes[child - 1]).level();
1064                if level >= child_level {
1065                    return err(format!(
1066                        "node level must be less than the children's levels ({level} >= {child_level}, line {line_no})",
1067                    ));
1068                }
1069            }
1070
1071            <M::Rules as DiagramRules<_, _, _>>::reduce(
1072                manager,
1073                level,
1074                children.iter().map(|&child| {
1075                    debug_assert_ne!(child, 0);
1076                    let e = manager.clone_edge(&nodes[child.unsigned_abs() - 1]);
1077                    if child < 0 {
1078                        complement(manager, e).unwrap()
1079                    } else {
1080                        e
1081                    }
1082                }),
1083            )
1084            .then_insert(manager, level)
1085        };
1086        let node = match res {
1087            Ok(e) => e,
1088            Err(OutOfMemory) => {
1089                for e in nodes {
1090                    manager.drop_edge(e);
1091                }
1092                return Err(io::ErrorKind::OutOfMemory.into());
1093            }
1094        };
1095
1096        nodes.push(node);
1097
1098        children.clear();
1099        line.clear();
1100        line_no += 1;
1101    }
1102
1103    Ok(nodes)
1104}
1105
1106fn import_bin<M: Manager>(
1107    mut input: impl io::BufRead,
1108    header: &DumpHeader,
1109    manager: &M,
1110    level_suppvar_map: &[u32],
1111    suppvar_level_map: &[LevelNo],
1112    complement: impl Fn(&M, M::Edge) -> AllocResult<M::Edge>,
1113) -> io::Result<Vec<M::Edge>>
1114where
1115    M::InnerNode: HasLevel,
1116{
1117    assert_eq!(
1118        M::InnerNode::ARITY,
1119        2,
1120        "binary mode is (currently) only supported for binary nodes"
1121    );
1122    let terminal = {
1123        let msg = "binary mode is (currently) only supported for diagrams with a single terminal";
1124        let mut it = manager.terminals();
1125        let t = EdgeDropGuard::new(manager, it.next().expect(msg));
1126        assert!(it.next().is_none(), "{msg}");
1127        t
1128    };
1129
1130    fn idx(input: impl io::BufRead, node_id: usize, code: Code) -> io::Result<usize> {
1131        debug_assert!(node_id >= 1);
1132        let id = match code {
1133            Code::Terminal => 1,
1134            Code::AbsoluteID => decode_7bit(input)?,
1135            Code::RelativeID => node_id - decode_7bit(input)?,
1136            Code::Relative1 => node_id - 1,
1137        };
1138        if id == 0 {
1139            return err("then/else ID must not be 0");
1140        }
1141        if id >= node_id {
1142            return err("then/else ID too large");
1143        }
1144        Ok((id - 1) as usize)
1145    }
1146
1147    let mut nodes = EdgeVecDropGuard::new(manager, Vec::with_capacity(header.nnodes));
1148    for node_id in 1..=header.nnodes {
1149        let node_code = read_unescape(&mut input)?;
1150        let var_code = Code::from((node_code >> 5) & 0b11);
1151        let t_code = Code::from((node_code >> 3) & 0b11);
1152        let e_complement = ((node_code >> 2) & 0b1) != 0;
1153        let e_code = Code::from(node_code & 0b11);
1154
1155        let vid = match var_code {
1156            Code::Terminal => {
1157                nodes.push(manager.clone_edge(&terminal));
1158                continue;
1159            }
1160            Code::AbsoluteID | Code::RelativeID => decode_7bit(&mut input)?,
1161            Code::Relative1 => 1,
1162        };
1163
1164        let t = manager.clone_edge(&nodes[idx(&mut input, node_id, t_code)?]);
1165        let t_level = manager.get_node(&t).level();
1166        let e = manager.clone_edge(&nodes[idx(&mut input, node_id, e_code)?]);
1167        let e_level = manager.get_node(&e).level();
1168        let e = if e_complement {
1169            match complement(manager, e) {
1170                Ok(e) => e,
1171                Err(OutOfMemory) => {
1172                    return Err(io::ErrorKind::OutOfMemory.into());
1173                }
1174            }
1175        } else {
1176            e
1177        };
1178
1179        let vid = match var_code {
1180            Code::Terminal => unreachable!(),
1181            Code::AbsoluteID if vid >= suppvar_level_map.len() => {
1182                return err("variable ID out of range")
1183            }
1184            Code::AbsoluteID => vid,
1185            Code::RelativeID | Code::Relative1 => {
1186                let min_level = std::cmp::min(t_level, e_level);
1187                let child_min_suppvar = if min_level == LevelNo::MAX {
1188                    level_suppvar_map.len()
1189                } else {
1190                    level_suppvar_map[min_level as usize] as usize
1191                };
1192                match child_min_suppvar.checked_sub(vid) {
1193                    Some(v) => v,
1194                    None => return err("variable ID out of range"),
1195                }
1196            }
1197        };
1198
1199        let level = suppvar_level_map[vid as usize];
1200        if level >= t_level || level >= e_level {
1201            return err("node level must be less than the children's levels");
1202        }
1203
1204        match <M::Rules as DiagramRules<_, _, _>>::reduce(manager, level, [t, e])
1205            .then_insert(manager, level)
1206        {
1207            Ok(e) => nodes.push(e),
1208            Err(OutOfMemory) => {
1209                return Err(io::ErrorKind::OutOfMemory.into());
1210            }
1211        }
1212    }
1213
1214    Ok(nodes.into_vec())
1215}
1216
1217/// Remove leading spaces and tabs
1218const fn trim_start(mut s: &[u8]) -> &[u8] {
1219    while let [b' ' | b'\t', rest @ ..] = s {
1220        s = rest;
1221    }
1222    s
1223}
1224
1225/// Remove trailing spaces and tabs
1226const fn trim_end(mut s: &[u8]) -> &[u8] {
1227    while let [rest @ .., b' ' | b'\t'] = s {
1228        s = rest;
1229    }
1230    s
1231}
1232
1233/// Remove leading and trailing spaces and tabs
1234const fn trim(s: &[u8]) -> &[u8] {
1235    trim_end(trim_start(s))
1236}
1237
1238/// Parse a list of strings
1239fn parse_str_list(input: &[u8], capacity: usize) -> Vec<String> {
1240    let mut res = Vec::with_capacity(capacity);
1241    let mut start = 0;
1242    for pos in memchr::memchr2_iter(b' ', b'\t', input).chain([input.len()]) {
1243        // skip empty strings
1244        if pos != start {
1245            res.push(String::from_utf8_lossy(&input[start..pos]).to_string())
1246        }
1247        start = pos + 1;
1248    }
1249    res
1250}
1251
1252macro_rules! parse_unsigned {
1253    ($f:ident, $t:ty) => {
1254        /// Parse an unsigned integer
1255        ///
1256        /// Returns the remaining input and the parsed value on success. Fails
1257        /// if no integer is present, the integer is too large for the return
1258        /// type, or the input contains non-digit characters (after the leading
1259        /// spaces/tabs and before the first space/tab after the number).
1260        fn $f(input: &[u8], line_no: usize) -> io::Result<(&[u8], $t)> {
1261            let mut res: $t = 0;
1262            let mut num = false;
1263            let mut consumed = 0;
1264            for &c in input {
1265                match c {
1266                    b'0'..=b'9' => {
1267                        num = true;
1268                        match res
1269                            .checked_mul(10)
1270                            .and_then(|v| v.checked_add((c - b'0') as _))
1271                        {
1272                            Some(v) => res = v,
1273                            None => {
1274                                return err(format!(
1275                                    "integer '{}' too large (line {line_no})",
1276                                    String::from_utf8_lossy(input),
1277                                ));
1278                            }
1279                        }
1280                    }
1281                    b' ' | b'\t' if num => break,
1282                    b' ' | b'\t' => {}
1283                    _ => {
1284                        return err(format!(
1285                            "unexpected char '{}' in integer (line {line_no})",
1286                            c as char,
1287                        ));
1288                    }
1289                }
1290                consumed += 1;
1291            }
1292            if !num {
1293                return err(format!(
1294                    "expected an integer, got an empty string (line {line_no})"
1295                ));
1296            }
1297            Ok((&input[consumed..], res))
1298        }
1299    };
1300}
1301parse_unsigned!(parse_u32, u32);
1302parse_unsigned!(parse_usize, usize);
1303
1304macro_rules! parse_single_unsigned {
1305    ($f:ident, $t:ty) => {
1306        /// Parse a single unsigned integer (no spaces etc. allowed)
1307        fn $f(input: &[u8], line_no: usize) -> io::Result<$t> {
1308            let mut res: $t = 0;
1309            let mut num = false;
1310            for &c in input {
1311                match c {
1312                    b'0'..=b'9' => {
1313                        num = true;
1314                        match res
1315                            .checked_mul(10)
1316                            .and_then(|v| v.checked_add((c - b'0') as _))
1317                        {
1318                            Some(v) => res = v,
1319                            None => {
1320                                return err(format!(
1321                                    "integer '{}' too large (line {line_no})",
1322                                    String::from_utf8_lossy(input),
1323                                ));
1324                            }
1325                        }
1326                    }
1327                    _ => {
1328                        return err(format!(
1329                            "unexpected char '{}' in integer (line {line_no})",
1330                            c as char,
1331                        ));
1332                    }
1333                }
1334            }
1335            if !num {
1336                return err(format!("expected an integer (line {line_no})"));
1337            }
1338            Ok(res)
1339        }
1340    };
1341}
1342parse_single_unsigned!(parse_single_u32, u32);
1343parse_single_unsigned!(parse_single_usize, usize);
1344
1345/// Parse a space (or tab) separated list of integers
1346fn parse_u32_list(input: &[u8], capacity: usize, line_no: usize) -> io::Result<Vec<u32>> {
1347    let mut res = Vec::with_capacity(capacity);
1348    let mut i = 0u32;
1349    let mut num = false;
1350
1351    for &c in input {
1352        match c {
1353            b'0'..=b'9' => {
1354                num = true;
1355                match i
1356                    .checked_mul(10)
1357                    .and_then(|v| v.checked_add((c - b'0') as _))
1358                {
1359                    Some(v) => i = v,
1360                    None => {
1361                        return err(format!("integer too large (line {line_no})"));
1362                    }
1363                }
1364            }
1365            b' ' | b'\t' => {
1366                if num {
1367                    res.push(i);
1368                    num = false;
1369                    i = 0;
1370                }
1371            }
1372            _ => {
1373                return err(format!(
1374                    "unexpected char '{}' in space-separated list of integers (line {line_no})",
1375                    c as char,
1376                ));
1377            }
1378        }
1379    }
1380    if num {
1381        res.push(i);
1382    }
1383
1384    Ok(res)
1385}
1386
1387/// Parse a space (or tab) separated list of edges into `dst`
1388///
1389/// The input consists of non-zero decimal integers, possibly negative. If the
1390/// value is negative, this indicates a complemented edge. In this case, the
1391/// boolean in the resulting entry is `true`.
1392///
1393/// `line_no` is only used for error reporting.
1394fn parse_edge_list(input: &[u8], dst: &mut Vec<isize>, line_no: usize) -> io::Result<()> {
1395    let mut i = 0isize;
1396    let mut neg = false;
1397    let mut num = false;
1398
1399    for &c in input {
1400        match c {
1401            b'0'..=b'9' => {
1402                num = true;
1403                match i
1404                    .checked_mul(10)
1405                    .and_then(|v| v.checked_add((c - b'0') as isize))
1406                {
1407                    Some(v) => i = v,
1408                    None => {
1409                        return err(format!("integer too large (line {line_no})"));
1410                    }
1411                }
1412            }
1413            b'-' => {
1414                if neg {
1415                    return err(format!("expected a digit after '-' (line {line_no})"));
1416                }
1417                if num {
1418                    return err(format!("expected a space before '-' (line {line_no})"));
1419                }
1420                neg = true;
1421            }
1422            b' ' | b'\t' => {
1423                if num {
1424                    dst.push(if neg { -i } else { i });
1425                    num = false;
1426                    neg = false;
1427                    i = 0;
1428                }
1429            }
1430            _ => {
1431                return err(format!(
1432                    "unexpected char '{}' in space-separated list of integers (line {line_no})",
1433                    c as char,
1434                ));
1435            }
1436        }
1437    }
1438    if num {
1439        dst.push(if neg { -i } else { i });
1440    }
1441
1442    Ok(())
1443}
1444
1445#[cfg(test)]
1446mod test {
1447    use super::*;
1448
1449    #[test]
1450    fn test_encode_7bit() -> io::Result<()> {
1451        let mut buf: Vec<u8> = Vec::new();
1452
1453        if usize::BITS == 64 {
1454            encode_7bit(&mut buf, usize::MAX)?;
1455            assert_eq!(
1456                &buf[..],
1457                &[
1458                    0b0000_0011, //  1
1459                    0b1111_1111, //  8
1460                    0b1111_1111, // 15
1461                    0b1111_1111, // 22
1462                    0b1111_1111, // 29
1463                    0b1111_1111, // 36
1464                    0b1111_1111, // 43
1465                    0b1111_1111, // 50
1466                    0b1111_1111, // 57
1467                    0b1111_1110, // 64
1468                ]
1469            );
1470        }
1471
1472        buf.clear();
1473        encode_7bit(&mut buf, 0)?;
1474        assert_eq!(&buf[..], &[0, 0]); // 2 times 0 due to escaping
1475
1476        buf.clear();
1477        encode_7bit(&mut buf, 1)?;
1478        assert_eq!(&buf[..], &[0b10]);
1479
1480        Ok(())
1481    }
1482
1483    #[test]
1484    fn test_decode_7bit() -> io::Result<()> {
1485        if usize::BITS == 64 {
1486            let b = [
1487                0b0000_0011, //  1
1488                0b1111_1111, //  8
1489                0b1111_1111, // 15
1490                0b1111_1111, // 22
1491                0b1111_1111, // 29
1492                0b1111_1111, // 36
1493                0b1111_1111, // 43
1494                0b1111_1111, // 50
1495                0b1111_1111, // 57
1496                0b1111_1110, // 64
1497            ];
1498            assert_eq!(decode_7bit(&b[..])?, usize::MAX);
1499
1500            let b = [
1501                0b0000_0101, //  1
1502                0b0000_0001, //  8
1503                0b0000_0001, // 15
1504                0b0000_0001, // 22
1505                0b0000_0001, // 29
1506                0b0000_0001, // 36
1507                0b0000_0001, // 43
1508                0b0000_0001, // 50
1509                0b0000_0001, // 57
1510                0b0000_0000, // 64
1511            ];
1512            assert!(decode_7bit(&b[..]).is_err());
1513        }
1514
1515        // 2 times 0 due to escaping
1516        assert_eq!(decode_7bit([0, 0].as_slice())?, 0);
1517
1518        assert_eq!(decode_7bit([0b10].as_slice())?, 1);
1519
1520        Ok(())
1521    }
1522
1523    // spell-checker:disable
1524
1525    #[test]
1526    fn test_trim_start() {
1527        assert_eq!(trim_start(b""), b"");
1528        assert_eq!(trim_start(b"abc"), b"abc");
1529        assert_eq!(trim_start(b"abc 123"), b"abc 123");
1530        assert_eq!(trim_start(b" abc 123"), b"abc 123");
1531        assert_eq!(trim_start(b"abc 123 "), b"abc 123 ");
1532        assert_eq!(trim_start(b"\t foo bar \t"), b"foo bar \t");
1533    }
1534
1535    #[test]
1536    fn test_trim_end() {
1537        assert_eq!(trim_end(b""), b"");
1538        assert_eq!(trim_end(b"abc"), b"abc");
1539        assert_eq!(trim_end(b"abc 123"), b"abc 123");
1540        assert_eq!(trim_end(b" abc 123"), b" abc 123");
1541        assert_eq!(trim_end(b"abc 123 "), b"abc 123");
1542        assert_eq!(trim_end(b"\t foo bar \t"), b"\t foo bar");
1543    }
1544
1545    #[test]
1546    fn test_trim() {
1547        assert_eq!(trim(b""), b"");
1548        assert_eq!(trim(b"abc"), b"abc");
1549        assert_eq!(trim(b"abc 123"), b"abc 123");
1550        assert_eq!(trim(b" abc 123"), b"abc 123");
1551        assert_eq!(trim(b"abc 123 "), b"abc 123");
1552        assert_eq!(trim(b"\t foo bar \t"), b"foo bar");
1553    }
1554
1555    #[test]
1556    fn test_parse_str_list() {
1557        assert_eq!(parse_str_list(b"", 0), Vec::<String>::new());
1558        assert_eq!(&parse_str_list(b"abc 123", 0)[..], ["abc", "123"]);
1559        assert_eq!(&parse_str_list(b" abc 123 ", 0)[..], ["abc", "123"]);
1560        assert_eq!(
1561            &parse_str_list(" a.-bc\tUniversität ".as_bytes(), 2)[..],
1562            ["a.-bc", "Universität"]
1563        );
1564    }
1565
1566    #[test]
1567    fn test_parse_single_u32() -> io::Result<()> {
1568        assert_eq!(parse_single_u32(b"0", 42)?, 0);
1569        assert_eq!(parse_single_u32(b"00", 42)?, 0);
1570        assert_eq!(parse_single_u32(b"123", 42)?, 123);
1571        assert_eq!(
1572            parse_single_u32(u32::MAX.to_string().as_bytes(), 42)?,
1573            u32::MAX
1574        );
1575
1576        assert!(parse_single_u32(b"", 42).is_err());
1577        assert!(parse_single_u32(b"42a", 42).is_err());
1578
1579        assert!(parse_single_u32((u32::MAX as u64 + 1).to_string().as_bytes(), 42).is_err());
1580
1581        Ok(())
1582    }
1583
1584    #[test]
1585    fn test_parse_single_usize() -> io::Result<()> {
1586        assert_eq!(parse_single_usize(b"0", 42)?, 0);
1587        assert_eq!(parse_single_usize(b"00", 42)?, 0);
1588        assert_eq!(parse_single_usize(b"123", 42)?, 123);
1589        assert_eq!(
1590            parse_single_usize(usize::MAX.to_string().as_bytes(), 42)?,
1591            usize::MAX
1592        );
1593
1594        assert!(parse_single_usize(b"", 42).is_err());
1595        assert!(parse_single_usize(b"42a", 42).is_err());
1596
1597        if u128::BITS > usize::BITS {
1598            assert!(
1599                parse_single_usize((usize::MAX as u128 + 1).to_string().as_bytes(), 42).is_err()
1600            );
1601        }
1602
1603        Ok(())
1604    }
1605
1606    #[test]
1607    fn test_parse_u32() -> io::Result<()> {
1608        assert_eq!(parse_u32(b"0", 42)?, (&b""[..], 0));
1609        assert_eq!(parse_u32(b"00", 42)?, (&b""[..], 0));
1610        assert_eq!(parse_u32(b"123", 42)?, (&b""[..], 123));
1611        assert_eq!(
1612            parse_u32(u32::MAX.to_string().as_bytes(), 42)?,
1613            (&b""[..], u32::MAX)
1614        );
1615
1616        let (rest, v) = parse_u32(b" 023 9 \t432\t", 42)?;
1617        assert_eq!(rest, b" 9 \t432\t");
1618        assert_eq!(v, 23);
1619        let (rest, v) = parse_u32(rest, 42)?;
1620        assert_eq!(rest, b" \t432\t");
1621        assert_eq!(v, 9);
1622        let (rest, v) = parse_u32(rest, 42)?;
1623        assert_eq!(rest, b"\t");
1624        assert_eq!(v, 432);
1625        assert!(parse_u32(rest, 42).is_err());
1626
1627        assert!(parse_u32(b"", 42).is_err());
1628        assert!(parse_u32(b"42a", 42).is_err());
1629
1630        assert!(parse_u32((u32::MAX as u64 + 1).to_string().as_bytes(), 42).is_err());
1631
1632        Ok(())
1633    }
1634
1635    #[test]
1636    fn test_parse_usize() -> io::Result<()> {
1637        assert_eq!(parse_usize(b"0", 42)?, (&b""[..], 0));
1638        assert_eq!(parse_usize(b"00", 42)?, (&b""[..], 0));
1639        assert_eq!(parse_usize(b"123", 42)?, (&b""[..], 123));
1640        assert_eq!(
1641            parse_usize(usize::MAX.to_string().as_bytes(), 42)?,
1642            (&b""[..], usize::MAX)
1643        );
1644
1645        let (rest, v) = parse_usize(b" 023 9 \t432\t", 42)?;
1646        assert_eq!(rest, b" 9 \t432\t");
1647        assert_eq!(v, 23);
1648        let (rest, v) = parse_usize(rest, 42)?;
1649        assert_eq!(rest, b" \t432\t");
1650        assert_eq!(v, 9);
1651        let (rest, v) = parse_usize(rest, 42)?;
1652        assert_eq!(rest, b"\t");
1653        assert_eq!(v, 432);
1654        assert!(parse_usize(rest, 42).is_err());
1655
1656        assert!(parse_usize(b"", 42).is_err());
1657        assert!(parse_usize(b"42a", 42).is_err());
1658
1659        if u128::BITS > usize::BITS {
1660            assert!(parse_usize((usize::MAX as u128 + 1).to_string().as_bytes(), 42).is_err());
1661        }
1662
1663        Ok(())
1664    }
1665
1666    #[test]
1667    fn test_parse_u32_list() -> io::Result<()> {
1668        assert_eq!(&parse_u32_list(b"", 0, 42)?[..], []);
1669        assert_eq!(&parse_u32_list(b" \t ", 1, 42)?[..], []);
1670        assert_eq!(&parse_u32_list(b" 123 \t 432 ", 2, 42)?[..], [123, 432]);
1671        assert_eq!(&parse_u32_list(b"0 00", 1, 42)?[..], [0, 0]);
1672        assert_eq!(
1673            &parse_u32_list(u32::MAX.to_string().as_bytes(), 1, 42)?[..],
1674            [u32::MAX]
1675        );
1676
1677        assert!(parse_u32_list(b"123 42a", 2, 42).is_err());
1678        assert!(parse_u32_list(b"123 -42", 2, 42).is_err());
1679
1680        assert!(parse_u32_list((u32::MAX as u64 + 1).to_string().as_bytes(), 1, 42).is_err());
1681
1682        Ok(())
1683    }
1684
1685    #[test]
1686    fn test_parse_edge_list() -> io::Result<()> {
1687        let mut res = Vec::with_capacity(32);
1688
1689        parse_edge_list(b"", &mut res, 42)?;
1690        assert_eq!(&res[..], []);
1691        parse_edge_list(b" \t ", &mut res, 42)?;
1692        assert_eq!(&res[..], []);
1693        parse_edge_list(b" 123 \t -432 ", &mut res, 42)?;
1694        assert_eq!(&res[..], [123, -432,]);
1695        res.clear();
1696        parse_edge_list(b"01 -02", &mut res, 42)?;
1697        assert_eq!(&res[..], [1, -2,]);
1698        res.clear();
1699        parse_edge_list(b"0 -0", &mut res, 42)?;
1700        assert_eq!(&res[..], [0, 0,]);
1701        res.clear();
1702        parse_edge_list(isize::MAX.to_string().as_bytes(), &mut res, 42)?;
1703        assert_eq!(&res[..], [isize::MAX]);
1704        res.clear();
1705        parse_edge_list(format!("-{}", isize::MAX).as_bytes(), &mut res, 42)?;
1706        assert_eq!(&res[..], [-isize::MAX]);
1707
1708        assert!(parse_edge_list(b"123 42a", &mut res, 42).is_err());
1709        assert!(parse_edge_list(b"123 4-2", &mut res, 42).is_err());
1710
1711        let isize_min = isize::MIN.to_string();
1712        assert!(parse_edge_list(isize_min.as_bytes(), &mut res, 42).is_err());
1713        assert!(parse_edge_list(isize_min[1..].as_bytes(), &mut res, 42).is_err());
1714
1715        Ok(())
1716    }
1717
1718    // spell-checker:enable
1719}