mustardscript 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/Cargo.lock +1579 -0
  2. package/Cargo.toml +40 -0
  3. package/LICENSE +201 -0
  4. package/README.md +828 -0
  5. package/SECURITY.md +34 -0
  6. package/crates/mustard/Cargo.toml +31 -0
  7. package/crates/mustard/src/cancellation.rs +28 -0
  8. package/crates/mustard/src/diagnostic.rs +145 -0
  9. package/crates/mustard/src/ir.rs +435 -0
  10. package/crates/mustard/src/lib.rs +21 -0
  11. package/crates/mustard/src/limits.rs +22 -0
  12. package/crates/mustard/src/parser/expressions.rs +723 -0
  13. package/crates/mustard/src/parser/mod.rs +115 -0
  14. package/crates/mustard/src/parser/operators.rs +105 -0
  15. package/crates/mustard/src/parser/patterns.rs +123 -0
  16. package/crates/mustard/src/parser/scope.rs +107 -0
  17. package/crates/mustard/src/parser/statements.rs +298 -0
  18. package/crates/mustard/src/parser/tests/acceptance.rs +339 -0
  19. package/crates/mustard/src/parser/tests/mod.rs +2 -0
  20. package/crates/mustard/src/parser/tests/rejections.rs +107 -0
  21. package/crates/mustard/src/runtime/accounting.rs +613 -0
  22. package/crates/mustard/src/runtime/api.rs +192 -0
  23. package/crates/mustard/src/runtime/async_runtime/mod.rs +5 -0
  24. package/crates/mustard/src/runtime/async_runtime/promises.rs +246 -0
  25. package/crates/mustard/src/runtime/async_runtime/reactions.rs +400 -0
  26. package/crates/mustard/src/runtime/async_runtime/scheduler.rs +224 -0
  27. package/crates/mustard/src/runtime/builtins/arrays.rs +1205 -0
  28. package/crates/mustard/src/runtime/builtins/collections.rs +573 -0
  29. package/crates/mustard/src/runtime/builtins/install.rs +501 -0
  30. package/crates/mustard/src/runtime/builtins/intl.rs +553 -0
  31. package/crates/mustard/src/runtime/builtins/mod.rs +25 -0
  32. package/crates/mustard/src/runtime/builtins/objects.rs +405 -0
  33. package/crates/mustard/src/runtime/builtins/primitives.rs +859 -0
  34. package/crates/mustard/src/runtime/builtins/promises.rs +335 -0
  35. package/crates/mustard/src/runtime/builtins/regexp.rs +356 -0
  36. package/crates/mustard/src/runtime/builtins/strings.rs +803 -0
  37. package/crates/mustard/src/runtime/builtins/support.rs +561 -0
  38. package/crates/mustard/src/runtime/bytecode.rs +123 -0
  39. package/crates/mustard/src/runtime/compiler/assignments.rs +690 -0
  40. package/crates/mustard/src/runtime/compiler/bindings.rs +92 -0
  41. package/crates/mustard/src/runtime/compiler/context.rs +46 -0
  42. package/crates/mustard/src/runtime/compiler/control.rs +342 -0
  43. package/crates/mustard/src/runtime/compiler/expressions.rs +372 -0
  44. package/crates/mustard/src/runtime/compiler/mod.rs +173 -0
  45. package/crates/mustard/src/runtime/compiler/statements.rs +459 -0
  46. package/crates/mustard/src/runtime/conversions/boundary.rs +293 -0
  47. package/crates/mustard/src/runtime/conversions/coercions.rs +217 -0
  48. package/crates/mustard/src/runtime/conversions/errors.rs +118 -0
  49. package/crates/mustard/src/runtime/conversions/mod.rs +14 -0
  50. package/crates/mustard/src/runtime/conversions/operators.rs +334 -0
  51. package/crates/mustard/src/runtime/env.rs +355 -0
  52. package/crates/mustard/src/runtime/exceptions.rs +377 -0
  53. package/crates/mustard/src/runtime/gc.rs +595 -0
  54. package/crates/mustard/src/runtime/mod.rs +318 -0
  55. package/crates/mustard/src/runtime/properties.rs +1762 -0
  56. package/crates/mustard/src/runtime/serialization.rs +127 -0
  57. package/crates/mustard/src/runtime/shared.rs +108 -0
  58. package/crates/mustard/src/runtime/snapshot_validation_tests.rs +93 -0
  59. package/crates/mustard/src/runtime/state.rs +652 -0
  60. package/crates/mustard/src/runtime/tests/async_host.rs +104 -0
  61. package/crates/mustard/src/runtime/tests/collections.rs +50 -0
  62. package/crates/mustard/src/runtime/tests/diagnostics.rs +36 -0
  63. package/crates/mustard/src/runtime/tests/exceptions.rs +122 -0
  64. package/crates/mustard/src/runtime/tests/execution.rs +553 -0
  65. package/crates/mustard/src/runtime/tests/gc.rs +533 -0
  66. package/crates/mustard/src/runtime/tests/mod.rs +56 -0
  67. package/crates/mustard/src/runtime/tests/serialization.rs +170 -0
  68. package/crates/mustard/src/runtime/validation/bytecode.rs +484 -0
  69. package/crates/mustard/src/runtime/validation/mod.rs +14 -0
  70. package/crates/mustard/src/runtime/validation/policy.rs +94 -0
  71. package/crates/mustard/src/runtime/validation/snapshot.rs +406 -0
  72. package/crates/mustard/src/runtime/validation/walk.rs +206 -0
  73. package/crates/mustard/src/runtime/vm.rs +1016 -0
  74. package/crates/mustard/src/span.rs +22 -0
  75. package/crates/mustard/src/structured.rs +107 -0
  76. package/crates/mustard-bridge/Cargo.toml +17 -0
  77. package/crates/mustard-bridge/src/codec.rs +46 -0
  78. package/crates/mustard-bridge/src/dto.rs +99 -0
  79. package/crates/mustard-bridge/src/lib.rs +12 -0
  80. package/crates/mustard-bridge/src/operations.rs +142 -0
  81. package/crates/mustard-node/Cargo.toml +24 -0
  82. package/crates/mustard-node/build.rs +3 -0
  83. package/crates/mustard-node/src/lib.rs +236 -0
  84. package/crates/mustard-sidecar/Cargo.toml +21 -0
  85. package/crates/mustard-sidecar/src/lib.rs +134 -0
  86. package/crates/mustard-sidecar/src/main.rs +36 -0
  87. package/dist/index.js +20 -0
  88. package/dist/install.js +117 -0
  89. package/dist/lib/cancellation.js +124 -0
  90. package/dist/lib/errors.js +46 -0
  91. package/dist/lib/executor.js +555 -0
  92. package/dist/lib/policy.js +292 -0
  93. package/dist/lib/progress.js +356 -0
  94. package/dist/lib/runtime.js +109 -0
  95. package/dist/lib/structured.js +286 -0
  96. package/dist/native-loader.js +227 -0
  97. package/index.d.ts +23 -0
  98. package/mustard.d.ts +220 -0
  99. package/package.json +97 -0
@@ -0,0 +1,803 @@
1
+ use super::*;
2
+
3
+ impl Runtime {
4
+ fn string_value_receiver(&self, value: Value, method: &str) -> MustardResult<String> {
5
+ match value {
6
+ Value::String(value) => Ok(value),
7
+ Value::Object(object) => match &self
8
+ .objects
9
+ .get(object)
10
+ .ok_or_else(|| MustardError::runtime("object missing"))?
11
+ .kind
12
+ {
13
+ ObjectKind::StringObject(value) => Ok(value.clone()),
14
+ _ => Err(MustardError::runtime(format!(
15
+ "TypeError: String.prototype.{method} called on incompatible receiver",
16
+ ))),
17
+ },
18
+ _ => Err(MustardError::runtime(format!(
19
+ "TypeError: String.prototype.{method} called on incompatible receiver",
20
+ ))),
21
+ }
22
+ }
23
+
24
+ fn string_receiver(&self, value: Value, method: &str) -> MustardResult<String> {
25
+ match value {
26
+ Value::String(value) => Ok(value),
27
+ Value::Object(object) => match &self
28
+ .objects
29
+ .get(object)
30
+ .ok_or_else(|| MustardError::runtime("object missing"))?
31
+ .kind
32
+ {
33
+ ObjectKind::StringObject(value) => Ok(value.clone()),
34
+ ObjectKind::NumberObject(value) => self.to_string(Value::Number(*value)),
35
+ ObjectKind::BooleanObject(value) => self.to_string(Value::Bool(*value)),
36
+ _ => Err(MustardError::runtime(format!(
37
+ "TypeError: String.prototype.{method} called on incompatible receiver",
38
+ ))),
39
+ },
40
+ _ => Err(MustardError::runtime(format!(
41
+ "TypeError: String.prototype.{method} called on incompatible receiver",
42
+ ))),
43
+ }
44
+ }
45
+
46
+ fn string_search_pattern(
47
+ &self,
48
+ value: Value,
49
+ method: &str,
50
+ ) -> MustardResult<StringSearchPattern> {
51
+ match value {
52
+ Value::Object(object) if self.is_regexp_object(object) => {
53
+ Ok(StringSearchPattern::RegExp {
54
+ object,
55
+ regex: self.regexp_object(object)?.clone(),
56
+ })
57
+ }
58
+ value => {
59
+ if is_callable(&value) {
60
+ return Err(MustardError::runtime(format!(
61
+ "TypeError: String.prototype.{method} does not support callback patterns",
62
+ )));
63
+ }
64
+ Ok(StringSearchPattern::Literal(self.to_string(value)?))
65
+ }
66
+ }
67
+ }
68
+
69
+ fn string_callback_replacement(
70
+ &mut self,
71
+ method: &str,
72
+ callback: Value,
73
+ input: &str,
74
+ matched: &RegExpMatchData,
75
+ ) -> MustardResult<String> {
76
+ let mut args = vec![Value::String(
77
+ input[matched.start_byte..matched.end_byte].to_string(),
78
+ )];
79
+ args.extend(
80
+ matched
81
+ .captures
82
+ .iter()
83
+ .map(|value| value.clone().map_or(Value::Undefined, Value::String)),
84
+ );
85
+ args.push(Value::Number(matched.start_index as f64));
86
+ args.push(Value::String(input.to_string()));
87
+ if !matched.named_groups.is_empty() {
88
+ let groups = matched
89
+ .named_groups
90
+ .iter()
91
+ .map(|(name, value)| {
92
+ (
93
+ name.clone(),
94
+ value.clone().map_or(Value::Undefined, Value::String),
95
+ )
96
+ })
97
+ .collect::<IndexMap<_, _>>();
98
+ let object = self.insert_object(groups, ObjectKind::Plain)?;
99
+ args.push(Value::Object(object));
100
+ }
101
+ let mut roots = vec![callback.clone()];
102
+ roots.extend(args.iter().cloned());
103
+ let value = self.with_temporary_roots(&roots, |runtime| {
104
+ runtime.call_callback(
105
+ callback.clone(),
106
+ Value::Undefined,
107
+ &args,
108
+ CallbackCallOptions {
109
+ non_callable_message: &format!(
110
+ "TypeError: String.prototype.{method} replacement callback is not callable"
111
+ ),
112
+ host_suspension_message: &format!(
113
+ "TypeError: String.prototype.{method} callback replacements do not support host suspensions"
114
+ ),
115
+ unsettled_message: &format!(
116
+ "synchronous String.prototype.{method} callback did not settle"
117
+ ),
118
+ allow_host_suspension: false,
119
+ allow_pending_promise_result: false,
120
+ },
121
+ )
122
+ })?;
123
+ self.to_string(value)
124
+ }
125
+
126
+ pub(crate) fn call_string_trim(&self, this_value: Value) -> MustardResult<Value> {
127
+ let value = self.string_receiver(this_value, "trim")?;
128
+ Ok(Value::String(value.trim().to_string()))
129
+ }
130
+
131
+ pub(crate) fn call_string_trim_start(&self, this_value: Value) -> MustardResult<Value> {
132
+ let value = self.string_receiver(this_value, "trimStart")?;
133
+ Ok(Value::String(value.trim_start().to_string()))
134
+ }
135
+
136
+ pub(crate) fn call_string_trim_end(&self, this_value: Value) -> MustardResult<Value> {
137
+ let value = self.string_receiver(this_value, "trimEnd")?;
138
+ Ok(Value::String(value.trim_end().to_string()))
139
+ }
140
+
141
+ pub(crate) fn call_string_to_string(&self, this_value: Value) -> MustardResult<Value> {
142
+ Ok(Value::String(
143
+ self.string_value_receiver(this_value, "toString")?,
144
+ ))
145
+ }
146
+
147
+ pub(crate) fn call_string_value_of(&self, this_value: Value) -> MustardResult<Value> {
148
+ Ok(Value::String(
149
+ self.string_value_receiver(this_value, "valueOf")?,
150
+ ))
151
+ }
152
+
153
+ pub(crate) fn call_string_includes(
154
+ &self,
155
+ this_value: Value,
156
+ args: &[Value],
157
+ ) -> MustardResult<Value> {
158
+ let value = self.string_receiver(this_value, "includes")?;
159
+ let chars = value.chars().collect::<Vec<_>>();
160
+ let needle = self
161
+ .to_string(args.first().cloned().unwrap_or(Value::Undefined))?
162
+ .chars()
163
+ .collect::<Vec<_>>();
164
+ let position = clamp_index(
165
+ self.to_integer(args.get(1).cloned().unwrap_or(Value::Number(0.0)))?,
166
+ chars.len(),
167
+ );
168
+ let haystack = chars[position..].iter().collect::<String>();
169
+ Ok(Value::Bool(
170
+ haystack.contains(&needle.iter().collect::<String>()),
171
+ ))
172
+ }
173
+
174
+ pub(crate) fn call_string_starts_with(
175
+ &self,
176
+ this_value: Value,
177
+ args: &[Value],
178
+ ) -> MustardResult<Value> {
179
+ let value = self.string_receiver(this_value, "startsWith")?;
180
+ let chars = value.chars().collect::<Vec<_>>();
181
+ let needle = self
182
+ .to_string(args.first().cloned().unwrap_or(Value::Undefined))?
183
+ .chars()
184
+ .collect::<Vec<_>>();
185
+ let position = clamp_index(
186
+ self.to_integer(args.get(1).cloned().unwrap_or(Value::Number(0.0)))?,
187
+ chars.len(),
188
+ );
189
+ Ok(Value::Bool(chars[position..].starts_with(&needle)))
190
+ }
191
+
192
+ pub(crate) fn call_string_ends_with(
193
+ &self,
194
+ this_value: Value,
195
+ args: &[Value],
196
+ ) -> MustardResult<Value> {
197
+ let value = self.string_receiver(this_value, "endsWith")?;
198
+ let chars = value.chars().collect::<Vec<_>>();
199
+ let needle = self
200
+ .to_string(args.first().cloned().unwrap_or(Value::Undefined))?
201
+ .chars()
202
+ .collect::<Vec<_>>();
203
+ let end = match args.get(1) {
204
+ Some(value) => clamp_index(self.to_integer(value.clone())?, chars.len()),
205
+ None => chars.len(),
206
+ };
207
+ Ok(Value::Bool(chars[..end].ends_with(&needle)))
208
+ }
209
+
210
+ pub(crate) fn call_string_index_of(
211
+ &self,
212
+ this_value: Value,
213
+ args: &[Value],
214
+ ) -> MustardResult<Value> {
215
+ let value = self.string_receiver(this_value, "indexOf")?;
216
+ let chars = value.chars().collect::<Vec<_>>();
217
+ let needle = self
218
+ .to_string(args.first().cloned().unwrap_or(Value::Undefined))?
219
+ .chars()
220
+ .collect::<Vec<_>>();
221
+ let position = clamp_index(
222
+ self.to_integer(args.get(1).cloned().unwrap_or(Value::Number(0.0)))?,
223
+ chars.len(),
224
+ );
225
+ let index = if needle.is_empty() {
226
+ position as f64
227
+ } else {
228
+ chars[position..]
229
+ .windows(needle.len())
230
+ .position(|window| window == needle.as_slice())
231
+ .map(|offset| (position + offset) as f64)
232
+ .unwrap_or(-1.0)
233
+ };
234
+ Ok(Value::Number(index))
235
+ }
236
+
237
+ pub(crate) fn call_string_last_index_of(
238
+ &self,
239
+ this_value: Value,
240
+ args: &[Value],
241
+ ) -> MustardResult<Value> {
242
+ let value = self.string_receiver(this_value, "lastIndexOf")?;
243
+ let chars = value.chars().collect::<Vec<_>>();
244
+ let needle = self
245
+ .to_string(args.first().cloned().unwrap_or(Value::Undefined))?
246
+ .chars()
247
+ .collect::<Vec<_>>();
248
+ let position = match args.get(1) {
249
+ Some(value) => clamp_index(self.to_integer(value.clone())?, chars.len()),
250
+ None => chars.len(),
251
+ };
252
+ let index = if needle.is_empty() {
253
+ position as f64
254
+ } else if needle.len() > chars.len() {
255
+ -1.0
256
+ } else {
257
+ let max_start = position.min(chars.len().saturating_sub(needle.len()));
258
+ (0..=max_start)
259
+ .rev()
260
+ .find(|start| chars[*start..*start + needle.len()] == needle[..])
261
+ .map(|index| index as f64)
262
+ .unwrap_or(-1.0)
263
+ };
264
+ Ok(Value::Number(index))
265
+ }
266
+
267
+ pub(crate) fn call_string_char_at(
268
+ &self,
269
+ this_value: Value,
270
+ args: &[Value],
271
+ ) -> MustardResult<Value> {
272
+ let value = self.string_receiver(this_value, "charAt")?;
273
+ let chars = value.chars().collect::<Vec<_>>();
274
+ let index = clamp_index(
275
+ self.to_integer(args.first().cloned().unwrap_or(Value::Number(0.0)))?,
276
+ chars.len(),
277
+ );
278
+ Ok(Value::String(
279
+ chars
280
+ .get(index)
281
+ .map(|ch| ch.to_string())
282
+ .unwrap_or_default(),
283
+ ))
284
+ }
285
+
286
+ pub(crate) fn call_string_at(&self, this_value: Value, args: &[Value]) -> MustardResult<Value> {
287
+ let value = self.string_receiver(this_value, "at")?;
288
+ let chars = value.chars().collect::<Vec<_>>();
289
+ let index = self.to_integer(args.first().cloned().unwrap_or(Value::Undefined))?;
290
+ let index = if index < 0 {
291
+ chars.len() as i64 + index
292
+ } else {
293
+ index
294
+ };
295
+ if index < 0 || index >= chars.len() as i64 {
296
+ Ok(Value::Undefined)
297
+ } else {
298
+ Ok(Value::String(chars[index as usize].to_string()))
299
+ }
300
+ }
301
+
302
+ pub(crate) fn call_string_slice(
303
+ &self,
304
+ this_value: Value,
305
+ args: &[Value],
306
+ ) -> MustardResult<Value> {
307
+ let value = self.string_receiver(this_value, "slice")?;
308
+ let chars = value.chars().collect::<Vec<_>>();
309
+ let start = normalize_relative_bound(
310
+ self.to_integer(args.first().cloned().unwrap_or(Value::Number(0.0)))?,
311
+ chars.len(),
312
+ );
313
+ let end = normalize_relative_bound(
314
+ match args.get(1) {
315
+ Some(value) => self.to_integer(value.clone())?,
316
+ None => chars.len() as i64,
317
+ },
318
+ chars.len(),
319
+ );
320
+ let end = end.max(start);
321
+ Ok(Value::String(chars[start..end].iter().collect()))
322
+ }
323
+
324
+ pub(crate) fn call_string_substring(
325
+ &self,
326
+ this_value: Value,
327
+ args: &[Value],
328
+ ) -> MustardResult<Value> {
329
+ let value = self.string_receiver(this_value, "substring")?;
330
+ let chars = value.chars().collect::<Vec<_>>();
331
+ let start = clamp_index(
332
+ self.to_integer(args.first().cloned().unwrap_or(Value::Number(0.0)))?,
333
+ chars.len(),
334
+ );
335
+ let end = match args.get(1) {
336
+ Some(value) => clamp_index(self.to_integer(value.clone())?, chars.len()),
337
+ None => chars.len(),
338
+ };
339
+ let (start, end) = if start <= end {
340
+ (start, end)
341
+ } else {
342
+ (end, start)
343
+ };
344
+ Ok(Value::String(chars[start..end].iter().collect()))
345
+ }
346
+
347
+ pub(crate) fn call_string_to_lower_case(&self, this_value: Value) -> MustardResult<Value> {
348
+ let value = self.string_receiver(this_value, "toLowerCase")?;
349
+ Ok(Value::String(value.to_lowercase()))
350
+ }
351
+
352
+ pub(crate) fn call_string_to_upper_case(&self, this_value: Value) -> MustardResult<Value> {
353
+ let value = self.string_receiver(this_value, "toUpperCase")?;
354
+ Ok(Value::String(value.to_uppercase()))
355
+ }
356
+
357
+ pub(crate) fn call_string_repeat(
358
+ &self,
359
+ this_value: Value,
360
+ args: &[Value],
361
+ ) -> MustardResult<Value> {
362
+ let value = self.string_receiver(this_value, "repeat")?;
363
+ let count = self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?;
364
+ if !count.is_finite() || count < 0.0 {
365
+ return Err(MustardError::runtime("RangeError: Invalid count value"));
366
+ }
367
+ let count = self.to_integer(Value::Number(count))? as usize;
368
+ Ok(Value::String(value.repeat(count)))
369
+ }
370
+
371
+ pub(crate) fn call_string_concat(
372
+ &self,
373
+ this_value: Value,
374
+ args: &[Value],
375
+ ) -> MustardResult<Value> {
376
+ let mut value = self.string_receiver(this_value, "concat")?;
377
+ for arg in args {
378
+ value.push_str(&self.to_string(arg.clone())?);
379
+ }
380
+ Ok(Value::String(value))
381
+ }
382
+
383
+ pub(crate) fn call_string_pad_start(
384
+ &self,
385
+ this_value: Value,
386
+ args: &[Value],
387
+ ) -> MustardResult<Value> {
388
+ self.call_string_pad(this_value, args, true)
389
+ }
390
+
391
+ pub(crate) fn call_string_pad_end(
392
+ &self,
393
+ this_value: Value,
394
+ args: &[Value],
395
+ ) -> MustardResult<Value> {
396
+ self.call_string_pad(this_value, args, false)
397
+ }
398
+
399
+ fn call_string_pad(
400
+ &self,
401
+ this_value: Value,
402
+ args: &[Value],
403
+ at_start: bool,
404
+ ) -> MustardResult<Value> {
405
+ let method = if at_start { "padStart" } else { "padEnd" };
406
+ let value = self.string_receiver(this_value, method)?;
407
+ let target_len = self.to_integer(args.first().cloned().unwrap_or(Value::Undefined))?;
408
+ let target_len = usize::try_from(target_len.max(0)).unwrap_or(usize::MAX);
409
+ let value_len = value.chars().count();
410
+ if target_len <= value_len {
411
+ return Ok(Value::String(value));
412
+ }
413
+ let fill = self.to_string(args.get(1).cloned().unwrap_or(Value::String(" ".into())))?;
414
+ if fill.is_empty() {
415
+ return Ok(Value::String(value));
416
+ }
417
+ let fill_chars = fill.chars().collect::<Vec<_>>();
418
+ let pad_len = target_len - value_len;
419
+ let padding = fill_chars
420
+ .iter()
421
+ .copied()
422
+ .cycle()
423
+ .take(pad_len)
424
+ .collect::<String>();
425
+ Ok(Value::String(if at_start {
426
+ format!("{padding}{value}")
427
+ } else {
428
+ format!("{value}{padding}")
429
+ }))
430
+ }
431
+
432
+ pub(crate) fn call_string_split(
433
+ &mut self,
434
+ this_value: Value,
435
+ args: &[Value],
436
+ ) -> MustardResult<Value> {
437
+ let value = self.string_receiver(this_value, "split")?;
438
+ let limit = match args.get(1) {
439
+ Some(value) => {
440
+ let limit = self.to_integer(value.clone())?;
441
+ if limit <= 0 {
442
+ 0
443
+ } else {
444
+ usize::try_from(limit).unwrap_or(usize::MAX)
445
+ }
446
+ }
447
+ None => usize::MAX,
448
+ };
449
+ if limit == 0 {
450
+ return Ok(Value::Array(
451
+ self.insert_array(Vec::new(), IndexMap::new())?,
452
+ ));
453
+ }
454
+ let pattern = match args.first() {
455
+ None | Some(Value::Undefined) => None,
456
+ Some(value) => Some(self.string_search_pattern(value.clone(), "split")?),
457
+ };
458
+ let elements = match pattern {
459
+ None => vec![Value::String(value)],
460
+ Some(StringSearchPattern::Literal(separator)) => {
461
+ split_string_by_pattern(&value, Some(separator.as_str()), limit)
462
+ .into_iter()
463
+ .map(Value::String)
464
+ .collect()
465
+ }
466
+ Some(StringSearchPattern::RegExp { regex, .. }) => {
467
+ let matches = self.collect_regexp_matches_from_state(&regex, &value, true)?;
468
+ let mut elements = Vec::new();
469
+ let mut last_end = 0usize;
470
+ for matched in matches {
471
+ if elements.len() >= limit {
472
+ break;
473
+ }
474
+ elements.push(Value::String(
475
+ value[last_end..matched.start_byte].to_string(),
476
+ ));
477
+ if elements.len() >= limit {
478
+ break;
479
+ }
480
+ for capture in matched.captures {
481
+ elements.push(capture.map_or(Value::Undefined, Value::String));
482
+ if elements.len() >= limit {
483
+ break;
484
+ }
485
+ }
486
+ last_end = matched.end_byte;
487
+ }
488
+ if elements.len() < limit {
489
+ elements.push(Value::String(value[last_end..].to_string()));
490
+ }
491
+ elements
492
+ }
493
+ };
494
+ Ok(Value::Array(self.insert_array(
495
+ elements.into_iter().take(limit).collect(),
496
+ IndexMap::new(),
497
+ )?))
498
+ }
499
+
500
+ pub(crate) fn call_string_replace(
501
+ &mut self,
502
+ this_value: Value,
503
+ args: &[Value],
504
+ ) -> MustardResult<Value> {
505
+ let value = self.string_receiver(this_value, "replace")?;
506
+ let search = self
507
+ .string_search_pattern(args.first().cloned().unwrap_or(Value::Undefined), "replace")?;
508
+ let replacement = args.get(1).cloned().unwrap_or(Value::Undefined);
509
+ match (search, replacement.clone()) {
510
+ (StringSearchPattern::Literal(search), replacement) if is_callable(&replacement) => {
511
+ let matched = if search.is_empty() {
512
+ Some(RegExpMatchData {
513
+ start_byte: 0,
514
+ end_byte: 0,
515
+ start_index: 0,
516
+ end_index: 0,
517
+ captures: Vec::new(),
518
+ named_groups: IndexMap::new(),
519
+ })
520
+ } else {
521
+ self.literal_match_data(&value, &search, 0)
522
+ };
523
+ if let Some(matched) = matched {
524
+ let replacement =
525
+ self.string_callback_replacement("replace", replacement, &value, &matched)?;
526
+ let mut result = String::new();
527
+ result.push_str(&value[..matched.start_byte]);
528
+ result.push_str(&replacement);
529
+ result.push_str(&value[matched.end_byte..]);
530
+ Ok(Value::String(result))
531
+ } else {
532
+ Ok(Value::String(value))
533
+ }
534
+ }
535
+ (StringSearchPattern::Literal(search), replacement) => Ok(Value::String(
536
+ replace_first_string_match(&value, &search, &self.to_string(replacement)?),
537
+ )),
538
+ (StringSearchPattern::RegExp { regex, .. }, replacement)
539
+ if is_callable(&replacement) =>
540
+ {
541
+ let all = regex.flags.contains('g');
542
+ let matches = self.collect_regexp_matches_from_state(&regex, &value, all)?;
543
+ if matches.is_empty() {
544
+ return Ok(Value::String(value));
545
+ }
546
+ let mut result = String::new();
547
+ let mut last_end = 0usize;
548
+ for matched in &matches {
549
+ result.push_str(&value[last_end..matched.start_byte]);
550
+ result.push_str(&self.string_callback_replacement(
551
+ "replace",
552
+ replacement.clone(),
553
+ &value,
554
+ matched,
555
+ )?);
556
+ last_end = matched.end_byte;
557
+ }
558
+ result.push_str(&value[last_end..]);
559
+ Ok(Value::String(result))
560
+ }
561
+ (StringSearchPattern::RegExp { regex, .. }, replacement) => {
562
+ let all = regex.flags.contains('g');
563
+ let matches = self.collect_regexp_matches_from_state(&regex, &value, all)?;
564
+ if matches.is_empty() {
565
+ return Ok(Value::String(value));
566
+ }
567
+ let replacement = self.to_string(replacement)?;
568
+ let mut result = String::new();
569
+ let mut last_end = 0usize;
570
+ for matched in &matches {
571
+ result.push_str(&value[last_end..matched.start_byte]);
572
+ result.push_str(&expand_regexp_replacement_template(
573
+ &replacement,
574
+ &value,
575
+ matched,
576
+ ));
577
+ last_end = matched.end_byte;
578
+ }
579
+ result.push_str(&value[last_end..]);
580
+ Ok(Value::String(result))
581
+ }
582
+ }
583
+ }
584
+
585
+ pub(crate) fn call_string_replace_all(
586
+ &mut self,
587
+ this_value: Value,
588
+ args: &[Value],
589
+ ) -> MustardResult<Value> {
590
+ let value = self.string_receiver(this_value, "replaceAll")?;
591
+ let search = self.string_search_pattern(
592
+ args.first().cloned().unwrap_or(Value::Undefined),
593
+ "replaceAll",
594
+ )?;
595
+ let replacement = args.get(1).cloned().unwrap_or(Value::Undefined);
596
+ match search {
597
+ StringSearchPattern::Literal(search) if is_callable(&replacement) => {
598
+ let mut matches = Vec::new();
599
+ if search.is_empty() {
600
+ let total = value.chars().count();
601
+ for index in 0..=total {
602
+ let byte = char_index_to_byte_index(&value, index);
603
+ matches.push(RegExpMatchData {
604
+ start_byte: byte,
605
+ end_byte: byte,
606
+ start_index: index,
607
+ end_index: index,
608
+ captures: Vec::new(),
609
+ named_groups: IndexMap::new(),
610
+ });
611
+ }
612
+ } else {
613
+ let mut start_index = 0usize;
614
+ while let Some(matched) = self.literal_match_data(&value, &search, start_index)
615
+ {
616
+ start_index = matched.end_index;
617
+ matches.push(matched);
618
+ }
619
+ }
620
+ let mut result = String::new();
621
+ let mut last_end = 0usize;
622
+ for matched in &matches {
623
+ result.push_str(&value[last_end..matched.start_byte]);
624
+ result.push_str(&self.string_callback_replacement(
625
+ "replaceAll",
626
+ replacement.clone(),
627
+ &value,
628
+ matched,
629
+ )?);
630
+ last_end = matched.end_byte;
631
+ }
632
+ result.push_str(&value[last_end..]);
633
+ Ok(Value::String(result))
634
+ }
635
+ StringSearchPattern::Literal(search) => Ok(Value::String(replace_all_string_matches(
636
+ &value,
637
+ &search,
638
+ &self.to_string(replacement)?,
639
+ ))),
640
+ StringSearchPattern::RegExp { regex, .. } => {
641
+ if !regex.flags.contains('g') {
642
+ return Err(MustardError::runtime(
643
+ "TypeError: String.prototype.replaceAll requires a global RegExp",
644
+ ));
645
+ }
646
+ let matches = self.collect_regexp_matches_from_state(&regex, &value, true)?;
647
+ if matches.is_empty() {
648
+ return Ok(Value::String(value));
649
+ }
650
+ let mut result = String::new();
651
+ let mut last_end = 0usize;
652
+ if is_callable(&replacement) {
653
+ for matched in &matches {
654
+ result.push_str(&value[last_end..matched.start_byte]);
655
+ result.push_str(&self.string_callback_replacement(
656
+ "replaceAll",
657
+ replacement.clone(),
658
+ &value,
659
+ matched,
660
+ )?);
661
+ last_end = matched.end_byte;
662
+ }
663
+ } else {
664
+ let replacement = self.to_string(replacement)?;
665
+ for matched in &matches {
666
+ result.push_str(&value[last_end..matched.start_byte]);
667
+ result.push_str(&expand_regexp_replacement_template(
668
+ &replacement,
669
+ &value,
670
+ matched,
671
+ ));
672
+ last_end = matched.end_byte;
673
+ }
674
+ }
675
+ result.push_str(&value[last_end..]);
676
+ Ok(Value::String(result))
677
+ }
678
+ }
679
+ }
680
+
681
+ pub(crate) fn call_string_search(
682
+ &self,
683
+ this_value: Value,
684
+ args: &[Value],
685
+ ) -> MustardResult<Value> {
686
+ let value = self.string_receiver(this_value, "search")?;
687
+ let needle = self
688
+ .string_search_pattern(args.first().cloned().unwrap_or(Value::Undefined), "search")?;
689
+ Ok(Value::Number(match needle {
690
+ StringSearchPattern::Literal(needle) => find_string_pattern(&value, &needle, 0)
691
+ .map(|index| index as f64)
692
+ .unwrap_or(-1.0),
693
+ StringSearchPattern::RegExp { regex, .. } => self
694
+ .first_regexp_match_from_state(&regex, &value, 0)?
695
+ .map(|matched| matched.start_index as f64)
696
+ .unwrap_or(-1.0),
697
+ }))
698
+ }
699
+
700
+ pub(crate) fn call_string_match(
701
+ &mut self,
702
+ this_value: Value,
703
+ args: &[Value],
704
+ ) -> MustardResult<Value> {
705
+ let value = self.string_receiver(this_value, "match")?;
706
+ let needle =
707
+ self.string_search_pattern(args.first().cloned().unwrap_or(Value::Undefined), "match")?;
708
+ match needle {
709
+ StringSearchPattern::Literal(needle) => {
710
+ let Some(index) = find_string_pattern(&value, &needle, 0) else {
711
+ return Ok(Value::Null);
712
+ };
713
+ let match_array = self.insert_array(
714
+ vec![Value::String(needle.clone())],
715
+ IndexMap::from([
716
+ ("index".to_string(), Value::Number(index as f64)),
717
+ ("input".to_string(), Value::String(value)),
718
+ ]),
719
+ )?;
720
+ Ok(Value::Array(match_array))
721
+ }
722
+ StringSearchPattern::RegExp { object, regex } => {
723
+ if regex.flags.contains('g') {
724
+ self.regexp_object_mut(object)?.last_index = 0;
725
+ self.refresh_object_accounting(object)?;
726
+ let matches = self.collect_regexp_matches_from_state(&regex, &value, true)?;
727
+ if matches.is_empty() {
728
+ return Ok(Value::Null);
729
+ }
730
+ let array = self.insert_array(
731
+ matches
732
+ .into_iter()
733
+ .map(|matched| {
734
+ Value::String(
735
+ value[matched.start_byte..matched.end_byte].to_string(),
736
+ )
737
+ })
738
+ .collect(),
739
+ IndexMap::new(),
740
+ )?;
741
+ Ok(Value::Array(array))
742
+ } else {
743
+ let Some(matched) = self.first_regexp_match_from_state(&regex, &value, 0)?
744
+ else {
745
+ return Ok(Value::Null);
746
+ };
747
+ self.regexp_match_array_value(&value, &matched)
748
+ }
749
+ }
750
+ }
751
+ }
752
+
753
+ pub(crate) fn call_string_match_all(
754
+ &mut self,
755
+ this_value: Value,
756
+ args: &[Value],
757
+ ) -> MustardResult<Value> {
758
+ let value = self.string_receiver(this_value, "matchAll")?;
759
+ let needle = self.string_search_pattern(
760
+ args.first().cloned().unwrap_or(Value::Undefined),
761
+ "matchAll",
762
+ )?;
763
+ let matches = match needle {
764
+ StringSearchPattern::Literal(needle) => collect_literal_matches(&value, &needle),
765
+ StringSearchPattern::RegExp { object, regex } => {
766
+ if !regex.flags.contains('g') {
767
+ return Err(MustardError::runtime(
768
+ "TypeError: String.prototype.matchAll requires a global RegExp",
769
+ ));
770
+ }
771
+ self.regexp_object_mut(object)?.last_index = 0;
772
+ self.refresh_object_accounting(object)?;
773
+ self.collect_regexp_matches_from_state(&regex, &value, true)?
774
+ }
775
+ };
776
+ let mut values = Vec::with_capacity(matches.len());
777
+ for matched in matches {
778
+ values.push(self.regexp_match_array_value(&value, &matched)?);
779
+ }
780
+ let array = self.insert_array(values, IndexMap::new())?;
781
+ self.call_array_values(Value::Array(array))
782
+ }
783
+
784
+ fn literal_match_data(
785
+ &self,
786
+ value: &str,
787
+ needle: &str,
788
+ start_index: usize,
789
+ ) -> Option<RegExpMatchData> {
790
+ let start_index = find_string_pattern(value, needle, start_index)?;
791
+ let start_byte = char_index_to_byte_index(value, start_index);
792
+ let end_index = start_index + needle.chars().count();
793
+ let end_byte = char_index_to_byte_index(value, end_index);
794
+ Some(RegExpMatchData {
795
+ start_byte,
796
+ end_byte,
797
+ start_index,
798
+ end_index,
799
+ captures: Vec::new(),
800
+ named_groups: IndexMap::new(),
801
+ })
802
+ }
803
+ }