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}