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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// Shared byte-scan machinery for the value scanners.
//
// Four scanners walk a CSS value's bytes with the same paren/quote/escape/comment
// state machine: `ValueParser::fast_scan` (the fused fast path), `ValueCursor::consume_until`
// (the split), `classify_separators` (the fallback classifier), and [`matching_close_paren`]
// below (the function-extent scan). Their nesting rules are deliberately identical — the
// fused-pass invariant — so the byte set that invariant rests on lives here once, rather
// than being re-spelled per scanner.
/// Bytes that drive a value scanner's state machine: the escape introducer, the
/// nesting/quote triggers, and the comment introducer. All ASCII, so none can occur
/// as a UTF-8 continuation byte.
///
/// This is the set every scanner must inspect no matter what it is looking *for*;
/// each adds its own bytes on top (its separator).
pub const
/// The bytes a value's **separator class** turns on: the comma that makes it a
/// comma list, and the ASCII whitespace that makes it a whitespace list.
///
/// One spelling, two scanners. [`super::parser::ValueParser::fast_scan`] classifies on
/// these directly; the declaration boundary scan
/// (`crate::parser::decl_scan::scan_value_core`) records the same two facts while it walks
/// the value for its terminator, so the class is already known when the value parser
/// starts. Those two answers must agree byte for byte — the boundary scan's is used *in
/// place of* the fused pass — so the set is named here rather than spelled twice.
///
/// ⚠️ `u8::is_ascii_whitespace`, **not** the lexer's `is_ascii_css_whitespace`: the
/// vertical tab is CSS whitespace to `parseCss` but is value *content* to this
/// classification, and both scanners must read it the same way.
pub const
// The comment-extent primitives live at the crate root ([`crate::comments`]), beside
// [`crate::escapes::escape_len`] — the two answer the same "how far does this reach"
// question for the two constructs a value scanner must step over whole. Re-exported here
// so a scanner imports its whole byte-scan vocabulary from one place.
pub use crate;
/// The bytes [`matching_close_paren`] hops between: [`is_value_structural`]'s set **less the
/// comment introducer**, the one member that scan deliberately does not act on (its doc says
/// why). Named once because the hop's needles and the walk's `debug_assert` are two spellings
/// of it, and a needle missing from one is a paren the other never sees.
const FUNCTION_EXTENT_NEEDLES: = ;
const _: = ;
/// The offset of the `)` that closes the `(` at `open`, or `None` when the run never
/// balances within `text`.
///
/// The **function-extent** member of the scanner family above, and it must be one: a
/// paren is only structure where the other three say it is, so a paren inside a quoted
/// string (`fn("(", …)`) or inside an escape (`fn(a\(b, …)`) closes nothing. A scan that
/// counted those balanced the function somewhere other than its real end — usually
/// nowhere — and the value fell through to an opaque `CssValue::Identifier` that the
/// printer spells back verbatim, which silently switched off **every** normalization the
/// declaration had (quote style, number form, colour case, and the argument list's own
/// break).
///
/// ⚠️ **A block comment is deliberately NOT stepped over here, alone in this family.**
/// The other three only ever *split* a value, so stepping a comment whole inside an
/// unquoted `url()` costs them nothing — the token comes back out as one leaf either way.
/// This one **bounds** the function, and it is entered on any leaf ending in `)`, so it
/// cannot tell `url(foo/*bar)` — where §4.3.6 consumes to the first unescaped `)` and `/*`
/// opens nothing — from `fn(/* ) */ a)`. Reading the url's `/*` as a comment loses its
/// closing paren, drops the value onto the identifier path, and `normalize_css_whitespace`
/// then pads the "comment" (`url(foo /*bar)`), rewriting the URL. The arm buys nothing on
/// the other side of that trade: a value carrying a comment is re-emitted from source
/// whatever this scan answers, so both readings of `fn(/* ) */ a, .10)` are the same bytes.
/// Fixture `css/values/functions/url_comment_chars`.
///
/// `open` must address a `(`, which is what lets the depth counter start at zero and
/// never underflow: the first byte read raises it to one, and the walk returns the moment
/// it comes back down.
///
/// The walk **hops between structural bytes** rather than reading every byte
/// ([`tsv_lang::swar::next_byte_of`]), the rung this site has always sat on — a function's
/// interior runs long between them (a mean of 18.7 bytes across 638 stylesheets) and only
/// about 5% of the bytes it reads can move its state. Widening the needles from the two
/// parens to the five that can now move it costs the word loop about one instruction a byte
/// (`N` = 2 → `N` = 5 on `next_byte_of`'s disassembled table, 2.0 → 3.0, the lane loop
/// vectorizing from `N` = 4) and leaves it well under a 256-entry skip table's flat six —
/// needle count is the weak axis here, run length the strong one. ⚠️ Not re-boarded: the
/// CSS format cell's `value/mod.rs` row pre-dates this widening.
///
/// ⚠️ A one-byte pre-test in front of the hop — `string_end`'s shape, for the
/// adjacent-paren case — is deliberately NOT here. That pre-test pays in proportion to how
/// often the run is empty, and `()` / `))` is only 8.6% of the hops on this surface against
/// roughly half of `string_end`'s; built and measured, it removed marginally fewer
/// instructions and did not separate from this spelling on cycles. Rung by the run length
/// the site actually sees.
pub
/// Where a value scanner resumes after the quoted string whose opening quote sits at
/// `open`: one past its closing quote, or the end of `text` when the string never closes.
///
/// A string is opaque to every value scanner — nothing inside it is a separator, a
/// paren or a comment — so a scanner standing on an opening quote has one question, where
/// the string ends, and [`crate::lexer::string_end`] answers it a word at a time. A
/// scanner that hops here keeps no quote state at all.
///
/// The hop and the family's byte-at-a-time quote state are ONE string model, and that is
/// an argument about escapes rather than an assumption. The byte walk steps an escape
/// with [`crate::escapes::escape_len`] (a hex escape's digits and terminator included);
/// `string_end` steps `\` and the one byte after it. The two land on the same closing
/// quote because nothing a longer escape reaches past that byte can be a quote or a `\`
/// — hex digits, one whitespace character, a multi-byte char's continuation bytes — and
/// they agree on the three shapes that are no escape at all: a `\` before a newline (the
/// newline is string content either way), a `\` as the text's last byte, and a string
/// that never closes (both run to the end). The assertion below holds every hop to the
/// byte walk's answer in every debug build.
///
/// Out of line on purpose: a string is the rare arm of the loop that hops here, and
/// inlined into `fast_scan` the word scan widened that function's frame — one that sits
/// on the value parser's recursive path (a comma list nested in a function's arguments).
pub
/// [`quoted_run_end`] as the scanner family's byte-at-a-time quote state reads it — the
/// in-quote arms of `ValueCursor::consume_until` and `classify_separators`, kept as the
/// model the hop is graded against.
/// Build a scanner's 256-entry "this byte cannot possibly matter" table: `true` for a
/// byte that is neither [`is_value_structural`] nor one the scanner is looking for, so
/// its whole loop body collapses to `i += 1`. The overwhelming majority of a value's
/// text is such content (identifier letters, digits, `-`, `#`, `%`, `.`), and one L1
/// load retires it — the per-byte branch chain is the cost these scanners are made of.
///
/// `$must_inspect` names the bytes the scanner cares about *beyond* the structural set.
///
/// **Only the ASCII half is populated** — a byte ≥ `0x80` is always `false`, i.e. never
/// skipped. That is load-bearing for [`super::cursor::ValueCursor::consume_until`],
/// which decodes a non-ASCII lead byte and hands the real `char` to an opaque predicate
/// (`char::is_whitespace` does treat NBSP and friends as delimiters). The pure byte-loop
/// scanners would be free to skip non-ASCII too — for them it is inert — but they take
/// the same rule so the tables mean exactly one thing everywhere, and a non-ASCII byte
/// costs only a walk through arms that all miss.
///
/// A `const fn` can't do this job: stable Rust forbids calling a function pointer in a
/// constant, so the predicate can't be a parameter.
pub use value_skip_table;