-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstring.ts
More file actions
146 lines (140 loc) · 6.27 KB
/
string.ts
File metadata and controls
146 lines (140 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* @fileoverview Safe references to `String` static methods and prototype
* methods.
*
* `StringPrototypeCharCodeAt` prefers the smol Fast API binding for
* ASCII inputs (single byte load) and translates the `-1` Fast API
* sentinel back to `NaN` to preserve spec parity. Two-byte strings
* fall back to the uncurried `String.prototype.charCodeAt`.
*
* ## Fast API surface — and why it's small
*
* Mirrors the design rationale from socket-btm's
* `primordial_binding.cc` (lines 41-72). The smol Fast API exposes
* exactly one string op (`stringCharCodeAt`) because that's the one
* shape where the C++ trampoline genuinely beats V8's existing
* hot path: a single ASCII byte load, no encoding dispatch, no
* HandleScope, returns a primitive.
*
* String **searches** (`startsWith` / `endsWith` / `includes` /
* `indexOf` / `lastIndexOf`) are intentionally NOT exposed. V8's
* existing hot path dispatches on encoding and runs native SIMD
* memcmp — a Fast API binding would add overhead without winning.
* Same for `Map.has` / `Set.has` / `Array.includes`.
*
* Fast API also has a hard constraint: a fast-path function cannot
* return a new V8 object — only primitives, Local<Value/Object/Array>,
* or FastOneByteString. That rules out anything that produces a new
* string (`slice`, `substring`, `toUpperCase`, `concat`, `repeat`,
* `padStart`/`padEnd`, formatted-number) from ever being a Fast API
* win on the return path.
*
* Net: the current surface is approximately the ceiling. Adding more
* Fast API string ops without a flamegraph showing the cost is a
* regression risk, not a perf win. See
* `socket-btm/packages/node-smol-builder/additions/source-patched/`
* `src/socketsecurity/primordial/primordial_binding.cc:41-72` for
* the canonical design statement.
*/
import { getSmolPrimordial } from '../smol/primordial'
import { uncurryThis } from './uncurry'
const _smolPrimordial = getSmolPrimordial()
export const StringCtor: StringConstructor = String
// ─── String (static) ───────────────────────────────────────────────────
export const StringFromCharCode = String.fromCharCode
export const StringFromCodePoint = String.fromCodePoint
export const StringRaw = String.raw
// ─── String (prototype) ────────────────────────────────────────────────
export const StringPrototypeAt = uncurryThis(String.prototype.at)
export const StringPrototypeCharAt = uncurryThis(String.prototype.charAt)
// `stringCharCodeAt` is a Fast API binding with a FastOneByteString
// receiver — V8 only invokes the C++ fast path for ASCII strings,
// where it does a single byte load. Two-byte strings fall back.
// The fast path returns -1 for OOB indices (Fast API can't return
// NaN from an int32 signature); the wrapper here translates -1 back
// to NaN to match `String.prototype.charCodeAt` spec.
const _smolCharCodeAt = _smolPrimordial?.stringCharCodeAt
// _smolCharCodeAt fast-path fires only on socket-btm's smol Node binary.
/* c8 ignore start */
export const StringPrototypeCharCodeAt: (s: string, i: number) => number =
_smolCharCodeAt
? (s, i) => {
const code = _smolCharCodeAt(s, i)
return code === -1 ? NaN : code
}
: uncurryThis(String.prototype.charCodeAt)
/* c8 ignore stop */
export const StringPrototypeCodePointAt = uncurryThis(
String.prototype.codePointAt,
)
export const StringPrototypeConcat = uncurryThis(String.prototype.concat) as (
self: string,
...strs: string[]
) => string
// Why uncurried, not Fast-API'd: see the fileoverview JSDoc above.
// V8's existing hot path beats trampoline overhead on these.
export const StringPrototypeEndsWith = uncurryThis(String.prototype.endsWith)
export const StringPrototypeIncludes = uncurryThis(String.prototype.includes)
export const StringPrototypeIndexOf = uncurryThis(String.prototype.indexOf)
export const StringPrototypeLastIndexOf = uncurryThis(
String.prototype.lastIndexOf,
)
export const StringPrototypeLocaleCompare = uncurryThis(
String.prototype.localeCompare,
)
export const StringPrototypeMatch = uncurryThis(
String.prototype.match as (
this: string,
matcher: string | RegExp,
) => RegExpMatchArray | null,
)
export const StringPrototypeMatchAll = uncurryThis(
String.prototype.matchAll as (
this: string,
matcher: RegExp | string,
) => IterableIterator<RegExpMatchArray>,
)
export const StringPrototypeNormalize = uncurryThis(String.prototype.normalize)
export const StringPrototypePadEnd = uncurryThis(String.prototype.padEnd)
export const StringPrototypePadStart = uncurryThis(String.prototype.padStart)
export const StringPrototypeRepeat = uncurryThis(String.prototype.repeat)
export const StringPrototypeReplace = uncurryThis(
String.prototype.replace as (
this: string,
searchValue: string | RegExp,
replaceValue: string | ((substring: string, ...args: any[]) => string),
) => string,
)
export const StringPrototypeReplaceAll = uncurryThis(
String.prototype.replaceAll as (
this: string,
searchValue: string | RegExp,
replaceValue: string | ((substring: string, ...args: any[]) => string),
) => string,
)
export const StringPrototypeSearch = uncurryThis(String.prototype.search)
export const StringPrototypeSlice = uncurryThis(String.prototype.slice)
export const StringPrototypeSplit = uncurryThis(String.prototype.split) as (
self: string,
separator: string | RegExp,
limit?: number,
) => string[]
export const StringPrototypeStartsWith = uncurryThis(
String.prototype.startsWith,
)
export const StringPrototypeSubstring = uncurryThis(String.prototype.substring)
export const StringPrototypeToLocaleLowerCase = uncurryThis(
String.prototype.toLocaleLowerCase,
)
export const StringPrototypeToLocaleUpperCase = uncurryThis(
String.prototype.toLocaleUpperCase,
)
export const StringPrototypeToLowerCase = uncurryThis(
String.prototype.toLowerCase,
)
export const StringPrototypeToUpperCase = uncurryThis(
String.prototype.toUpperCase,
)
export const StringPrototypeTrim = uncurryThis(String.prototype.trim)
export const StringPrototypeTrimEnd = uncurryThis(String.prototype.trimEnd)
export const StringPrototypeTrimStart = uncurryThis(String.prototype.trimStart)