1use crate::host::{with_host, JsObj};
16use fusevm::Value;
17use indexmap::IndexMap;
18
19pub const STATIC_METHODS: &[&str] = &["from", "of"];
20
21pub fn is_ctor(name: &str) -> bool {
23 matches!(
24 name,
25 "Uint8Array"
26 | "Int8Array"
27 | "Uint8ClampedArray"
28 | "Int16Array"
29 | "Uint16Array"
30 | "Int32Array"
31 | "Uint32Array"
32 | "Float32Array"
33 | "Float64Array"
34 | "ArrayBuffer"
35 )
36}
37
38fn bytes_per_element(kind: &str) -> usize {
40 match kind {
41 "Int8Array" | "Uint8Array" | "Uint8ClampedArray" => 1,
42 "Int16Array" | "Uint16Array" => 2,
43 "Int32Array" | "Uint32Array" | "Float32Array" => 4,
44 "Float64Array" => 8,
45 _ => 1,
46 }
47}
48
49fn coerce(kind: &str, n: f64) -> f64 {
52 match kind {
53 "Int8Array" => (n as i64 as i8) as f64,
54 "Uint8Array" => (n as i64 as u8) as f64,
55 "Uint8ClampedArray" => {
56 if n.is_nan() {
57 0.0
58 } else {
59 n.round().clamp(0.0, 255.0)
60 }
61 }
62 "Int16Array" => (n as i64 as i16) as f64,
63 "Uint16Array" => (n as i64 as u16) as f64,
64 "Int32Array" => (n as i64 as i32) as f64,
65 "Uint32Array" => (n as i64 as u32) as f64,
66 "Float32Array" => n as f32 as f64,
67 _ => n, }
69}
70
71fn make(kind: &str, elems: Vec<f64>) -> Value {
73 with_host(|h| {
74 let bpe = bytes_per_element(kind);
75 let len = elems.len();
76 let arr = h.new_array(elems.into_iter().map(Value::Float).collect());
77 let mut m = IndexMap::new();
78 m.insert("@@native".into(), h.new_str("TypedArray"));
79 m.insert("@@kind".into(), h.new_str(kind));
80 m.insert("@@elems".into(), arr);
81 m.insert("length".into(), Value::Float(len as f64));
82 m.insert("byteLength".into(), Value::Float((len * bpe) as f64));
83 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(bpe as f64));
84 h.new_object(m)
85 })
86}
87
88pub fn construct(kind: &str, args: &[Value]) -> Result<Value, String> {
91 if kind == "ArrayBuffer" {
92 let n = super::arg_num(args, 0).max(0.0) as usize;
93 return Ok(with_host(|h| {
94 let mut m = IndexMap::new();
95 m.insert("@@native".into(), h.new_str("ArrayBuffer"));
96 m.insert("byteLength".into(), Value::Float(n as f64));
97 h.new_object(m)
98 }));
99 }
100 let elems = build_elems(kind, args)?;
101 Ok(make(kind, elems))
102}
103
104fn build_elems(kind: &str, args: &[Value]) -> Result<Vec<f64>, String> {
108 match args.first() {
109 None | Some(Value::Undef) => Ok(Vec::new()),
110 Some(Value::Int(_)) | Some(Value::Float(_)) => {
111 let n = super::arg_num(args, 0).max(0.0) as usize;
112 Ok(vec![0.0; n])
113 }
114 Some(v) => {
115 if let Some(src) = elems_of(v) {
117 return Ok(src.iter().map(|x| coerce(kind, *x)).collect());
118 }
119 let items = crate::host::iter_all(v).unwrap_or_default();
121 Ok(items
122 .iter()
123 .map(|x| coerce(kind, with_host(|h| h.to_number(x))))
124 .collect())
125 }
126 }
127}
128
129pub fn static_call(kind: &str, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
131 Some(match method {
132 "of" => Ok(make(
133 kind,
134 args.iter()
135 .map(|x| coerce(kind, with_host(|h| h.to_number(x))))
136 .collect(),
137 )),
138 "from" => from(kind, args),
139 _ => return None,
140 })
141}
142
143fn from(kind: &str, args: &[Value]) -> Result<Value, String> {
144 let src = args.first().cloned().unwrap_or(Value::Undef);
145 let map_fn = args
146 .get(1)
147 .cloned()
148 .filter(|f| with_host(|h| crate::host::is_callable(h, f)));
149 let items = if let Some(e) = elems_of(&src) {
150 e.into_iter().map(Value::Float).collect()
151 } else {
152 crate::host::iter_all(&src).unwrap_or_default()
153 };
154 let mut out = Vec::with_capacity(items.len());
155 for (i, it) in items.into_iter().enumerate() {
156 let mapped = match &map_fn {
157 Some(f) => crate::host::invoke(f, vec![it, Value::Float(i as f64)], None)?,
158 None => it,
159 };
160 out.push(coerce(kind, with_host(|h| h.to_number(&mapped))));
161 }
162 Ok(make(kind, out))
163}
164
165fn elems_of(v: &Value) -> Option<Vec<f64>> {
167 let tag = super::native_tag(v)?;
168 let field = match tag.as_str() {
169 "TypedArray" => "@@elems",
170 "Buffer" => "@@bytes",
171 _ => return None,
172 };
173 with_host(|h| match h.get(v) {
174 Some(JsObj::Object(p)) => match p.get(field).and_then(|a| h.get(a)) {
175 Some(JsObj::Array(items)) => Some(items.iter().map(|x| h.to_number(x)).collect()),
176 _ => None,
177 },
178 _ => None,
179 })
180}
181
182fn kind_of(recv: &Value) -> String {
184 with_host(|h| match h.get(recv) {
185 Some(JsObj::Object(p)) => p
186 .get("@@kind")
187 .map(|v| h.str_of(v))
188 .unwrap_or_else(|| "Uint8Array".into()),
189 _ => "Uint8Array".into(),
190 })
191}
192
193pub fn elem_get(recv: &Value, key: &str) -> Option<Value> {
198 let i: usize = key.parse().ok()?;
199 with_host(|h| match h.get(recv) {
200 Some(JsObj::Object(p)) => match p.get("@@elems").and_then(|a| h.get(a)) {
201 Some(JsObj::Array(items)) => items.get(i).cloned(),
202 _ => None,
203 },
204 _ => None,
205 })
206}
207
208pub fn elem_set(recv: &Value, key: &str, val: &Value) -> bool {
210 let Ok(i) = key.parse::<usize>() else {
211 return false;
212 };
213 let kind = kind_of(recv);
214 let n = coerce(&kind, with_host(|h| h.to_number(val)));
215 with_host(|h| {
216 if let Some(JsObj::Object(p)) = h.get(recv) {
217 if let Some(arr) = p.get("@@elems").cloned() {
218 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
219 if i < items.len() {
220 items[i] = Value::Float(n);
221 return true;
222 }
223 }
224 }
225 }
226 false
227 })
228}
229
230pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
232 let kind = kind_of(recv);
233 let elems = elems_of(recv).unwrap_or_default();
234 match method {
235 "toString" | "join" => {
236 let sep = if method == "join" && !args.is_empty() {
237 super::arg_str(args, 0)
238 } else {
239 ",".into()
240 };
241 let parts: Vec<String> =
242 with_host(|h| elems.iter().map(|n| h.str_of(&Value::Float(*n))).collect());
243 Ok(with_host(|h| h.new_str(parts.join(&sep))))
244 }
245 "slice" | "subarray" => {
246 let len = elems.len();
247 let norm = |n: f64| -> usize {
248 if n < 0.0 {
249 (len as f64 + n).max(0.0) as usize
250 } else {
251 (n as usize).min(len)
252 }
253 };
254 let s = if args.is_empty() {
255 0
256 } else {
257 norm(super::arg_num(args, 0))
258 };
259 let e = if args.len() < 2 {
260 len
261 } else {
262 norm(super::arg_num(args, 1))
263 };
264 Ok(make(&kind, elems[s.min(e)..e.max(s)].to_vec()))
265 }
266 "indexOf" => {
267 let needle = super::arg_num(args, 0);
268 Ok(Value::Float(
269 elems
270 .iter()
271 .position(|x| *x == needle)
272 .map(|p| p as f64)
273 .unwrap_or(-1.0),
274 ))
275 }
276 "includes" => {
277 let needle = super::arg_num(args, 0);
278 Ok(Value::Bool(elems.contains(&needle)))
279 }
280 "fill" => {
281 let v = coerce(&kind, super::arg_num(args, 0));
282 Ok(make(&kind, vec![v; elems.len()]))
283 }
284 "set" => {
285 let src = elems_of(&args.first().cloned().unwrap_or(Value::Undef))
287 .or_else(|| {
288 Some(
289 crate::host::iter_all(&args.first().cloned().unwrap_or(Value::Undef))
290 .ok()?
291 .iter()
292 .map(|x| with_host(|h| h.to_number(x)))
293 .collect(),
294 )
295 })
296 .unwrap_or_default();
297 let off = super::arg_num(args, 1).max(0.0) as usize;
298 with_host(|h| {
299 if let Some(JsObj::Object(p)) = h.get(recv) {
300 if let Some(arr) = p.get("@@elems").cloned() {
301 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
302 for (k, v) in src.iter().enumerate() {
303 if off + k < items.len() {
304 items[off + k] = Value::Float(coerce(&kind, *v));
305 }
306 }
307 }
308 }
309 }
310 });
311 Ok(Value::Undef)
312 }
313 _ => Err(crate::host::type_error(&format!(
314 "{method} is not a function"
315 ))),
316 }
317}
318
319pub fn construct_weakref(args: &[Value]) -> Result<Value, String> {
322 let target = args.first().cloned().unwrap_or(Value::Undef);
323 Ok(with_host(|h| {
324 let mut m = IndexMap::new();
325 m.insert("@@native".into(), h.new_str("WeakRef"));
326 m.insert("@@target".into(), target);
327 h.new_object(m)
328 }))
329}
330
331pub fn weakref_call(recv: &Value, method: &str) -> Result<Value, String> {
332 match method {
333 "deref" => Ok(with_host(|h| match h.get(recv) {
334 Some(JsObj::Object(p)) => p.get("@@target").cloned().unwrap_or(Value::Undef),
335 _ => Value::Undef,
336 })),
337 _ => Err(crate::host::type_error(&format!(
338 "{method} is not a function"
339 ))),
340 }
341}
342
343fn is_object_value(v: &Value) -> bool {
356 matches!(v, Value::Obj(_))
357 && with_host(|h| {
358 !matches!(
359 h.get(v),
360 Some(JsObj::Str(_))
361 | Some(JsObj::Symbol { .. })
362 | Some(JsObj::BigInt(_))
363 | Some(JsObj::Null)
364 )
365 })
366}
367
368pub fn construct_finalization_registry(args: &[Value]) -> Result<Value, String> {
369 let cb = args.first().cloned().unwrap_or(Value::Undef);
370 if !with_host(|h| crate::host::is_callable(h, &cb)) {
371 return Err(crate::host::type_error(
372 "FinalizationRegistry: cleanup must be callable",
373 ));
374 }
375 Ok(with_host(|h| {
376 let tokens = h.new_array(Vec::new());
377 let mut m = IndexMap::new();
378 m.insert("@@native".into(), h.new_str("FinalizationRegistry"));
379 m.insert("@@fr_cb".into(), cb);
380 m.insert("@@fr_tokens".into(), tokens);
381 h.new_object(m)
382 }))
383}
384
385pub fn finalization_registry_call(
386 recv: &Value,
387 method: &str,
388 args: &[Value],
389) -> Result<Value, String> {
390 match method {
391 "register" => {
392 let target = args.first().cloned().unwrap_or(Value::Undef);
393 let held = args.get(1).cloned().unwrap_or(Value::Undef);
394 let token = args.get(2).cloned().unwrap_or(Value::Undef);
395 if !is_object_value(&target) {
396 return Err(crate::host::type_error(
397 "FinalizationRegistry.prototype.register: target must be an object",
398 ));
399 }
400 if with_host(|h| h.strict_eq(&target, &held)) {
401 return Err(crate::host::type_error(
402 "FinalizationRegistry.prototype.register: target and holdings must not be same",
403 ));
404 }
405 if !matches!(token, Value::Undef) {
408 if !is_object_value(&token) {
409 return Err(crate::host::type_error(
410 "FinalizationRegistry.prototype.register: unregister token must be an object",
411 ));
412 }
413 with_host(|h| {
414 let toks = registry_tokens(h, recv);
415 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
416 items.push(token);
417 }
418 });
419 }
420 Ok(Value::Undef)
421 }
422 "unregister" => {
423 let token = args.first().cloned().unwrap_or(Value::Undef);
424 if !is_object_value(&token) {
425 return Err(crate::host::type_error(
426 "FinalizationRegistry.prototype.unregister: unregister token must be an object",
427 ));
428 }
429 Ok(Value::Bool(with_host(|h| {
430 let toks = registry_tokens(h, recv);
431 let kept: Vec<Value> = match h.get(&toks) {
432 Some(JsObj::Array(items)) => items
433 .iter()
434 .filter(|t| !h.strict_eq(t, &token))
435 .cloned()
436 .collect(),
437 _ => Vec::new(),
438 };
439 let removed = match h.get(&toks) {
440 Some(JsObj::Array(items)) => items.len() != kept.len(),
441 _ => false,
442 };
443 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
444 *items = kept;
445 }
446 removed
447 })))
448 }
449 _ => Err(crate::host::type_error(&format!(
450 "{method} is not a function"
451 ))),
452 }
453}
454
455fn registry_tokens(h: &crate::host::JsHost, recv: &Value) -> Value {
457 match h.get(recv) {
458 Some(JsObj::Object(p)) => p.get("@@fr_tokens").cloned().unwrap_or(Value::Undef),
459 _ => Value::Undef,
460 }
461}
462
463pub fn construct_text_encoder() -> Result<Value, String> {
466 Ok(with_host(|h| {
467 let mut m = IndexMap::new();
468 m.insert("@@native".into(), h.new_str("TextEncoder"));
469 m.insert("encoding".into(), h.new_str("utf-8"));
470 h.new_object(m)
471 }))
472}
473
474pub fn text_encoder_call(_recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
475 match method {
476 "encode" => {
478 let s = super::arg_str(args, 0);
479 Ok(make(
480 "Uint8Array",
481 s.as_bytes().iter().map(|b| *b as f64).collect(),
482 ))
483 }
484 _ => Err(crate::host::type_error(&format!(
485 "{method} is not a function"
486 ))),
487 }
488}
489
490pub fn construct_text_decoder(args: &[Value]) -> Result<Value, String> {
491 let label = if args.is_empty() {
492 "utf-8".to_string()
493 } else {
494 super::arg_str(args, 0)
495 };
496 Ok(with_host(|h| {
497 let mut m = IndexMap::new();
498 m.insert("@@native".into(), h.new_str("TextDecoder"));
499 m.insert("encoding".into(), h.new_str(label.to_ascii_lowercase()));
500 h.new_object(m)
501 }))
502}
503
504pub fn text_decoder_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
505 match method {
506 "decode" => {
508 let bytes: Vec<u8> = elems_of(&args.first().cloned().unwrap_or(Value::Undef))
509 .unwrap_or_default()
510 .iter()
511 .map(|n| *n as u8)
512 .collect();
513 let enc = with_host(|h| match h.get(recv) {
514 Some(JsObj::Object(p)) => p
515 .get("encoding")
516 .map(|v| h.str_of(v))
517 .unwrap_or_else(|| "utf-8".into()),
518 _ => "utf-8".into(),
519 });
520 let s = match enc.as_str() {
521 "latin1" | "iso-8859-1" | "ascii" => bytes.iter().map(|b| *b as char).collect(),
522 _ => String::from_utf8_lossy(&bytes).into_owned(),
523 };
524 Ok(with_host(|h| h.new_str(s)))
525 }
526 _ => Err(crate::host::type_error(&format!(
527 "{method} is not a function"
528 ))),
529 }
530}