1use crate::runtime::host::{abi_arg, abi_result};
7use wasmtime::{
8 ArrayRef, ArrayRefPre, ArrayType, Caller, FuncType, HeapType, Linker, RefType, Rooted,
9 StructRef, StructRefPre, StructType, Val, ValType,
10};
11
12use super::{
13 AdmittedStr, RangeError, Str, at_index, cmp, code_point, concat, concat_length,
14 ends_with_window, from_char_code, from_code_point, includes, index_of, is_well_formed,
15 last_index_of, normalize, pad_end, pad_length, pad_start, repeat, repeat_length, slice_range,
16 starts_with_window, substring_range, to_lower_case, to_upper_case, to_well_formed, trim,
17 trim_end, trim_start, unit_index,
18};
19use crate::runtime::StoreData;
20use crate::runtime::fuel;
21use crate::runtime::host::{
22 intrinsic_string_type, read_code_unit, read_code_units, read_code_units_range,
23 register_host_fn, register_host_fn_async, string_array_type, write_boxed_number_struct,
24 write_submilli_string_struct, write_submilli_string_struct_units,
25};
26use crate::runtime::intrinsic_types::{build_intrinsic_types, intrinsic_types};
27use crate::runtime::limits::TenantLimits;
28use crate::runtime::prelude::{MODULE_NAME, declare_method};
29use crate::{MangledName, PackageDeclaration, Param, Type};
30
31fn method_key(method: &str) -> MangledName {
35 crate::mangle::extend(&crate::mangle::prelude("String"), method)
36}
37
38fn ctor_key(method: &str) -> MangledName {
42 crate::mangle::extend(&crate::mangle::prelude("StringConstructor"), method)
43}
44
45pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
46 let engine = linker.engine().clone();
47 let abi = StringAbi::recover(&engine)?;
48
49 reg_unit_to_str(linker, &engine, &abi, "charAt", unit_index)?;
52 reg_unit_to_str_or_undefined(linker, &engine, &abi, "at", at_index)?;
53 reg_char_code_at(linker, &engine, &abi)?;
54 reg_code_point_at(linker, &engine, &abi)?;
55
56 reg_range_to_str(linker, &engine, &abi, "slice", slice_range)?;
57 reg_range_to_str(linker, &engine, &abi, "substring", substring_range)?;
58
59 reg_str_str_num_to_num(linker, &engine, &abi, "indexOf", index_of)?;
60 reg_str_str_num_to_num(linker, &engine, &abi, "lastIndexOf", last_index_of)?;
61 register_string_search_predicate(linker, &engine, &abi, "includes", includes)?;
62 reg_window_predicate(linker, &engine, &abi, "startsWith", starts_with_window)?;
63 reg_window_predicate(linker, &engine, &abi, "endsWith", ends_with_window)?;
64
65 reg_str_str_to_str(linker, &engine, &abi, "concat", concat)?;
66 reg_str_num_str_to_str(linker, &engine, &abi, "padStart", pad_start)?;
67 reg_str_num_str_to_str(linker, &engine, &abi, "padEnd", pad_end)?;
68
69 reg_str_to_bool(linker, &engine, &abi, "isWellFormed", is_well_formed)?;
70 reg_str_to_str(linker, &engine, &abi, "toWellFormed", to_well_formed)?;
71
72 super::transforms::prepare_case_properties()?;
73 reg_str_transform(linker, &engine, &abi, "toUpperCase", to_upper_case)?;
74 reg_str_transform(linker, &engine, &abi, "toLowerCase", to_lower_case)?;
75 reg_str_transform(linker, &engine, &abi, "trim", trim)?;
76 reg_str_transform(linker, &engine, &abi, "trimStart", trim_start)?;
77 reg_str_transform(linker, &engine, &abi, "trimEnd", trim_end)?;
78 reg_str_str_to_str_fallible(linker, &engine, &abi, "normalize", normalize)?;
79
80 reg_str_num_to_str_fallible(linker, &engine, &abi, "repeat", repeat)?;
81
82 let array_ref = ValType::Ref(RefType::new(
85 false,
86 HeapType::ConcreteStruct(build_intrinsic_types(&engine)?.array),
87 ));
88 register_host_fn(
89 linker,
90 MODULE_NAME,
91 ctor_key("fromCharCode"),
92 FuncType::new(&engine, [array_ref.clone()], [abi.value_type()]),
93 true,
94 move |caller, params, results| {
95 let codes = read_number_array(caller, abi_arg(params, 0)?, "String.fromCharCode")?;
96 let out = from_char_code(&codes);
97 let st = write_submilli_string_struct_units(caller, out.units())?;
98 *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
99 Ok(())
100 },
101 )?;
102 register_host_fn(
103 linker,
104 MODULE_NAME,
105 ctor_key("fromCodePoint"),
106 FuncType::new(&engine, [array_ref], [abi.value_type()]),
107 true,
108 move |caller, params, results| {
109 let codes = read_number_array(caller, abi_arg(params, 0)?, "String.fromCodePoint")?;
110 let out = from_code_point(&codes).map_err(throw)?;
111 let st = write_submilli_string_struct_units(caller, out.units())?;
112 *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
113 Ok(())
114 },
115 )?;
116
117 register_host_fn(
120 linker,
121 MODULE_NAME,
122 method_key("toString"),
123 FuncType::new(&engine, [abi.value_type()], [abi.value_type()]),
124 true,
125 |_caller, params, results| {
126 *abi_result(results, 0)? = *abi_arg(params, 0)?;
127 Ok(())
128 },
129 )?;
130 let json_abi = abi.clone();
131 register_host_fn(
132 linker,
133 MODULE_NAME,
134 method_key("toJson"),
135 FuncType::new(&engine, [abi.value_type()], [abi.value_type()]),
136 true,
137 move |caller, params, results| {
138 let recv = json_abi.read(caller, abi_arg(params, 0)?, "String#toJson")?;
139 *abi_result(results, 0)? =
140 crate::runtime::prelude::vtable::quote_string(caller, recv.value.units())?;
141 Ok(())
142 },
143 )?;
144 let equals_abi = abi.clone();
145 register_host_fn(
146 linker,
147 MODULE_NAME,
148 method_key("equals"),
149 FuncType::new(
150 &engine,
151 [abi.value_type(), abi.value_type()],
152 [ValType::I32],
153 ),
154 true,
155 move |caller, params, results| {
156 *abi_result(results, 0)? = Val::I32(super::super::vtable::string_equals(
157 caller,
158 abi_arg(params, 0)?,
159 abi_arg(params, 1)?,
160 &equals_abi.string_ty,
161 )? as i32);
162 Ok(())
163 },
164 )?;
165 let lc_abi = abi.clone();
166 register_host_fn(
167 linker,
168 MODULE_NAME,
169 method_key("localeCompare"),
170 FuncType::new(
171 &engine,
172 [abi.value_type(), abi.value_type()],
173 [ValType::F64],
174 ),
175 true,
176 move |caller, params, results| {
177 let a = lc_abi.read(caller, abi_arg(params, 0)?, "String#localeCompare")?;
178 let b = lc_abi.read(caller, abi_arg(params, 1)?, "String#localeCompare")?;
179 *abi_result(results, 0)? = Val::F64(f64::from(cmp(&a.value, &b.value)).to_bits());
180 Ok(())
181 },
182 )?;
183
184 let obj_param = ValType::Ref(RefType::new(
189 true,
190 HeapType::ConcreteStruct(build_intrinsic_types(&engine)?.object),
191 ));
192 register_host_fn_async(
193 linker,
194 MODULE_NAME,
195 crate::mangle::extend(&crate::mangle::prelude("StringConstructor"), "@call"),
196 FuncType::new(&engine, [obj_param], [abi.value_type()]),
197 true,
198 |caller, params, results| {
199 Box::pin(async move {
200 *abi_result(results, 0)? = string_ctor_call(caller, abi_arg(params, 0)?).await?;
201 Ok(())
202 })
203 },
204 )?;
205
206 let obj_ret = ValType::Ref(RefType::new(
208 true,
209 HeapType::ConcreteStruct(build_intrinsic_types(&engine)?.object),
210 ));
211 register_host_fn(
212 linker,
213 MODULE_NAME,
214 method_key("iterator"),
215 FuncType::new(&engine, [abi.value_type()], [obj_ret]),
216 true,
217 |caller, params, results| {
218 *abi_result(results, 0)? = crate::runtime::prelude::iterator::make_string_iterator(
219 caller,
220 *abi_arg(params, 0)?,
221 )?;
222 Ok(())
223 },
224 )?;
225
226 let s = abi.value_type();
229 let concat_abi = abi.clone();
230 register_host_fn(
231 linker,
232 MODULE_NAME,
233 crate::mangle::prelude("string_concat"),
234 FuncType::new(&engine, [s.clone(), s.clone()], [s.clone()]),
235 true,
236 move |caller, params, results| {
237 let recv = concat_abi.read_admitted(caller, abi_arg(params, 0)?, "string_concat")?;
238 let other = concat_abi.read_admitted(caller, abi_arg(params, 1)?, "string_concat")?;
239 fuel::charge(
240 &mut *caller,
241 fuel::COPY,
242 concat_length(recv.value.as_str().len(), other.value.as_str().len())? as u64,
243 )?;
244 let out = concat(
245 recv.value.as_str(),
246 other.value.as_str(),
247 &caller.data().tenant_limits,
248 )?;
249 *abi_result(results, 0)? = concat_abi.write_precharged(caller, recv.vtable, &out)?;
250 Ok(())
251 },
252 )?;
253 let eq_abi = abi.clone();
254 register_host_fn(
255 linker,
256 MODULE_NAME,
257 crate::mangle::prelude("string_eq"),
258 FuncType::new(&engine, [s.clone(), s.clone()], [ValType::I32]),
259 true,
260 move |caller, params, results| {
261 let equal = super::super::vtable::string_equals(
262 caller,
263 abi_arg(params, 0)?,
264 abi_arg(params, 1)?,
265 &eq_abi.string_ty,
266 )?;
267 *abi_result(results, 0)? = Val::I32(equal as i32);
268 Ok(())
269 },
270 )?;
271 let cmp_abi = abi.clone();
272 register_host_fn(
273 linker,
274 MODULE_NAME,
275 crate::mangle::prelude("string_cmp"),
276 FuncType::new(&engine, [s.clone(), s], [ValType::I32]),
277 true,
278 move |caller, params, results| {
279 let a = cmp_abi.read(caller, abi_arg(params, 0)?, "string_cmp")?;
280 let b = cmp_abi.read(caller, abi_arg(params, 1)?, "string_cmp")?;
281 *abi_result(results, 0)? = Val::I32(cmp(&a.value, &b.value));
282 Ok(())
283 },
284 )?;
285 Ok(())
286}
287
288pub fn declare(defs: &mut PackageDeclaration) {
289 let m = |defs: &mut PackageDeclaration, name: &str, params: Vec<Param>, ret: Type| {
290 declare_method(defs, name, method_key(name), params, ret);
291 };
292 let s = || Param::new("s", Type::String);
293
294 m(
295 defs,
296 "charAt",
297 vec![s(), Param::new("index", Type::Number)],
298 Type::String,
299 );
300 m(
301 defs,
302 "at",
303 vec![s(), Param::new("index", Type::Number)],
304 Type::Union(vec![Type::String, Type::Undefined]),
305 );
306 m(
307 defs,
308 "charCodeAt",
309 vec![s(), Param::new("index", Type::Number)],
310 Type::Number,
311 );
312 m(
313 defs,
314 "codePointAt",
315 vec![s(), Param::new("index", Type::Number)],
316 Type::Union(vec![Type::Number, Type::Undefined]),
317 );
318 m(
319 defs,
320 "slice",
321 vec![
322 s(),
323 Param::new("start", Type::Number),
324 Param::new("end", Type::Number),
325 ],
326 Type::String,
327 );
328 m(
329 defs,
330 "substring",
331 vec![
332 s(),
333 Param::new("start", Type::Number),
334 Param::new("end", Type::Number),
335 ],
336 Type::String,
337 );
338 m(
339 defs,
340 "indexOf",
341 vec![
342 s(),
343 Param::new("search", Type::String),
344 Param::new("fromIndex", Type::Number),
345 ],
346 Type::Number,
347 );
348 m(
349 defs,
350 "lastIndexOf",
351 vec![
352 s(),
353 Param::new("search", Type::String),
354 Param::new("fromIndex", Type::Number),
355 ],
356 Type::Number,
357 );
358 m(
359 defs,
360 "includes",
361 vec![
362 s(),
363 Param::new("search", Type::Unknown),
364 Param::new("fromIndex", Type::Unknown),
365 ],
366 Type::Boolean,
367 );
368 m(
369 defs,
370 "startsWith",
371 vec![
372 s(),
373 Param::new("search", Type::Unknown),
374 Param::new("position", Type::Unknown),
375 ],
376 Type::Boolean,
377 );
378 m(
379 defs,
380 "endsWith",
381 vec![
382 s(),
383 Param::new("search", Type::Unknown),
384 Param::new("endPosition", Type::Unknown),
385 ],
386 Type::Boolean,
387 );
388 m(
389 defs,
390 "concat",
391 vec![s(), Param::new("other", Type::String)],
392 Type::String,
393 );
394 m(
395 defs,
396 "padStart",
397 vec![
398 s(),
399 Param::new("targetLength", Type::Number),
400 Param::new("padString", Type::String),
401 ],
402 Type::String,
403 );
404 m(
405 defs,
406 "padEnd",
407 vec![
408 s(),
409 Param::new("targetLength", Type::Number),
410 Param::new("padString", Type::String),
411 ],
412 Type::String,
413 );
414 m(defs, "isWellFormed", vec![s()], Type::Boolean);
415 m(defs, "toWellFormed", vec![s()], Type::String);
416 m(defs, "toUpperCase", vec![s()], Type::String);
417 m(defs, "toLowerCase", vec![s()], Type::String);
418 m(defs, "trim", vec![s()], Type::String);
419 m(defs, "trimStart", vec![s()], Type::String);
420 m(defs, "trimEnd", vec![s()], Type::String);
421 m(
422 defs,
423 "normalize",
424 vec![s(), Param::new("form", Type::String)],
425 Type::String,
426 );
427 m(
428 defs,
429 "repeat",
430 vec![s(), Param::new("count", Type::Number)],
431 Type::String,
432 );
433
434 declare_method(
435 defs,
436 "fromCharCode",
437 ctor_key("fromCharCode"),
438 vec![Param::rest("codes", Type::Array(Box::new(Type::Number)))],
439 Type::String,
440 );
441 declare_method(
442 defs,
443 "fromCodePoint",
444 ctor_key("fromCodePoint"),
445 vec![Param::rest(
446 "codePoints",
447 Type::Array(Box::new(Type::Number)),
448 )],
449 Type::String,
450 );
451
452 m(
453 defs,
454 "iterator",
455 vec![s()],
456 Type::prelude_interface("Iterator".to_string(), vec![Type::String]),
457 );
458 m(defs, "toString", vec![s()], Type::String);
459 m(defs, "toJson", vec![s()], Type::String);
460 m(
461 defs,
462 "equals",
463 vec![s(), Param::new("other", Type::String)],
464 Type::Boolean,
465 );
466 m(
467 defs,
468 "localeCompare",
469 vec![s(), Param::new("other", Type::String)],
470 Type::Number,
471 );
472 declare_method(
473 defs,
474 "@call",
475 crate::mangle::extend(&crate::mangle::prelude("StringConstructor"), "@call"),
476 vec![Param::new("value", Type::Unknown)],
477 Type::String,
478 );
479
480 let two_strings = || vec![Param::anon(Type::String), Param::anon(Type::String)];
483 declare_method(
484 defs,
485 "string_concat",
486 crate::mangle::prelude("string_concat"),
487 two_strings(),
488 Type::String,
489 );
490 declare_method(
491 defs,
492 "string_eq",
493 crate::mangle::prelude("string_eq"),
494 two_strings(),
495 Type::Boolean,
496 );
497 declare_method(
498 defs,
499 "string_cmp",
500 crate::mangle::prelude("string_cmp"),
501 two_strings(),
502 Type::Boolean,
503 );
504}
505
506fn reg_unit_to_str(
509 linker: &mut Linker<StoreData>,
510 engine: &wasmtime::Engine,
511 abi: &StringAbi,
512 name: &'static str,
513 pick: fn(usize, f64) -> Option<usize>,
514) -> wasmtime::Result<()> {
515 let s = abi.value_type();
516 let abi = abi.clone();
517 register_host_fn(
518 linker,
519 MODULE_NAME,
520 method_key(name),
521 FuncType::new(engine, [s.clone(), ValType::F64], [s]),
522 true,
523 move |caller, params, results| {
524 let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
525 let arg = number(abi_arg(params, 1)?, name)?;
526 let unit = pick(recv.len, arg)
527 .map(|i| read_code_unit(&mut *caller, recv.array, i))
528 .transpose()?;
529 let out = Str::from_units(unit.map_or_else(Vec::new, |unit| vec![unit]));
530 *abi_result(results, 0)? = abi.write(caller, recv.vtable, &out)?;
531 Ok(())
532 },
533 )
534}
535
536fn reg_unit_to_str_or_undefined(
540 linker: &mut Linker<StoreData>,
541 engine: &wasmtime::Engine,
542 abi: &StringAbi,
543 name: &'static str,
544 pick: fn(usize, f64) -> Option<usize>,
545) -> wasmtime::Result<()> {
546 let s = abi.value_type();
547 let object = ValType::Ref(RefType::new(
548 true,
549 HeapType::ConcreteStruct(build_intrinsic_types(engine)?.object),
550 ));
551 let abi = abi.clone();
552 register_host_fn(
553 linker,
554 MODULE_NAME,
555 method_key(name),
556 FuncType::new(engine, [s, ValType::F64], [object]),
557 true,
558 move |caller, params, results| {
559 let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
560 let arg = number(abi_arg(params, 1)?, name)?;
561 *abi_result(results, 0)? = match pick(recv.len, arg) {
562 Some(i) => {
563 let unit = read_code_unit(&mut *caller, recv.array, i)?;
564 abi.write(caller, recv.vtable, &Str::from_units(vec![unit]))?
565 }
566 None => crate::runtime::prelude::undefined::value(caller)?,
567 };
568 Ok(())
569 },
570 )
571}
572
573fn reg_char_code_at(
575 linker: &mut Linker<StoreData>,
576 engine: &wasmtime::Engine,
577 abi: &StringAbi,
578) -> wasmtime::Result<()> {
579 const NAME: &str = "charCodeAt";
580 let s = abi.value_type();
581 let abi = abi.clone();
582 register_host_fn(
583 linker,
584 MODULE_NAME,
585 method_key(NAME),
586 FuncType::new(engine, [s, ValType::F64], [ValType::F64]),
587 true,
588 move |caller, params, results| {
589 let recv = abi.payload(caller, abi_arg(params, 0)?, NAME)?;
590 let arg = number(abi_arg(params, 1)?, NAME)?;
591 let value = match unit_index(recv.len, arg) {
592 Some(i) => f64::from(read_code_unit(&mut *caller, recv.array, i)?),
593 None => f64::NAN,
594 };
595 *abi_result(results, 0)? = Val::F64(value.to_bits());
596 Ok(())
597 },
598 )
599}
600
601fn reg_code_point_at(
604 linker: &mut Linker<StoreData>,
605 engine: &wasmtime::Engine,
606 abi: &StringAbi,
607) -> wasmtime::Result<()> {
608 const NAME: &str = "codePointAt";
609 let s = abi.value_type();
610 let object = ValType::Ref(RefType::new(
611 true,
612 HeapType::ConcreteStruct(build_intrinsic_types(engine)?.object),
613 ));
614 let abi = abi.clone();
615 register_host_fn(
616 linker,
617 MODULE_NAME,
618 method_key(NAME),
619 FuncType::new(engine, [s, ValType::F64], [object]),
620 true,
621 move |caller, params, results| {
622 let recv = abi.payload(caller, abi_arg(params, 0)?, NAME)?;
623 let arg = number(abi_arg(params, 1)?, NAME)?;
624 let Some(i) = unit_index(recv.len, arg) else {
625 *abi_result(results, 0)? = crate::runtime::prelude::undefined::value(caller)?;
626 return Ok(());
627 };
628 let unit = read_code_unit(&mut *caller, recv.array, i)?;
629 let next = if i + 1 < recv.len {
630 Some(read_code_unit(&mut *caller, recv.array, i + 1)?)
631 } else {
632 None
633 };
634 let boxed = write_boxed_number_struct(caller, code_point(unit, next))?;
635 *abi_result(results, 0)? = Val::AnyRef(Some(boxed.to_anyref()));
636 Ok(())
637 },
638 )
639}
640
641fn reg_str_num_to_str_fallible(
643 linker: &mut Linker<StoreData>,
644 engine: &wasmtime::Engine,
645 abi: &StringAbi,
646 name: &'static str,
647 op: fn(&Str, f64, &TenantLimits) -> wasmtime::Result<AdmittedStr>,
648) -> wasmtime::Result<()> {
649 let s = abi.value_type();
650 let abi = abi.clone();
651 register_host_fn(
652 linker,
653 MODULE_NAME,
654 method_key(name),
655 FuncType::new(engine, [s.clone(), ValType::F64], [s]),
656 true,
657 move |caller, params, results| {
658 let recv = abi.read_admitted(caller, abi_arg(params, 0)?, name)?;
659 let arg = number(abi_arg(params, 1)?, name)?;
660 fuel::charge(
661 &mut *caller,
662 fuel::COPY,
663 repeat_length(recv.value.as_str().len(), arg).map_err(throw)? as u64,
664 )?;
665 let out = op(recv.value.as_str(), arg, &caller.data().tenant_limits)?;
666 *abi_result(results, 0)? = abi.write_precharged(caller, recv.vtable, &out)?;
667 Ok(())
668 },
669 )
670}
671
672fn reg_range_to_str(
675 linker: &mut Linker<StoreData>,
676 engine: &wasmtime::Engine,
677 abi: &StringAbi,
678 name: &'static str,
679 range: fn(usize, f64, f64) -> std::ops::Range<usize>,
680) -> wasmtime::Result<()> {
681 let s = abi.value_type();
682 let abi = abi.clone();
683 register_host_fn(
684 linker,
685 MODULE_NAME,
686 method_key(name),
687 FuncType::new(engine, [s.clone(), ValType::F64, ValType::F64], [s]),
688 true,
689 move |caller, params, results| {
690 let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
691 let a = number(abi_arg(params, 1)?, name)?;
692 let b = number(abi_arg(params, 2)?, name)?;
693 let kept = range(recv.len, a, b);
694 let units = read_code_units_range(&mut *caller, recv.array, kept.start, kept.len())?;
695 *abi_result(results, 0)? = abi.write(caller, recv.vtable, &Str::from_units(units))?;
696 Ok(())
697 },
698 )
699}
700
701fn reg_str_str_num_to_num(
703 linker: &mut Linker<StoreData>,
704 engine: &wasmtime::Engine,
705 abi: &StringAbi,
706 name: &'static str,
707 op: fn(&Str, &Str, f64) -> f64,
708) -> wasmtime::Result<()> {
709 let s = abi.value_type();
710 let abi = abi.clone();
711 register_host_fn(
712 linker,
713 MODULE_NAME,
714 method_key(name),
715 FuncType::new(engine, [s.clone(), s, ValType::F64], [ValType::F64]),
716 true,
717 move |caller, params, results| {
718 let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
719 let search = abi.read(caller, abi_arg(params, 1)?, name)?;
720 let from = number(abi_arg(params, 2)?, name)?;
721 fuel::charge(
722 &mut *caller,
723 fuel::SCAN,
724 (recv.value.len() + search.value.len()) as u64,
725 )?;
726 *abi_result(results, 0)? = Val::F64(op(&recv.value, &search.value, from).to_bits());
727 Ok(())
728 },
729 )
730}
731
732fn register_string_search_predicate(
734 linker: &mut Linker<StoreData>,
735 engine: &wasmtime::Engine,
736 abi: &StringAbi,
737 name: &'static str,
738 op: fn(&Str, &Str, f64) -> bool,
739) -> wasmtime::Result<()> {
740 let intr = build_intrinsic_types(engine)?;
741 let value = ValType::Ref(RefType::new(true, HeapType::ConcreteStruct(intr.object)));
742 let abi = abi.clone();
743 register_host_fn_async(
744 linker,
745 MODULE_NAME,
746 method_key(name),
747 FuncType::new(
748 engine,
749 [abi.value_type(), value.clone(), value],
750 [ValType::I32],
751 ),
752 true,
753 move |caller, params, results| {
754 let abi = abi.clone();
755 Box::pin(async move {
756 let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
757 let search =
758 super::super::value::search_string(caller, abi_arg(params, 1)?).await?;
759 let from = super::super::value::to_number(caller, abi_arg(params, 2)?).await?;
760 fuel::charge(
761 &mut *caller,
762 fuel::SCAN,
763 (recv.value.len() + search.len()) as u64,
764 )?;
765 *abi_result(results, 0)? =
766 Val::I32(op(&recv.value, &Str::from_units(search), from) as i32);
767 Ok(())
768 })
769 },
770 )
771}
772
773fn reg_window_predicate(
777 linker: &mut Linker<StoreData>,
778 engine: &wasmtime::Engine,
779 abi: &StringAbi,
780 name: &'static str,
781 window: fn(usize, usize, f64) -> Option<std::ops::Range<usize>>,
782) -> wasmtime::Result<()> {
783 let intr = build_intrinsic_types(engine)?;
784 let value = ValType::Ref(RefType::new(true, HeapType::ConcreteStruct(intr.object)));
785 let abi = abi.clone();
786 register_host_fn_async(
787 linker,
788 MODULE_NAME,
789 method_key(name),
790 FuncType::new(
791 engine,
792 [abi.value_type(), value.clone(), value],
793 [ValType::I32],
794 ),
795 true,
796 move |caller, params, results| {
797 let abi = abi.clone();
798 Box::pin(async move {
799 let recv = abi.payload(caller, abi_arg(params, 0)?, name)?;
800 let needle =
801 super::super::value::search_string(caller, abi_arg(params, 1)?).await?;
802 let position = super::super::value::to_number(caller, abi_arg(params, 2)?).await?;
803 let found = match window(recv.len, needle.len(), position) {
804 Some(range) => {
805 read_code_units_range(caller, recv.array, range.start, range.len())?
806 == needle
807 }
808 None => false,
809 };
810 *abi_result(results, 0)? = Val::I32(found as i32);
811 Ok(())
812 })
813 },
814 )
815}
816
817fn reg_str_str_to_str(
819 linker: &mut Linker<StoreData>,
820 engine: &wasmtime::Engine,
821 abi: &StringAbi,
822 name: &'static str,
823 op: fn(&Str, &Str, &TenantLimits) -> wasmtime::Result<AdmittedStr>,
824) -> wasmtime::Result<()> {
825 let s = abi.value_type();
826 let abi = abi.clone();
827 register_host_fn(
828 linker,
829 MODULE_NAME,
830 method_key(name),
831 FuncType::new(engine, [s.clone(), s.clone()], [s]),
832 true,
833 move |caller, params, results| {
834 let recv = abi.read_admitted(caller, abi_arg(params, 0)?, name)?;
835 let other = abi.read_admitted(caller, abi_arg(params, 1)?, name)?;
836 fuel::charge(
837 &mut *caller,
838 fuel::COPY,
839 concat_length(recv.value.as_str().len(), other.value.as_str().len())? as u64,
840 )?;
841 let out = op(
842 recv.value.as_str(),
843 other.value.as_str(),
844 &caller.data().tenant_limits,
845 )?;
846 *abi_result(results, 0)? = abi.write_precharged(caller, recv.vtable, &out)?;
847 Ok(())
848 },
849 )
850}
851
852fn reg_str_num_str_to_str(
854 linker: &mut Linker<StoreData>,
855 engine: &wasmtime::Engine,
856 abi: &StringAbi,
857 name: &'static str,
858 op: fn(&Str, f64, &Str, &TenantLimits) -> wasmtime::Result<AdmittedStr>,
859) -> wasmtime::Result<()> {
860 let s = abi.value_type();
861 let abi = abi.clone();
862 register_host_fn(
863 linker,
864 MODULE_NAME,
865 method_key(name),
866 FuncType::new(engine, [s.clone(), ValType::F64, s.clone()], [s]),
867 true,
868 move |caller, params, results| {
869 let recv = abi.read_admitted(caller, abi_arg(params, 0)?, name)?;
870 let target = number(abi_arg(params, 1)?, name)?;
871 let pad = abi.read_admitted(caller, abi_arg(params, 2)?, name)?;
872 fuel::charge(
873 &mut *caller,
874 fuel::COPY,
875 pad_length(recv.value.as_str().len(), target, pad.value.as_str().len())
876 .map_err(throw)? as u64,
877 )?;
878 let out = op(
879 recv.value.as_str(),
880 target,
881 pad.value.as_str(),
882 &caller.data().tenant_limits,
883 )?;
884 *abi_result(results, 0)? = abi.write_precharged(caller, recv.vtable, &out)?;
885 Ok(())
886 },
887 )
888}
889
890fn reg_str_str_to_str_fallible(
893 linker: &mut Linker<StoreData>,
894 engine: &wasmtime::Engine,
895 abi: &StringAbi,
896 name: &'static str,
897 op: fn(&Str, &Str) -> super::Result<Str>,
898) -> wasmtime::Result<()> {
899 let s = abi.value_type();
900 let abi = abi.clone();
901 register_host_fn(
902 linker,
903 MODULE_NAME,
904 method_key(name),
905 FuncType::new(engine, [s.clone(), s.clone()], [s]),
906 true,
907 move |caller, params, results| {
908 let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
909 let arg = abi.read(caller, abi_arg(params, 1)?, name)?;
910 let out = op(&recv.value, &arg.value).map_err(throw)?;
911 *abi_result(results, 0)? = abi.write(caller, recv.vtable, &out)?;
912 Ok(())
913 },
914 )
915}
916
917fn reg_str_to_bool(
919 linker: &mut Linker<StoreData>,
920 engine: &wasmtime::Engine,
921 abi: &StringAbi,
922 name: &'static str,
923 op: fn(&Str) -> bool,
924) -> wasmtime::Result<()> {
925 let s = abi.value_type();
926 let abi = abi.clone();
927 register_host_fn(
928 linker,
929 MODULE_NAME,
930 method_key(name),
931 FuncType::new(engine, [s], [ValType::I32]),
932 true,
933 move |caller, params, results| {
934 let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
935 *abi_result(results, 0)? = Val::I32(op(&recv.value) as i32);
936 Ok(())
937 },
938 )
939}
940
941fn reg_str_to_str(
943 linker: &mut Linker<StoreData>,
944 engine: &wasmtime::Engine,
945 abi: &StringAbi,
946 name: &'static str,
947 op: fn(&Str) -> Str,
948) -> wasmtime::Result<()> {
949 let s = abi.value_type();
950 let abi = abi.clone();
951 register_host_fn(
952 linker,
953 MODULE_NAME,
954 method_key(name),
955 FuncType::new(engine, [s.clone()], [s]),
956 true,
957 move |caller, params, results| {
958 let recv = abi.read(caller, abi_arg(params, 0)?, name)?;
959 fuel::charge(
961 &mut *caller,
962 fuel::SCAN,
963 2 * recv.value.units().len() as u64,
964 )?;
965 *abi_result(results, 0)? = abi.write(caller, recv.vtable, &op(&recv.value))?;
966 Ok(())
967 },
968 )
969}
970
971fn reg_str_transform(
972 linker: &mut Linker<StoreData>,
973 engine: &wasmtime::Engine,
974 abi: &StringAbi,
975 name: &'static str,
976 op: fn(&Str) -> wasmtime::Result<Str>,
977) -> wasmtime::Result<()> {
978 let s = abi.value_type();
979 let abi = abi.clone();
980 register_host_fn(
981 linker,
982 MODULE_NAME,
983 method_key(name),
984 FuncType::new(engine, [s.clone()], [s]),
985 true,
986 move |caller, params, results| {
987 let payload = abi.payload(caller, ¶ms[0], name)?;
988 let _native = crate::runtime::limits::HostBytes::new(
989 &caller.data().tenant_limits,
990 (payload.len as u64).saturating_mul(8),
991 )?;
992 let recv = Receiver {
993 vtable: payload.vtable,
994 value: payload.read_all(caller, name)?,
995 };
996 fuel::charge(&mut *caller, fuel::SCAN, recv.value.len() as u64)?;
997 results[0] = abi.write(caller, recv.vtable, &op(&recv.value)?)?;
998 Ok(())
999 },
1000 )
1001}
1002
1003#[derive(Clone)]
1006struct StringAbi {
1007 string_ty: StructType,
1008 payload_ty: ArrayType,
1009}
1010
1011struct Receiver {
1015 vtable: Val,
1016 value: Str,
1017}
1018
1019struct AdmittedReceiver {
1020 vtable: Val,
1021 value: AdmittedStr,
1022}
1023
1024struct Payload {
1027 vtable: Val,
1028 array: Rooted<ArrayRef>,
1029 len: usize,
1030}
1031
1032impl Payload {
1033 fn read_all(&self, caller: &mut Caller<'_, StoreData>, name: &str) -> wasmtime::Result<Str> {
1034 Ok(Str::from_units(read_code_units(
1035 &mut *caller,
1036 self.array,
1037 name,
1038 )?))
1039 }
1040}
1041
1042impl StringAbi {
1043 fn recover(engine: &wasmtime::Engine) -> wasmtime::Result<Self> {
1044 Ok(Self {
1045 string_ty: intrinsic_string_type(engine)?,
1046 payload_ty: string_array_type(engine),
1047 })
1048 }
1049
1050 fn value_type(&self) -> ValType {
1051 ValType::Ref(RefType::new(
1052 false,
1053 HeapType::ConcreteStruct(self.string_ty.clone()),
1054 ))
1055 }
1056
1057 fn read(
1058 &self,
1059 caller: &mut Caller<'_, StoreData>,
1060 val: &Val,
1061 name: &str,
1062 ) -> wasmtime::Result<Receiver> {
1063 let payload = self.payload(caller, val, name)?;
1064 Ok(Receiver {
1065 vtable: payload.vtable,
1066 value: payload.read_all(caller, name)?,
1067 })
1068 }
1069
1070 fn read_admitted(
1071 &self,
1072 caller: &mut Caller<'_, StoreData>,
1073 val: &Val,
1074 name: &str,
1075 ) -> wasmtime::Result<AdmittedReceiver> {
1076 let payload = self.payload(caller, val, name)?;
1077 let native = super::admit_units(&caller.data().tenant_limits, payload.len)?;
1078 let value = payload.read_all(caller, name)?;
1079 Ok(AdmittedReceiver {
1080 vtable: payload.vtable,
1081 value: AdmittedStr {
1082 value,
1083 _native: native,
1084 },
1085 })
1086 }
1087
1088 fn payload(
1089 &self,
1090 caller: &mut Caller<'_, StoreData>,
1091 val: &Val,
1092 name: &str,
1093 ) -> wasmtime::Result<Payload> {
1094 let Val::AnyRef(Some(any)) = val else {
1095 return Err(wasmtime::Error::msg(format!(
1096 "{name} expects a string, got {val:?}"
1097 )));
1098 };
1099 let st = any
1100 .as_struct(&mut *caller)?
1101 .ok_or_else(|| wasmtime::Error::msg(format!("{name}: expected a $string struct")))?;
1102 let vtable = st.field(&mut *caller, 0)?;
1103 let array = match st.field(&mut *caller, 1)? {
1104 Val::AnyRef(Some(arr)) => arr.unwrap_array(&mut *caller)?,
1105 other => {
1106 return Err(wasmtime::Error::msg(format!(
1107 "{name}: malformed $string payload {other:?}"
1108 )));
1109 }
1110 };
1111 let len = usize::try_from(array.len(&mut *caller)?)
1112 .map_err(crate::runtime::host::fatal_host_error)?;
1113 Ok(Payload { vtable, array, len })
1114 }
1115
1116 fn write(
1117 &self,
1118 caller: &mut Caller<'_, StoreData>,
1119 vtable: Val,
1120 s: &Str,
1121 ) -> wasmtime::Result<Val> {
1122 fuel::charge(&mut *caller, fuel::COPY, s.units().len() as u64)?;
1123 self.write_units(caller, vtable, s)
1124 }
1125
1126 fn write_precharged(
1129 &self,
1130 caller: &mut Caller<'_, StoreData>,
1131 vtable: Val,
1132 s: &AdmittedStr,
1133 ) -> wasmtime::Result<Val> {
1134 self.write_units(caller, vtable, s.as_str())
1135 }
1136
1137 fn write_units(
1138 &self,
1139 caller: &mut Caller<'_, StoreData>,
1140 vtable: Val,
1141 s: &Str,
1142 ) -> wasmtime::Result<Val> {
1143 let pre = ArrayRefPre::new(&mut *caller, self.payload_ty.clone());
1144 let payload = ArrayRef::new_from_i16_slice(&mut *caller, &pre, s.units())?;
1145 let pre = StructRefPre::new(&mut *caller, self.string_ty.clone());
1146 let st = StructRef::new(
1147 &mut *caller,
1148 &pre,
1149 &[vtable, Val::AnyRef(Some(payload.to_anyref())), Val::I64(0)],
1150 )?;
1151 Ok(Val::AnyRef(Some(st.to_anyref())))
1152 }
1153}
1154
1155async fn string_ctor_call(
1161 caller: &mut Caller<'_, StoreData>,
1162 value: &Val,
1163) -> wasmtime::Result<Val> {
1164 let Val::AnyRef(Some(any)) = value else {
1165 let st = write_submilli_string_struct(caller, "null")?;
1166 return Ok(Val::AnyRef(Some(st.to_anyref())));
1167 };
1168 let intr = intrinsic_types(&mut *caller)?;
1169 if let Some(st) = any.as_struct(&mut *caller)?
1170 && StructType::eq(&st.ty(&*caller)?, &intr.bigint)
1171 {
1172 let (sign, limbs) = crate::runtime::prelude::bigint::ops::read_bigint_struct(
1173 caller,
1174 value,
1175 "String(bigint)",
1176 )?;
1177 let value = crate::runtime::prelude::bigint::ops::limbs_to_bigint(sign, &limbs)?;
1178 let text = crate::runtime::prelude::bigint::ops::format_bigint(caller, &value, 10)?;
1179 let st = write_submilli_string_struct(caller, &text)?;
1180 return Ok(Val::AnyRef(Some(st.to_anyref())));
1181 }
1182 crate::runtime::prelude::vtable::dispatch_vtable_slot(caller, value, 0, &[]).await
1183}
1184
1185fn read_number_array(
1186 caller: &mut Caller<'_, StoreData>,
1187 val: &Val,
1188 name: &str,
1189) -> wasmtime::Result<Vec<f64>> {
1190 let storage = crate::runtime::array_storage::ArrayStorage::read(caller, val)?;
1191 let backing = storage.backing;
1192 let len = storage.len;
1193 let mut nums = Vec::new();
1194 nums.try_reserve_exact(len as usize)
1195 .map_err(crate::runtime::host::fatal_host_error)?;
1196 for i in 0..len {
1197 let boxed = match backing.get(&mut *caller, i)? {
1198 Val::AnyRef(Some(b)) => b
1199 .as_struct(&mut *caller)?
1200 .ok_or_else(|| wasmtime::Error::msg(format!("{name}: arg {i} not a struct")))?,
1201 other => {
1202 return Err(wasmtime::Error::msg(format!(
1203 "{name}: arg {i} is {other:?}, not a boxed number"
1204 )));
1205 }
1206 };
1207 match boxed.field(&mut *caller, 1)? {
1208 Val::F64(bits) => nums.push(f64::from_bits(bits)),
1209 other => {
1210 return Err(wasmtime::Error::msg(format!(
1211 "{name}: arg {i} payload {other:?} is not f64"
1212 )));
1213 }
1214 }
1215 }
1216 Ok(nums)
1217}
1218
1219fn number(val: &Val, name: &str) -> wasmtime::Result<f64> {
1220 match val {
1221 Val::F64(bits) => Ok(f64::from_bits(*bits)),
1222 other => Err(wasmtime::Error::msg(format!(
1223 "{name} expects f64, got {other:?}"
1224 ))),
1225 }
1226}
1227
1228fn throw(e: RangeError) -> wasmtime::Error {
1229 crate::runtime::host::range_error(e.message())
1230}
1231
1232#[allow(clippy::too_many_lines)]
1235pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
1236 use crate::runtime::prelude::declaration::doc;
1237 use crate::{
1238 Dispatch, MethodSig, Param, PropertySig, Span, Type, TypeKind, TypeSymbol, ValueKind,
1239 ValueSymbol,
1240 };
1241 use std::collections::BTreeMap;
1242 defs.values.insert(
1243 "string_concat".to_string(),
1244 ValueSymbol {
1245 name: "string_concat".to_string(),
1246 mangled_name: crate::mangle::prelude("string_concat"),
1247 declaration_span: Span::at(crate::FileId::PRELUDE),
1248 kind: ValueKind::Function {
1249 generics: Vec::new(),
1250 params: vec![Param::anon(Type::String), Param::anon(Type::String)],
1251 ret: Type::String,
1252 type_predicate: None,
1253 doc: doc("/** Internal: concatenate two strings. */"),
1254 },
1255 },
1256 );
1257 defs.values.insert(
1258 "string_eq".to_string(),
1259 ValueSymbol {
1260 name: "string_eq".to_string(),
1261 mangled_name: crate::mangle::prelude("string_eq"),
1262 declaration_span: Span::at(crate::FileId::PRELUDE),
1263 kind: ValueKind::Function {
1264 generics: Vec::new(),
1265 params: vec![Param::anon(Type::String), Param::anon(Type::String)],
1266 ret: Type::Boolean,
1267 type_predicate: None,
1268 doc: doc("/** Internal: structural string equality. */"),
1269 },
1270 },
1271 );
1272 defs.values.insert(
1273 "string_length".to_string(),
1274 ValueSymbol {
1275 name: "string_length".to_string(),
1276 mangled_name: crate::mangle::prelude("string_length"),
1277 declaration_span: Span::at(crate::FileId::PRELUDE),
1278 kind: ValueKind::Function {
1279 generics: Vec::new(),
1280 params: vec![Param::anon(Type::String)],
1281 ret: Type::Number,
1282 type_predicate: None,
1283 doc: doc("/** Internal: UTF-16 code-unit length. */"),
1284 },
1285 },
1286 );
1287 defs.values.insert(
1290 "string_cmp".to_string(),
1291 ValueSymbol {
1292 name: "string_cmp".to_string(),
1293 mangled_name: crate::mangle::prelude("string_cmp"),
1294 declaration_span: Span::at(crate::FileId::PRELUDE),
1295 kind: ValueKind::Function {
1296 generics: Vec::new(),
1297 params: vec![Param::anon(Type::String), Param::anon(Type::String)],
1298 ret: Type::Boolean,
1299 type_predicate: None,
1300 doc: doc("/** Internal: lexicographic compare; i32 sign result. */"),
1301 },
1302 },
1303 );
1304
1305 for name in [
1306 "string_true",
1307 "string_false",
1308 "string_comma",
1309 "string_object_object",
1310 "string_object_function",
1311 "string_null",
1312 ] {
1313 defs.values.insert(
1314 name.to_string(),
1315 ValueSymbol {
1316 name: name.to_string(),
1317 mangled_name: crate::mangle::prelude(name),
1318 declaration_span: Span::at(crate::FileId::PRELUDE),
1319 kind: ValueKind::Const {
1320 ty: Type::String,
1321 doc: None,
1322 },
1323 },
1324 );
1325 }
1326 defs.types.insert(
1327 "String".to_string(),
1328 TypeSymbol {
1329 name: "String".to_string(),
1330 mangled_name: crate::mangle::prelude("String"),
1331 declaration_span: Span::at(crate::FileId::PRELUDE),
1332 kind: TypeKind::Interface { index: None,
1333 generics: Vec::new(),
1334 methods: BTreeMap::from([
1335 (
1336 "toString".to_string(),
1337 MethodSig {
1338 optional: false,
1339 generics: Vec::new(),
1340 params: Vec::new(),
1341 ret: Type::String,
1342 predicate: None,
1343 doc: doc("/** Returns this string unchanged (identity). */"),
1344 },
1345 ),
1346 (
1347 "toJson".to_string(),
1348 MethodSig {
1349 optional: false,
1350 generics: Vec::new(),
1351 params: Vec::new(),
1352 ret: Type::String,
1353 predicate: None,
1354 doc: doc(
1355 "/** Returns this string wrapped in `\"…\"` with JSON escapes applied (`\\\"`, `\\\\`, `\\b`, `\\f`, `\\n`, `\\r`, `\\t`, and `\\u00XX` for control codepoints). */",
1356 ),
1357 },
1358 ),
1359 (
1360 "concat".to_string(),
1361 MethodSig {
1362 optional: false,
1363 generics: Vec::new(),
1364 params: vec![Param::new("other", Type::String)],
1365 ret: Type::String,
1366 predicate: None,
1367 doc: doc(
1368 "/**\n * Returns a new string with `other` appended.\n * The receiver is not modified.\n * @param other String to append.\n */",
1369 ),
1370 },
1371 ),
1372 (
1373 "equals".to_string(),
1374 MethodSig {
1375 optional: false,
1376 generics: Vec::new(),
1377 params: vec![Param::new("other", Type::String)],
1378 ret: Type::Boolean,
1379 predicate: None,
1380 doc: doc(
1381 "/**\n * Returns `true` when both strings have identical code units.\n * @param other String to compare against.\n */",
1382 ),
1383 },
1384 ),
1385 (
1386 "localeCompare".to_string(),
1387 MethodSig {
1388 optional: false,
1389 generics: Vec::new(),
1390 params: vec![Param::new("other", Type::String)],
1391 ret: Type::Number,
1392 predicate: None,
1393 doc: doc(
1394 "/**\n * Compares this string with `other` in UTF-16 code-unit order.\n * @param other The string to compare against.\n * @returns A negative number if this sorts before `other`, `0` if equal, a positive number if after. No locale awareness — pure code-unit order.\n */",
1395 ),
1396 },
1397 ),
1398 (
1399 "iterator".to_string(),
1400 MethodSig {
1401 optional: false,
1402 generics: Vec::new(),
1403 params: Vec::new(),
1404 ret: Type::prelude_interface("Iterator".to_string(), vec![Type::String]),
1405 predicate: None,
1406 doc: doc(
1407 "/** Returns an `Iterator<string>` of CODE POINTS — surrogate pairs arrive as one two-unit string. Drives `for-of` over a string. */",
1408 ),
1409 },
1410 ),
1411 (
1412 "isWellFormed".to_string(),
1413 MethodSig {
1414 optional: false,
1415 generics: Vec::new(),
1416 params: Vec::new(),
1417 ret: Type::Boolean,
1418 predicate: None,
1419 doc: doc(
1420 "/** Returns `true` when the string contains no lone surrogates (it round-trips losslessly through UTF-8). */",
1421 ),
1422 },
1423 ),
1424 (
1425 "toWellFormed".to_string(),
1426 MethodSig {
1427 optional: false,
1428 generics: Vec::new(),
1429 params: Vec::new(),
1430 ret: Type::String,
1431 predicate: None,
1432 doc: doc(
1433 "/** Returns a copy with every lone surrogate replaced by U+FFFD (`\u{fffd}`); well-formed strings come back unchanged. */",
1434 ),
1435 },
1436 ),
1437 (
1438 "charAt".to_string(),
1439 MethodSig {
1440 optional: false,
1441 generics: Vec::new(),
1442 params: vec![Param::new("index", Type::Number)],
1443 ret: Type::String,
1444 predicate: None,
1445 doc: doc(
1446 "/**\n * Returns the UTF-16 code unit at `index` as a single-character string.\n * Returns `\"\"` if `index` is negative or beyond the string length.\n * @param index Zero-based index of the code unit.\n */",
1447 ),
1448 },
1449 ),
1450 (
1451 "at".to_string(),
1452 MethodSig {
1453 optional: false,
1454 generics: Vec::new(),
1455 params: vec![Param::new("index", Type::Number)],
1456 ret: Type::Union(vec![Type::String, Type::Undefined]),
1457 predicate: None,
1458 doc: doc(
1459 "/**\n * Returns the code unit at `index` as a single-character string. Negative `index` counts from the end.\n * Returns `undefined` on out-of-range. Use `charAt` for the `\"\"`-on-miss form.\n * @param index Zero-based index; negatives count from the end.\n */",
1460 ),
1461 },
1462 ),
1463 (
1464 "charCodeAt".to_string(),
1465 MethodSig {
1466 optional: false,
1467 generics: Vec::new(),
1468 params: vec![Param::new("index", Type::Number)],
1469 ret: Type::Number,
1470 predicate: None,
1471 doc: doc(
1472 "/**\n * Returns the UTF-16 code unit at `index` as an integer (0..65535), or `NaN` if out of range.\n * @param index Zero-based index of the code unit.\n */",
1473 ),
1474 },
1475 ),
1476 (
1477 "codePointAt".to_string(),
1478 MethodSig {
1479 optional: false,
1480 generics: Vec::new(),
1481 params: vec![Param::new("index", Type::Number)],
1482 ret: Type::Union(vec![Type::Number, Type::Undefined]),
1483 predicate: None,
1484 doc: doc(
1485 "/**\n * Returns the Unicode code point starting at `index`, decoding surrogate pairs into values up to 0x10FFFF.\n * Returns `undefined` if `index` is out of range.\n * @param index Zero-based index of the first code unit of the code point.\n */",
1486 ),
1487 },
1488 ),
1489 (
1490 "slice".to_string(),
1491 MethodSig {
1492 optional: false,
1493 generics: Vec::new(),
1494 params: vec![
1495 Param::with_default(
1496 "start",
1497 Type::Number,
1498 crate::DefaultValue::Number(0.0),
1499 ),
1500 Param::with_default(
1502 "end",
1503 Type::Number,
1504 crate::DefaultValue::Number(f64::INFINITY),
1505 ),
1506 ],
1507 ret: Type::String,
1508 predicate: None,
1509 doc: doc(
1510 "/**\n * Returns a new string containing the code units from `start` (inclusive) to `end` (exclusive).\n * Negative values count from the end. The receiver is not modified.\n * @param start First index to copy. Defaults to `0`.\n * @param end Index one past the last to copy. Defaults to the string length.\n */",
1511 ),
1512 },
1513 ),
1514 (
1515 "substring".to_string(),
1516 MethodSig {
1517 optional: false,
1518 generics: Vec::new(),
1519 params: vec![
1520 Param::with_default(
1521 "start",
1522 Type::Number,
1523 crate::DefaultValue::Number(0.0),
1524 ),
1525 Param::with_default(
1526 "end",
1527 Type::Number,
1528 crate::DefaultValue::Number(f64::INFINITY),
1529 ),
1530 ],
1531 ret: Type::String,
1532 predicate: None,
1533 doc: doc(
1534 "/**\n * Like `slice`, but negative values clamp to `0`, and `start` and `end` are swapped when `start > end`.\n * @param start First index to copy. Defaults to `0`.\n * @param end Index one past the last to copy. Defaults to the string length.\n */",
1535 ),
1536 },
1537 ),
1538 (
1539 "indexOf".to_string(),
1540 MethodSig {
1541 optional: false,
1542 generics: Vec::new(),
1543 params: vec![
1544 Param::new("search", Type::String),
1545 Param::with_default(
1546 "fromIndex",
1547 Type::Number,
1548 crate::DefaultValue::Number(0.0),
1549 ),
1550 ],
1551 ret: Type::Number,
1552 predicate: None,
1553 doc: doc(
1554 "/**\n * Returns the index of the first occurrence of `search` at or after `fromIndex`, or `-1` if none.\n * An empty `search` returns `min(fromIndex, length)`.\n * @param search Substring to search for.\n * @param fromIndex Index to begin searching from.\n */",
1555 ),
1556 },
1557 ),
1558 (
1559 "lastIndexOf".to_string(),
1560 MethodSig {
1561 optional: false,
1562 generics: Vec::new(),
1563 params: vec![
1564 Param::new("search", Type::String),
1565 Param::with_default(
1566 "fromIndex",
1567 Type::Number,
1568 crate::DefaultValue::Number(f64::INFINITY),
1569 ),
1570 ],
1571 ret: Type::Number,
1572 predicate: None,
1573 doc: doc(
1574 "/**\n * Returns the index of the last occurrence of `search` at or before `fromIndex`, or `-1` if none.\n * @param search Substring to search for.\n * @param fromIndex Highest valid start position to consider. Defaults to the string length.\n */",
1575 ),
1576 },
1577 ),
1578 (
1579 "includes".to_string(),
1580 MethodSig {
1581 optional: false,
1582 generics: Vec::new(),
1583 params: vec![
1584 Param::new("search", Type::String),
1585 Param::with_default(
1586 "fromIndex",
1587 Type::Number,
1588 crate::DefaultValue::Number(0.0),
1589 ),
1590 ],
1591 ret: Type::Boolean,
1592 predicate: None,
1593 doc: doc(
1594 "/**\n * Returns `true` if `search` occurs at or after `fromIndex`.\n * @param search Substring to search for.\n * @param fromIndex Index to begin searching from.\n */",
1595 ),
1596 },
1597 ),
1598 (
1599 "startsWith".to_string(),
1600 MethodSig {
1601 optional: false,
1602 generics: Vec::new(),
1603 params: vec![
1604 Param::new("search", Type::String),
1605 Param::with_default(
1606 "position",
1607 Type::Number,
1608 crate::DefaultValue::Number(0.0),
1609 ),
1610 ],
1611 ret: Type::Boolean,
1612 predicate: None,
1613 doc: doc(
1614 "/**\n * Returns `true` if the substring starting at `position` begins with `search`.\n * @param search Prefix to test for.\n * @param position Index at which to start the comparison.\n */",
1615 ),
1616 },
1617 ),
1618 (
1619 "endsWith".to_string(),
1620 MethodSig {
1621 optional: false,
1622 generics: Vec::new(),
1623 params: vec![
1624 Param::new("search", Type::String),
1625 Param::with_default(
1626 "endPosition",
1627 Type::Number,
1628 crate::DefaultValue::Number(f64::INFINITY),
1629 ),
1630 ],
1631 ret: Type::Boolean,
1632 predicate: None,
1633 doc: doc(
1634 "/**\n * Returns `true` if the substring ending at `endPosition` ends with `search`.\n * @param search Suffix to test for.\n * @param endPosition Index marking the end (exclusive) of the substring considered.\n */",
1635 ),
1636 },
1637 ),
1638 (
1639 "repeat".to_string(),
1640 MethodSig {
1641 optional: false,
1642 generics: Vec::new(),
1643 params: vec![Param::new("count", Type::Number)],
1644 ret: Type::String,
1645 predicate: None,
1646 doc: doc(
1647 "/**\n * Returns a new string containing `count` copies of this string concatenated.\n * Zero `count` (or an empty receiver) returns `\"\"`; a negative `count` throws a `RangeError`.\n * @param count Number of repetitions.\n */",
1648 ),
1649 },
1650 ),
1651 (
1652 "padStart".to_string(),
1653 MethodSig {
1654 optional: false,
1655 generics: Vec::new(),
1656 params: vec![
1657 Param::new("targetLength", Type::Number),
1658 Param::with_default(
1659 "padString",
1660 Type::String,
1661 crate::DefaultValue::String(" ".to_string()),
1662 ),
1663 ],
1664 ret: Type::String,
1665 predicate: None,
1666 doc: doc(
1667 "/**\n * Pads this string with `padString` on the left until the result reaches `targetLength` code units. Returns this string unchanged when no padding is needed or when `padString` is empty.\n * @param targetLength Length of the padded result.\n * @param padString Filler repeated to reach the target.\n */",
1668 ),
1669 },
1670 ),
1671 (
1672 "padEnd".to_string(),
1673 MethodSig {
1674 optional: false,
1675 generics: Vec::new(),
1676 params: vec![
1677 Param::new("targetLength", Type::Number),
1678 Param::with_default(
1679 "padString",
1680 Type::String,
1681 crate::DefaultValue::String(" ".to_string()),
1682 ),
1683 ],
1684 ret: Type::String,
1685 predicate: None,
1686 doc: doc(
1687 "/**\n * Pads this string with `padString` on the right until the result reaches `targetLength` code units. Returns this string unchanged when no padding is needed or when `padString` is empty.\n * @param targetLength Length of the padded result.\n * @param padString Filler repeated to reach the target.\n */",
1688 ),
1689 },
1690 ),
1691 (
1692 "toUpperCase".to_string(),
1693 MethodSig {
1694 optional: false,
1695 generics: Vec::new(),
1696 params: Vec::new(),
1697 ret: Type::String,
1698 predicate: None,
1699 doc: doc(
1700 "/**\n * Returns a new string with every character converted to its Unicode uppercase form.\n */",
1701 ),
1702 },
1703 ),
1704 (
1705 "toLowerCase".to_string(),
1706 MethodSig {
1707 optional: false,
1708 generics: Vec::new(),
1709 params: Vec::new(),
1710 ret: Type::String,
1711 predicate: None,
1712 doc: doc(
1713 "/**\n * Returns a new string with every character converted to its Unicode lowercase form.\n */",
1714 ),
1715 },
1716 ),
1717 (
1718 "trim".to_string(),
1719 MethodSig {
1720 optional: false,
1721 generics: Vec::new(),
1722 params: Vec::new(),
1723 ret: Type::String,
1724 predicate: None,
1725 doc: doc(
1726 "/**\n * Returns a new string with Unicode whitespace removed from both ends.\n */",
1727 ),
1728 },
1729 ),
1730 (
1731 "trimStart".to_string(),
1732 MethodSig {
1733 optional: false,
1734 generics: Vec::new(),
1735 params: Vec::new(),
1736 ret: Type::String,
1737 predicate: None,
1738 doc: doc(
1739 "/**\n * Returns a new string with Unicode whitespace removed from the start.\n */",
1740 ),
1741 },
1742 ),
1743 (
1744 "trimEnd".to_string(),
1745 MethodSig {
1746 optional: false,
1747 generics: Vec::new(),
1748 params: Vec::new(),
1749 ret: Type::String,
1750 predicate: None,
1751 doc: doc(
1752 "/**\n * Returns a new string with Unicode whitespace removed from the end.\n */",
1753 ),
1754 },
1755 ),
1756 (
1757 "normalize".to_string(),
1758 MethodSig {
1759 optional: false,
1760 generics: Vec::new(),
1761 params: vec![Param::with_default(
1762 "form",
1763 Type::String,
1764 crate::DefaultValue::String("NFC".to_string()),
1765 )],
1766 ret: Type::String,
1767 predicate: None,
1768 doc: doc(
1769 "/**\n * Returns a new string in the specified Unicode normalization form.\n * @param form One of `\"NFC\"`, `\"NFD\"`, `\"NFKC\"`, `\"NFKD\"`; any other value throws `Error`. Defaults to `\"NFC\"`.\n */",
1770 ),
1771 },
1772 ),
1773 (
1778 "match".to_string(),
1779 MethodSig {
1780 optional: false,
1781 generics: Vec::new(),
1782 params: vec![Param::new(
1783 "re",
1784 Type::prelude_interface("RegExp".to_string(), Vec::new()),
1785 )],
1786 ret: Type::Union(vec![
1787 Type::prelude_interface("RegExpMatch".to_string(), Vec::new()),
1788 Type::Null,
1789 ]),
1790 predicate: None,
1791 doc: doc(
1792 "/**\n * Find the next match of `re` in this string. Equivalent to `re.exec(this)` (v1; the `g`-flag array shape is a follow-up).\n * @param re The pattern to search for.\n */",
1793 ),
1794 },
1795 ),
1796 (
1797 "search".to_string(),
1798 MethodSig {
1799 optional: false,
1800 generics: Vec::new(),
1801 params: vec![Param::new(
1802 "re",
1803 Type::prelude_interface("RegExp".to_string(), Vec::new()),
1804 )],
1805 ret: Type::Number,
1806 predicate: None,
1807 doc: doc(
1808 "/**\n * Index of the first match of `re` in this string, or `-1` if no match.\n * @param re The pattern to search for.\n */",
1809 ),
1810 },
1811 ),
1812 (
1813 "replace".to_string(),
1814 MethodSig {
1815 optional: false,
1816 generics: Vec::new(),
1817 params: vec![
1818 Param::new(
1819 "search",
1820 Type::Union(vec![
1821 Type::String,
1822 Type::prelude_interface("RegExp".to_string(), Vec::new()),
1823 ]),
1824 ),
1825 Param::new("replacement", Type::String),
1826 ],
1827 ret: Type::String,
1828 predicate: None,
1829 doc: doc(
1830 "/**\n * Replace the first match of `search` with `replacement`. For a `RegExp` `search`, the replacement may reference numbered captures (`$1`, `$2`) and named captures (`$<name>`); for both `string` and `RegExp` arms, the special tokens `$$` (literal `$`), `$&` (matched substring), `` $` `` (substring before the match), and `$'` (substring after the match) are honoured.\n * @param search Substring or pattern to match.\n * @param replacement The replacement string.\n */",
1831 ),
1832 },
1833 ),
1834 (
1835 "replaceAll".to_string(),
1836 MethodSig {
1837 optional: false,
1838 generics: Vec::new(),
1839 params: vec![
1840 Param::new(
1841 "search",
1842 Type::Union(vec![
1843 Type::String,
1844 Type::prelude_interface("RegExp".to_string(), Vec::new()),
1845 ]),
1846 ),
1847 Param::new("replacement", Type::String),
1848 ],
1849 ret: Type::String,
1850 predicate: None,
1851 doc: doc(
1852 "/**\n * Replace every non-overlapping match of `search` with `replacement`. Replacement-token semantics match `replace`.\n * @param search Substring or pattern to match.\n * @param replacement The replacement string.\n */",
1853 ),
1854 },
1855 ),
1856 (
1857 "split".to_string(),
1858 MethodSig {
1859 optional: false,
1860 generics: Vec::new(),
1861 params: vec![
1862 Param::new(
1863 "separator",
1864 Type::Union(vec![
1865 Type::String,
1866 Type::prelude_interface("RegExp".to_string(), Vec::new()),
1867 ]),
1868 ),
1869 Param::with_default(
1870 "limit",
1871 Type::Number,
1872 crate::DefaultValue::Number(f64::INFINITY),
1873 ),
1874 ],
1875 ret: Type::Array(Box::new(Type::String)),
1876 predicate: None,
1877 doc: doc(
1878 "/**\n * Split this string into pieces by matches of `separator`. For an empty string `separator`, returns each code unit as a separate part. For a `RegExp` `separator`, each match's captured groups are inserted after the part before it, as in JavaScript (`undefined` for a group that didn't participate).\n * @param separator Substring or pattern to split on.\n * @param limit Optional cap on the number of returned parts (default: no limit).\n */",
1879 ),
1880 },
1881 ),
1882 (
1883 "matchAll".to_string(),
1884 MethodSig {
1885 optional: false,
1886 generics: Vec::new(),
1887 params: vec![Param::new(
1888 "re",
1889 Type::prelude_interface("RegExp".to_string(), Vec::new()),
1890 )],
1891 ret: Type::Array(Box::new(Type::prelude_interface("RegExpMatch".to_string(), Vec::new()))),
1892 predicate: None,
1893 doc: doc(
1894 "/**\n * Find every non-overlapping match of `re` in this string. Zero-length matches advance by one to avoid infinite loops, per ECMA-262.\n * @param re The pattern to match.\n */",
1895 ),
1896 },
1897 ),
1898 ]),
1899 properties: BTreeMap::from([(
1900 "length".to_string(),
1901 PropertySig {
1902 ty: Type::Number,
1903 readonly: true,
1904 intrinsic: true,
1905 optional: false,
1906 doc: doc("/** The number of UTF-16 code units in the string. */"),
1907 },
1908 )]),
1909 dispatch: Dispatch::Direct,
1910 doc: doc("/** The UTF-16 string type. */"),
1911 },
1912 },
1913 );
1914 defs.types.insert(
1915 "StringConstructor".to_string(),
1916 TypeSymbol {
1917 name: "StringConstructor".to_string(),
1918 mangled_name: crate::mangle::prelude("StringConstructor"),
1919 declaration_span: Span::at(crate::FileId::PRELUDE),
1920 kind: TypeKind::Interface { index: None,
1921 generics: Vec::new(),
1922 methods: BTreeMap::from([
1923 (
1924 "@call".to_string(),
1925 MethodSig {
1926 optional: false,
1927 generics: Vec::new(),
1928 params: vec![Param::new("value", Type::Unknown)],
1929 ret: Type::String,
1930 predicate: None,
1931 doc: doc(
1932 "/**\n * Convert any value to its string representation: `\"null\"` and `\"undefined\"` for those values, otherwise the value's `toString()` method.\n */",
1933 ),
1934 },
1935 ),
1936 (
1937 "fromCharCode".to_string(),
1938 MethodSig {
1939 optional: false,
1940 generics: Vec::new(),
1941 params: vec![Param::rest("codes", Type::Array(Box::new(Type::Number)))],
1942 ret: Type::String,
1943 predicate: None,
1944 doc: doc(
1945 "/**\n * Builds a string from UTF-16 code units — one unit per argument.\n * Each value is truncated and reduced modulo 65536, as JavaScript does, so `-1` is U+FFFF.\n * @param codes Zero or more UTF-16 code units (0–65535).\n */",
1946 ),
1947 },
1948 ),
1949 (
1950 "fromCodePoint".to_string(),
1951 MethodSig {
1952 optional: false,
1953 generics: Vec::new(),
1954 params: vec![Param::rest(
1955 "codePoints",
1956 Type::Array(Box::new(Type::Number)),
1957 )],
1958 ret: Type::String,
1959 predicate: None,
1960 doc: doc(
1961 "/**\n * Builds a string from Unicode code points; astral code points (> U+FFFF) encode as surrogate pairs.\n * Throws a `RangeError` on a negative, non-integer, or > U+10FFFF input.\n * @param codePoints Zero or more Unicode code points (0–0x10FFFF).\n */",
1962 ),
1963 },
1964 ),
1965 ]),
1966 properties: BTreeMap::new(),
1967 dispatch: Dispatch::Static,
1968 doc: doc(
1969 "/** Constructor object for `string`. Accessed via the global `String` binding. */",
1970 ),
1971 },
1972 },
1973 );
1974 defs.values.insert(
1975 "String".to_string(),
1976 ValueSymbol {
1977 name: "String".to_string(),
1978 mangled_name: crate::mangle::prelude("String"),
1979 declaration_span: Span::at(crate::FileId::PRELUDE),
1980 kind: ValueKind::Const {
1981 ty: Type::prelude_interface("StringConstructor".to_string(), Vec::new()),
1982 doc: doc(
1983 "/** The `String` constructor — call `String(x)` to convert any value to a string via its `toString()` method. */",
1984 ),
1985 },
1986 },
1987 );
1988}