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
//! The vendored registries, as questions rather than arrays: is this subtag registered, what is it
//! called, what does it fold onto, what script does it imply.
//!
//! `table` is generated — 8275 languages, 224 scripts, 303 regions and four fold tables, every
//! word of them read out of a file vendored under `xtask/vendor/`. This module is the half that is
//! written, and it holds NO language knowledge at all: what is here is binary search, a range test
//! and the names of the columns.
//!
//! That split is the point. `cargo xtask check` regenerates `table` and refuses if the checked-in
//! file differs — the same command that checks this crate's pixel-format, colour and codec tables
//! against their own vendored files — so the vocabulary is a function of the vendored files and of
//! nothing else, and reviewing a registry bump is reading a diff of `("bh", "Bihari languages")`
//! rows rather than auditing hand-typed lists for a subtag someone got wrong.
//!
//! # It is PUBLIC, and what that is for
//!
//! [`Language`](super::Language) republishes the answers it needs — `name`, `is_registered`,
//! `is_deprecated` — so this module is not the route a caller normally takes. What it is for is the
//! question those methods cannot answer: **why** a value came out the way it did. A tag that
//! arrived as `ger-Latn-DE` and canonicalised to `de-DE` has taken two folds and a suppression, and
//! [`alpha3`], [`language_preferred`] and [`language_suppress_script`] are the three rows that
//! performed them. A fold with no way to read its own table is a canonicalisation a caller can only
//! disagree with.
//!
//! # Every lookup here takes the REGISTRY's own case, and none of them folds
//!
//! `language_name("DE")` is [`None`], and that is a floor rather than a gap. The registry spells a
//! language lower, a script Titlecase and a region UPPER, and each of the three types folds a wide
//! spelling onto that case ONCE, in its own constructor — so a value that exists is already in the
//! case its table is keyed by, and every later lookup is a direct hit. A second fold here would be
//! that work done twice, on a value that cannot need it.
//!
//! # The four folds, and which file each comes out of
//!
//! ```text
//! ger, deu ──alpha3──► de ISO 639-2's table — BCP 47 has neither word
//! iw ──language_preferred──► he the registry's `Preferred-Value`
//! BU ──region_preferred──► MM the same column, on a region
//! i-klingon ──grandfathered──► tlh a whole TAG, which is `LanguageId`'s business
//! ```
//!
//! The first three fold a SUBTAG and are one hop each, which the generator refuses a registry
//! without. The fourth folds the whole tag — so the other three, and the `Suppress-Script` deletion
//! beside them, can hand it a tag it then folds again. That is why it is applied to a fixed point
//! rather than once, and why [`MAX_GRANDFATHERED_HOPS`] is a generated number.
pub
pub use FILE_DATE;
use Ascii;
/// How many primary language subtags the vendored registry holds.
///
/// The reserved private-use RANGE is not one of them: it is a single record spelling `qaa..qtz`,
/// which names 512 subtags and registers none of them individually. [`LANGUAGE_PRIVATE_USE`] is
/// that record.
pub const LANGUAGE_COUNT: usize = LANGUAGES.len;
/// How many script subtags the vendored registry holds, the private-use range excluded.
pub const SCRIPT_COUNT: usize = SCRIPTS.len;
/// How many region subtags the vendored registry holds, the two private-use ranges excluded.
///
/// Two grammars are counted together, because the registry holds them in one list: ISO 3166-1
/// alpha-2 codes and UN M.49 three-digit area codes are both region subtags.
pub const REGION_COUNT: usize = REGIONS.len;
/// How many whole tags the registry grandfathered — those that name a replacement and those that do
/// not.
pub const GRANDFATHERED_COUNT: usize = GRANDFATHERED.len + GRANDFATHERED_KEPT.len;
/// The bounds of the language range reserved for private use, inclusive at both ends.
///
/// Published because it is a fact about the REGISTRY that a reader may want to check a subtag
/// against directly; [`language_is_private_use`] is the same fact as a question.
pub const LANGUAGE_PRIVATE_USE: = LANGUAGE_PRIVATE_USE;
/// The bounds of the script range reserved for private use, inclusive at both ends.
pub const SCRIPT_PRIVATE_USE: = SCRIPT_PRIVATE_USE;
/// The bounds of the region ranges reserved for private use, inclusive at both ends.
///
/// TWO ranges, where a language and a script have one each — `QM`..`QZ` and `XA`..`XZ`.
pub const REGION_PRIVATE_USE: & = REGION_PRIVATE_USE;
/// The region subtags reserved for private use INDIVIDUALLY, outside those ranges.
///
/// `AA` and `ZZ`. A region is the only subtag kind with any, so it is the only one whose
/// private-use question is not a range test alone — see [`region_is_private_use`]. The generator
/// refuses a registry in which a language or a script grows one, which is what keeps the asymmetry
/// checked rather than assumed.
pub const REGION_PRIVATE_USE_SUBTAGS: & = REGION_PRIVATE_USE_SUBTAGS;
// -----------------------------------------------------------------------------------------------
// Language
// -----------------------------------------------------------------------------------------------
/// The registry's first `Description` for a language subtag, or [`None`] where it registers none.
///
/// Answering [`Some`] IS registration — [`Language::is_registered`](super::Language::is_registered)
/// is this lookup asked for its emptiness — so there is one table behind both questions and no way
/// for a name and a membership to disagree.
///
/// The FIRST description where a subtag carries several: `zh` is *Chinese*, and `ro` is *Romanian*
/// where the registry also lists *Moldavian* and *Moldovan*. The first is the one the registry
/// leads with, and picking it here is what keeps `name` a function rather than a list a caller has
/// to choose from.
/// The subtag the registry says to use INSTEAD of this one, or [`None`] where it names none.
///
/// The `Preferred-Value` column: `iw` prefers `he`, `in` prefers `id`, `mo` prefers `ro`. It is
/// ONE hop by construction — the generator refuses a table where a preferred value itself prefers
/// something else — so a fold applies this once and is done.
///
/// A deprecated subtag is not always here. 120 of the 232 deprecated languages name no replacement
/// at all, and those stay themselves: [`language_is_deprecated`] is the question that finds them.
/// The script this language implies, or [`None`] where it implies none.
///
/// The `Suppress-Script` column, and the whole of what makes `en-Latn` compose as `en` while
/// `zh-Hans` composes as itself: `en` implies `Latn` and `zh` implies nothing, Chinese being written
/// in more than one script.
/// Has the registry deprecated this language subtag?
///
/// Independent of [`language_preferred`]: a subtag can be deprecated with a replacement, deprecated
/// without one, or neither. What it is never is replaced without being deprecated.
/// Does this subtag fall in the range the registry reserves for private use?
///
/// `qaa` through `qtz` — 512 subtags the registry names as a block and registers not one of. So a
/// private-use subtag is structurally fine, carries no name, and is not registered, which is three
/// separate answers rather than one.
/// The shortest BCP 47 spelling of an ISO 639-2 code, or [`None`] where the code IS the shortest
/// spelling or is not a 639-2 code at all.
///
/// The one column the SECOND vendored file feeds, and the one BCP 47 cannot: `ger` and `deu` are
/// German's bibliographic and terminological alpha-3 codes, an mkv writes the first and an mp4 the
/// second, and the registry contains neither word because `de` exists. Both answer `de` here, in
/// one hop.
///
/// `haw` answers [`None`], and that is the same rule rather than a miss: Hawaiian has no two-letter
/// code, so `haw` is already the shortest spelling and the registry carries it.
// -----------------------------------------------------------------------------------------------
// Script
// -----------------------------------------------------------------------------------------------
/// The registry's first `Description` for a script subtag, or [`None`] where it registers none.
/// Does this subtag fall in the script range reserved for private use — `Qaaa` through `Qabx`?
// -----------------------------------------------------------------------------------------------
// Region
// -----------------------------------------------------------------------------------------------
/// The registry's first `Description` for a region subtag, or [`None`] where it registers none.
/// The region the registry says to use INSTEAD of this one, or [`None`] where it names none.
///
/// Six rows, and every one of them a state that was succeeded by exactly one other: `BU` prefers
/// `MM`, `ZR` prefers `CD`, `TP` prefers `TL`. One hop, for [`language_preferred`]'s reason and
/// under the same generated guard.
/// Has the registry deprecated this region subtag?
///
/// Eleven have been, and five of those name no successor — `AN`, `CS`, `NT`, `SU` and `YU`, each a
/// state that dissolved into SEVERAL, where there is no single region to fold onto. Those keep
/// their own spelling and answer `true` here, which is the only honest pair of answers available.
/// Is this subtag one the registry reserves for private use — by range, or by a record of its own?
///
/// FOUR spellings and only two of them ranges: `QM`..`QZ`, `XA`..`XZ`, and then `AA` and `ZZ`, each
/// an individually registered record carrying the description *Private use*. A range test alone
/// answers `false` to the two a container actually writes.
///
/// It is therefore the ONE predicate in this module that can be `true` at the same time as
/// [`region_name`] answering [`Some`]. That is the registry's shape, not a reading invented here.
// -----------------------------------------------------------------------------------------------
// Grandfathered whole tags
// -----------------------------------------------------------------------------------------------
/// The tag the registry says to use INSTEAD of this grandfathered one, or [`None`] where it names
/// none — including for every tag that is not grandfathered at all.
///
/// A whole TAG rather than a subtag, which is what makes this [`LanguageId`](super::LanguageId)'s
/// table and not [`Language`](super::Language)'s: `i-klingon` prefers `tlh` and `zh-guoyu` prefers
/// `cmn`, and neither `i-klingon` nor `zh-guoyu` is a subtag of anything.
///
/// LOWER-CASED on both sides: the caller folds the tag's case before asking, because case is not
/// part of a tag's identity and `I-KLINGON` names what `i-klingon` names.
/// How many times the whole-tag fold can fire before a canonicalisation is a FIXED POINT.
///
/// **The one number here the generator PROVED rather than read off a column**, and the reason it
/// had to: [`grandfathered_preferred`] is applied to the whole tag, and the other folds REWRITE the
/// whole tag. `en-Latn-GB-oed` is not grandfathered as written — the suppression drops `Latn` and
/// leaves `en-GB-oed`, which is — so a canonicalisation that applied each fold once would render
/// text that reads back as a different identity, and the rendering is what serde and the wire codec
/// store. [`LanguageId::new`](super::LanguageId::new) therefore iterates to a fixed point, and this
/// is the bound it iterates to.
///
/// The bound is a property of the REGISTRY, so it is settled where the registry is read: the
/// generator walks every grandfathered tag through the composition rules, follows the chain each
/// fold's own output opens, and refuses a registry in which one cycles. A bump that lengthened a
/// chain fails `cargo xtask check`; it cannot reach production as a fold that quietly stops short.
pub const MAX_GRANDFATHERED_HOPS: usize = MAX_GRANDFATHERED_HOPS;
/// Is this whole tag one the registry grandfathered, whether or not it names a replacement?
///
/// Twenty-six are. Twenty-one name a replacement and are folded onto it; the five that do not are
/// [`grandfathered_preferred`]'s [`None`] and this predicate's `true` at the same time, which is the
/// distinction a single lookup could not carry.
// -----------------------------------------------------------------------------------------------
// The three shapes every lookup above is one of
// -----------------------------------------------------------------------------------------------
/// The second half of the row whose first half is `key`, by binary search.
///
/// Every generated pair table is sorted on its first element — the generator builds them in a
/// [`BTreeMap`](std::collections::BTreeMap), so the order is a property of how they are emitted
/// rather than a claim about them — which is what makes this a search rather than a scan over eight
/// thousand rows.
///
/// GENERIC over the key column's own inline seat width, `N`: each table rides its family's own
/// `Ascii<N>` (language, script, region or the whole grandfathered tag), and `N` is inferred from
/// `table`'s own type at every call site rather than chosen here. A `key` wider than `N` cannot
/// equal any row — every row's key already fits `N`, or the table would not have compiled — so it
/// answers [`None`] on the length alone, before a search or a fold.
///
/// The comparison stays `key`'s own text order: [`Ascii`]'s derived `Ord` is PINNED to agree with
/// `str`'s by `lang::tests::the_derived_order_is_the_texts_order_across_the_registry`, so a probe
/// built from `key`'s own bytes — [`Ascii::verbatim`], which folds nothing — sorts exactly where
/// `key` itself would have, and the table stays searchable by the same order it was emitted in.
/// Does this sorted table hold `key`?
/// Does `key` fall inside an inclusive range of same-width ASCII subtags?
///
/// The WIDTH test is what makes the comparison a range test rather than a lexicographic accident:
/// `qaa`..`qtz` is a block of three-letter subtags, and `qq` sorts between its bounds as text while
/// naming nothing inside it. Every subtag in a registry range has the width of the range's bounds,
/// so requiring it costs nothing and closes the case.
///
/// Byte comparison is the right relation here because both bounds and every subtag that can reach
/// this are ASCII: a structural check has already refused anything else, so no two subtags of equal
/// width compare by anything but their code points.