Skip to main content

vstd/
utf8.rs

1//! Definitions for UTF-8 encoding and decoding of character sequences.
2//!
3//! [UTF-8](https://en.wikipedia.org/wiki/UTF-8) is a variable-width character encoding scheme.
4//! Each character is encoded with between 1 and 4 bytes.
5//! Specifications for encoding and decoding characters to their UTF-8 byte sequences are given by [`encode_utf8`] and [`decode_utf8`], respectively.
6//! Characters in the ASCII character set are encoded in UTF-8 with 1-byte encodings identical to those used by ASCII.
7//! Thus, some UTF-8 byte sequences can also be considered ASCII byte sequences, as defined in [`is_ascii_chars`].
8//!
9//! UTF-8 encodes numerical values called Unicode _scalars_ (see below), which assign a unique value to each Unicode character.
10//! A scalar value is encoded in UTF-8 using a leading byte and between 0 and 3 continuation bytes, where larger scalar values require more continuation bytes.
11//! The first part of the bit pattern in the leading byte is reserved for describing the number of bytes in the scalar's encoding (e.g., [`is_leading_byte_width_1`]).
12//! The rest of the leading byte contains data bits corresponding to the scalar's value (e.g., [`leading_bits_width_1`]).
13//! The continuation bytes also follow a specific bit pattern ([`is_continuation_byte`]) and contain the remainder of the data bits ([`continuation_bits`]).
14//!
15//! This module makes use of terminology from the [Unicode standard](https://www.unicode.org/glossary/).
16//! A Unicode _scalar_ is a numerical value (represented in this module as a `u32`) corresponding to a character that can be encoded in UTF-8.
17//! All Rust `char`s correspond to Unicode scalars ([`char_is_scalar`]),
18//! and every numerical value encoded in a UTF-8 byte sequence must fall within the range defined for Unicode scalars ([`is_scalar`]).
19//! The Unicode standard also defines a _codepoint_ to be a numerical value which falls in the range available for encoding characters in UTF-8.
20//! This may sound similar to the definition of scalar.
21//! However, the definition of codepoint is more permissive than that for scalars,
22//! as it includes some values which are technically possible to encode in the UTF-8 scheme,
23//! but in fact are not legal Unicode values
24//! (namely, the [high-surrogate and low-surrogate ranges](https://en.wikipedia.org/wiki/UTF-8#Surrogates)).
25//! To align with the Unicode terminology, in this module, we use the term "scalar" to describe the numerical values
26//! which can be encoded in valid UTF-8 byte sequences, and the term "codepoint" to describe numerical values which
27//! are learned upon decoding a byte sequence but may or may not be legal Unicode values.
28use super::prelude::*;
29use super::seq::*;
30
31verus! {
32
33broadcast use super::seq::group_seq_lemmas;
34/* Decoding UTF-8 to chars */
35
36/// True when the given byte conforms to the bit pattern for the first byte of a 1-byte UTF-8 encoding of a single codepoint.
37/// The byte must have the form 0xxxxxxx.
38pub open spec fn is_leading_byte_width_1(byte: u8) -> bool {
39    0x00 <= byte <= 0x7f
40}
41
42/// True when the given byte conforms to the bit pattern for the first byte of a 2-byte UTF-8 encoding of a single codepoint.
43/// The byte must have the form 110xxxxx.
44pub open spec fn is_leading_byte_width_2(byte: u8) -> bool {
45    0xc0 <= byte <= 0xdf
46}
47
48/// True when the given byte conforms to the bit pattern for the first byte of a 3-byte UTF-8 encoding of a single codepoint.
49/// The byte must have the form 1110xxxx.
50pub open spec fn is_leading_byte_width_3(byte: u8) -> bool {
51    0xe0 <= byte <= 0xef
52}
53
54/// True when the given byte conforms to the bit pattern for the first byte of a 4-byte UTF-8 encoding of a single codepoint.
55/// The byte must have the form 11110xxx.
56pub open spec fn is_leading_byte_width_4(byte: u8) -> bool {
57    0xf0 <= byte <= 0xf7
58}
59
60/// True when the given byte conforms to the bit pattern for a continuation byte of a UTF-8 encoding of a single codepoint.
61/// The byte must have the form 10xxxxxx.
62pub open spec fn is_continuation_byte(byte: u8) -> bool {
63    0x80 <= byte <= 0xbf
64}
65
66/// Value of the 6 data bits from the given continuation byte, assuming that it is a valid continuation byte for a UTF-8 encoding.
67pub open spec fn continuation_bits(byte: u8) -> u32
68    recommends
69        is_continuation_byte(byte),
70{
71    // 0x3f = 0011 1111
72    (byte & 0x3f) as u32
73}
74
75/// Value of the 7 data bits from the given byte, assuming that it is a valid leading byte for a 1-byte UTF-8 encoding.
76pub open spec fn leading_bits_width_1(byte: u8) -> u32
77    recommends
78        is_leading_byte_width_1(byte),
79{
80    // 0x7f = 0111 1111
81    (byte & 0x7F) as u32
82}
83
84/// Value of the 5 data bits from the given byte, assuming that it is a valid leading byte for a 2-byte UTF-8 encoding.
85pub open spec fn leading_bits_width_2(byte: u8) -> u32
86    recommends
87        is_leading_byte_width_2(byte),
88{
89    // 0x1f = 0001 1111
90    (byte & 0x1F) as u32
91}
92
93/// Value of the 4 data bits from the given byte, assuming that it is a valid leading byte for a 3-byte UTF-8 encoding.
94pub open spec fn leading_bits_width_3(byte: u8) -> u32
95    recommends
96        is_leading_byte_width_3(byte),
97{
98    // 0x0f = 0000 1111
99    (byte & 0x0F) as u32
100}
101
102/// Value of the 3 data bits from the given byte, assuming that it is a valid leading byte for a 4-byte UTF-8 encoding.
103pub open spec fn leading_bits_width_4(byte: u8) -> u32
104    recommends
105        is_leading_byte_width_4(byte),
106{
107    // 0x07 = 0000 0111
108    (byte & 0x07) as u32
109}
110
111/// The codepoint encoded by the given byte, assuming that it is a valid leading byte for a 1-byte UTF-8 encoding.
112pub open spec fn codepoint_width_1(byte1: u8) -> u32
113    recommends
114        is_leading_byte_width_1(byte1),
115{
116    leading_bits_width_1(byte1)
117}
118
119// 0xc1 = 1100 0001
120// 0xc1 & 0x1f = 0x01
121// 0x01 << 6 = 0100 0000 = 0x40
122// If byte2 = 0xff then byte2 & 0x3f = 0x3f = 0011 1111
123// so highest possible is 0111 1111 = 0x7f < 0x80
124/// The codepoint encoded by the given 2 bytes, assuming that they are a valid leading and continuation byte, respectively, for 2-byte UTF-8 encoding.
125pub open spec fn codepoint_width_2(byte1: u8, byte2: u8) -> u32
126    recommends
127        is_leading_byte_width_2(byte1),
128        is_continuation_byte(byte2),
129{
130    (leading_bits_width_2(byte1) << 6) | continuation_bits(byte2)
131}
132
133/// The codepoint encoded by the given 3 bytes, assuming that they are a valid leading and continuation bytes, respectively, for 3-byte UTF-8 encoding.
134pub open spec fn codepoint_width_3(byte1: u8, byte2: u8, byte3: u8) -> u32
135    recommends
136        is_leading_byte_width_3(byte1),
137        is_continuation_byte(byte2),
138        is_continuation_byte(byte3),
139{
140    (leading_bits_width_3(byte1) << 12) | (continuation_bits(byte2) << 6) | continuation_bits(byte3)
141}
142
143// 0xf7 = 1111 0111
144// 0xf7 & 0x07 = 0x07
145// 0x07 << 18 = 0001 1100 0000 0000 0000 0000 = 0x1c0000
146// 0xf5 = 1111 0101
147// 0xf5 & 0x07 = 0x05
148// 0x05 << 18 = 0001 0100 0000 0000 0000 0000 = 0x140000
149/// The codepoint encoded by the given 4 bytes, assuming that they are a valid leading and continuation bytes, respectively, for 4-byte UTF-8 encoding.
150pub open spec fn codepoint_width_4(byte1: u8, byte2: u8, byte3: u8, byte4: u8) -> u32
151    recommends
152        is_leading_byte_width_4(byte1),
153        is_continuation_byte(byte2),
154        is_continuation_byte(byte3),
155        is_continuation_byte(byte4),
156{
157    (leading_bits_width_4(byte1) << 18) | (continuation_bits(byte2) << 12) | (continuation_bits(
158        byte3,
159    ) << 6) | continuation_bits(byte4)
160}
161
162/// True when the given byte sequence begins with a well-formed leading byte and an appropriate number of well-formed continuation bytes for a UTF-8 encoding of a single codepoint.
163pub open spec fn valid_leading_and_continuation_bytes_first_codepoint(bytes: Seq<u8>) -> bool {
164    ||| (bytes.len() >= 1 && is_leading_byte_width_1(bytes[0]))
165    ||| (bytes.len() >= 2 && is_leading_byte_width_2(bytes[0]) && is_continuation_byte(bytes[1]))
166    ||| (bytes.len() >= 3 && is_leading_byte_width_3(bytes[0]) && is_continuation_byte(bytes[1])
167        && is_continuation_byte(bytes[2]))
168    ||| (bytes.len() >= 4 && is_leading_byte_width_4(bytes[0]) && is_continuation_byte(bytes[1])
169        && is_continuation_byte(bytes[2]) && is_continuation_byte(bytes[3]))
170}
171
172/// Returns the first codepoint encoded in UTF-8 in the given byte sequence, assuming that the sequence begins with a well-formed leading byte and an appropriate number of well-formed continuation bytes.
173pub open spec fn decode_first_codepoint(bytes: Seq<u8>) -> u32
174    recommends
175        valid_leading_and_continuation_bytes_first_codepoint(bytes),
176{
177    if is_leading_byte_width_1(bytes[0]) {
178        codepoint_width_1(bytes[0])
179    } else if is_leading_byte_width_2(bytes[0]) {
180        codepoint_width_2(bytes[0], bytes[1])
181    } else if is_leading_byte_width_3(bytes[0]) {
182        codepoint_width_3(bytes[0], bytes[1], bytes[2])
183    } else {
184        codepoint_width_4(bytes[0], bytes[1], bytes[2], bytes[3])
185    }
186}
187
188/// The length in bytes of the first codepoint encoded in UTF-8 in the given byte sequence, assuming that the sequence begins with a well-formed leading byte and an appropriate number of well-formed continuation bytes.
189pub open spec fn length_of_first_codepoint(bytes: Seq<u8>) -> int
190    recommends
191        valid_leading_and_continuation_bytes_first_codepoint(bytes),
192{
193    if is_leading_byte_width_1(bytes[0]) {
194        1
195    } else if is_leading_byte_width_2(bytes[0]) {
196        2
197    } else if is_leading_byte_width_3(bytes[0]) {
198        3
199    } else {
200        4
201    }
202}
203
204/// True when the given codepoint, when encoded in UTF-8 using `len` number of bytes, would not be an "overlong encoding".
205/// An overlong encoding is one that uses more bytes than needed to encode the given value.
206pub open spec fn not_overlong_encoding(codepoint: u32, len: int) -> bool {
207    &&& (len == 2 ==> 0x80 <= codepoint)
208    &&& (len == 3 ==> 0x800 <= codepoint)
209    &&& (len == 4 ==> 0x10000 <= codepoint <= 0x10ffff)
210}
211
212/// True when the given codepoint does not fall into the "surrogate range" of the Unicode standard.
213/// The surrogate range contains values which are technically possible to encode in UTF-8 but are not valid Unicode scalars.
214pub open spec fn not_surrogate(codepoint: u32) -> bool {
215    !(0xD800 <= codepoint <= 0xDFFF)
216}
217
218/// True when the given byte sequence begins with a well-formed UTF-8 encoding of a single scalar.
219/// To be a well-formed encoding, the bytes must: follow the expected bit pattern for leading and continuation bytes
220/// for a single scalar encoding, not be an "overlong encoding", and not fall in the surrogate range.
221pub open spec fn valid_first_scalar(bytes: Seq<u8>) -> bool {
222    &&& valid_leading_and_continuation_bytes_first_codepoint(bytes)
223    &&& not_overlong_encoding(decode_first_codepoint(bytes), length_of_first_codepoint(bytes))
224    &&& not_surrogate(decode_first_codepoint(bytes))
225}
226
227/// The first scalar encoded in UTF-8 in the given byte sequence, assuming that the sequence begins with a well-formed encoding of a single scalar.
228pub open spec fn decode_first_scalar(bytes: Seq<u8>) -> u32
229    recommends
230        valid_first_scalar(bytes),
231{
232    decode_first_codepoint(bytes)
233}
234
235/// The length in bytes of first scalar encoded in UTF-8 in the given byte sequence, assuming that the sequence begins with a well-formed encoding of a single scalar.
236pub open spec fn length_of_first_scalar(bytes: Seq<u8>) -> int
237    recommends
238        valid_first_scalar(bytes),
239{
240    length_of_first_codepoint(bytes)
241}
242
243/// Removes the first scalar encoded in UTF-8 in the given byte sequence and returns the rest of the sequence, assuming that the sequence begins with a well-formed encoding of a single scalar.
244pub open spec fn pop_first_scalar(bytes: Seq<u8>) -> Seq<u8>
245    recommends
246        valid_first_scalar(bytes),
247{
248    bytes.subrange(length_of_first_scalar(bytes), bytes.len() as int)
249}
250
251proof fn lemma_pop_first_scalar_decreases(bytes: Seq<u8>)
252    requires
253        valid_first_scalar(bytes),
254    ensures
255        pop_first_scalar(bytes).len() < bytes.len(),
256{
257    assert(length_of_first_scalar(bytes) <= bytes.len() as int);
258    assert(pop_first_scalar(bytes).len() == bytes.len() as int - length_of_first_scalar(bytes)) by {
259        lemma_seq_subrange_len(bytes, length_of_first_scalar(bytes), bytes.len() as int)
260    };
261}
262
263/// Takes the bytes corresponding to the first scalar encoded in UTF-8 in the given byte sequence, assuming that the sequence begins with a well-formed encoding of a single scalar.
264pub open spec fn take_first_scalar(bytes: Seq<u8>) -> Seq<u8>
265    recommends
266        valid_first_scalar(bytes),
267{
268    bytes.subrange(0, length_of_first_scalar(bytes))
269}
270
271/// True when the given bytes form a valid UTF-8 encoding.
272pub open spec fn valid_utf8(bytes: Seq<u8>) -> bool
273    decreases bytes.len(),
274{
275    bytes.len() != 0 ==> valid_first_scalar(bytes) && valid_utf8(pop_first_scalar(bytes))
276}
277
278/// The sequence of characters encoded as Unicode scalars in the given bytes, assuming that the bytes form a valid UTF-8 encoding.
279pub open spec fn decode_utf8(bytes: Seq<u8>) -> Seq<char>
280    recommends
281        valid_utf8(bytes),
282    decreases bytes.len(),
283    when valid_utf8(bytes)
284{
285    if bytes.len() == 0 {
286        seq![]
287    } else {
288        seq![decode_first_scalar(bytes) as char] + decode_utf8(pop_first_scalar(bytes))
289    }
290}
291
292/// The length in bytes of the last scalar encoded in UTF-8 in the given byte sequence, assuming that the bytes form a valid UTF-8 encoding.
293pub open spec fn length_of_last_scalar(bytes: Seq<u8>) -> int
294    recommends
295        valid_utf8(bytes),
296        bytes.len() > 0,
297{
298    let n = bytes.len() as int;
299    if !is_continuation_byte(bytes[n - 1]) {
300        1
301    } else if !is_continuation_byte(bytes[n - 2]) {
302        2
303    } else if !is_continuation_byte(bytes[n - 3]) {
304        3
305    } else {
306        4
307    }
308}
309
310/// Takes the bytes corresponding to the last scalar encoded in UTF-8 in the given byte sequence, assuming that the bytes form a valid UTF-8 encoding.
311pub open spec fn take_last_scalar(bytes: Seq<u8>) -> Seq<u8>
312    recommends
313        valid_utf8(bytes),
314        bytes.len() > 0,
315{
316    let len = length_of_last_scalar(bytes);
317    bytes.subrange(bytes.len() - len, bytes.len() as int)
318}
319
320/// The last scalar encoded in UTF-8 in the given byte sequence, assuming that the bytes form a valid UTF-8 encoding.
321pub open spec fn decode_last_scalar(bytes: Seq<u8>) -> u32
322    recommends
323        valid_utf8(bytes),
324        bytes.len() > 0,
325{
326    let n = bytes.len() as int;
327    if !is_continuation_byte(bytes[n - 1]) {
328        codepoint_width_1(bytes[n - 1])
329    } else if !is_continuation_byte(bytes[n - 2]) {
330        codepoint_width_2(bytes[n - 2], bytes[n - 1])
331    } else if !is_continuation_byte(bytes[n - 3]) {
332        codepoint_width_3(bytes[n - 3], bytes[n - 2], bytes[n - 1])
333    } else {
334        codepoint_width_4(bytes[n - 4], bytes[n - 3], bytes[n - 2], bytes[n - 1])
335    }
336}
337
338/* Encoding chars as UTF-8 */
339
340/// True when the given value is a Unicode scalar with a 1-byte UTF-8 encoding.
341pub open spec fn has_width_1_encoding(v: u32) -> bool {
342    0 <= v <= 0x7F
343}
344
345/// True when the given value is a Unicode scalar with a 2-byte UTF-8 encoding.
346pub open spec fn has_width_2_encoding(v: u32) -> bool {
347    0x80 <= v <= 0x7FF
348}
349
350/// True when the given value is a Unicode scalar with a 3-byte UTF-8 encoding.
351pub open spec fn has_width_3_encoding(v: u32) -> bool {
352    0x800 <= v <= 0xFFFF && !(0xD800 <= v <= 0xDFFF)
353}
354
355/// True when the given value is a Unicode scalar with a 4-byte UTF-8 encoding.
356pub open spec fn has_width_4_encoding(v: u32) -> bool {
357    0x10000 <= v <= 0x10FFFF
358}
359
360/// True when the given `u32` represents a Unicode scalar, i.e., a value that can be encoded in UTF-8.
361/// This definition is equivalent to: `0 <= v <= 0x10ffff && !(0xD800 <= v <= 0xDFFF)`.
362pub open spec fn is_scalar(v: u32) -> bool {
363    ||| has_width_1_encoding(v)
364    ||| has_width_2_encoding(v)
365    ||| has_width_3_encoding(v)
366    ||| has_width_4_encoding(v)
367}
368
369/// The first (and only) byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 1-byte UTF-8 encoding.
370pub open spec fn leading_byte_width_1(scalar: u32) -> u8
371    recommends
372        has_width_1_encoding(scalar),
373{
374    (scalar & 0x7F) as u8
375}
376
377/// The first byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 2-byte UTF-8 encoding.
378pub open spec fn leading_byte_width_2(scalar: u32) -> u8
379    recommends
380        has_width_2_encoding(scalar),
381{
382    0xC0 | ((scalar >> 6) & 0x1F) as u8
383}
384
385/// The first byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 3-byte UTF-8 encoding.
386pub open spec fn leading_byte_width_3(scalar: u32) -> u8
387    recommends
388        has_width_3_encoding(scalar),
389{
390    0xE0 | ((scalar >> 12) & 0x0F) as u8
391}
392
393/// The first byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 4-byte UTF-8 encoding.
394pub open spec fn leading_byte_width_4(scalar: u32) -> u8
395    recommends
396        has_width_4_encoding(scalar),
397{
398    0xF0 | ((scalar >> 18) & 0x7) as u8
399}
400
401/// The last continuation byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 2, 3, or 4-byte UTF-8 encoding.
402pub open spec fn last_continuation_byte(scalar: u32) -> u8
403    recommends
404        has_width_2_encoding(scalar) || has_width_3_encoding(scalar) || has_width_4_encoding(
405            scalar,
406        ),
407{
408    0x80 | (scalar & 0x3F) as u8
409}
410
411/// The second-to-last continuation byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 3 or 4-byte UTF-8 encoding.
412pub open spec fn second_last_continuation_byte(scalar: u32) -> u8
413    recommends
414        has_width_3_encoding(scalar) || has_width_4_encoding(scalar),
415{
416    0x80 | ((scalar >> 6) & 0x3F) as u8
417}
418
419/// The third-to-last continuation byte of the UTF-8 encoding of the given scalar value, assuming that the scalar has a 4-byte UTF-8 encoding.
420pub open spec fn third_last_continuation_byte(scalar: u32) -> u8
421    recommends
422        has_width_4_encoding(scalar),
423{
424    0x80 | ((scalar >> 12) & 0x3F) as u8
425}
426
427/// The UTF-8 encoding of the given value, assuming that it is a Unicode scalar.
428pub open spec fn encode_scalar(scalar: u32) -> Seq<u8>
429    recommends
430        is_scalar(scalar),
431{
432    if has_width_1_encoding(scalar) {
433        seq![leading_byte_width_1(scalar)]
434    } else if has_width_2_encoding(scalar) {
435        seq![leading_byte_width_2(scalar), last_continuation_byte(scalar)]
436    } else if has_width_3_encoding(scalar) {
437        seq![
438            leading_byte_width_3(scalar),
439            second_last_continuation_byte(scalar),
440            last_continuation_byte(scalar),
441        ]
442    } else {
443        seq![
444            leading_byte_width_4(scalar),
445            third_last_continuation_byte(scalar),
446            second_last_continuation_byte(scalar),
447            last_continuation_byte(scalar),
448        ]
449    }
450}
451
452/// The UTF-8 encoding of the given `char` sequence.
453pub open spec fn encode_utf8(chars: Seq<char>) -> Seq<u8>
454    decreases chars.len(),
455{
456    if chars.len() == 0 {
457        seq![]
458    } else {
459        encode_scalar(chars[0] as u32) + encode_utf8(chars.drop_first())
460    }
461}
462
463// See `vstd::std_specs::char` for the `char::len_utf8`/`char::is_whitespace`
464// `assume_specification`s that relate this module's model to those real
465// std-library methods.
466/// [`encode_utf8`] distributes over sequence concatenation.
467pub broadcast proof fn encode_utf8_concat(a: Seq<char>, b: Seq<char>)
468    ensures
469        #[trigger] encode_utf8(a + b) == encode_utf8(a) + encode_utf8(b),
470    decreases a.len(),
471{
472    if a.len() == 0 {
473        assert(a + b =~= b);
474    } else {
475        assert((a + b).drop_first() =~= a.drop_first() + b);
476        encode_utf8_concat(a.drop_first(), b);
477        assert(encode_scalar(a[0] as u32) + (encode_utf8(a.drop_first()) + encode_utf8(b)) =~= (
478        encode_scalar(a[0] as u32) + encode_utf8(a.drop_first())) + encode_utf8(b));
479    }
480}
481
482/// Specialization of [`encode_utf8_concat`] for appending one `char`.
483pub broadcast proof fn encode_utf8_push(chars: Seq<char>, c: char)
484    ensures
485        #[trigger] encode_utf8(chars.push(c)) == encode_utf8(chars) + encode_scalar(c as u32),
486{
487    assert(chars.push(c) =~= chars + seq![c]);
488    encode_utf8_concat(chars, seq![c]);
489    assert(seq![c].drop_first() =~= Seq::<char>::empty());
490    assert(encode_utf8(seq![c]) =~= encode_scalar(c as u32) + encode_utf8(Seq::<char>::empty()));
491}
492
493/// Growing a prefix by at least one `char` strictly increases its encoded
494/// byte length.
495pub proof fn lemma_encode_utf8_len_strictly_monotonic(s: Seq<char>, i: int, j: int)
496    requires
497        0 <= i < j <= s.len(),
498    ensures
499        encode_utf8(s.subrange(0, i)).len() < encode_utf8(s.subrange(0, j)).len(),
500{
501    assert(s.subrange(0, i) + s.subrange(i, j) =~= s.subrange(0, j));
502    encode_utf8_concat(s.subrange(0, i), s.subrange(i, j));
503    assert(s.subrange(i, j).len() == j - i);
504}
505
506/* Correspondence between encode_utf8 and decode_utf8 definitions */
507
508// Performing encode followed by decode on a scalar with a 1-byte UTF-8 encoding results in the same value.
509proof fn encode_decode_width_1(c: u32)
510    by (bit_vector)
511    requires
512        has_width_1_encoding(c),
513    ensures
514        ({
515            let b1 = leading_byte_width_1(c);
516            &&& is_leading_byte_width_1(b1)
517            &&& codepoint_width_1(b1) == c
518        }),
519{
520}
521
522// Performing decode followed by encode on a 1-byte UTF-8 encoding results in the same byte.
523proof fn decode_encode_width_1(b1: u8)
524    by (bit_vector)
525    requires
526        is_leading_byte_width_1(b1),
527    ensures
528        ({
529            let c = codepoint_width_1(b1);
530            &&& has_width_1_encoding(c)
531            &&& leading_byte_width_1(c) == b1
532        }),
533{
534}
535
536// Performing encode followed by decode on a scalar with a 2-byte UTF-8 encoding  results in the same value.
537proof fn encode_decode_width_2(c: u32)
538    by (bit_vector)
539    requires
540        has_width_2_encoding(c),
541    ensures
542        ({
543            let b1 = leading_byte_width_2(c);
544            let b2 = last_continuation_byte(c);
545            &&& is_leading_byte_width_2(b1)
546            &&& is_continuation_byte(b2)
547            &&& codepoint_width_2(b1, b2) == c
548        }),
549{
550}
551
552// Performing decode followed by encode on a 2-byte UTF-8 encoding results in the same bytes.
553proof fn decode_encode_width_2(b1: u8, b2: u8)
554    by (bit_vector)
555    requires
556        is_leading_byte_width_2(b1),
557        is_continuation_byte(b2),
558        not_overlong_encoding(codepoint_width_2(b1, b2), 2),
559    ensures
560        ({
561            let c = codepoint_width_2(b1, b2);
562            &&& has_width_2_encoding(c)
563            &&& leading_byte_width_2(c) == b1
564            &&& last_continuation_byte(c) == b2
565        }),
566{
567}
568
569// Performing encode followed by decode on a scalar with a 3-byte UTF-8 encoding  results in the same value.
570proof fn encode_decode_width_3(c: u32)
571    by (bit_vector)
572    requires
573        has_width_3_encoding(c),
574    ensures
575        ({
576            let b1 = leading_byte_width_3(c);
577            let b2 = second_last_continuation_byte(c);
578            let b3 = last_continuation_byte(c);
579            &&& is_leading_byte_width_3(b1)
580            &&& is_continuation_byte(b2)
581            &&& is_continuation_byte(b3)
582            &&& codepoint_width_3(b1, b2, b3) == c
583        }),
584{
585}
586
587// Performing decode followed by encode on a 3-byte UTF-8 encoding results in the same bytes.
588proof fn decode_encode_width_3(b1: u8, b2: u8, b3: u8)
589    by (bit_vector)
590    requires
591        is_leading_byte_width_3(b1),
592        is_continuation_byte(b2),
593        is_continuation_byte(b3),
594        not_overlong_encoding(codepoint_width_3(b1, b2, b3), 3),
595        not_surrogate(codepoint_width_3(b1, b2, b3)),
596    ensures
597        ({
598            let c = codepoint_width_3(b1, b2, b3);
599            &&& has_width_3_encoding(c)
600            &&& leading_byte_width_3(c) == b1
601            &&& second_last_continuation_byte(c) == b2
602            &&& last_continuation_byte(c) == b3
603        }),
604{
605}
606
607// Performing encode followed by decode on a scalar with a 4-byte UTF-8 encoding results in the same value.
608proof fn encode_decode_width_4(c: u32)
609    by (bit_vector)
610    requires
611        has_width_4_encoding(c),
612    ensures
613        ({
614            let b1 = leading_byte_width_4(c);
615            let b2 = third_last_continuation_byte(c);
616            let b3 = second_last_continuation_byte(c);
617            let b4 = last_continuation_byte(c);
618            &&& is_leading_byte_width_4(b1)
619            &&& is_continuation_byte(b2)
620            &&& is_continuation_byte(b3)
621            &&& is_continuation_byte(b4)
622            &&& codepoint_width_4(b1, b2, b3, b4) == c
623        }),
624{
625}
626
627// Performing decode followed by encode on a 4-byte UTF-8 encoding results in the same bytes.
628proof fn decode_encode_width_4(b1: u8, b2: u8, b3: u8, b4: u8)
629    by (bit_vector)
630    requires
631        is_leading_byte_width_4(b1),
632        is_continuation_byte(b2),
633        is_continuation_byte(b3),
634        is_continuation_byte(b4),
635        not_overlong_encoding(codepoint_width_4(b1, b2, b3, b4), 4),
636    ensures
637        ({
638            let c = codepoint_width_4(b1, b2, b3, b4);
639            &&& has_width_4_encoding(c)
640            &&& leading_byte_width_4(c) == b1
641            &&& third_last_continuation_byte(c) == b2
642            &&& second_last_continuation_byte(c) == b3
643            &&& last_continuation_byte(c) == b4
644        }),
645{
646}
647
648/// A `char` always represents a Unicode scalar value.
649pub broadcast proof fn char_is_scalar(c: char)
650    ensures
651        is_scalar(#[trigger] (c as u32)),
652{
653}
654
655/// Ensures that a `char`, when cast to a `u32`, can be cast back to a `char`.
656pub broadcast proof fn char_u32_cast(c: char, u: u32)
657    requires
658        u == #[trigger] (c as u32),
659    ensures
660        #[trigger] (u as char) == c,
661{
662}
663
664/// Properties of the first scalar from the result of [`encode_utf8`].
665pub proof fn encode_utf8_first_scalar(chars: Seq<char>)
666    requires
667        chars.len() > 0,
668    ensures
669        decode_first_scalar(encode_utf8(chars)) == chars[0] as u32,
670        length_of_first_scalar(encode_utf8(chars)) == encode_scalar(chars[0] as u32).len(),
671        valid_first_scalar(encode_utf8(chars)),
672{
673    char_is_scalar(chars[0]);
674    let s = chars[0] as u32;
675    if has_width_1_encoding(s) {
676        encode_decode_width_1(s);
677    } else if has_width_2_encoding(s) {
678        encode_decode_width_2(s);
679    } else if has_width_3_encoding(s) {
680        encode_decode_width_3(s);
681    } else {
682        encode_decode_width_4(s);
683    }
684}
685
686/// Ensures the result of [`encode_utf8`] always satisfies [`valid_utf8`].
687pub broadcast proof fn encode_utf8_valid_utf8(chars: Seq<char>)
688    ensures
689        valid_utf8(#[trigger] encode_utf8(chars)),
690    decreases chars.len(),
691{
692    if chars.len() == 0 {
693    } else {
694        let bytes = encode_utf8(chars);
695        encode_utf8_first_scalar(chars);
696        assert(pop_first_scalar(bytes) =~= encode_utf8(chars.drop_first()));
697        encode_utf8_valid_utf8(chars.drop_first());
698    }
699}
700
701/// Ensures that performing [`encode_utf8`] followed by [`decode_utf8`] results in the original `char` sequence.
702pub broadcast proof fn encode_utf8_decode_utf8(chars: Seq<char>)
703    ensures
704        #[trigger] decode_utf8(encode_utf8(chars)) == chars,
705    decreases chars.len(),
706{
707    broadcast use encode_utf8_valid_utf8;
708
709    if chars.len() == 0 {
710    } else {
711        let bytes = encode_utf8(chars);
712        encode_utf8_first_scalar(chars);
713        char_u32_cast(chars[0], decode_first_scalar(bytes));
714
715        assert(pop_first_scalar(bytes) =~= encode_utf8(chars.drop_first()));
716        let rest = chars.drop_first();
717        encode_utf8_decode_utf8(rest);
718    }
719}
720
721/// Properties of the first scalar from the result of [`decode_utf8`].
722pub proof fn decode_utf8_first_scalar(bytes: Seq<u8>)
723    requires
724        valid_utf8(bytes),
725        bytes.len() > 0,
726    ensures
727        encode_scalar((decode_first_scalar(bytes) as char) as u32) == take_first_scalar(bytes),
728{
729    if is_leading_byte_width_1(bytes[0]) {
730        decode_encode_width_1(bytes[0]);
731    } else if is_leading_byte_width_2(bytes[0]) {
732        decode_encode_width_2(bytes[0], bytes[1]);
733    } else if is_leading_byte_width_3(bytes[0]) {
734        decode_encode_width_3(bytes[0], bytes[1], bytes[2]);
735    } else {
736        decode_encode_width_4(bytes[0], bytes[1], bytes[2], bytes[3]);
737    }
738}
739
740/// Ensures that performing [`decode_utf8`] followed by [`encode_utf8`] results in the original byte sequence.
741pub broadcast proof fn decode_utf8_encode_utf8(bytes: Seq<u8>)
742    requires
743        valid_utf8(bytes),
744    ensures
745        #[trigger] encode_utf8(decode_utf8(bytes)) == bytes,
746    decreases bytes.len(),
747{
748    broadcast use encode_utf8_valid_utf8;
749
750    if bytes.len() == 0 {
751    } else {
752        let chars = decode_utf8(bytes);
753        let first = decode_first_scalar(bytes) as char;
754        let rest = pop_first_scalar(bytes);
755
756        char_is_scalar(first);
757        assert(encode_scalar(first as u32) == take_first_scalar(bytes)) by {
758            decode_utf8_first_scalar(bytes);
759        }
760
761        assert(chars.drop_first() =~= decode_utf8(rest));
762        decode_utf8_encode_utf8(rest);
763    }
764}
765
766/* Partial UTF-8 sequences */
767
768/// True when the first `i` bytes in the given sequence represent a valid UTF-8 encoding.
769pub open spec fn partial_valid_utf8(bytes: Seq<u8>, i: int) -> bool {
770    0 <= i <= bytes.len() && valid_utf8(bytes.subrange(0, i))
771}
772
773/// Ensures that a byte sequence is not a valid UTF-8 byte sequence when it has a suffix that is not a valid UTF-8 byte sequence.
774pub proof fn partial_valid_partial_invalid_utf8(bytes: Seq<u8>, i: int)
775    requires
776        0 <= i <= bytes.len(),
777        valid_utf8(bytes.subrange(0, i)),
778        !valid_utf8(bytes.subrange(i, bytes.len() as int)),
779    ensures
780        !valid_utf8(bytes),
781{
782    partial_valid_utf8_invalid_subrange_helper(bytes, i, 0);
783    assert(bytes.subrange(0, bytes.len() as int) =~= bytes);
784}
785
786proof fn partial_valid_utf8_invalid_subrange_helper(bytes: Seq<u8>, i: int, j: int)
787    requires
788        0 <= j <= i <= bytes.len(),
789        valid_utf8(bytes.subrange(0, i)),
790        !valid_utf8(bytes.subrange(i, bytes.len() as int)),
791        valid_utf8(bytes.subrange(0, j)),
792        valid_utf8(bytes.subrange(j, i)),
793    ensures
794        !valid_utf8(bytes.subrange(j, bytes.len() as int)),
795    decreases (bytes.len() - j),
796{
797    if j == i {
798    } else {
799        let bytes_j = bytes.subrange(j, bytes.len() as int);
800        if valid_first_scalar(bytes_j) {
801            partial_valid_utf8_extend(bytes, j);
802            let k = length_of_first_scalar(bytes_j);
803
804            assert(pop_first_scalar(bytes.subrange(j, i)) == bytes.subrange(j + k, i));
805
806            partial_valid_utf8_invalid_subrange_helper(bytes, i, j + k);
807
808            assert(bytes_j.subrange(k, bytes_j.len() as int) == bytes.subrange(
809                j + k,
810                bytes.len() as int,
811            ));
812        }
813    }
814}
815
816/// Ensures that concatenating two valid UTF-8 byte sequence results in a valid UTF-8 byte sequence.
817pub broadcast proof fn valid_utf8_concat(b1: Seq<u8>, b2: Seq<u8>)
818    requires
819        #[trigger] valid_utf8(b1),
820        #[trigger] valid_utf8(b2),
821    ensures
822        #[trigger] valid_utf8(b1 + b2),
823    decreases b1.len(),
824{
825    if b1.len() == 0 {
826        assert(b1 + b2 == b2) by { Seq::add_empty_left(b1, b2) };
827        assert(valid_utf8(b1 + b2));
828    } else {
829        let rest = pop_first_scalar(b1);
830        assert(pop_first_scalar(b1).len() < b1.len()) by { lemma_pop_first_scalar_decreases(b1) };
831        valid_utf8_concat(rest, b2);
832        assert(pop_first_scalar(b1 + b2) =~= rest + b2);
833        assert(valid_utf8(b1 + b2));
834    }
835}
836
837/// Ensures that if the prefix of a byte sequence is valid UTF-8, and remainder of the sequence begins with a valid UTF-8 encoding of a single scalar,
838/// then the prefix extended by that scalar encoding is also valid UTF-8.
839pub broadcast proof fn partial_valid_utf8_extend(bytes: Seq<u8>, i: int)
840    requires
841        #[trigger] partial_valid_utf8(bytes, i),
842        #[trigger] valid_first_scalar(bytes.subrange(i, bytes.len() as int)),
843    ensures
844        #[trigger] partial_valid_utf8(
845            bytes,
846            i + length_of_first_scalar(bytes.subrange(i, bytes.len() as int)),
847        ),
848{
849    reveal_with_fuel(valid_utf8, 2);
850    let scalar = bytes.subrange(
851        i,
852        i + length_of_first_scalar(bytes.subrange(i, bytes.len() as int)),
853    );
854    valid_utf8_concat(bytes.subrange(0, i), scalar);
855    assert(bytes.subrange(0, i) + scalar =~= bytes.subrange(
856        0,
857        i + length_of_first_scalar(bytes.subrange(i, bytes.len() as int)),
858    ));
859}
860
861/// Ensures that if the prefix of a byte sequence is valid UTF-8, and remainder of the sequence begins with a subsequence of valid UTF-8 encodings for 1-byte scalars (i.e. ASCII characters),
862/// then the prefix extended by that subsequence is also valid UTF-8.
863pub broadcast proof fn partial_valid_utf8_extend_ascii_block(bytes: Seq<u8>, start: int, end: int)
864    requires
865        forall|i: int|
866            0 <= start <= i < end <= bytes.len() ==> #[trigger] is_leading_byte_width_1(bytes[i]),
867        partial_valid_utf8(bytes, start),
868        0 <= start <= end <= bytes.len(),
869    ensures
870        #![trigger partial_valid_utf8(bytes, start), partial_valid_utf8(bytes, end)]
871        partial_valid_utf8(bytes, end),
872    decreases end - start,
873{
874    if end == start {
875    } else {
876        partial_valid_utf8_extend_ascii_block(bytes, start, end - 1);
877
878        let b = bytes[end - 1];
879        assert(is_leading_byte_width_1(b));
880        partial_valid_utf8_extend(bytes, end - 1);
881    }
882}
883
884/* Reasoning about character boundaries */
885
886/// True when the given index into the byte sequence is the first byte of a character's encoding or the end of the sequence, assuming that the sequence is valid UTF-8.
887pub open spec fn is_char_boundary(bytes: Seq<u8>, index: int) -> bool
888    recommends
889        valid_utf8(bytes),
890    decreases bytes.len(),
891    when valid_utf8(bytes)
892{
893    if index == 0 {
894        true
895    } else if index < 0 || bytes.len() < index {
896        false
897    } else {
898        is_char_boundary(pop_first_scalar(bytes), index - length_of_first_scalar(bytes))
899    }
900}
901
902proof fn take_first_scalar_valid_utf8(bytes: Seq<u8>)
903    requires
904        valid_utf8(bytes),
905    ensures
906        bytes.len() > 0 ==> valid_utf8(take_first_scalar(bytes)),
907{
908    reveal_with_fuel(valid_utf8, 2);
909}
910
911/// Ensures that the two subsequences formed by splitting a valid UTF-8 byte sequence at a character boundary are also valid UTF-8 byte sequences.
912pub broadcast proof fn valid_utf8_split(bytes: Seq<u8>, index: int)
913    requires
914        valid_utf8(bytes),
915        is_char_boundary(bytes, index),
916    ensures
917        #![trigger valid_utf8(bytes.subrange(0, index)), is_char_boundary(bytes, index)]
918        #![trigger valid_utf8(bytes.subrange(index, bytes.len() as int)), is_char_boundary(bytes, index)]
919        valid_utf8(bytes.subrange(0, index)),
920        valid_utf8(bytes.subrange(index, bytes.len() as int)),
921    decreases bytes.len(),
922{
923    if index == 0 {
924        assert(bytes =~= bytes.subrange(index, bytes.len() as int));
925    } else {
926        broadcast use lemma_seq_subrange_len;
927
928        let s1 = bytes.subrange(0, index);
929        let s2 = bytes.subrange(index, bytes.len() as int);
930        let head = take_first_scalar(bytes);
931        let tail = pop_first_scalar(bytes);
932        let new_offset = index - length_of_first_scalar(bytes);
933        // recursive call: show valid on split for tail
934        valid_utf8_split(tail, new_offset);
935        let n1 = tail.subrange(0, new_offset);
936        let n2 = tail.subrange(new_offset, tail.len() as int);
937        // now we need to concatenate the head back on
938        assert(s1 =~= head + n1) by {
939            assert(s1.len() == head.len() + n1.len()) by {
940                // to use subrange len axiom, we need to show that new_offset is in bounds for tail
941                is_char_boundary_len_first_scalar(bytes, index);
942            }
943        }
944        assert(valid_utf8(head + n1)) by {
945            take_first_scalar_valid_utf8(bytes);
946            valid_utf8_concat(head, n1);
947        }
948        assert(s2 =~= n2);
949    }
950}
951
952/// Ensures that a valid UTF-8 byte sequence can be decoded by separately decoding the two subsequences formed by splitting the original sequence at a character boundary.
953pub broadcast proof fn decode_utf8_split(bytes: Seq<u8>, index: int)
954    requires
955        valid_utf8(bytes),
956        is_char_boundary(bytes, index),
957    ensures
958        #![trigger decode_utf8(bytes.subrange(0, index)), is_char_boundary(bytes, index)]
959        #![trigger decode_utf8(bytes.subrange(index, bytes.len() as int)), is_char_boundary(bytes, index)]
960        decode_utf8(bytes) =~= decode_utf8(bytes.subrange(0, index)) + decode_utf8(
961            bytes.subrange(index, bytes.len() as int),
962        ),
963    decreases index,
964{
965    if index == 0 {
966        assert(bytes.subrange(index, bytes.len() as int) =~= bytes);
967    } else {
968        let first = bytes.subrange(0, index);
969        let second = bytes.subrange(index, bytes.len() as int);
970        is_char_boundary_len_first_scalar(bytes, index);
971        valid_utf8_split(bytes, index);
972        let bytes_tail = pop_first_scalar(bytes);
973        let first_tail = pop_first_scalar(first);
974        let bytes_head = decode_first_scalar(bytes) as char;
975        let first_head = decode_first_scalar(first) as char;
976        let new_index = (index - length_of_first_scalar(bytes)) as int;
977        decode_utf8_split(bytes_tail, new_index);
978        assert(second =~= bytes_tail.subrange(new_index, bytes_tail.len() as int));
979        assert(first_tail =~= bytes_tail.subrange(0, new_index));
980    }
981}
982
983proof fn is_char_boundary_len_first_scalar(bytes: Seq<u8>, index: int)
984    requires
985        valid_utf8(bytes),
986        is_char_boundary(bytes, index),
987    ensures
988        index > 0 ==> index >= length_of_first_scalar(bytes),
989{
990    reveal_with_fuel(is_char_boundary, 2);
991}
992
993/// Ensures that the start and end of a valid UTF-8 byte sequence are character boundaries.
994pub broadcast proof fn is_char_boundary_start_end_of_seq(bytes: Seq<u8>)
995    requires
996        valid_utf8(bytes),
997    ensures
998        #![trigger is_char_boundary(bytes, 0)]
999        #![trigger is_char_boundary(bytes, bytes.len() as int)]
1000        is_char_boundary(bytes, 0),
1001        is_char_boundary(bytes, bytes.len() as int),
1002    decreases bytes.len(),
1003{
1004    if bytes.len() == 0 {
1005    } else {
1006        is_char_boundary_start_end_of_seq(pop_first_scalar(bytes));
1007    }
1008}
1009
1010/// Ensures that any byte in a valid UTF-8 byte sequence falls on a character boundary (i.e. the first byte in a codepoint's encoding) if and only if it does not have the form of a UTF-8 continuation byte.
1011pub broadcast proof fn is_char_boundary_iff_not_is_continuation_byte(bytes: Seq<u8>, index: int)
1012    requires
1013        valid_utf8(bytes),
1014        0 <= index < bytes.len(),
1015    ensures
1016        #[trigger] is_char_boundary(bytes, index) <==> !(#[trigger] is_continuation_byte(
1017            bytes[index],
1018        )),
1019    decreases bytes.len(),
1020{
1021    if 0 <= index < length_of_first_scalar(bytes) {
1022        reveal_with_fuel(is_char_boundary, 2);
1023    } else {
1024        is_char_boundary_iff_not_is_continuation_byte(
1025            pop_first_scalar(bytes),
1026            index - length_of_first_scalar(bytes),
1027        );
1028    }
1029}
1030
1031/// Ensures that any byte in a valid UTF-8 byte sequence falls on a character boundary (i.e. the first byte in a codepoint's encoding) if and only if it has the form of a UTF-8 leading byte.
1032pub broadcast proof fn is_char_boundary_iff_is_leading_byte(bytes: Seq<u8>, index: int)
1033    requires
1034        valid_utf8(bytes),
1035        0 <= index < bytes.len(),
1036    ensures
1037        #![trigger is_char_boundary(bytes, index), is_leading_byte_width_1(bytes[index])]
1038        #![trigger is_char_boundary(bytes, index), is_leading_byte_width_2(bytes[index])]
1039        #![trigger is_char_boundary(bytes, index), is_leading_byte_width_3(bytes[index])]
1040        #![trigger is_char_boundary(bytes, index), is_leading_byte_width_4(bytes[index])]
1041        is_char_boundary(bytes, index) <==> (is_leading_byte_width_1(bytes[index])
1042            || is_leading_byte_width_2(bytes[index]) || is_leading_byte_width_3(bytes[index])
1043            || is_leading_byte_width_4(bytes[index])),
1044    decreases bytes.len(),
1045{
1046    if 0 <= index < length_of_first_scalar(bytes) {
1047        reveal_with_fuel(is_char_boundary, 2);
1048    } else {
1049        is_char_boundary_iff_is_leading_byte(
1050            pop_first_scalar(bytes),
1051            index - length_of_first_scalar(bytes),
1052        );
1053    }
1054}
1055
1056pub broadcast proof fn valid_utf8_last(s: Seq<u8>)
1057    requires
1058        valid_utf8(s),
1059        s.len() > 0,
1060    ensures
1061        #![trigger is_continuation_byte(s.last())]
1062        #![trigger is_leading_byte_width_1(s.last())]
1063        !is_continuation_byte(s.last()) ==> is_leading_byte_width_1(s.last()),
1064    decreases s.len(),
1065{
1066    // this proof must be discharged recursively, since valid_utf8 only tells you information
1067    // about the first codepoint and recurses from there
1068    let first = decode_first_scalar(s);
1069    let rest = pop_first_scalar(s);
1070
1071    if rest.len() == 0 {
1072        if s.len() > 1 {
1073            assert(is_continuation_byte(s[s.len() - 1]));
1074        }
1075    } else {
1076        valid_utf8_last(rest);
1077    }
1078}
1079
1080/* Bit-level reasoning */
1081
1082/// Formulates the byte ranges for each type of byte in UTF-8 (leading and continuation) in terms of bitwise operators instead of ranges.
1083pub broadcast proof fn utf8_byte_ranges_bitwise(b: u8)
1084    by (bit_vector)
1085    ensures
1086        #![trigger b & 0x80]
1087        #![trigger b & 0xf0]
1088        #![trigger b & 0xf8]
1089        #![trigger b & 0xe0]
1090        #![trigger b & 0xc0]
1091        0x00 <= b <= 0x7f <==> b & 0x80 == 0,
1092        0xc0 <= b <= 0xdf <==> b & 0xe0 == 0xc0,
1093        0xe0 <= b <= 0xef <==> b & 0xf0 == 0xe0,
1094        0xf0 <= b <= 0xf7 <==> b & 0xf8 == 0xf0,
1095        0x80 <= b <= 0xbf <==> b & 0xc0 == 0x80,
1096{
1097}
1098
1099/* ASCII */
1100
1101/// True when the given character sequence only contains ASCII characters.
1102pub open spec fn is_ascii_chars(chars: Seq<char>) -> bool {
1103    forall|i| 0 <= i < chars.len() ==> '\0' <= #[trigger] chars[i] <= '\u{7f}'
1104}
1105
1106/// Ensures that the UTF-8 encoding for an ASCII character sequence has the same length of the original sequence and corresponds byte-by-byte to the characters in the original sequence.
1107pub broadcast proof fn is_ascii_chars_encode_utf8(chars: Seq<char>)
1108    requires
1109        #[trigger] is_ascii_chars(chars),
1110    ensures
1111        chars.len() == encode_utf8(chars).len(),
1112        forall|i|
1113            #![trigger chars[i]]
1114            #![trigger encode_utf8(chars)[i]]
1115            0 <= i < chars.len() ==> chars[i] as u8 == encode_utf8(chars)[i],
1116    decreases chars.len(),
1117{
1118    if chars.len() == 0 {
1119    } else {
1120        let c0 = chars[0] as u32;
1121        assert(c0 as u8 == leading_byte_width_1(c0)) by (bit_vector)
1122            requires
1123                has_width_1_encoding(c0),
1124        ;
1125        is_ascii_chars_encode_utf8(chars.drop_first());
1126    }
1127}
1128
1129/// Ensures that all characters in an ASCII character sequence have a numerical representation that falls in the range 0 (inclusive) to 128 (exclusive).
1130pub broadcast proof fn is_ascii_chars_nat_bound(chars: Seq<char>)
1131    ensures
1132        #[trigger] is_ascii_chars(chars) ==> forall|i: int|
1133            0 <= i < chars.len() ==> (chars.index(i) as nat) < 128,
1134{
1135}
1136
1137/// Ensures that an ASCII character sequence is formed by the concatenation of two ASCII character sequences.
1138pub broadcast proof fn is_ascii_chars_concat(c1: Seq<char>, c2: Seq<char>, c3: Seq<char>)
1139    requires
1140        c1 =~= c2 + c3,
1141    ensures
1142        #![trigger c2 + c3, is_ascii_chars(c1), is_ascii_chars(c2), is_ascii_chars(c3)]
1143        is_ascii_chars(c1) <==> is_ascii_chars(c2) && is_ascii_chars(c3),
1144{
1145    if (is_ascii_chars(c1)) {
1146        assert(c2 =~= c1.subrange(0, c2.len() as int));
1147        assert(c3 =~= c1.subrange(c2.len() as int, c1.len() as int));
1148    }
1149}
1150
1151pub broadcast group group_utf8_lib {
1152    encode_utf8_valid_utf8,
1153    encode_utf8_decode_utf8,
1154    decode_utf8_encode_utf8,
1155    char_is_scalar,
1156    char_u32_cast,
1157    valid_utf8_concat,
1158    partial_valid_utf8_extend,
1159    partial_valid_utf8_extend_ascii_block,
1160    valid_utf8_split,
1161    decode_utf8_split,
1162    is_char_boundary_start_end_of_seq,
1163    is_char_boundary_iff_not_is_continuation_byte,
1164    is_char_boundary_iff_is_leading_byte,
1165    valid_utf8_last,
1166    utf8_byte_ranges_bitwise,
1167    is_ascii_chars_encode_utf8,
1168    is_ascii_chars_nat_bound,
1169    is_ascii_chars_concat,
1170    encode_utf8_concat,
1171    encode_utf8_push,
1172}
1173
1174} // verus!