1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! `EntityScanner::find_entity_end`, the record-terminator walk.
//!
//! A sibling file for the same reason `scanner_header.rs`,
//! `scanner_attributes.rs` and `scanner_diagnostics.rs` are: `scanner.rs` is
//! at its `module_size_ratchet` budget. This walk reads ONE already-located
//! record body looking for its `;`, where `next_entity` hunts the next
//! declaration, and the #4179 record-boundary rules it implements are argued
//! next door in `lexical.rs` beside the exact walk they approximate.
impl<'a> super::EntityScanner<'a> {
/// Offset of the record's terminating `;` from the start of the slice.
///
/// `memchr3` jumps straight to the next quote, semicolon or `=` in one
/// SIMD pass, so a string-free geometry primitive
/// (`#7=IFCCARTESIANPOINT((1.,2.,3.));`), the overwhelming majority of
/// records, resolves in one vectorized hop rather than a per-byte loop.
/// A comment opener is then looked for only in the plain span before the
/// hit, and a comment is consumed whole, which is what makes a `/*` inside
/// a literal text and a `;` or quote inside a comment text:
/// `#1=IFCWALL('a', /* p; q */ $);` is legal 10303-21 and used to come
/// back truncated at that inner `;`.
///
/// Returns the first `;` outside a string and outside a comment, doubled
/// `''` being an escaped in-string quote per STEP (ISO 10303-21), and
/// `None` unless that `;` is preceded by the `)` closing the parameter
/// list with no `=` before it, which bounds the search to the record's OWN
/// body (#4179). [`close_step_record`](crate::parser::lexical::close_step_record)
/// argues both rules, the general property they approximate, and why a
/// refusal recovers rather than stops; the TypeScript halves are
/// `step-record-boundary.ts` and `scan-worker-source.ts`. This is the
/// single hottest structural-scan function: every entity of every model,
/// native and wasm, through `build_entity_index` and the processor scan
/// loop alike.
///
/// `=` is IN the SIMD triple rather than checked on the plain span after
/// the fact, and that placement is what keeps a file of unterminated
/// records linear. A refused record resumes one byte past its `#`, so the
/// walk from the next declaration must stop at THAT declaration's `=`
/// rather than scanning on to a terminator that may be anywhere in the
/// remaining file. With `=` outside the triple the walk ran to the next
/// `'`, `;` or `/` first, which for `#1=A(\n` repeated is end of file:
/// O(n^2), measured 4x per doubling (`refused_records_do_not_rescan_the_remainder_4179`).
/// One rule bounds both this walk and `close_step_record`'s.
#[inline]
pub(super) fn find_entity_end(&self, content: &[u8]) -> Option<usize> {
let mut pos = 0;
// Only the two arms that can make it true compute it (#4179).
let mut closes_paren = false;
'record: loop {
// Outside a quoted string: jump to the next quote, terminating
// semicolon or next-declaration `=` in one SIMD pass. No hit at
// all means this record cannot end. `hit` is ABSOLUTE so the
// comment/division loop below can advance `pos` towards it
// without ever re-running the SIMD pass over bytes it has passed.
let hit = pos + memchr::memchr3(b'\'', b';', b'=', &content[pos..])?;
// Every comment opener and division between `pos` and `hit` is
// handled BEFORE the hit is trusted, because a comment may run
// past it and swallow it. Each `/` moves `pos` past itself, and
// the search for the next one covers only `[pos, hit)`, so a
// record dense with divisions or comments costs one walk of its
// own length. Re-scanning from `pos` to the hit per `/` made a
// single 400 KB record quadratic in itself (1.23s against 0.32ms).
while let Some(slash) = memchr::memchr(b'/', &content[pos..hit]).map(|i| pos + i) {
if content.get(slash + 1) == Some(&b'*') {
// A comment is trivia, so a ')' before it still counts:
// `#1=IFCWALL($) /* c */ ;` closes at that ')'.
closes_paren = crate::parser::lexical::closes_with_paren(
&content[pos..slash],
closes_paren,
);
// Unterminated: the rest of the input is inside the
// comment, so this record has no terminator. `None` drops
// it and ends the scan rather than inventing an end.
pos = crate::parser::lexical::skip_step_comment(content, slash)?;
if pos > hit {
// The comment swallowed the hit: find the next one.
continue 'record;
}
} else {
// A lone '/' is STEP division inside a value list.
closes_paren = false;
pos = slash + 1;
}
}
let plain = &content[pos..hit];
let found = content[hit];
pos = hit;
if found == b'=' {
// The NEXT declaration's: this record never ended.
return None;
}
if found == b';' {
// A ';' closing nothing belongs to what FOLLOWS the record.
return crate::parser::lexical::closes_with_paren(plain, closes_paren).then_some(pos);
}
// found == b'\'' : entered a quoted string. Scan to the closing
// quote, treating a doubled '' as an escaped quote.
pos += 1;
loop {
pos += memchr::memchr(b'\'', &content[pos..])?;
if content.get(pos + 1) == Some(&b'\'') {
// Escaped quote ('') - skip both, stay in the string.
pos += 2;
continue;
}
// Closing quote.
pos += 1;
break;
}
closes_paren = false; // A literal is a parameter, not a close.
}
}
}