The member functions a string answers, reached from string in ../lambda/string.rs, and a number's formatters, from number in ../lambda/number.rs with their arithmetic in ../number/format.rs. Each is ECMAScript 2025's algorithm on every input a module can build, pinned by the shared corpus in fjs/nanvm, and the completeness table in fjs/nanvm/methods keeps this set equal to what the compiler admits. The same arrangement for Array is ../array/README.md.
| file | built-ins |
|---|---|
reads.rs | at, charAt, charCodeAt, isWellFormed, toWellFormed |
search.rs | includes, indexOf, lastIndexOf, endsWith |
building.rs | slice, substring, concat, repeat, padStart, padEnd, trim, trimStart, trimEnd |
patterns.rs | replace, replaceAll, split |
casing.rs | toLowerCase, toUpperCase |
startsWith has no entry here: its body is written once, in fjs/js/runtime, which npm run gen compiles into ../gen.runtime.rs, and the call step reaches it through ../runtime.rs after the table above has no entry. The names still here have no body yet: the primitives a body is written over, and the names whose bodies wait on a primitive the language has not admitted. A name moves out of this table when its body lands, or stays beside the body where a measurement justifies the second implementation.
The language refuses these names on a string (fjs/js/prototype/README.md), and this crate grows no entry for any of them:
| names | why |
|---|---|
match, matchAll, search | Coerce the argument to a RegExp, a type the language lacks. |
normalize | Depends on the engine's Unicode version, which nothing pins for it (case conversion pins one for toLowerCase and toUpperCase). |
toLocaleLowerCase, toLocaleUpperCase, localeCompare | Read the host locale. |
substr, trimLeft, trimRight | Annex B aliases. |
anchor, big, blink, bold, fixed, fontcolor, fontsize, italics, link, small, strike, sub, sup | Annex B HTML wrappers. |
valueOf, constructor | The coercion protocol, and a data property. |
Several branches of the algorithms have no input here, and the code does not model them:
- Regular expressions. A module cannot spell a
RegExpor a symbol, so every patternreplace,replaceAllandsplitsee is a value converted byToString, andincludes,startsWithandendsWithnever meet theIsRegExpthrow. - Capture groups. A string pattern has none, so
GetSubstitutionsubstitutes$$,$&,$`and$'and leaves$1and$<name>as written, as JavaScript does.
Every position, length and index counts UTF-16 code units, as JavaScript does: a surrogate pair is two positions, "😀".split("") is two lone surrogates. Only isWellFormed and toWellFormed look at pairs here; codePointAt does in its body in the runtime, over the intrinsic charCodeAt. A lone surrogate has no Rust &str spelling, so the corpus and fjs compile write such a string as its code units, string_any_utf16(&[…]).
toLowerCase and toUpperCase are the Unicode Default Case Conversion, full mappings included — "ß".toUpperCase() is "SS", a final sigma reads its context — through str::to_lowercase and str::to_uppercase, at the Unicode version the language pins: casing.rs names it and asserts char::UNICODE_VERSION against it at compile time, so a build whose standard library carries another version does not build (fjs/js/prototype/README.md). The receiver is converted a well-formed run at a time, a lone surrogate copied unchanged between runs: it has no case, and the lossy conversion to a Rust string would make it U+FFFD. A lone surrogate is neither cased nor case-ignorable, so a run ending at one leaves the final-sigma context as JavaScript reads it. A full mapping can lengthen the result, which is counted and refused past the limit like any other.
lastIndexOfToNumbers its position first and readsNaN—undefinedincluded — as the end, unlikeArray's, which reads a passedundefinedas0.padStartandpadEndconvert the fill only when padding is needed.splitreads its limit, then converts its separator, and only then does a limit of0answer[], as ECMAScript orders it: a separator whose conversion throws throws even with a limit of0.replaceandreplaceAlldecide whether the replacement is a function, and convert a template, before any match is looked for.toFixedchecks its digit range before the number, soInfinity.toFixed(101)throws.toExponentialandtoPrecisionkeep anundefinedargument apart, absent or passed, for its own meaning; any other they convert first, soNaNis0,"2.9"is2, and a bigint throws even onInfinity; then they look at the number before the range, soInfinity.toExponential(101)is"Infinity".
toFixed, toExponential and toPrecision round the double's exact binary value, the larger digit string on a tie: (2.5).toFixed(0) is "3", and (1.005).toFixed(2) is "1.00" because the double nearest 1.005 is below it. All three round the magnitude and put the sign back, so (-2.5).toFixed(0) is "-3", (-2.5).toExponential(0) is "-3e+0" and (-2.5).toPrecision(1) is "-3". Only -0 itself stays unsigned: "0.00", "0.00e+0", "0.00". Rust's format!("{:.0}", 2.5) rounds half to even and answers "2", so the arithmetic is BigInt<A>'s.
- The longest string.
String<A>holds at most2³² − 1code units, andcreate.rsrefuses a longer result with theRangeErrorengines throw. V8's limit is far lower, so a length between the two answers here and throws there. - Radix digits of a fraction. ECMAScript leaves
(0.5).toString(2)to the engine, so NaNVM refuses a fraction with a radix other than ten. An integer, and every bigint, converts exactly. - Radix digits of a large integer. Exact is not what V8 answers: above
2⁵³, in a radix that is not a power of two, itsNumber.prototype.toStringcan lose digits —(1e21).toString(36)is"5v1j4f4ds7c000"there, and"5v1j4f4ds79m9s"here and inBigInt(1e21).toString(36). The shared corpus is checked against Node, so such a case belongs in the corpus only with arustnote, or as a bigint.
vm/number/format.rs holds the formatters' arithmetic, and ../bigint/radix.rs the radix digits.