Skip to main content

oxidd_dump/dddmp/
import.rs

1use std::io;
2
3use is_sorted::IsSorted;
4
5use oxidd_core::error::OutOfMemory;
6use oxidd_core::function::{ETagOfFunc, EdgeOfFunc, Function, INodeOfFunc, TermOfFunc};
7use oxidd_core::util::{AllocResult, EdgeDropGuard, EdgeVecDropGuard};
8use oxidd_core::{DiagramRules, Edge, HasLevel, InnerNode, LevelNo, Manager, VarNo};
9
10use crate::ParseTagged;
11
12use super::{Code, VarInfo};
13
14// spell-checker:dictionaries dddmp
15
16/// Compare, mapping `Equal` to `Greater`
17fn cmp_strict<T: Ord>(a: &T, b: &T) -> Option<std::cmp::Ordering> {
18    Some(a.cmp(b).then(std::cmp::Ordering::Greater))
19}
20
21/// Helper function to return a parse error
22fn err<T>(msg: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Result<T> {
23    Err(io::Error::new(io::ErrorKind::InvalidData, msg))
24}
25
26/// Information from the header of a DDDMP file
27#[derive(Debug)]
28pub struct DumpHeader {
29    /// Whether this is ASCII or binary mode (from `.mode A|B`)
30    ascii: bool,
31    varinfo: VarInfo,
32    /// Decision diagram name (optional)
33    dd: String,
34    nnodes: usize,
35    nvars: u32,
36    /// Support variable IDs (in strictly ascending order)
37    ids: Vec<VarNo>,
38    /// Mapping from positions to support variables
39    support_var_order: Vec<VarNo>,
40    /// Mapping from support variables to levels
41    permids: Vec<LevelNo>,
42    auxids: Vec<u32>, // optional
43    /// Variable names in the original order (optional)
44    varnames: Vec<String>,
45    rootids: Vec<isize>,
46    rootnames: Vec<String>, // optional
47
48    lines: usize, // number of header lines (including `.nodes`)
49}
50
51impl DumpHeader {
52    /// Load the DDDMP header of the given input
53    ///
54    /// This always needs to be called before [`import()`] to retrieve some
55    /// necessary information and to position the `input`'s cursor.
56    pub fn load(mut input: impl io::BufRead) -> io::Result<Self> {
57        let mut header = DumpHeader {
58            ascii: true,
59            varinfo: VarInfo::None,
60            dd: String::new(),
61            nnodes: 0,
62            nvars: 0,
63            ids: Vec::new(),
64            support_var_order: Vec::new(),
65            permids: Vec::new(),
66            auxids: Vec::new(),
67            varnames: Vec::new(),
68            rootids: Vec::new(),
69            rootnames: Vec::new(),
70            lines: 1,
71        };
72        let mut nsuppvars = 0;
73        let mut nroots = 0;
74        let mut suppvarnames = Vec::new();
75        let mut orderedvarnames = Vec::new();
76
77        let mut line = Vec::new();
78        let mut line_no = 1usize;
79
80        loop {
81            match input.read_until(b'\n', &mut line) {
82                Ok(0) => return err("unexpected end of file"),
83                Ok(_) => {}
84                Err(e) => return Err(e),
85            }
86            while let Some(b'\n' | b'\r') = line.last() {
87                line.pop();
88            }
89
90            let (key, value) = match memchr::memchr2(b' ', b'\t', &line) {
91                Some(p) => (&line[..p], &line[p + 1..]),
92                None => (&line[..], [].as_slice()),
93            };
94            let value = trim(value);
95
96            // we don't check for duplicate entries; the last one counts
97            match key {
98                b".ver" => match value {
99                    b"DDDMP-2.0" | b"DDDMP-3.0" => {}
100                    _ => {
101                        return err(format!(
102                            "unsupported version '{}' (line {line_no})",
103                            String::from_utf8_lossy(value)
104                        ))
105                    }
106                },
107                b".mode" => {
108                    header.ascii = match value {
109                        b"A" => true,
110                        b"B" => false,
111                        _ => {
112                            return err(format!(
113                                "unknown value '{}' for key '.mode' (line {line_no})",
114                                String::from_utf8_lossy(value),
115                            ))
116                        }
117                    };
118                }
119                b".varinfo" => {
120                    header.varinfo = match value {
121                        b"0" => VarInfo::VariableID,
122                        b"1" => VarInfo::PermutationID,
123                        b"2" => VarInfo::AuxiliaryID,
124                        b"3" => VarInfo::VariableName,
125                        b"4" => VarInfo::None,
126                        _ => {
127                            return err(format!(
128                                "unknown value '{}' for key '.varinfo' (line {line_no})",
129                                String::from_utf8_lossy(value),
130                            ))
131                        }
132                    };
133                }
134                b".dd" => header.dd = String::from_utf8_lossy(value).to_string(),
135                b".nnodes" => header.nnodes = parse_single_usize(value, line_no)?,
136                b".nvars" => header.nvars = parse_single_u32(value, line_no)?,
137                b".nsuppvars" => nsuppvars = parse_single_u32(value, line_no)?,
138                b".varnames" => header.varnames = parse_str_list(value, header.nvars as usize),
139                b".suppvarnames" => suppvarnames = parse_str_list(value, nsuppvars as usize),
140                b".orderedvarnames" => {
141                    orderedvarnames = parse_str_list(value, header.nvars as usize)
142                }
143                b".ids" => {
144                    header.ids = parse_u32_list(value, nsuppvars as usize, line_no)?;
145                }
146                b".permids" => {
147                    header.permids = parse_u32_list(value, nsuppvars as usize, line_no)?;
148                }
149                b".auxids" => {
150                    header.auxids = parse_u32_list(value, nsuppvars as usize, line_no)?;
151                }
152                b".nroots" => nroots = parse_single_usize(value, line_no)?,
153                b".rootids" => {
154                    header.rootids.clear();
155                    header.rootids.reserve(nroots);
156                    parse_edge_list(value, &mut header.rootids, line_no)?;
157                }
158                b".rootnames" => header.rootnames = parse_str_list(value, nroots),
159                b".nodes" => break,
160                _ => {
161                    return err(format!(
162                        "unknown key '{}' (line {line_no})",
163                        String::from_utf8_lossy(key),
164                    ))
165                }
166            }
167
168            line_no += 1;
169            line.clear();
170        }
171
172        header.lines = line_no;
173
174        // validation
175
176        if nsuppvars > header.nvars {
177            return err(format!(
178                ".nsuppvars ({nsuppvars}) must not be greater than .nvars ({})",
179                header.nvars,
180            ));
181        }
182
183        if header.ids.len() != nsuppvars as usize {
184            return err(format!(
185            "number of support variables in .ids entry ({}) does not match .nsuppvars ({nsuppvars})",
186            header.ids.len(),
187        ));
188        }
189        if header.permids.len() != nsuppvars as usize {
190            return err(format!(
191            "number of support variables in .permids entry ({}) does not match .nsuppvars ({nsuppvars})",
192            header.permids.len(),
193        ));
194        }
195        if !header.auxids.is_empty() && header.auxids.len() != nsuppvars as usize {
196            return err(format!(
197            "number of support variables in .auxids entry ({}) does not match .nsuppvars ({nsuppvars})",
198            header.auxids.len(),
199        ));
200        }
201
202        if !header.ids.is_empty() {
203            if !IsSorted::is_sorted_by(&mut header.ids.iter(), cmp_strict) {
204                return err("support variables in .ids must be ascending");
205            }
206            if *header.ids.last().unwrap() >= header.nvars {
207                return err(format!(
208                    "support variables in .ids must be less than .nvars ({})",
209                    header.nvars,
210                ));
211            }
212        }
213
214        let mut level_count = vec![0u32; header.nvars as usize];
215        for &level in &header.permids {
216            let Some(count) = level_count.get_mut(level as usize) else {
217                return err(format!(
218                    "levels in .permids must be less than .nvars ({})",
219                    header.nvars,
220                ));
221            };
222            if *count != 0 {
223                return err(format!("level ({level}) occurs twice in .permids"));
224            }
225            *count = 1;
226        }
227        // accumulate the counts such that level_count maps from the level
228        // number to the position
229        let mut count = 0;
230        for i in level_count.iter_mut() {
231            let present = *i;
232            *i = count;
233            count += present;
234        }
235        header.support_var_order.resize(nsuppvars as usize, 0);
236        for (&var, &level) in header.ids.iter().zip(&header.permids) {
237            header.support_var_order[level_count[level as usize] as usize] = var;
238        }
239        drop(level_count);
240
241        if !orderedvarnames.is_empty() && orderedvarnames.len() != header.nvars as usize {
242            return err(format!(
243                "number of variables in .orderedvarnames entry ({}) does not match .nvars ({})",
244                orderedvarnames.len(),
245                header.nvars
246            ));
247        }
248        if !suppvarnames.is_empty() && suppvarnames.len() != nsuppvars as usize {
249            return err(format!(
250                "number of variables in .suppvarnames entry ({}) does not match .nsuppvars ({nsuppvars})",
251                suppvarnames.len(),
252            ));
253        }
254
255        'var_names: {
256            if header.varnames.is_empty() {
257                if orderedvarnames.is_empty() {
258                    if suppvarnames.is_empty() {
259                        break 'var_names;
260                    }
261                    debug_assert!(!suppvarnames.is_empty());
262                    header.varnames = vec![String::new(); header.nvars as usize];
263                    for (name, &target) in suppvarnames.into_iter().zip(&header.ids) {
264                        header.varnames[target as usize] = name;
265                    }
266                    break 'var_names;
267                }
268
269                // Note that `.ids` and `.permids` only define the positions of
270                // support variables. For all other variables, we use the
271                // relative ordering from `.orderedvarnames`.
272                header.varnames = vec![String::new(); header.nvars as usize];
273                for (&id, &permid) in header.ids.iter().zip(&header.permids) {
274                    header.varnames[id as usize] =
275                        std::mem::take(&mut orderedvarnames[permid as usize]);
276                }
277
278                let mut non_suppvarnames = orderedvarnames.into_iter().filter(|s| !s.is_empty());
279                for name in &mut header.varnames {
280                    if name.is_empty() {
281                        *name = non_suppvarnames.next().unwrap();
282                    }
283                }
284            } else {
285                if header.varnames.len() != header.nvars as usize {
286                    return err(format!(
287                        "number of variables in .varnames entry ({}) does not match .nvars ({})",
288                        header.varnames.len(),
289                        header.nvars
290                    ));
291                }
292
293                if !orderedvarnames.is_empty() {
294                    // Check that the names for the support agree in `.varnames`
295                    // and `.orderedvarnames`. For all remaining variables, we
296                    // could only perform a matching provided that the names are
297                    // actually unique. However, since the variables are unused,
298                    // a mismatch would not lead to wrong semantics of the
299                    // imported decision diagram functions.
300                    for (&id, &permid) in header.ids.iter().zip(&header.permids) {
301                        let name = header.varnames[id as usize].as_str();
302                        let order_name = orderedvarnames[permid as usize].as_str();
303                        if name != order_name {
304                            return err(format!(
305                                ".varnames and .orderedvarnames do not match \
306                            (variable {id} has name '{name}', but the entry at \
307                            position {permid} of .orderedvarnames is \
308                            '{order_name}'"
309                            ));
310                        }
311                    }
312                }
313            }
314
315            // no special check whether suppvarnames is empty needed here
316            for (i, (name, &id)) in suppvarnames.into_iter().zip(&header.ids).enumerate() {
317                let expected = header.varnames[id as usize].as_str();
318                if name != expected {
319                    return err(format!(
320                        ".suppvarnames and .varnames/.orderedvarnames do not \
321                        match (entry {i} of .suppvarnames is '{name}' \
322                        but the variable ID is {id} with name '{expected}')"
323                    ));
324                }
325            }
326        }
327
328        if header.rootids.len() != nroots {
329            return err(format!(
330                "number of roots in .rootids entry ({}) does not match .nroots ({nroots})",
331                header.rootids.len(),
332            ));
333        }
334        for &id in &header.rootids {
335            if id == 0 {
336                return err(".rootids must not be 0");
337            }
338            if id.unsigned_abs() > header.nnodes {
339                return err(format!(
340                    "entry in .rootids out of range ({id} > {})",
341                    header.nnodes,
342                ));
343            }
344        }
345        if !header.rootnames.is_empty() && header.rootnames.len() != nroots {
346            return err(format!(
347                "number of roots in .rootnames entry ({}) does not match .nroots ({nroots})",
348                header.rootnames.len(),
349            ));
350        }
351
352        Ok(header)
353    }
354
355    /// Name of the decision diagram
356    ///
357    /// Corresponds to the DDDMP `.dd` field.
358    pub fn diagram_name(&self) -> Option<&str> {
359        if self.dd.is_empty() {
360            None
361        } else {
362            Some(&self.dd)
363        }
364    }
365
366    /// Number of nodes in the dumped decision diagram
367    ///
368    /// Corresponds to the DDDMP `.nnodes` field.
369    pub fn num_nodes(&self) -> usize {
370        self.nnodes
371    }
372
373    /// Number of all variables in the exported decision diagram
374    ///
375    /// Corresponds to the DDDMP `.nvars` field.
376    pub fn num_vars(&self) -> u32 {
377        self.nvars
378    }
379
380    /// Number of variables in the true support of the decision diagram
381    ///
382    /// Corresponds to the DDDMP `.nsuppvars` field.
383    pub fn num_support_vars(&self) -> u32 {
384        self.ids.len() as _
385    }
386
387    /// Variables in the true support of the decision diagram. Concretely, these
388    /// are indices of the original variable numbering. Hence, the returned
389    /// slice contains [`DumpHeader::num_support_vars()`] integers in strictly
390    /// ascending order.
391    ///
392    /// Example: Consider a decision diagram that was created with the variables
393    /// `x`, `y`, and `z`, in this order (`x` is the top-most variable). Suppose
394    /// that only `y` and `z` are used by the dumped functions. Then, the
395    /// returned slice is `[1, 2]`, regardless of any subsequent reordering.
396    ///
397    /// Corresponds to the DDDMP `.ids` field.
398    pub fn support_vars(&self) -> &[VarNo] {
399        &self.ids
400    }
401
402    /// Order of the support variables
403    ///
404    /// The returned slice is always [`DumpHeader::num_support_vars()`] elements
405    /// long and represents a mapping from positions to variable numbers.
406    ///
407    /// Example: Consider a decision diagram that was created with the variables
408    /// `x`, `y`, and `z` (`x` is the top-most variable). The variables were
409    /// re-ordered to `z`, `x`, `y`. Suppose that only `y` and `z` are used by
410    /// the dumped functions. Then, the returned slice is `[2, 1]`.
411    pub fn support_var_order(&self) -> &[VarNo] {
412        &self.support_var_order
413    }
414
415    /// Mapping from the variables in the true support of the decision diagram
416    /// to their levels.
417    ///
418    /// The returned slice is always [`DumpHeader::num_support_vars()`]
419    /// elements long. If the value at the `i`th index is `l`, then the `i`th
420    /// support variable is at level `l` in the dumped decision diagram. By the
421    /// `i`th support variable, we mean the variable `header.support_vars()[i]`
422    /// in the original numbering.
423    ///
424    /// Example: Consider a decision diagram that was created with the variables
425    /// `x`, `y`, and `z` (`x` is the top-most variable). The variables were
426    /// re-ordered to `z`, `x`, `y`. Suppose that only `y` and `z` are used by
427    /// the dumped functions. Then, the returned slice is `[2, 0]`.
428    ///
429    /// Corresponds to the DDDMP `.permids` field.
430    pub fn support_var_to_level(&self) -> &[LevelNo] {
431        &self.permids
432    }
433    /// Deprecated alias for [`Self::support_var_to_level()`]
434    #[deprecated(since = "0.11.0", note = "use support_var_to_level instead")]
435    pub fn support_var_permutation(&self) -> &[LevelNo] {
436        &self.permids
437    }
438
439    /// Auxiliary variable IDs. The returned slice contains
440    /// [`DumpHeader::num_support_vars()`] elements.
441    ///
442    /// Corresponds to the DDDMP `.auxids` field.
443    pub fn auxiliary_var_ids(&self) -> &[u32] {
444        &self.auxids
445    }
446
447    /// Names of all variables in the decision diagram. If
448    /// present, the returned slice contains [`DumpHeader::num_vars()`]
449    /// many elements. The order is the "original" variable order.
450    ///
451    /// Corresponds to the DDDMP `.varnames` field, but `.orderedvarnames` and
452    /// `.suppvarnames` are also considered if one of the fields is missing. All
453    /// variable names are non-empty unless only `.suppvarnames` is given in the
454    /// input (in which case only the names of support variables are non-empty).
455    /// The return value is only `None` if neither of `.varnames`,
456    /// `.orderedvarnames`, and `.suppvarnames` is present in the input.
457    pub fn var_names(&self) -> Option<&[String]> {
458        if self.varnames.is_empty() {
459            None
460        } else {
461            Some(&self.varnames[..])
462        }
463    }
464
465    /// Number of roots
466    ///
467    /// [`import()`] returns this number of roots on success.
468    /// Corresponds to the DDDMP `.nroots` field.
469    pub fn num_roots(&self) -> usize {
470        self.rootids.len()
471    }
472
473    /// Names of roots, if present, in the same order as returned by
474    /// [`import()`]
475    ///
476    /// Corresponds to the DDDMP `.rootnames` field.
477    pub fn root_names(&self) -> Option<&[String]> {
478        if self.rootnames.is_empty() {
479            None
480        } else {
481            Some(&self.rootnames[..])
482        }
483    }
484}
485
486/// Import the decision diagram from `input` into `manager` after loading
487/// `header` from `input`
488///
489/// Important: there must not be any read/seek/... operations on `input` after
490/// reading `header` using [`DumpHeader::load()`].
491///
492/// `support_vars` can be used to adjust the mapping from support variables in
493/// the DDDMP file to variables in the manager. The iterator must yield
494/// [`header.num_support_vars()`][DumpHeader::num_support_vars()] variable
495/// numbers valid in the manager. If the `i`-th value is `v`, then the `i`-th
496/// support variable (see [`DumpHeader::support_vars()`]) will be mapped to
497/// variable `v` in `manager`. In the simplest case,
498/// [`header.support_var_order()`][DumpHeader::support_var_order()] can be used.
499///
500/// Note that the support variables must also be ordered by their current level
501/// (lower level numbers first). To this end, you can use
502/// [`oxidd_reorder::set_var_order()`][set_var_order] with `support_vars`.
503///
504/// `complement` is a function that returns the complemented edge for a given
505/// edge.
506///
507/// [set_var_order]: https://docs.rs/oxidd-reorder/latest/oxidd_reorder/fn.set_var_order.html
508pub fn import<'id, F: Function>(
509    mut input: impl io::BufRead,
510    header: &DumpHeader,
511    manager: &F::Manager<'id>,
512    support_vars: impl IntoIterator<Item = VarNo>,
513    complement: impl Fn(&F::Manager<'id>, EdgeOfFunc<'id, F>) -> AllocResult<EdgeOfFunc<'id, F>>,
514) -> io::Result<Vec<F>>
515where
516    INodeOfFunc<'id, F>: HasLevel,
517    TermOfFunc<'id, F>: ParseTagged<ETagOfFunc<'id, F>>,
518{
519    let suppvar_level_map =
520        Vec::from_iter(support_vars.into_iter().map(|v| manager.var_to_level(v)));
521    assert_eq!(
522        suppvar_level_map.len(),
523        header.ids.len(),
524        "`support_vars` must provide one target variable per support variable in the DDDMP file",
525    );
526    assert!(
527        IsSorted::is_sorted_by(&mut suppvar_level_map.iter(), cmp_strict),
528        "`support_vars` must be sorted by the variables' current level",
529    );
530
531    let nodes = if header.ascii {
532        import_ascii::<F::Manager<'id>>(
533            &mut input,
534            header,
535            manager,
536            &suppvar_level_map,
537            &complement,
538        )
539    } else {
540        let mut level_suppvar_map = vec![u32::MAX; manager.num_levels() as usize];
541        for (i, &level) in suppvar_level_map.iter().enumerate() {
542            level_suppvar_map[level as usize] = i as u32;
543        }
544        import_bin::<F::Manager<'id>>(
545            &mut input,
546            header,
547            manager,
548            &level_suppvar_map,
549            &suppvar_level_map,
550            &complement,
551        )
552    }?;
553    let nodes = EdgeVecDropGuard::new(manager, nodes);
554    debug_assert_eq!(nodes.len(), header.nnodes);
555
556    if !reads_expected(input, b".end")? {
557        return err("file must end with '.end'");
558    }
559
560    let mut roots = Vec::with_capacity(header.rootids.len());
561    for &root in &header.rootids {
562        debug_assert_ne!(root, 0);
563        let node_index = root.unsigned_abs() - 1;
564        let e = manager.clone_edge(&nodes[node_index]);
565        roots.push(F::from_edge(
566            manager,
567            if root > 0 { e } else { complement(manager, e)? },
568        ));
569    }
570
571    Ok(roots)
572}
573
574/// Check if the remaining bytes in `file` are `expected` plus whitespace
575/// characters only
576fn reads_expected(mut file: impl io::Read, expected: &[u8]) -> io::Result<bool> {
577    let mut buf = Vec::with_capacity(expected.len() + 2);
578    file.read_to_end(&mut buf)?;
579    Ok(buf.starts_with(expected) && buf[expected.len()..].iter().all(u8::is_ascii_whitespace))
580}
581
582fn import_ascii<M: Manager>(
583    mut input: impl io::BufRead,
584    header: &DumpHeader,
585    manager: &M,
586    suppvar_level_map: &[LevelNo],
587    complement: impl Fn(&M, M::Edge) -> AllocResult<M::Edge>,
588) -> io::Result<Vec<M::Edge>>
589where
590    M::InnerNode: HasLevel,
591    M::Terminal: ParseTagged<M::EdgeTag>,
592{
593    let mut nodes = EdgeVecDropGuard::new(manager, Vec::with_capacity(header.nnodes));
594    let mut line = Vec::new();
595    let mut line_no = header.lines + 1;
596    let mut children = Vec::with_capacity(M::InnerNode::ARITY);
597    for node_id in 1..=header.nnodes {
598        match input.read_until(b'\n', &mut line) {
599            Ok(0) => return err("unexpected end of file"),
600            Ok(_) => {}
601            Err(e) => return Err(e),
602        }
603        while let Some(b'\n' | b'\r') = line.last() {
604            line.pop();
605        }
606
607        let (rest, node_id_lineno) = parse_usize(&line, line_no)?;
608        if node_id_lineno != node_id {
609            return err(format!("expected node ID {node_id} on line {line_no}"));
610        }
611
612        let rest = trim_start(rest);
613        let rest = match header.varinfo {
614            VarInfo::VariableID
615            | VarInfo::PermutationID
616            | VarInfo::AuxiliaryID
617            | VarInfo::VariableName => match memchr::memchr2(b' ', b'\t', rest) {
618                Some(pos) => &rest[pos + 1..],
619                None => {
620                    return err(format!(
621                        "expected a space after variable extra info (line {line_no})"
622                    ))
623                }
624            },
625            VarInfo::None => rest,
626        };
627
628        let rest = trim_start(rest);
629        let (rest, var_id) = match memchr::memchr2(b' ', b'\t', rest) {
630            Some(pos) => (&rest[pos + 1..], &rest[..pos]),
631            None => {
632                return err(format!(
633                    "expected a space after variable internal index (line {line_no})"
634                ))
635            }
636        };
637
638        parse_edge_list(rest, &mut children, line_no)?;
639        if children.len() != M::InnerNode::ARITY {
640            return err(format!(
641                "expected {} children, got {} (line {line_no})",
642                M::InnerNode::ARITY,
643                children.len()
644            ));
645        }
646
647        let node = if children.contains(&0) {
648            // terminal
649            let string = match std::str::from_utf8(var_id) {
650                Ok(s) => s,
651                Err(_) => {
652                    return err(format!(
653                        "terminal description must be valid UTF-8 (line {line_no})"
654                    ));
655                }
656            };
657            let Some((terminal, tag)) = M::Terminal::parse(string) else {
658                return err(format!(
659                    "invalid terminal description '{string}' (line {line_no})"
660                ));
661            };
662            manager.get_terminal(terminal)?.with_tag_owned(tag)
663        } else {
664            let (_, var_id) = parse_u32(var_id, line_no)?;
665            let Some(&level) = suppvar_level_map.get(var_id as usize) else {
666                return err(format!("variable out of range (line {line_no})"));
667            };
668
669            for &child in &children {
670                let child = child.unsigned_abs();
671                if child >= node_id {
672                    return err(format!(
673                        "children ids must be less than node ({child} >= {node_id}, line {line_no})",
674                    ));
675                }
676                let child_level = manager.get_node(&nodes[child - 1]).level();
677                if level >= child_level {
678                    return err(format!(
679                        "node level must be less than the children's levels ({level} >= {child_level}, line {line_no})",
680                    ));
681                }
682            }
683
684            <M::Rules as DiagramRules<_, _, _>>::reduce(
685                manager,
686                level,
687                children.iter().map(|&child| {
688                    debug_assert_ne!(child, 0);
689                    let e = manager.clone_edge(&nodes[child.unsigned_abs() - 1]);
690                    if child < 0 {
691                        complement(manager, e).unwrap()
692                    } else {
693                        e
694                    }
695                }),
696            )
697            .then_insert(manager, level)?
698        };
699        nodes.push(node);
700
701        children.clear();
702        line.clear();
703        line_no += 1;
704    }
705
706    Ok(nodes.into_vec())
707}
708
709fn import_bin<M: Manager>(
710    mut input: impl io::BufRead,
711    header: &DumpHeader,
712    manager: &M,
713    level_suppvar_map: &[u32],
714    suppvar_level_map: &[LevelNo],
715    complement: impl Fn(&M, M::Edge) -> AllocResult<M::Edge>,
716) -> io::Result<Vec<M::Edge>>
717where
718    M::InnerNode: HasLevel,
719{
720    assert_eq!(
721        M::InnerNode::ARITY,
722        2,
723        "binary mode is (currently) only supported for binary nodes"
724    );
725    let terminal = {
726        let msg = "binary mode is (currently) only supported for diagrams with a single terminal";
727        let mut it = manager.terminals();
728        let t = EdgeDropGuard::new(manager, it.next().expect(msg));
729        if let Some(t) = it.next() {
730            manager.drop_edge(t);
731            panic!("{msg}");
732        }
733        t
734    };
735
736    fn idx(input: impl io::BufRead, node_id: usize, code: Code) -> io::Result<usize> {
737        debug_assert!(node_id >= 1);
738        let id = match code {
739            Code::Terminal => 1,
740            Code::AbsoluteID => decode_7bit(input)?,
741            Code::RelativeID => node_id - decode_7bit(input)?,
742            Code::Relative1 => node_id - 1,
743        };
744        if id == 0 {
745            return err("then/else ID must not be 0");
746        }
747        if id >= node_id {
748            return err("then/else ID too large");
749        }
750        Ok((id - 1) as usize)
751    }
752
753    let mut nodes = EdgeVecDropGuard::new(manager, Vec::with_capacity(header.nnodes));
754    for node_id in 1..=header.nnodes {
755        let node_code = read_unescape(&mut input)?;
756        let var_code = Code::from((node_code >> 5) & 0b11);
757        let t_code = Code::from((node_code >> 3) & 0b11);
758        let e_complement = ((node_code >> 2) & 0b1) != 0;
759        let e_code = Code::from(node_code & 0b11);
760
761        let vid = match var_code {
762            Code::Terminal => {
763                nodes.push(manager.clone_edge(&terminal));
764                continue;
765            }
766            Code::AbsoluteID | Code::RelativeID => decode_7bit(&mut input)?,
767            Code::Relative1 => 1,
768        };
769
770        let t = EdgeDropGuard::new(
771            manager,
772            manager.clone_edge(&nodes[idx(&mut input, node_id, t_code)?]),
773        );
774        let t_level = manager.get_node(&t).level();
775        let e = EdgeDropGuard::new(
776            manager,
777            manager.clone_edge(&nodes[idx(&mut input, node_id, e_code)?]),
778        );
779        let e_level = manager.get_node(&e).level();
780        let e = if e_complement {
781            match complement(manager, e.into_edge()) {
782                Ok(e) => EdgeDropGuard::new(manager, e),
783                Err(OutOfMemory) => {
784                    return Err(io::ErrorKind::OutOfMemory.into());
785                }
786            }
787        } else {
788            e
789        };
790
791        let vid = match var_code {
792            Code::Terminal => unreachable!(),
793            Code::AbsoluteID if vid >= suppvar_level_map.len() => {
794                return err("variable ID out of range")
795            }
796            Code::AbsoluteID => vid,
797            Code::RelativeID | Code::Relative1 => {
798                let min_level = std::cmp::min(t_level, e_level);
799                let child_min_suppvar = if min_level == LevelNo::MAX {
800                    level_suppvar_map.len()
801                } else {
802                    level_suppvar_map[min_level as usize] as usize
803                };
804                match child_min_suppvar.checked_sub(vid) {
805                    Some(v) => v,
806                    None => return err("variable ID out of range"),
807                }
808            }
809        };
810
811        let level = suppvar_level_map[vid as usize];
812        if level >= t_level || level >= e_level {
813            return err("node level must be less than the children's levels");
814        }
815
816        let children = [t.into_edge(), e.into_edge()];
817        match <M::Rules as DiagramRules<_, _, _>>::reduce(manager, level, children)
818            .then_insert(manager, level)
819        {
820            Ok(e) => nodes.push(e),
821            Err(OutOfMemory) => {
822                return Err(io::ErrorKind::OutOfMemory.into());
823            }
824        }
825    }
826
827    Ok(nodes.into_vec())
828}
829
830/// Read and unescape a byte. Counterpart of [`write_escaped()`]
831fn read_unescape(input: impl io::BufRead) -> io::Result<u8> {
832    // In principle, `io::Read` (instead of `io::BufRead`) is enough, but not
833    // passing a buffered reader would be very bad for performance.
834    let mut bytes = input.bytes();
835    let eof_msg = "unexpected end of file";
836    match bytes.next() {
837        None => return err(eof_msg),
838        Some(Ok(0)) => {}
839        Some(res) => return res,
840    };
841    match bytes.next() {
842        None => err(eof_msg),
843        Some(Ok(0x00)) => Ok(0x00),
844        Some(Ok(0x01)) => Ok(0x0a),
845        Some(Ok(0x02)) => Ok(0x0d),
846        Some(Ok(0x03)) => Ok(0x1a),
847        Some(Ok(b)) => Err(io::Error::new(
848            io::ErrorKind::InvalidData,
849            format!("invalid escape sequence 0x00 0x{b:x}"),
850        )),
851        Some(e) => e,
852    }
853}
854
855/// 7-bit decode a number from `input` (unescaping the bytes via
856/// [`read_unescape()`])
857fn decode_7bit(mut input: impl io::BufRead) -> io::Result<usize> {
858    let mut res = 0usize;
859    loop {
860        let b = read_unescape(&mut input)?;
861        match res.checked_shl(7) {
862            Some(v) => res = v,
863            None => return err("integer too large"),
864        }
865        res |= (b >> 1) as usize;
866        if b & 1 == 0 {
867            return Ok(res);
868        }
869    }
870}
871
872/// Remove leading spaces and tabs
873const fn trim_start(mut s: &[u8]) -> &[u8] {
874    while let [b' ' | b'\t', rest @ ..] = s {
875        s = rest;
876    }
877    s
878}
879
880/// Remove trailing spaces and tabs
881const fn trim_end(mut s: &[u8]) -> &[u8] {
882    while let [rest @ .., b' ' | b'\t'] = s {
883        s = rest;
884    }
885    s
886}
887
888/// Remove leading and trailing spaces and tabs
889const fn trim(s: &[u8]) -> &[u8] {
890    trim_end(trim_start(s))
891}
892
893/// Parse a list of space- or tab-separated  strings
894///
895/// All strings in the returned vector are guaranteed to be non-empty.
896fn parse_str_list(input: &[u8], capacity: usize) -> Vec<String> {
897    let mut res = Vec::with_capacity(capacity);
898    let mut start = 0;
899    for pos in memchr::memchr2_iter(b' ', b'\t', input).chain([input.len()]) {
900        // skip empty strings
901        if pos != start {
902            res.push(String::from_utf8_lossy(&input[start..pos]).to_string())
903        }
904        start = pos + 1;
905    }
906    res
907}
908
909macro_rules! parse_unsigned {
910    ($f:ident, $t:ty) => {
911        /// Parse an unsigned integer
912        ///
913        /// Returns the remaining input and the parsed value on success. Fails
914        /// if no integer is present, the integer is too large for the return
915        /// type, or the input contains non-digit characters (after the leading
916        /// spaces/tabs and before the first space/tab after the number).
917        fn $f(input: &[u8], line_no: usize) -> io::Result<(&[u8], $t)> {
918            let mut res: $t = 0;
919            let mut num = false;
920            let mut consumed = 0;
921            for &c in input {
922                match c {
923                    b'0'..=b'9' => {
924                        num = true;
925                        match res
926                            .checked_mul(10)
927                            .and_then(|v| v.checked_add((c - b'0') as _))
928                        {
929                            Some(v) => res = v,
930                            None => {
931                                return err(format!(
932                                    "integer '{}' too large (line {line_no})",
933                                    String::from_utf8_lossy(input),
934                                ));
935                            }
936                        }
937                    }
938                    b' ' | b'\t' if num => break,
939                    b' ' | b'\t' => {}
940                    _ => {
941                        return err(format!(
942                            "unexpected char '{}' in integer (line {line_no})",
943                            c as char,
944                        ));
945                    }
946                }
947                consumed += 1;
948            }
949            if !num {
950                return err(format!(
951                    "expected an integer, got an empty string (line {line_no})"
952                ));
953            }
954            Ok((&input[consumed..], res))
955        }
956    };
957}
958parse_unsigned!(parse_u32, u32);
959parse_unsigned!(parse_usize, usize);
960
961macro_rules! parse_single_unsigned {
962    ($f:ident, $t:ty) => {
963        /// Parse a single unsigned integer (no spaces etc. allowed)
964        fn $f(input: &[u8], line_no: usize) -> io::Result<$t> {
965            let mut res: $t = 0;
966            let mut num = false;
967            for &c in input {
968                match c {
969                    b'0'..=b'9' => {
970                        num = true;
971                        match res
972                            .checked_mul(10)
973                            .and_then(|v| v.checked_add((c - b'0') as _))
974                        {
975                            Some(v) => res = v,
976                            None => {
977                                return err(format!(
978                                    "integer '{}' too large (line {line_no})",
979                                    String::from_utf8_lossy(input),
980                                ));
981                            }
982                        }
983                    }
984                    _ => {
985                        return err(format!(
986                            "unexpected char '{}' in integer (line {line_no})",
987                            c as char,
988                        ));
989                    }
990                }
991            }
992            if !num {
993                return err(format!("expected an integer (line {line_no})"));
994            }
995            Ok(res)
996        }
997    };
998}
999parse_single_unsigned!(parse_single_u32, u32);
1000parse_single_unsigned!(parse_single_usize, usize);
1001
1002/// Parse a space (or tab) separated list of integers
1003fn parse_u32_list(input: &[u8], capacity: usize, line_no: usize) -> io::Result<Vec<u32>> {
1004    let mut res = Vec::with_capacity(capacity);
1005    let mut i = 0u32;
1006    let mut num = false;
1007
1008    for &c in input {
1009        match c {
1010            b'0'..=b'9' => {
1011                num = true;
1012                match i
1013                    .checked_mul(10)
1014                    .and_then(|v| v.checked_add((c - b'0') as _))
1015                {
1016                    Some(v) => i = v,
1017                    None => {
1018                        return err(format!("integer too large (line {line_no})"));
1019                    }
1020                }
1021            }
1022            b' ' | b'\t' => {
1023                if num {
1024                    res.push(i);
1025                    num = false;
1026                    i = 0;
1027                }
1028            }
1029            _ => {
1030                return err(format!(
1031                    "unexpected char '{}' in space-separated list of integers (line {line_no})",
1032                    c as char,
1033                ));
1034            }
1035        }
1036    }
1037    if num {
1038        res.push(i);
1039    }
1040
1041    Ok(res)
1042}
1043
1044/// Parse a space (or tab) separated list of edges into `dst`
1045///
1046/// The input consists of non-zero decimal integers, possibly negative. If the
1047/// value is negative, this indicates a complemented edge. In this case, the
1048/// boolean in the resulting entry is `true`.
1049///
1050/// `line_no` is only used for error reporting.
1051fn parse_edge_list(input: &[u8], dst: &mut Vec<isize>, line_no: usize) -> io::Result<()> {
1052    let mut i = 0isize;
1053    let mut neg = false;
1054    let mut num = false;
1055
1056    for &c in input {
1057        match c {
1058            b'0'..=b'9' => {
1059                num = true;
1060                match i
1061                    .checked_mul(10)
1062                    .and_then(|v| v.checked_add((c - b'0') as isize))
1063                {
1064                    Some(v) => i = v,
1065                    None => {
1066                        return err(format!("integer too large (line {line_no})"));
1067                    }
1068                }
1069            }
1070            b'-' => {
1071                if neg {
1072                    return err(format!("expected a digit after '-' (line {line_no})"));
1073                }
1074                if num {
1075                    return err(format!("expected a space before '-' (line {line_no})"));
1076                }
1077                neg = true;
1078            }
1079            b' ' | b'\t' => {
1080                if num {
1081                    dst.push(if neg { -i } else { i });
1082                    num = false;
1083                    neg = false;
1084                    i = 0;
1085                }
1086            }
1087            _ => {
1088                return err(format!(
1089                    "unexpected char '{}' in space-separated list of integers (line {line_no})",
1090                    c as char,
1091                ));
1092            }
1093        }
1094    }
1095    if num {
1096        dst.push(if neg { -i } else { i });
1097    }
1098
1099    Ok(())
1100}
1101
1102#[cfg(test)]
1103mod test {
1104    use super::*;
1105
1106    #[test]
1107    fn test_decode_7bit() -> io::Result<()> {
1108        if usize::BITS == 64 {
1109            let b = [
1110                0b0000_0011, //  1
1111                0b1111_1111, //  8
1112                0b1111_1111, // 15
1113                0b1111_1111, // 22
1114                0b1111_1111, // 29
1115                0b1111_1111, // 36
1116                0b1111_1111, // 43
1117                0b1111_1111, // 50
1118                0b1111_1111, // 57
1119                0b1111_1110, // 64
1120            ];
1121            assert_eq!(decode_7bit(&b[..])?, usize::MAX);
1122
1123            let b = [
1124                0b0000_0101, //  1
1125                0b0000_0001, //  8
1126                0b0000_0001, // 15
1127                0b0000_0001, // 22
1128                0b0000_0001, // 29
1129                0b0000_0001, // 36
1130                0b0000_0001, // 43
1131                0b0000_0001, // 50
1132                0b0000_0001, // 57
1133                0b0000_0000, // 64
1134            ];
1135            assert!(decode_7bit(&b[..]).is_err());
1136        }
1137
1138        // 2 times 0 due to escaping
1139        assert_eq!(decode_7bit([0, 0].as_slice())?, 0);
1140
1141        assert_eq!(decode_7bit([0b10].as_slice())?, 1);
1142
1143        Ok(())
1144    }
1145
1146    // spell-checker:disable
1147
1148    #[test]
1149    fn test_trim_start() {
1150        assert_eq!(trim_start(b""), b"");
1151        assert_eq!(trim_start(b"abc"), b"abc");
1152        assert_eq!(trim_start(b"abc 123"), b"abc 123");
1153        assert_eq!(trim_start(b" abc 123"), b"abc 123");
1154        assert_eq!(trim_start(b"abc 123 "), b"abc 123 ");
1155        assert_eq!(trim_start(b"\t foo bar \t"), b"foo bar \t");
1156    }
1157
1158    #[test]
1159    fn test_trim_end() {
1160        assert_eq!(trim_end(b""), b"");
1161        assert_eq!(trim_end(b"abc"), b"abc");
1162        assert_eq!(trim_end(b"abc 123"), b"abc 123");
1163        assert_eq!(trim_end(b" abc 123"), b" abc 123");
1164        assert_eq!(trim_end(b"abc 123 "), b"abc 123");
1165        assert_eq!(trim_end(b"\t foo bar \t"), b"\t foo bar");
1166    }
1167
1168    #[test]
1169    fn test_trim() {
1170        assert_eq!(trim(b""), b"");
1171        assert_eq!(trim(b"abc"), b"abc");
1172        assert_eq!(trim(b"abc 123"), b"abc 123");
1173        assert_eq!(trim(b" abc 123"), b"abc 123");
1174        assert_eq!(trim(b"abc 123 "), b"abc 123");
1175        assert_eq!(trim(b"\t foo bar \t"), b"foo bar");
1176    }
1177
1178    #[test]
1179    fn test_parse_str_list() {
1180        assert_eq!(parse_str_list(b"", 0), Vec::<String>::new());
1181        assert_eq!(&parse_str_list(b"abc 123", 0)[..], ["abc", "123"]);
1182        assert_eq!(&parse_str_list(b" abc 123 ", 0)[..], ["abc", "123"]);
1183        assert_eq!(
1184            &parse_str_list(" a.-bc\tUniversität ".as_bytes(), 2)[..],
1185            ["a.-bc", "Universität"]
1186        );
1187    }
1188
1189    #[test]
1190    fn test_parse_single_u32() -> io::Result<()> {
1191        assert_eq!(parse_single_u32(b"0", 42)?, 0);
1192        assert_eq!(parse_single_u32(b"00", 42)?, 0);
1193        assert_eq!(parse_single_u32(b"123", 42)?, 123);
1194        assert_eq!(
1195            parse_single_u32(u32::MAX.to_string().as_bytes(), 42)?,
1196            u32::MAX
1197        );
1198
1199        assert!(parse_single_u32(b"", 42).is_err());
1200        assert!(parse_single_u32(b"42a", 42).is_err());
1201
1202        assert!(parse_single_u32((u32::MAX as u64 + 1).to_string().as_bytes(), 42).is_err());
1203
1204        Ok(())
1205    }
1206
1207    #[test]
1208    fn test_parse_single_usize() -> io::Result<()> {
1209        assert_eq!(parse_single_usize(b"0", 42)?, 0);
1210        assert_eq!(parse_single_usize(b"00", 42)?, 0);
1211        assert_eq!(parse_single_usize(b"123", 42)?, 123);
1212        assert_eq!(
1213            parse_single_usize(usize::MAX.to_string().as_bytes(), 42)?,
1214            usize::MAX
1215        );
1216
1217        assert!(parse_single_usize(b"", 42).is_err());
1218        assert!(parse_single_usize(b"42a", 42).is_err());
1219
1220        if u128::BITS > usize::BITS {
1221            assert!(
1222                parse_single_usize((usize::MAX as u128 + 1).to_string().as_bytes(), 42).is_err()
1223            );
1224        }
1225
1226        Ok(())
1227    }
1228
1229    #[test]
1230    fn test_parse_u32() -> io::Result<()> {
1231        assert_eq!(parse_u32(b"0", 42)?, (&b""[..], 0));
1232        assert_eq!(parse_u32(b"00", 42)?, (&b""[..], 0));
1233        assert_eq!(parse_u32(b"123", 42)?, (&b""[..], 123));
1234        assert_eq!(
1235            parse_u32(u32::MAX.to_string().as_bytes(), 42)?,
1236            (&b""[..], u32::MAX)
1237        );
1238
1239        let (rest, v) = parse_u32(b" 023 9 \t432\t", 42)?;
1240        assert_eq!(rest, b" 9 \t432\t");
1241        assert_eq!(v, 23);
1242        let (rest, v) = parse_u32(rest, 42)?;
1243        assert_eq!(rest, b" \t432\t");
1244        assert_eq!(v, 9);
1245        let (rest, v) = parse_u32(rest, 42)?;
1246        assert_eq!(rest, b"\t");
1247        assert_eq!(v, 432);
1248        assert!(parse_u32(rest, 42).is_err());
1249
1250        assert!(parse_u32(b"", 42).is_err());
1251        assert!(parse_u32(b"42a", 42).is_err());
1252
1253        assert!(parse_u32((u32::MAX as u64 + 1).to_string().as_bytes(), 42).is_err());
1254
1255        Ok(())
1256    }
1257
1258    #[test]
1259    fn test_parse_usize() -> io::Result<()> {
1260        assert_eq!(parse_usize(b"0", 42)?, (&b""[..], 0));
1261        assert_eq!(parse_usize(b"00", 42)?, (&b""[..], 0));
1262        assert_eq!(parse_usize(b"123", 42)?, (&b""[..], 123));
1263        assert_eq!(
1264            parse_usize(usize::MAX.to_string().as_bytes(), 42)?,
1265            (&b""[..], usize::MAX)
1266        );
1267
1268        let (rest, v) = parse_usize(b" 023 9 \t432\t", 42)?;
1269        assert_eq!(rest, b" 9 \t432\t");
1270        assert_eq!(v, 23);
1271        let (rest, v) = parse_usize(rest, 42)?;
1272        assert_eq!(rest, b" \t432\t");
1273        assert_eq!(v, 9);
1274        let (rest, v) = parse_usize(rest, 42)?;
1275        assert_eq!(rest, b"\t");
1276        assert_eq!(v, 432);
1277        assert!(parse_usize(rest, 42).is_err());
1278
1279        assert!(parse_usize(b"", 42).is_err());
1280        assert!(parse_usize(b"42a", 42).is_err());
1281
1282        if u128::BITS > usize::BITS {
1283            assert!(parse_usize((usize::MAX as u128 + 1).to_string().as_bytes(), 42).is_err());
1284        }
1285
1286        Ok(())
1287    }
1288
1289    #[test]
1290    fn test_parse_u32_list() -> io::Result<()> {
1291        assert_eq!(&parse_u32_list(b"", 0, 42)?[..], []);
1292        assert_eq!(&parse_u32_list(b" \t ", 1, 42)?[..], []);
1293        assert_eq!(&parse_u32_list(b" 123 \t 432 ", 2, 42)?[..], [123, 432]);
1294        assert_eq!(&parse_u32_list(b"0 00", 1, 42)?[..], [0, 0]);
1295        assert_eq!(
1296            &parse_u32_list(u32::MAX.to_string().as_bytes(), 1, 42)?[..],
1297            [u32::MAX]
1298        );
1299
1300        assert!(parse_u32_list(b"123 42a", 2, 42).is_err());
1301        assert!(parse_u32_list(b"123 -42", 2, 42).is_err());
1302
1303        assert!(parse_u32_list((u32::MAX as u64 + 1).to_string().as_bytes(), 1, 42).is_err());
1304
1305        Ok(())
1306    }
1307
1308    #[test]
1309    fn test_parse_edge_list() -> io::Result<()> {
1310        let mut res = Vec::with_capacity(32);
1311
1312        parse_edge_list(b"", &mut res, 42)?;
1313        assert_eq!(&res[..], []);
1314        parse_edge_list(b" \t ", &mut res, 42)?;
1315        assert_eq!(&res[..], []);
1316        parse_edge_list(b" 123 \t -432 ", &mut res, 42)?;
1317        assert_eq!(&res[..], [123, -432,]);
1318        res.clear();
1319        parse_edge_list(b"01 -02", &mut res, 42)?;
1320        assert_eq!(&res[..], [1, -2,]);
1321        res.clear();
1322        parse_edge_list(b"0 -0", &mut res, 42)?;
1323        assert_eq!(&res[..], [0, 0,]);
1324        res.clear();
1325        parse_edge_list(isize::MAX.to_string().as_bytes(), &mut res, 42)?;
1326        assert_eq!(&res[..], [isize::MAX]);
1327        res.clear();
1328        parse_edge_list(format!("-{}", isize::MAX).as_bytes(), &mut res, 42)?;
1329        assert_eq!(&res[..], [-isize::MAX]);
1330
1331        assert!(parse_edge_list(b"123 42a", &mut res, 42).is_err());
1332        assert!(parse_edge_list(b"123 4-2", &mut res, 42).is_err());
1333
1334        let isize_min = isize::MIN.to_string();
1335        assert!(parse_edge_list(isize_min.as_bytes(), &mut res, 42).is_err());
1336        assert!(parse_edge_list(&isize_min.as_bytes()[1..], &mut res, 42).is_err());
1337
1338        Ok(())
1339    }
1340
1341    // spell-checker:enable
1342}