ifc_lite_core/parser/
scanner_diagnostics.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! What an [`EntityScanner`](super::EntityScanner) scan DROPPED, and why.
6//!
7//! A sibling file for the same reason `scanner_header.rs` and
8//! `scanner_attributes.rs` are: `scanner.rs` is at its `module_size_ratchet`
9//! budget, and these four accessors share no control flow with the record
10//! hunt. They are the reporting surface, and the reasoning they carry (why
11//! each is a `Vec` of offsets rather than a count, and why a SHARDED caller
12//! needs the offsets) is the same argument twice, so it belongs in one place.
13
14impl<'a> super::EntityScanner<'a> {
15    /// How many records this scanner has skipped because their instance name
16    /// does not fit `u32` (issue #3395).
17    ///
18    /// ISO 10303-21 puts no upper bound on `#<digits>`, but every express-id
19    /// column in this workspace is `u32` (`ColumnarIndex::ids`,
20    /// `MeshData::express_id`, the wasm `express_ids` buffers), so a wider id
21    /// cannot be represented — it used to wrap, making `#4294967297`
22    /// indistinguishable from `#1`. The record is dropped instead, and this
23    /// counter is the other half of that guard: callers report it rather than
24    /// letting the model come back quietly short.
25    pub fn skipped_oversized_ids(&self) -> usize {
26        self.skipped_oversized_id_starts.len()
27    }
28
29    /// The `line_start` byte offset of every record this scanner refused,
30    /// strictly increasing.
31    ///
32    /// A whole-file scan only needs the count above. A SHARDED scan needs the
33    /// offsets, and the difference is not cosmetic: shard `i > 0` starts at an
34    /// arbitrary byte, so it can begin inside a quoted value and parse a
35    /// string literal such as `'…#4294967297=IFCWALL(…'` as a record — and
36    /// refuse it. That refusal is an artefact of where the shard started, not
37    /// a record the file declares, so a count alone would let a file with
38    /// NOTHING oversized in it be reported as incomplete. The offset lets the
39    /// stitch keep only the refusals inside the byte region it actually
40    /// retained from that shard (issue #3395/#3430).
41    pub fn skipped_oversized_id_starts(&self) -> &[usize] {
42        &self.skipped_oversized_id_starts
43    }
44
45    /// The first record this scan dropped for having no terminator of its own,
46    /// or `None`. See [`Self::malformed_record_starts`].
47    pub fn malformed_record_start(&self) -> Option<usize> {
48        self.malformed_record_starts.first().copied()
49    }
50
51    /// The `line_start` of EVERY record this scan dropped for having no
52    /// terminator of its own, strictly increasing.
53    ///
54    /// A REPORT, not a stop signal: a record merely missing its `;` is dropped
55    /// and the scan CONTINUES past the `)` closing its parameter list (#4179).
56    /// Ask [`position`](Self::position), not this, whether a scan finished.
57    ///
58    /// A `Vec` for the reason
59    /// [`skipped_oversized_id_starts`](Self::skipped_oversized_id_starts) is
60    /// one, which recovery made load-bearing: a SHARDED scan can now hit a
61    /// speculative drop inside a quoted value AND a real one after it, and
62    /// keeping only the first made the stitch's "inside my retained region"
63    /// filter test the speculative offset and discard the real report with it.
64    pub fn malformed_record_starts(&self) -> &[usize] {
65        &self.malformed_record_starts
66    }
67}