macroonz_compiler/token/capture/types.rs
1//! The capture home's declarations: what one captured declaration is, how a producer's span table answers, and how a text read refuses.
2//!
3//! Declarations only.
4//! Every road that reaches a private field lives in `type_guard.rs`, this file's own child, which is where all five magnitudes below are settled.
5
6use crate::bounded::Bounded;
7
8#[path = "type_guard.rs"]
9mod guard;
10
11/// Steps one token path may carry, and so how deeply a declared input may nest.
12///
13/// A width bound alone bounds each level and says nothing about the depth, so an input nested a million groups deep would satisfy it at every level while the walk reading it did not terminate.
14pub const TOKEN_PATH_DEPTH_LIMIT: usize = 32;
15
16/// Token trees one captured input may carry at any one nesting level.
17pub const CAPTURED_TOKEN_LIMIT: usize = 4096;
18
19/// Tokens one captured input may carry across the whole tree, and positions one span table may hold.
20///
21/// The level bound and the depth bound multiply, so the total is bounded in its own right rather than left as the product of two other magnitudes; a table is not a level, so it stands here too.
22pub const CAPTURED_TREE_TOKEN_LIMIT: usize = 16_384;
23
24/// Units of capture work one walk may spend, one unit per examined token.
25///
26/// Deliberately wider than the whole-tree magnitude, because a walk may look at more than it keeps, and a budget at the tree magnitude exactly would refuse a lawful input the moment its producer looked twice at anything.
27pub const CAPTURE_WORK_LIMIT: usize = 65_536;
28
29/// Source bytes one text capture may read before tokenization.
30///
31/// This magnitude is independent of token count, tree depth, and capture work so a hostile trivia-only input cannot evade every structural bound by producing no retained token.
32pub const TEXT_SOURCE_BYTE_LIMIT: usize = 65_536;
33
34/// An opaque index into the producer's span table.
35///
36/// It carries no position, no file, and no length: the producer built the table while capturing, and only the producer can turn one back into a compiler span.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct SpanHandle(u32);
39
40/// The coordinate system one source position is counted in.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum CoordinateRole {
43 /// A zero-based byte offset in the captured text.
44 Byte,
45 /// A zero-based ordinal retained by the source producer.
46 SemanticOrigin,
47}
48
49/// One compiler-local source position with its coordinate system stated.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub struct SourceCoordinate {
52 /// The coordinate system in which the position is counted.
53 pub role: CoordinateRole,
54 /// The zero-based position in that coordinate system.
55 pub position: u64,
56}
57
58/// The delimiter one captured group is written with.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub enum CapturedDelimiter {
61 /// `( … )`.
62 Parenthesis,
63 /// `{ … }`.
64 Brace,
65 /// `[ … ]`.
66 Bracket,
67 /// A group with no delimiter written — the invisible grouping a compiler inserts around a captured fragment.
68 ///
69 /// It is a real group and is never flattened away, and a reader of text can never write one, because text that carries no delimiter carries no group.
70 Bare,
71}
72
73/// Which declared magnitude one capture ran past.
74///
75/// Every row refuses before any partial tree exists: a truncated capture is a different declaration, and capturing one would put everything downstream to work on material nobody wrote.
76#[must_use = "a bound refusal names which declared magnitude the capture would have passed"]
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78pub enum CaptureBound {
79 /// The declared input nests deeper than the declared magnitude.
80 Depth,
81 /// One nesting level carries more token trees than the declared magnitude.
82 Level,
83 /// The whole tree carries more tokens than the declared magnitude.
84 Tree,
85 /// The walk spent the declared capture-work budget.
86 Work,
87}
88
89/// Where one captured token sits, as the index route from the root of the declared input.
90///
91/// The route is unique by construction: `[3, 0, 5]` is the sixth token of the first token of the fourth top-level token, and nothing else in the tree spells that.
92/// It is stable under everything a span is not stable under — which producer read the input, where the file moved, how the source was formatted — so two captures of one declaration agree on every route.
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94pub struct TokenPath {
95 steps: Bounded<u32, TOKEN_PATH_DEPTH_LIMIT>,
96}
97
98/// The running state of one capture walk: what the walk has spent, and how much of the whole-tree magnitude it has taken.
99///
100/// The two are charged separately, because a producer that reads material it discards — a frontend skipping trivia, a reader backtracking over an alternative — spends work the result never shows, and the budget is the only magnitude that can see it.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102pub struct CaptureWalk {
103 remaining: usize,
104 taken: usize,
105}
106
107/// One non-group value a capture producer offers to the checked builder.
108///
109/// Groups have their own builder operation so no caller can smuggle child trees carrying foreign paths or handles through an atom seat.
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111pub enum CapturedAtom {
112 /// An ordinary identifier-shaped word or keyword.
113 Word(String),
114 /// One punctuation character that stands alone.
115 Punct(char),
116 /// A text literal's text.
117 Text(String),
118 /// A numeric literal, exactly as written.
119 Number(String),
120 /// A byte-string literal's material.
121 ByteText(Vec<u8>),
122 /// One character literal's character.
123 Character(char),
124 /// One byte literal's byte.
125 Byte(u8),
126 /// A C string literal's material without its terminating NUL.
127 NulTerminatedText(Vec<u8>),
128 /// A raw identifier's name without its `r#` spelling marker.
129 RawIdentifier(String),
130 /// One punctuation character joined to the token after it.
131 JointPunct(char),
132}
133
134/// What one captured token carries.
135///
136/// An arm carries a literal's value and never the characters it was spelled with, so `"x"` and `r"x"` are one text and which prefix a producer read is not a fact the tree keeps.
137///
138/// # Ordering
139///
140/// The roster grows at its end and nowhere else: each arm's slot is a byte of the canonical bytes a captured declaration's identity is derived over.
141#[derive(Debug, Clone, PartialEq, Eq, Hash)]
142pub enum CapturedPayload {
143 /// An ordinary identifier-shaped word or keyword.
144 Word(String),
145 /// One punctuation character that stands alone.
146 Punct(char),
147 /// A text literal's text: `"…"` and `r"…"` alike, escapes read and quotes removed.
148 Text(String),
149 /// A numeric literal, exactly as written: the base, the digit separators, and the suffix that types it are all part of what the declaration says.
150 Number(String),
151 /// A delimited group and the tokens inside it.
152 Group {
153 /// The delimiter written around the group.
154 delimiter: CapturedDelimiter,
155 /// The tokens inside, in the order they were written.
156 trees: Bounded<CapturedTokenTree, CAPTURED_TOKEN_LIMIT>,
157 },
158 /// A byte-string literal's material: `b"…"` and `br"…"`, kept as bytes because material that is not text crosses without a lossy road existing for it to take.
159 ByteText(Vec<u8>),
160 /// One character literal's character: `'…'`.
161 Character(char),
162 /// One byte literal's byte: `b'…'`.
163 Byte(u8),
164 /// A C string literal's material: `c"…"` and `cr"…"`, without the terminating NUL, which is the literal form's and never the value's.
165 NulTerminatedText(Vec<u8>),
166 /// A raw identifier's name without its `r#` spelling marker.
167 RawIdentifier(String),
168 /// One punctuation character joined to the token after it.
169 JointPunct(char),
170}
171
172/// Why one literal spelling could not be read into the value it names.
173///
174/// Neither row is the caller's mistake: every spelling that reaches this road was already lexed by a compiler, so a refusal is this crate saying it does not read what the compiler admitted.
175#[must_use = "a literal refusal names why the spelling could not be read into a value"]
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
177pub enum LiteralReadCause {
178 /// The spelling opens with no literal form this grammar has a row for.
179 NotAKnownForm,
180 /// The form is one this grammar reads, and its body carries material this grammar could not read the value of.
181 NotReadable,
182}
183
184/// One captured token: what it carries, where it sits, and how to reach the compiler span it came from.
185#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186pub struct CapturedTokenTree {
187 payload: CapturedPayload,
188 path: TokenPath,
189 span: SpanHandle,
190}
191
192/// One captured declared input: the top-level token trees, and how many span handles the producer issued.
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
194pub struct CapturedInput {
195 trees: Bounded<CapturedTokenTree, CAPTURED_TOKEN_LIMIT>,
196 issued: usize,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200enum CaptureBuilderStanding {
201 Ready,
202 Refused { retained_before_capture: usize },
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206enum CaptureLevelStanding {
207 Open,
208 Finished,
209}
210
211/// The only state that issues capture handles and retains the producer's matching source positions.
212#[derive(Debug, Clone, PartialEq, Eq, Hash)]
213pub struct CaptureBuilder<Position> {
214 positions: Vec<Position>,
215 walk: CaptureWalk,
216 standing: CaptureBuilderStanding,
217}
218
219/// One nesting level borrowed from a [`CaptureBuilder`].
220///
221/// A producer can append atoms or groups and cannot state a path, a handle, or a denominator.
222/// Every operation consumes the level, and only a successful operation returns it, so a refused partial level cannot be finished.
223pub struct CaptureLevel<'capture, Position> {
224 positions: &'capture mut Vec<Position>,
225 walk: &'capture mut CaptureWalk,
226 builder_standing: &'capture mut CaptureBuilderStanding,
227 retained_before_capture: usize,
228 path: TokenPath,
229 trees: Bounded<CapturedTokenTree, CAPTURED_TOKEN_LIMIT>,
230 standing: CaptureLevelStanding,
231}
232
233/// Why a checked capture was not completed.
234#[must_use = "a capture refusal names whether a declared bound or the producer's own reading stopped construction"]
235#[derive(Debug, Clone, PartialEq, Eq, Hash)]
236pub enum CaptureBuildRefusal<Position, ProducerRefusal> {
237 /// One declared capture magnitude was exceeded at this producer position.
238 Unbounded {
239 /// The magnitude exceeded.
240 bound: CaptureBound,
241 /// The producer's own position for the token that reached it.
242 at: Position,
243 },
244 /// The producer could not read one token after the builder issued its declaration path and producer handle.
245 ProducerRefused {
246 /// The producer's typed reason.
247 cause: ProducerRefusal,
248 /// The declaration-local route to the token the producer could not read.
249 path: TokenPath,
250 /// The handle already bound to the token's retained source position.
251 at: SpanHandle,
252 },
253}
254
255/// Why one span table could not say where a handle sits.
256///
257/// A caller holding the handle and the table's reach can tell a handle issued by another producer from a handle issued past the end of a truncated table, which is the whole of what is knowable from this side.
258#[must_use = "a resolution refusal carries the handle and how far the table reaches"]
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
260pub struct SpanResolutionRefusal {
261 /// The handle the table was asked to resolve.
262 pub handle: SpanHandle,
263 /// How many positions the table carries; a handle at or past this index names no position in it.
264 pub reaches: usize,
265}
266
267/// How a producer answers "where is the token this handle names?".
268///
269/// Not an option and not a default: nothing here invents a position for a handle it cannot resolve, and a diagnostic coordinate reading `byte 0` under a producer-held table would be a fiction.
270#[derive(Debug, Clone, PartialEq, Eq, Hash)]
271pub enum SpanTable {
272 /// Byte offsets into the declared input, one per issued handle.
273 ByteOffsets(Bounded<u64, CAPTURED_TREE_TOKEN_LIMIT>),
274 /// The producer holds the compiler's spans and resolves handles itself.
275 ProducerHeld,
276}
277
278/// Why the low-level lexer could not normalize one spelling.
279#[must_use = "a lexical refusal names the spelling distinction that could not be normalized"]
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
281pub enum TextLexicalCause {
282 /// A block comment was not terminated.
283 BlockCommentNotTerminated,
284 /// An identifier contains a character the compiler lexer rejects.
285 InvalidIdentifier,
286 /// A prefix is reserved or not meaningful without an edition-aware parser.
287 UnknownPrefix,
288 /// A lifetime prefix is reserved or not meaningful without an edition-aware parser.
289 UnknownLifetimePrefix,
290 /// A guarded-string prefix requires parser context this boundary does not own.
291 GuardedStringPrefix,
292 /// A literal carries a malformed low-level spelling.
293 MalformedLiteral,
294 /// A lifetime begins with a number.
295 LifetimeStartsWithNumber,
296 /// Frontmatter is not Rust token input at this boundary.
297 Frontmatter,
298 /// The lexer reported a character with no lawful Rust token kind.
299 UnknownToken,
300}
301
302/// Why one text read refused.
303///
304/// Dependent checks: there is no group to balance until the characters were cut, and no magnitude to exceed until the trees were built.
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
306pub enum TextReadCause {
307 /// A literal was never closed.
308 NotTerminated,
309 /// A literal carries an escape sequence the literal owner could not read.
310 NotEscapeFree,
311 /// A delimited group was never closed.
312 NotBalanced,
313 /// A closing delimiter arrived with no group open.
314 NotOpened,
315 /// The declared text exceeds the independent source-byte magnitude.
316 SourceBytesUnbounded,
317 /// The low-level lexer established a malformed or context-dependent spelling.
318 Lexical(TextLexicalCause),
319 /// The read exceeds a declared magnitude, and this is which one — a reader told only "unbounded" cannot tell a tree that nests too deep from one that spends the walk's budget.
320 Unbounded(CaptureBound),
321}
322
323/// One refused text read: the established cause, and the byte it sits at.
324#[must_use = "a read refusal carries the established cause and the byte it sits at"]
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
326pub struct TextReadRefusal {
327 /// The established cause.
328 pub cause: TextReadCause,
329 /// The byte position the cause was established at.
330 pub at: u64,
331}
332
333/// One declared input read from text: the captured trees, and the byte offsets that resolve every handle the read issued.
334///
335/// The callable route — a compiler is one producer of captured input, a test is another, and text is the third — so that the reproduction route a diagnostic names is a real road and not a promise.
336/// The two seats are visible to `text.rs` alone, and that read establishes the relationship between them: the offsets table resolves exactly the handles the capture beside it issued.
337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub struct TextCapture {
339 /// The captured input the read produced.
340 pub(super) input: CapturedInput,
341 /// The byte offsets that resolve the handles that read issued.
342 pub(super) spans: SpanTable,
343}