Skip to main content

lua_stdlib/
utf8_lib.rs

1//! The `utf8` standard library: `char`, `codepoint`, `codes`, `len`, `offset`,
2//! and `charpattern`. Present from Lua 5.3.
3//!
4//! GRADUATED (Phase-2 idiomatization). The line-by-line C correspondence to
5//! `lutf8lib.c` has been removed; what guards this module now is the behavioral
6//! net, in two layers:
7//!   - the official 5.4 suite (`reference/lua-5.4.7-tests/utf8.lua`, run via the
8//!     harness) and the cross-crate `multiversion_oracle`, and
9//!   - `crates/lua-stdlib/tests/utf8_strengthen.rs`, which pins the **version
10//!     seam** the 5.4-only suite cannot see: the decode/encode regime differs
11//!     between 5.3 and 5.4+.
12//!
13//! The version seam is a single source of truth, [`DecodeMode`], resolved once
14//! per call by [`decode_mode_for`]:
15//!   - **5.3** — cap at `MAX_UNICODE`, at most a 4-byte sequence, surrogates
16//!     ALWAYS accepted, the `lax` argument ignored (5.3's `utf8_decode` has no
17//!     strict/lax parameter). `charpattern` stops the lead byte at `\xF4`.
18//!   - **5.4+ strict** (default) — cap at `MAX_UTF` while decoding, then reject
19//!     surrogates and values above `MAX_UNICODE`.
20//!   - **5.4+ lax** — cap at `MAX_UTF`, accept surrogates. `charpattern` widens
21//!     the lead byte to `\xFD`.
22//!
23//! LOAD-BEARING (do not reshape — only the validity ceiling is version-split):
24//! the continuation-byte decode loop in [`utf8_decode`] and the backward-fill
25//! encode in [`encode_utf8_codepoint`]. Those carry the bit-exact arithmetic and
26//! are documented in place.
27
28use crate::state_stub::{LuaState, LuaStateStubExt as _};
29use lua_types::error::LuaError;
30use lua_types::value::LuaValue;
31
32const MAX_UNICODE: u32 = 0x10_FFFF;
33
34const MAX_UTF: u32 = 0x7FFF_FFFF;
35
36/// Integer wide enough for a decoded codepoint: 31 bits are needed for
37/// `MAX_UTF`, so `u32` suffices on every Rust target.
38type UtfInt = u32;
39
40/// The 5.4+ `charpattern` (`lutf8lib.c` `UTF8PATT`): the lead-byte range runs to
41/// `\xFD`, the ceiling for the extended (≤ `MAX_UTF`) range.
42///
43/// Embeds a NUL; length is `sizeof(UTF8PATT)/sizeof(char) - 1`.
44const UTF8_PATT: &[u8] = b"[\x00-\x7F\xC2-\xFD][\x80-\xBF]*";
45
46/// The 5.3 `charpattern`: the lead byte stops at `\xF4`, the ceiling for a
47/// ≤ `MAX_UNICODE` (4-byte) sequence. 5.3's `utf8_decode` has no extended range,
48/// so `lutf8lib.c` (5.3.6) ships this narrower pattern.
49const UTF8_PATT_V53: &[u8] = b"[\x00-\x7F\xC2-\xF4][\x80-\xBF]*";
50
51/// How one UTF-8 sequence is validated, derived once from the version + `lax`
52/// flag (the C `lutf8lib.c` regime differs by family, not just by an argument):
53///
54/// - [`Self::V53`] — the 5.3 regime: cap at `MAX_UNICODE`, accept at most a
55///   4-byte sequence, **never** reject surrogates, and ignore the `lax` argument
56///   entirely (5.3's `utf8_decode` takes no strict/lax parameter).
57/// - [`Self::Strict`] — 5.4+ default: cap at `MAX_UTF` while decoding, then
58///   reject surrogates and values above `MAX_UNICODE`.
59/// - [`Self::Lax`] — 5.4+ extended: cap at `MAX_UTF`, accept any well-formed
60///   sequence including surrogates.
61#[derive(Clone, Copy, PartialEq, Eq)]
62enum DecodeMode {
63    V53,
64    Strict,
65    Lax,
66}
67
68impl DecodeMode {
69    /// The `(max continuation count, max codepoint)` pair the decode loop checks.
70    ///
71    /// 5.3 caps at a 4-byte / `MAX_UNICODE` sequence (no extended range); 5.4+
72    /// allow a 6-byte / `MAX_UTF` sequence and apply the Unicode/surrogate
73    /// ceiling separately via [`Self::rejects_surrogates`].
74    fn length_and_value_ceiling(self) -> (usize, UtfInt) {
75        match self {
76            DecodeMode::V53 => (3, MAX_UNICODE),
77            DecodeMode::Strict | DecodeMode::Lax => (5, MAX_UTF),
78        }
79    }
80
81    /// Whether decoding rejects surrogates and values above `MAX_UNICODE`.
82    ///
83    /// Only the 5.4+ strict (default) regime does: 5.3 never rejects surrogates,
84    /// and the 5.4+ lax regime disables the guard.
85    fn rejects_surrogates(self) -> bool {
86        self == DecodeMode::Strict
87    }
88}
89
90/// Resolve the decode regime for `version` and the user's `lax` flag.
91///
92/// 5.3 is its own regime regardless of `lax`; 5.4+ pick strict-vs-lax from the
93/// flag. This is the single source of truth for the version seam — the callers
94/// never branch on the version for decoding.
95fn decode_mode_for(version: lua_types::LuaVersion, lax: bool) -> DecodeMode {
96    if version == lua_types::LuaVersion::V53 {
97        DecodeMode::V53
98    } else if lax {
99        DecodeMode::Lax
100    } else {
101        DecodeMode::Strict
102    }
103}
104
105// ── Internal helpers ───────────────────────────────────────────────────────
106
107/// Translate a relative string position: negative values count backward from
108/// the end, clamping to `0` when they reach before the start.
109fn pos_relat(pos: i64, len: usize) -> i64 {
110    if pos >= 0 {
111        pos
112    } else {
113        let abs_pos = pos.unsigned_abs() as u64;
114        if abs_pos > len as u64 {
115            0
116        } else {
117            len as i64 + pos + 1
118        }
119    }
120}
121
122/// Return `true` if byte `c` is a UTF-8 continuation byte (`10xxxxxx`).
123///
124#[inline]
125fn is_cont(c: u8) -> bool {
126    (c & 0xC0) == 0x80
127}
128
129/// Return `true` if the byte at 0-based index `pos` in `s` is a continuation
130/// byte, treating out-of-bounds (or negative) positions as non-continuation —
131/// the bounds check stands in for the NUL terminator the upstream relies on.
132#[inline]
133fn is_cont_at(s: &[u8], pos: i64) -> bool {
134    if pos < 0 {
135        return false;
136    }
137    s.get(pos as usize).map_or(false, |&b| is_cont(b))
138}
139
140/// Decode one UTF-8 sequence from the start of `s`.
141///
142/// Returns `None` if the byte sequence is invalid, else
143/// `Some((remaining_slice, codepoint))`.
144///
145/// `mode` selects the validity regime (see [`DecodeMode`]): the 5.3 path caps at
146/// `MAX_UNICODE` and accepts surrogates; the 5.4+ strict/lax paths cap at
147/// `MAX_UTF` and differ only on surrogate rejection.
148///
149/// LOAD-BEARING: the continuation-byte loop below is the faithful translation of
150/// `lutf8lib.c`'s `utf8_decode`, including the order-sensitive details that look
151/// odd in isolation — the high-bit walk `while c & 0x40` consumes one
152/// continuation byte per iteration then shifts `c` left so the next leading bit
153/// can be tested; `r` accumulates the low six bits of each continuation byte and
154/// the leading byte's payload is folded in afterward via `(c & 0x7F) << count*5`;
155/// `LIMITS[count]` is the smallest value legal for that sequence length (so a
156/// too-small `r` is an overlong encoding), with `LIMITS[0] = u32::MAX` forcing an
157/// error for a stray non-ASCII byte. Only the validity ceiling is version-split
158/// (via [`DecodeMode`]); the arithmetic is shared and must not be reshaped.
159fn utf8_decode(s: &[u8], mode: DecodeMode) -> Option<(&[u8], UtfInt)> {
160    const LIMITS: [UtfInt; 6] = [u32::MAX, 0x80, 0x800, 0x10000, 0x200000, 0x4000000];
161
162    if s.is_empty() {
163        return None;
164    }
165
166    let mut c = s[0] as u32;
167    let res: UtfInt;
168    let advance: usize;
169
170    if c < 0x80 {
171        res = c;
172        advance = 1;
173    } else {
174        let mut count: usize = 0;
175        let mut r: UtfInt = 0;
176
177        while c & 0x40 != 0 {
178            count += 1;
179            if count >= s.len() {
180                return None;
181            }
182            let cc = s[count] as u32;
183
184            if (cc & 0xC0) != 0x80 {
185                return None;
186            }
187
188            r = (r << 6) | (cc & 0x3F);
189
190            c <<= 1;
191        }
192
193        r |= (c & 0x7F) << (count as u32 * 5);
194
195        let (max_count, max_value) = mode.length_and_value_ceiling();
196        if count > max_count || r > max_value || r < LIMITS[count] {
197            return None;
198        }
199
200        res = r;
201        advance = count + 1;
202        if advance > s.len() {
203            return None;
204        }
205    }
206
207    if mode.rejects_surrogates() && (res > MAX_UNICODE || (0xD800 <= res && res <= 0xDFFF)) {
208        return None;
209    }
210
211    Some((&s[advance..], res))
212}
213
214/// Encode a codepoint (≤ `MAX_UTF`) as extended UTF-8 bytes, returned as a
215/// `Vec<u8>` (the upstream fills a fixed buffer backwards).
216///
217/// LOAD-BEARING: `mfb` ("max value for a first byte") tracks the payload room in
218/// the leading byte, halving each round as another continuation byte is emitted;
219/// the continuation bytes are built low-to-high (reversed at the end) and the
220/// leading byte's marker is `!mfb << 1`. The `wrapping_shl` is required: `!mfb`
221/// overflows a plain `<< 1` in debug builds (e.g. `!0x1F = 0xFFFF_FFE0`,
222/// `<< 1 = 0xFFFF_FFC0`, `as u8 = 0xC0`).
223fn encode_utf8_codepoint(code: u32) -> Vec<u8> {
224    debug_assert!(code <= MAX_UTF);
225
226    if code < 0x80 {
227        return vec![code as u8];
228    }
229
230    let mut x = code;
231    let mut mfb: u32 = 0x3F;
232    let mut bytes_rev: Vec<u8> = Vec::with_capacity(6);
233
234    loop {
235        bytes_rev.push(0x80 | (x & 0x3F) as u8);
236        x >>= 6;
237        mfb >>= 1;
238        if x <= mfb {
239            break;
240        }
241    }
242
243    let leading = ((!mfb).wrapping_shl(1) as u8) | (x as u8);
244
245    let mut result = Vec::with_capacity(bytes_rev.len() + 1);
246    result.push(leading);
247    for &b in bytes_rev.iter().rev() {
248        result.push(b);
249    }
250    result
251}
252
253// ── Library functions ──────────────────────────────────────────────────────
254
255/// `utf8.len(s [, i [, j [, lax]]])` → integer | (nil, integer)
256///
257/// Returns the number of UTF-8 characters that start in the byte range `[i,j]`
258/// of string `s` (1-based, defaulting to the whole string).
259/// On a malformed sequence, returns `(nil, position)` where `position` is the
260/// 1-based byte offset of the first bad byte.
261///
262fn utf_len(state: &mut LuaState) -> Result<usize, LuaError> {
263    // Clone to avoid holding a borrow across subsequent mutable state calls.
264    let s: Vec<u8> = state.check_arg_string(1)?.to_vec();
265    let len = s.len();
266
267    let raw_posi: i64 = state.opt_arg_integer(2, 1)?;
268    let mut posi: i64 = pos_relat(raw_posi, len);
269
270    let raw_posj: i64 = state.opt_arg_integer(3, -1)?;
271    let mut posj: i64 = pos_relat(raw_posj, len);
272
273    let lax: bool = state.to_boolean(4);
274
275    let version = state.global().lua_version;
276    let mode = decode_mode_for(version, lax);
277    let is_v53 = version == lua_types::LuaVersion::V53;
278    let initial_msg: &[u8] = if is_v53 {
279        b"initial position out of string"
280    } else {
281        b"initial position out of bounds"
282    };
283    let final_msg: &[u8] = if is_v53 {
284        b"final position out of string"
285    } else {
286        b"final position out of bounds"
287    };
288
289    if posi < 1 {
290        return Err(lua_vm::debug::arg_error_impl(state, 2, initial_msg));
291    }
292    posi -= 1;
293    if posi > len as i64 {
294        return Err(lua_vm::debug::arg_error_impl(state, 2, initial_msg));
295    }
296
297    posj -= 1;
298    if posj >= len as i64 {
299        return Err(lua_vm::debug::arg_error_impl(state, 3, final_msg));
300    }
301
302    let mut n: i64 = 0;
303
304    while posi <= posj {
305        match utf8_decode(&s[posi as usize..], mode) {
306            None => {
307                state.push(LuaValue::Nil);
308                state.push(LuaValue::Int(posi + 1));
309                return Ok(2);
310            }
311            Some((remaining, _)) => {
312                posi = (len - remaining.len()) as i64;
313                n += 1;
314            }
315        }
316    }
317
318    state.push(LuaValue::Int(n));
319    Ok(1)
320}
321
322/// `utf8.codepoint(s [, i [, j [, lax]]])` → integer, ...
323///
324/// Returns the codepoints (as integers) for all characters starting in `s[i..j]`.
325///
326fn codepoint(state: &mut LuaState) -> Result<usize, LuaError> {
327    let s: Vec<u8> = state.check_arg_string(1)?.to_vec();
328    let len = s.len();
329
330    let raw_posi: i64 = state.opt_arg_integer(2, 1)?;
331    let posi: i64 = pos_relat(raw_posi, len);
332
333    // Default for the end position is posi (1-based), giving a single character.
334    let raw_pose: i64 = state.opt_arg_integer(3, posi)?;
335    let pose: i64 = pos_relat(raw_pose, len);
336
337    let lax: bool = state.to_boolean(4);
338
339    let version = state.global().lua_version;
340    let mode = decode_mode_for(version, lax);
341    let bounds_msg: &[u8] = if version == lua_types::LuaVersion::V53 {
342        b"out of range"
343    } else {
344        b"out of bounds"
345    };
346    if posi < 1 {
347        return Err(lua_vm::debug::arg_error_impl(state, 2, bounds_msg));
348    }
349
350    if pose > len as i64 {
351        return Err(lua_vm::debug::arg_error_impl(state, 3, bounds_msg));
352    }
353
354    if posi > pose {
355        return Ok(0); // empty interval: no values
356    }
357
358    if pose - posi >= i32::MAX as i64 {
359        return Err(LuaError::runtime(format_args!("string slice too long")));
360    }
361
362    let n_max = (pose - posi + 1) as i32;
363    state.ensure_stack(n_max, "string slice too long")?;
364
365    let mut pos: usize = (posi - 1) as usize;
366    let end: usize = pose as usize;
367    let mut count: usize = 0;
368
369    while pos < end {
370        match utf8_decode(&s[pos..], mode) {
371            None => return Err(LuaError::runtime(format_args!("invalid UTF-8 code"))),
372            Some((remaining, code)) => {
373                state.push(LuaValue::Int(code as i64));
374                count += 1;
375                pos = len - remaining.len();
376            }
377        }
378    }
379
380    Ok(count)
381}
382
383/// Encode the codepoint at stack argument `arg` and return its UTF-8 bytes.
384///
385/// The accepted ceiling is version-split (the `utf8.char` `luaL_argcheck`):
386/// 5.3 caps at `MAX_UNICODE`, 5.4+ at `MAX_UTF`. Returning the bytes lets
387/// `utf_char` concatenate them without per-codepoint stack traffic.
388fn get_utf_char_bytes(state: &mut LuaState, arg: i32) -> Result<Vec<u8>, LuaError> {
389    let code = state.check_arg_integer(arg)? as u64;
390
391    let max_code: u64 = if state.global().lua_version == lua_types::LuaVersion::V53 {
392        MAX_UNICODE as u64
393    } else {
394        MAX_UTF as u64
395    };
396    if code > max_code {
397        return crate::auxlib::arg_error(state, arg, b"value out of range").map(|_| Vec::new());
398    }
399
400    Ok(encode_utf8_codepoint(code as u32))
401}
402
403/// `utf8.char(n1, n2, ...)` → string
404///
405/// Returns the string formed by the UTF-8 encoding of the given codepoints.
406///
407fn utf_char(state: &mut LuaState) -> Result<usize, LuaError> {
408    let n: i32 = state.stack_top() as i32;
409
410    if n == 1 {
411        let bytes = get_utf_char_bytes(state, 1)?;
412        let s = state.intern_str(&bytes)?;
413        state.push(LuaValue::Str(s));
414    } else {
415        let mut buf: Vec<u8> = Vec::new();
416        for i in 1..=n {
417            buf.extend_from_slice(&get_utf_char_bytes(state, i)?);
418        }
419        let s = state.intern_str(&buf)?;
420        state.push(LuaValue::Str(s));
421    }
422
423    Ok(1)
424}
425
426/// `utf8.offset(s, n [, i])` → integer | nil
427///
428/// Returns the byte offset where the n-th character (counting from position `i`)
429/// starts. Negative `n` counts from the end; `n == 0` returns the start of the
430/// character that contains position `i`. Returns `nil` if the character cannot
431/// be found.
432///
433/// `count` is `n` driven toward zero as characters are crossed. Each step does a
434/// do-while-style inner walk: it moves one byte unconditionally, then skips over
435/// any continuation bytes to land on the next leading byte (forward for `n > 0`,
436/// backward for `n < 0`). Where C stops the inner walk on the NUL terminator,
437/// the bounds check on `posi` does the same job here. Lua 5.5 additionally
438/// returns the character's end byte (inclusive); 5.3/5.4 return only the start.
439fn byte_offset(state: &mut LuaState) -> Result<usize, LuaError> {
440    let s: Vec<u8> = state.check_arg_string(1)?.to_vec();
441    let len = s.len();
442
443    let n: i64 = state.check_arg_integer(2)?;
444
445    let default_posi: i64 = if n >= 0 { 1 } else { len as i64 + 1 };
446
447    let raw_posi: i64 = state.opt_arg_integer(3, default_posi)?;
448    let posi_1based: i64 = pos_relat(raw_posi, len);
449
450    let pos_msg: &[u8] = if state.global().lua_version == lua_types::LuaVersion::V53 {
451        b"position out of range"
452    } else {
453        b"position out of bounds"
454    };
455    if posi_1based < 1 {
456        return Err(lua_vm::debug::arg_error_impl(state, 3, pos_msg));
457    }
458    let mut posi: i64 = posi_1based - 1;
459    if posi > len as i64 {
460        return Err(lua_vm::debug::arg_error_impl(state, 3, pos_msg));
461    }
462
463    let mut count = n;
464
465    if count == 0 {
466        while posi > 0 && is_cont_at(&s, posi) {
467            posi -= 1;
468        }
469    } else {
470        if is_cont_at(&s, posi) {
471            return Err(LuaError::runtime(format_args!(
472                "initial position is a continuation byte"
473            )));
474        }
475
476        if count < 0 {
477            while count < 0 && posi > 0 {
478                loop {
479                    posi -= 1;
480                    if posi == 0 || !is_cont_at(&s, posi) {
481                        break;
482                    }
483                }
484                count += 1;
485            }
486        } else {
487            count -= 1;
488            while count > 0 && posi < len as i64 {
489                loop {
490                    posi += 1;
491                    if !is_cont_at(&s, posi) {
492                        break;
493                    }
494                }
495                count -= 1;
496            }
497        }
498    }
499
500    if count != 0 {
501        state.push(LuaValue::Nil);
502        return Ok(1);
503    }
504
505    state.push(LuaValue::Int(posi + 1));
506
507    if state.global().lua_version != lua_types::LuaVersion::V55 {
508        return Ok(1);
509    }
510
511    if s.get(posi as usize).is_some_and(|&b| b & 0x80 != 0) {
512        if is_cont_at(&s, posi) {
513            return Err(LuaError::runtime(format_args!(
514                "initial position is a continuation byte"
515            )));
516        }
517        while is_cont_at(&s, posi + 1) {
518            posi += 1;
519        }
520    }
521    state.push(LuaValue::Int(posi + 1));
522    Ok(2)
523}
524
525/// Internal iterator body shared by `iter_aux_strict` and `iter_aux_lax`.
526///
527/// Stack on entry (from the generic for): (1) string, (2) current byte position
528/// (0-based; initially pushed as 0 by `iter_codes`). Advances past any leading
529/// continuation bytes, decodes the next character, and returns
530/// `(next_1based_pos, codepoint)`, or nothing (0) when the string is exhausted.
531/// A decode failure — or a decoded sequence immediately followed by a stray
532/// continuation byte — raises "invalid UTF-8 code".
533///
534/// `strict` is the requested mode; the actual [`DecodeMode`] is resolved against
535/// the version (5.3 ignores it, decoding in its own regime).
536fn iter_aux(state: &mut LuaState, strict: bool) -> Result<usize, LuaError> {
537    let s: Vec<u8> = state.check_arg_string(1)?.to_vec();
538    let len = s.len();
539
540    let mode = decode_mode_for(state.global().lua_version, !strict);
541
542    let mut n: u64 = state.to_integer(2).unwrap_or(0) as u64;
543
544    if (n as usize) < len {
545        while (n as usize) < len && is_cont(s[n as usize]) {
546            n += 1;
547        }
548    }
549
550    if (n as usize) >= len {
551        return Ok(0);
552    }
553
554    match utf8_decode(&s[n as usize..], mode) {
555        None => Err(lua_vm::debug::c_api_runtime(
556            state,
557            b"invalid UTF-8 code".to_vec(),
558        )),
559        Some((remaining, code)) => {
560            let next_pos = len - remaining.len();
561            if next_pos < len && is_cont(s[next_pos]) {
562                return Err(lua_vm::debug::c_api_runtime(
563                    state,
564                    b"invalid UTF-8 code".to_vec(),
565                ));
566            }
567            state.push(LuaValue::Int((n + 1) as i64));
568            state.push(LuaValue::Int(code as i64));
569            Ok(2)
570        }
571    }
572}
573
574/// Strict iterator body: 5.4+ reject surrogates and values > `MAX_UNICODE`
575/// (5.3 decodes in its own regime regardless).
576fn iter_aux_strict(state: &mut LuaState) -> Result<usize, LuaError> {
577    iter_aux(state, true)
578}
579
580/// Lax iterator body: 5.4+ accept extended UTF-8 up to `MAX_UTF`
581/// (5.3 decodes in its own regime regardless).
582fn iter_aux_lax(state: &mut LuaState) -> Result<usize, LuaError> {
583    iter_aux(state, false)
584}
585
586/// `utf8.codes(s [, lax])` → function, string, integer
587///
588/// Returns the iterator triple `(f, s, 0)` for use in a generic for loop.
589/// Each call to `f(s, pos)` returns the next `(pos, codepoint)` pair.
590///
591fn iter_codes(state: &mut LuaState) -> Result<usize, LuaError> {
592    let lax: bool = state.to_boolean(2);
593
594    let s: Vec<u8> = state.check_arg_string(1)?.to_vec();
595
596    if s.first().map_or(false, |&b| is_cont(b)) {
597        return Err(LuaError::arg_error(1, "invalid UTF-8 code"));
598    }
599
600    let iter_fn: fn(&mut LuaState) -> Result<usize, LuaError> =
601        if lax { iter_aux_lax } else { iter_aux_strict };
602    state.push_c_function(iter_fn)?;
603
604    state.push_value_at(1)?;
605
606    state.push(LuaValue::Int(0));
607
608    Ok(3)
609}
610
611// ── Library registration ───────────────────────────────────────────────────
612
613/// Function registration table for the `utf8` library.
614///
615/// "charpattern" is intentionally absent here; it is a string value and is
616/// registered separately inside `open_utf8` via `lua_setfield`.
617pub const FUNCS: &[(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)] = &[
618    (b"offset", byte_offset),
619    (b"codepoint", codepoint),
620    (b"char", utf_char),
621    (b"len", utf_len),
622    (b"codes", iter_codes),
623];
624
625/// Open the `utf8` library.
626///
627/// Registers all functions from `FUNCS` into a new table, then sets
628/// `utf8.charpattern` to the byte-string pattern matching one UTF-8 sequence.
629/// The pattern's lead-byte ceiling is version-split: 5.3 stops at `\xF4`
630/// (≤ `MAX_UNICODE`), 5.4+ extend it to `\xFD` (≤ `MAX_UTF`).
631///
632pub fn open_utf8(state: &mut LuaState) -> Result<usize, LuaError> {
633    state.new_lib(FUNCS)?;
634
635    let patt_bytes: &[u8] = if state.global().lua_version == lua_types::LuaVersion::V53 {
636        UTF8_PATT_V53
637    } else {
638        UTF8_PATT
639    };
640    let patt = state.intern_str(patt_bytes)?;
641    state.push(LuaValue::Str(patt));
642
643    state.set_field(-2, b"charpattern")?;
644
645    Ok(1)
646}