Skip to main content

luaur_vm/functions/
str_format.rs

1//! Node: `cxx:Function:Luau.VM:VM/src/lstrlib.cpp:966:str_format`
2//!
3//! `string.format` — walk the format string, copying literals and dispatching
4//! each `%` spec to the matching argument: `%c/d/i/o/u/x/X/e/E/f/g/G` go through
5//! C `snprintf` with the scanned format (integer specs get an int64 length
6//! modifier), `%q` quotes, `%s` formats or fast-paths long strings, `%*` appends
7//! any value, and `%%` is a literal percent.
8
9use crate::functions::add_int_64_format::add_int_64_format;
10use crate::functions::addquoted::addquoted;
11use crate::functions::lua_gettop::lua_gettop;
12use crate::functions::lua_l_addchar::lua_l_addchar;
13use crate::functions::lua_l_addlstring::lua_l_addlstring;
14use crate::functions::lua_l_addvalueany::lua_l_addvalueany;
15use crate::functions::lua_l_buffinit::lua_l_buffinit;
16use crate::functions::lua_l_checkinteger_64::lua_l_checkinteger_64;
17use crate::functions::lua_l_checklstring::lua_l_checklstring;
18use crate::functions::lua_l_checknumber::lua_l_checknumber;
19use crate::functions::lua_l_pushresult::lua_l_pushresult;
20use crate::functions::scanformat::scanformat;
21use crate::macros::l_esc::L_ESC;
22use crate::macros::lua_isinteger_64::lua_isinteger_64;
23use crate::macros::lua_l_error::luaL_error;
24use crate::macros::max_format::MAX_FORMAT;
25use crate::macros::max_item::MAX_ITEM;
26use crate::records::lua_l_strbuf::LuaLStrbuf;
27use crate::type_aliases::lua_state::lua_State;
28use core::ffi::{c_char, c_int};
29
30extern "C" {
31    fn snprintf(s: *mut c_char, n: usize, format: *const c_char, ...) -> c_int;
32    fn strchr(s: *const c_char, c: c_int) -> *mut c_char;
33}
34
35pub unsafe fn str_format(L: *mut lua_State) -> c_int {
36    let top = lua_gettop(L);
37    let mut arg: c_int = 1;
38    let mut sfl: usize = 0;
39    let mut strfrmt = lua_l_checklstring(L, arg, &mut sfl);
40    let strfrmt_end = strfrmt.add(sfl);
41
42    let mut b: LuaLStrbuf = LuaLStrbuf {
43        p: core::ptr::null_mut(),
44        end: core::ptr::null_mut(),
45        L: core::ptr::null_mut(),
46        storage: core::ptr::null_mut(),
47        buffer: [0; 512],
48    };
49    lua_l_buffinit(L, &mut b);
50
51    while strfrmt < strfrmt_end {
52        if *strfrmt != L_ESC {
53            lua_l_addchar(&mut b, *strfrmt);
54            strfrmt = strfrmt.add(1);
55        } else {
56            strfrmt = strfrmt.add(1); // *++strfrmt
57            if *strfrmt == L_ESC {
58                lua_l_addchar(&mut b, *strfrmt);
59                strfrmt = strfrmt.add(1); // %%
60            } else if *strfrmt == b'*' as c_char {
61                strfrmt = strfrmt.add(1);
62                arg += 1;
63                if arg > top {
64                    luaL_error!(L, "missing argument #{}", arg);
65                }
66                lua_l_addvalueany(&mut b, arg);
67            } else {
68                // format item
69                let mut form: [c_char; MAX_FORMAT as usize] = [0; MAX_FORMAT as usize];
70                let mut buff: [c_char; MAX_ITEM] = [0; MAX_ITEM];
71                arg += 1;
72                if arg > top {
73                    luaL_error!(L, "missing argument #{}", arg);
74                }
75                let mut format_item_size: usize = 0;
76                strfrmt = scanformat(L, strfrmt, form.as_mut_ptr(), &mut format_item_size);
77                let format_indicator = *strfrmt;
78                strfrmt = strfrmt.add(1);
79                match format_indicator as u8 {
80                    b'c' => {
81                        let count = snprintf(
82                            buff.as_mut_ptr(),
83                            buff.len(),
84                            form.as_ptr(),
85                            lua_l_checknumber(L, arg) as c_int,
86                        );
87                        lua_l_addlstring(&mut b, buff.as_ptr(), count as usize);
88                        continue; // skip the 'luaL_addlstring' at the end
89                    }
90                    b'd' | b'i' => {
91                        let value: i64 = if lua_isinteger_64!(L, arg) {
92                            lua_l_checkinteger_64(L, arg)
93                        } else {
94                            lua_l_checknumber(L, arg) as i64
95                        };
96                        add_int_64_format(&mut form, format_indicator, format_item_size);
97                        snprintf(buff.as_mut_ptr(), buff.len(), form.as_ptr(), value);
98                    }
99                    b'o' | b'u' | b'x' | b'X' => {
100                        let v: u64 = if lua_isinteger_64!(L, arg) {
101                            lua_l_checkinteger_64(L, arg) as u64
102                        } else {
103                            let arg_value = lua_l_checknumber(L, arg);
104                            if arg_value < 0.0 {
105                                (arg_value as i64) as u64
106                            } else {
107                                arg_value as u64
108                            }
109                        };
110                        add_int_64_format(&mut form, format_indicator, format_item_size);
111                        snprintf(buff.as_mut_ptr(), buff.len(), form.as_ptr(), v);
112                    }
113                    b'e' | b'E' | b'f' | b'g' | b'G' => {
114                        snprintf(
115                            buff.as_mut_ptr(),
116                            buff.len(),
117                            form.as_ptr(),
118                            lua_l_checknumber(L, arg),
119                        );
120                    }
121                    b'q' => {
122                        addquoted(L, &mut b, arg);
123                        continue; // skip the 'luaL_addlstring' at the end
124                    }
125                    b's' => {
126                        let mut l: usize = 0;
127                        let s = lua_l_checklstring(L, arg, &mut l);
128                        // no precision and string too long to format, or no format necessary
129                        if form[2] == 0
130                            || (strchr(form.as_ptr(), b'.' as c_int).is_null() && l >= 100)
131                        {
132                            lua_l_addlstring(&mut b, s, l);
133                            continue; // skip the 'luaL_addlstring' at the end
134                        } else {
135                            snprintf(buff.as_mut_ptr(), buff.len(), form.as_ptr(), s);
136                        }
137                    }
138                    b'*' => {
139                        // %* is parsed above, so if we got here we must have %...*
140                        luaL_error!(L, "'%*' does not take a form");
141                    }
142                    _ => {
143                        // also treat cases 'pnLlh'
144                        luaL_error!(
145                            L,
146                            "invalid option '%{}' to 'format'",
147                            *(strfrmt.offset(-1)) as u8 as char
148                        );
149                    }
150                }
151                let blen = core::ffi::CStr::from_ptr(buff.as_ptr()).to_bytes().len();
152                lua_l_addlstring(&mut b, buff.as_ptr(), blen);
153            }
154        }
155    }
156
157    lua_l_pushresult(&mut b);
158    1
159}