Skip to main content

shape_jit/ffi/call_method/
string.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 18 sites
3//     box_string(...) — toUpperCase, toLowerCase, trim, replace,
4//     charAt, substring, concat, padStart, padEnd, repeat, trimStart, trimEnd
5//     jit_box(HK_ARRAY, ...) — split, chars
6//   Category B (intermediate/consumed): 0 sites
7//   Category C (heap islands): 2 sites (split, chars)
8//!
9//! String method implementations for JIT
10
11// crate::jit_array::JitArray removed — see jit_array.rs SURFACE comment.
12// `.split()` and `.chars()` returned `JitArray`-of-strings results;
13// they now route to surface-and-stop per ADR-006 §2.7.4 / W10
14// jit-playbook §5.
15use crate::ffi::jit_kinds::*;
16use crate::ffi::value_ffi::*;
17
18/// Call a method on a string value
19#[inline(always)]
20pub fn call_string_method(receiver_bits: u64, method_name: &str, args: &[u64]) -> u64 {
21    unsafe {
22        let s = unbox_string(receiver_bits);
23
24        match method_name {
25            "length" | "len" => box_number(s.len() as f64),
26            "toUpperCase" | "to_upper_case" => box_string(s.to_uppercase()),
27            "toLowerCase" | "to_lower_case" => box_string(s.to_lowercase()),
28            "trim" => box_string(s.trim().to_string()),
29            "split" => {
30                // SURFACE (W10 jit-playbook §5 / ADR-006 §2.7.4):
31                // result allocation went through the deleted
32                // `JitArray::from_vec(...).heap_box()`. Kinded
33                // rebuild allocates a `TypedArray<Arc<String>>` (or
34                // equivalent string-element kind) per §2.7.6/Q8.
35                let _ = args;
36                todo!(
37                    "phase-2c §2.7.4 / W10 jit-playbook §5: \
38                     JitArray rebuild — String.split. Result \
39                     allocation needs `TypedArray<Arc<String>>`."
40                )
41            }
42            "includes" | "contains" => {
43                if args.is_empty() {
44                    return TAG_BOOL_FALSE;
45                }
46                if is_heap_kind(args[0], HK_STRING) {
47                    let needle = unbox_string(args[0]);
48                    if s.contains(needle) {
49                        return TAG_BOOL_TRUE;
50                    }
51                }
52                TAG_BOOL_FALSE
53            }
54            "startsWith" | "starts_with" => {
55                if args.is_empty() {
56                    return TAG_BOOL_FALSE;
57                }
58                if is_heap_kind(args[0], HK_STRING) {
59                    let prefix = unbox_string(args[0]);
60                    if s.starts_with(prefix) {
61                        return TAG_BOOL_TRUE;
62                    }
63                }
64                TAG_BOOL_FALSE
65            }
66            "endsWith" | "ends_with" => {
67                if args.is_empty() {
68                    return TAG_BOOL_FALSE;
69                }
70                if is_heap_kind(args[0], HK_STRING) {
71                    let suffix = unbox_string(args[0]);
72                    if s.ends_with(suffix) {
73                        return TAG_BOOL_TRUE;
74                    }
75                }
76                TAG_BOOL_FALSE
77            }
78            "replace" | "replaceAll" | "replace_all" => {
79                if args.len() < 2 {
80                    return receiver_bits;
81                }
82                if is_heap_kind(args[0], HK_STRING) && is_heap_kind(args[1], HK_STRING) {
83                    let from = unbox_string(args[0]);
84                    let to = unbox_string(args[1]);
85                    let replaced = s.replace(from, to);
86                    return box_string(replaced);
87                }
88                receiver_bits
89            }
90            "charAt" | "char_at" => {
91                if args.is_empty() {
92                    return box_string(String::new());
93                }
94                if is_number(args[0]) {
95                    let idx = unbox_number(args[0]) as usize;
96                    if let Some(ch) = s.chars().nth(idx) {
97                        return box_string(ch.to_string());
98                    }
99                }
100                box_string(String::new())
101            }
102            "substring" | "slice" => {
103                if args.is_empty() {
104                    return receiver_bits;
105                }
106                let start = if is_number(args[0]) {
107                    unbox_number(args[0]) as usize
108                } else {
109                    0
110                };
111                let end = if args.len() > 1 && is_number(args[1]) {
112                    unbox_number(args[1]) as usize
113                } else {
114                    s.len()
115                };
116                let len = if end >= start { end - start + 1 } else { 0 };
117                let sub: String = s.chars().skip(start).take(len).collect();
118                box_string(sub)
119            }
120            "concat" => {
121                let mut result = s.to_string();
122                for arg in args.iter() {
123                    if is_heap_kind(*arg, HK_STRING) {
124                        let arg_s = unbox_string(*arg);
125                        result.push_str(arg_s);
126                    } else if is_number(*arg) {
127                        result.push_str(&format!("{}", unbox_number(*arg)));
128                    }
129                }
130                box_string(result)
131            }
132            "indexOf" | "index_of" => {
133                if args.is_empty() {
134                    return box_number(-1.0);
135                }
136                if is_heap_kind(args[0], HK_STRING) {
137                    let needle = unbox_string(args[0]);
138                    if let Some(idx) = s.find(needle) {
139                        return box_number(idx as f64);
140                    }
141                }
142                box_number(-1.0)
143            }
144            "lastIndexOf" | "last_index_of" => {
145                if args.is_empty() {
146                    return box_number(-1.0);
147                }
148                if is_heap_kind(args[0], HK_STRING) {
149                    let needle = unbox_string(args[0]);
150                    if let Some(idx) = s.rfind(needle) {
151                        return box_number(idx as f64);
152                    }
153                }
154                box_number(-1.0)
155            }
156            "trimStart" | "trim_start" => box_string(s.trim_start().to_string()),
157            "trimEnd" | "trim_end" => box_string(s.trim_end().to_string()),
158            "toNumber" | "to_number" => match s.trim().parse::<f64>() {
159                Ok(n) => box_number(n),
160                Err(_) => TAG_NULL,
161            },
162            "toBool" | "to_bool" => match s.trim() {
163                "true" => TAG_BOOL_TRUE,
164                "false" => TAG_BOOL_FALSE,
165                _ => TAG_NULL,
166            },
167            "chars" => {
168                // SURFACE (W10 jit-playbook §5 / ADR-006 §2.7.4):
169                // result allocation went through the deleted
170                // `JitArray::from_vec(...).heap_box()`. Kinded
171                // rebuild allocates a `TypedArray<Arc<String>>` per
172                // §2.7.6/Q8.
173                todo!(
174                    "phase-2c §2.7.4 / W10 jit-playbook §5: \
175                     JitArray rebuild — String.chars. Result \
176                     allocation needs `TypedArray<Arc<String>>`."
177                )
178            }
179            "isEmpty" | "is_empty" => {
180                if s.is_empty() {
181                    TAG_BOOL_TRUE
182                } else {
183                    TAG_BOOL_FALSE
184                }
185            }
186            "repeat" => {
187                if args.is_empty() {
188                    return receiver_bits;
189                }
190                if is_number(args[0]) {
191                    let count = unbox_number(args[0]) as usize;
192                    let repeated = s.repeat(count);
193                    return box_string(repeated);
194                }
195                receiver_bits
196            }
197            "padStart" | "pad_start" => {
198                if args.is_empty() {
199                    return receiver_bits;
200                }
201                let target_len = if is_number(args[0]) {
202                    unbox_number(args[0]) as usize
203                } else {
204                    return receiver_bits;
205                };
206                let pad_char = if args.len() > 1 && is_heap_kind(args[1], HK_STRING) {
207                    let pad_s = unbox_string(args[1]);
208                    pad_s.chars().next().unwrap_or(' ')
209                } else {
210                    ' '
211                };
212                if s.len() >= target_len {
213                    return receiver_bits;
214                }
215                let padding: String = std::iter::repeat_n(pad_char, target_len - s.len()).collect();
216                let padded = format!("{}{}", padding, s);
217                box_string(padded)
218            }
219            "padEnd" | "pad_end" => {
220                if args.is_empty() {
221                    return receiver_bits;
222                }
223                let target_len = if is_number(args[0]) {
224                    unbox_number(args[0]) as usize
225                } else {
226                    return receiver_bits;
227                };
228                let pad_char = if args.len() > 1 && is_heap_kind(args[1], HK_STRING) {
229                    let pad_s = unbox_string(args[1]);
230                    pad_s.chars().next().unwrap_or(' ')
231                } else {
232                    ' '
233                };
234                if s.len() >= target_len {
235                    return receiver_bits;
236                }
237                let padding: String = std::iter::repeat_n(pad_char, target_len - s.len()).collect();
238                let padded = format!("{}{}", s, padding);
239                box_string(padded)
240            }
241            _ => TAG_NULL,
242        }
243    }
244}