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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
//! The [`LanguageId`] composite: the four seats, and the composition rules that fill them.
use ;
use ;
use Utf8Bytes;
use ;
/// A whole language identity — what UTS-35 calls a *unicode language id* and what a track's
/// metadata actually carries: `de`, `zh-Hans`, `en-US`, `sr-Cyrl-RS`, `en-US-x-lorem`.
///
/// FOUR seats, and the fourth is what makes the type lossless:
///
/// ```text
/// language Language always present — a tag with no language is not a tag
/// script Option<ScriptSubtag> absent where none was declared, or where the language
/// implies it
/// region Option<Region> absent where none was declared
/// rest Option<…> everything past the region, VERBATIM
/// ```
///
/// # The composition rules, and every one of them is the registry's
///
/// | sent | held | the rule |
/// |---|---|---|
/// | `zh_Hans_CN` | `zh-Hans-CN` | `_` is read as `-`, which is what a filename-safe tag uses |
/// | `en-Latn` | `en` | `en` implies `Latn`, so declaring it says nothing — `Suppress-Script` |
/// | `zh-Hans` | `zh-Hans` | `zh` implies NO script, so the declared one is the whole content |
/// | `i-klingon` | `tlh` | a grandfathered tag, folded by its `Preferred-Value` |
/// | `GER-latn-de` | `de-DE` | each seat's own door, then the suppression |
/// | `en-Latn-Cyrl` | `en-Latn-Cyrl` | the suppression is skipped where it would not reparse |
///
/// **The `Suppress-Script` fold is the one that earns its keep**, and the pair above is why: it
/// removes a subtag that carries no information (`en-Latn` and `en` are the same identity) and
/// leaves alone one that carries all of it (`zh-Hans` and `zh-Hant` are two identities). A fold
/// spelled by hand would have had to decide which languages imply which scripts; this one reads a
/// column of a vendored file, so the answer is right for all 134 of them and stays right when the
/// registry moves.
///
/// **And it fires only where it is REVERSIBLE**, which is the last row and the one rule of this
/// table that is about the table itself: a fold here rewrites the TEXT, the text is the form serde
/// and the wire codec store, so a fold may only rewrite text that reads back as the same value. A
/// tail is held verbatim and its envelope is loose, so it can open with a subtag SHAPED like a
/// script — and dropping the script would leave that slot vacant for a reparse to fill from the
/// tail. `en-Latn-Cyrl` keeps its `Latn` for that reason and no other.
///
/// # `rest` is LOSSLESS, and that is the whole difference from what this type replaced
///
/// Everything past the region — variants (`en-US-posix`), extensions (`de-u-co-phonebk`), private
/// use (`en-x-lorem`) — is held VERBATIM, rendered back verbatim, and counts towards equality. It
/// is not parsed, not folded, and not case-normalised.
///
/// So `en-US-x-Foo` and `en-US-x-foo` are TWO values here, where RFC 5646's own canonical form would
/// lower-case both. That is the price of the seat being lossless rather than a defect in it: a
/// private-use sequence's meaning belongs to the private party that wrote it, and this type cannot
/// know that its case is insignificant. The tail is carried exactly as the file spelled it, and a
/// caller comparing tails as identifiers gets what the file said.
///
/// **The tail is not a seat anything asks a question ABOUT**, and that follows from the same fact.
/// There is nothing here that could be asked honestly of a text seat holding a mixture of three
/// grammars: what a caller wants to know about an identity is which language, script or region it
/// names, and each of those is a seat of its own. The tail rides along with them.
///
/// **The envelope the tail is checked against is deliberately loose**: each of its subtags must be
/// one to eight ASCII alphanumeric characters, which is the outer shape of every variant, extension
/// and private-use subtag together. `en-USA` is admitted, though `USA` is not a legal variant,
/// because a container writes what its muxer believed and a metadata layer's job is to carry it —
/// the same posture that admits an unregistered language subtag.
///
/// # An extlang has no seat, so it rides the tail — and takes the rest of the tag with it
///
/// `zh-yue` holds `zh` with a tail of `yue`, because `yue` is three letters and a script is four
/// while a region is two or three DIGITS. That is the roster doing what it says: the seats are
/// language, script and region, and BCP 47's extlang position is none of them.
///
/// The consequence is worth stating rather than discovering: the tail is a TAIL, so once a subtag
/// falls into it every later subtag does too. `zh-yue-Hant-HK` holds `zh` with a tail of
/// `yue-Hant-HK` — the script and region are IN the tail, and no seat accessor reaches them. The
/// tag round-trips exactly, and the LANGUAGE is still `zh`, which is the question most callers ask.
/// Reading its script off the seat is what is lost, and a `zh-Hant-HK` tag — which is what a
/// well-formed file writes — has that.
///
/// # Equality is the four seats, so it is the CANONICAL identity
///
/// `GER-latn-de` and `de-DE` are one value, because each seat folded before it was filled and the
/// suppression then removed the script. That is what makes a stored row and a later query meet:
/// four seats hold the canonical parts, and a value built from any spelling of the same identity
/// fills the same four.
///
/// It is EQUALITY of identities and not of languages, which the same pair says from the other side:
/// `de` and `de-DE` are two values, because one names a region and the other does not. Asking about
/// the language they share is [`language`](Self::language)'s business, not equality's.
///
/// # Ordering is the four seats in order, and it means what a text sort means
///
/// [`Ord`] is derived, so it compares the language first, then the script, then the region, then
/// the tail — each alphabetically, each absent sorting before present. It is a total, stable order,
/// and it is nothing else: `de-AT` sorts before `de-DE` because `A` sorts before `D`.
/// The undetermined tag, `und`.
///
/// **`und` is a VALUE, and it is not the absence of a tag** — the distinction
/// [`Language`]'s own doc argues, and the one this row is most at risk of blurring. A track nobody
/// tagged is an `Option::None`; a track a muxer tagged `und` has an identity, and it says the muxer
/// looked and could not tell. This is the second of those, which is what a wire codec needs when it
/// has to seed a value before reading one.
/// The widest subtag a tail may hold — the outer envelope of every variant, extension and
/// private-use subtag BCP 47 defines.
const TAIL_SUBTAG_MAX: usize = 8;
/// One tail through its envelope, held verbatim.
///
/// The envelope is deliberately LOOSE: one to eight ASCII alphanumeric characters per subtag, which
/// is the outer shape of every variant, extension and private-use subtag together rather than any
/// one of their grammars. See [`LanguageId`], where the reason is the same one that admits an
/// unregistered language subtag.
/// The widest whole tag the registry grandfathered, in bytes.
///
/// Nothing wider can be one, so nothing wider is looked up — which is what lets the case fold for
/// that lookup use a stack buffer rather than allocating a lower-cased copy of every tag that comes
/// through the door. `the_grandfathered_table_fits_the_lookup_buffer` is the pin that keeps the
/// shortcut from silently missing a tag the registry adds.
///
/// `pub(super)` rather than private: it is also the width the generated registry's `GRANDFATHERED`
/// key column is stored inline at, which is `subtag::MAX`'s reason for the same visibility — a
/// table's row width is not `id`'s secret once something outside the type needs to store the same
/// tag shape.
pub const GRANDFATHERED_MAX: usize = 16;
/// The replacement the registry names for a grandfathered tag, or [`None`] for a tag that is not
/// grandfathered or is grandfathered without one.
///
/// The five with no replacement answer [`None`] here and fall through to the ordinary parse, which
/// is the honest handling: two of them ARE ordinary compositions read subtag by subtag, and the
/// three beginning `i-` are refused by the language door for a reason it can state.
///
/// UTF-8 validity of the folded buffer is PROVEN BY PROVENANCE, so the conversion back is
/// unchecked — see the `SAFETY` note, and [`subtag::Cased::as_str`](super::subtag) where the same
/// trade is argued. The claim is checked by `simdutf8` under `debug_assert!`, which is the
/// configuration this crate's `miri` and sanitizer lanes run in.
///
/// This buffer is WIDER than the case fold's, and unlike that one it is not preceded by an
/// alphabet test — a whole tag reaches here before any subtag door has seen it, so the bytes may be
/// non-ASCII. That costs nothing: `to_ascii_lowercase` leaves every byte above `0x7F` exactly as it
/// found it, so a multi-byte sequence is copied through unchanged rather than re-cased.
/// Append `part`'s ASCII lower case to `buffer` at `width`, answering the width that leaves — or
/// [`None`] where it would not fit.
///
/// Overflow is an ANSWER rather than a failure, and that is the shortcut
/// [`lowered_canonical`](LanguageId::lowered_canonical) rests on: the only question asked of this
/// buffer is whether the text is a grandfathered tag, no such tag is wider than
/// [`GRANDFATHERED_MAX`], so text that does not fit is text the table cannot hold.
/// Is this subtag in the SCRIPT position's shape — exactly four ASCII letters?
/// Is this subtag in the REGION position's shape — two ASCII letters, or three ASCII digits?
/// Would a REPARSE of the suppressed text read the tail's first subtag as the SCRIPT?
///
/// The guard on the `Suppress-Script` fold, and the whole of why that fold is not unconditional.
///
/// # The invariant it defends
///
/// [`Display`] and the door are inverse, and everything downstream leans on that: serde writes the
/// rendered tag and reads it back through [`LanguageId::new`], and the wire codec does the same. So
/// a canonicalisation here does not merely tidy a rendering — it decides what a stored row
/// deserialises to, and it may rewrite the text ONLY where the rewritten text reads back as the
/// same four seats.
///
/// # The case where the suppression would not
///
/// The tail is a TAIL, so it holds whatever the seats declined, and its envelope is deliberately
/// loose — which makes `Cyrl` a legal tail subtag. `en-Latn-Cyrl` fills both: `Latn` on the script
/// seat, `Cyrl` on the tail. Drop `Latn` and the rendering is `en-Cyrl`, whose script slot is now
/// VACANT and first in line, so a reparse reads `Cyrl` as the script and an empty tail. Three
/// answers change — [`script`](LanguageId::script), [`rest`](LanguageId::rest) and equality — and
/// every stored `en-Latn-Cyrl` becomes a different identity on its next read. `en-Latn-Latn` is the
/// same case with the tail's subtag landing back on the seat it was never on.
///
/// Retaining the script is what this answers `true` for, and retaining is always safe: with the
/// script in the text, the script slot is filled by the subtag that belongs in it and the tail
/// begins exactly where it began.
///
/// # Why there is no REGION-shaped arm
///
/// The sibling question is the same one asked of the next slot down — with the script gone, could a
/// reparse's REGION slot claim a tail opening with two letters or three digits? It would; no value
/// that reaches here can be in that state. `composed` offers the subtag after the script to the
/// region door FIRST, so only a subtag that door's shape test turned down can become the head of a
/// tail: an empty [`region`](LanguageId::region) beside a region-shaped tail head is
/// unconstructible. That parse road is the ONLY road that fills `rest` — `From<Language>` and
/// `Default` leave it empty, `compose` is `pub(crate)` and reached from nowhere else, there is no
/// setter, and serde and the wire codec are that same road under other names. The pin on the door's
/// greed is `the_region_slot_takes_its_subtag_before_a_tail_can_open_with_one`.
///
/// Worth saying, since it is why the census had to be total rather than defensive: retaining the
/// script would NOT have rescued that state. The region slot sits after the script slot, so a
/// script in the text does not shield the tail from it — only the door's greed does.
///
/// # Why it does not consult the region seat
///
/// With a region filled, the vacated script slot is taken by the region subtag — two letters or
/// three digits, never four letters — so the tail cannot climb into it, and `en-Latn-US-Cyrl` could
/// safely render as `en-US-Cyrl`. This test does not look, and the price is that such a tag keeps a
/// script it could have dropped. The price is nil: a four-letter tail head is outside BCP 47's tail
/// grammar to begin with — a variant is five to eight alphanumerics, or four characters beginning
/// with a DIGIT, and an extension or private-use singleton is one character — so every tag this
/// retains a script on is already outside the well-formed set. One shape test on one subtag is the
/// whole guard, and the answer it gives is never wrong, only occasionally generous.
/// The canonical spelling: the seats that are filled, in order, joined by `-`.
///
/// The inverse of [`FromStr`] over canonical text, which is what makes it the one text form
/// everything else in this crate — serde, the wire codec — reads and writes.
/// The canonical tag, in quotes — not the four seats, which would make an assertion message four
/// times longer than the value it is about.
/// The standard fallible conversion, which is [`FromStr`] under the name a generic caller reaches
/// for. One door, so there is no second grammar to drift.
/// The same door, off the text seat this household carries — so a tag that arrived as a
/// [`Utf8Bytes`] crosses without a caller reaching for `.as_str()` first.
/// A tag with no other seats filled — the shape a bare language subtag composes to.
/// A string does not name a language identity.
///
/// The three seat variants CARRY the seat's own error rather than restating it, which is what keeps
/// one grammar per subtag kind: a caller sending `zh-Hans-XYZ` is told that a region written in
/// letters is exactly two, by [`Region`]'s own words.