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