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,335 @@
1
+ use super::*;
2
+
3
+ pub(crate) struct PromiseSetupPolicy<'a> {
4
+ pub(crate) non_callable_message: &'a str,
5
+ pub(crate) host_suspension_message: &'a str,
6
+ pub(crate) async_message: &'a str,
7
+ }
8
+
9
+ impl Runtime {
10
+ fn reject_promise_from_setup_error(
11
+ &mut self,
12
+ target: PromiseKey,
13
+ error: MustardError,
14
+ ) -> MustardResult<()> {
15
+ match error {
16
+ MustardError::Message {
17
+ kind: DiagnosticKind::Runtime,
18
+ message,
19
+ ..
20
+ } if message == super::super::INTERNAL_CALLBACK_THROW_MARKER => {
21
+ let rejection = self.pending_internal_exception.take().ok_or_else(|| {
22
+ MustardError::runtime("missing internal callback exception state")
23
+ })?;
24
+ self.reject_promise(target, rejection)
25
+ }
26
+ other => self.reject_promise_from_error(target, other),
27
+ }
28
+ }
29
+
30
+ pub(crate) fn promise_settler(&self, target: PromiseKey, rejected: bool) -> Value {
31
+ Value::BuiltinFunction(if rejected {
32
+ BuiltinFunction::PromiseRejectFunction(target)
33
+ } else {
34
+ BuiltinFunction::PromiseResolveFunction(target)
35
+ })
36
+ }
37
+
38
+ pub(crate) fn promise_thenable_handler(&self, value: &Value) -> MustardResult<Option<Value>> {
39
+ match value {
40
+ Value::Object(_) | Value::Array(_) => {
41
+ let then =
42
+ self.get_property(value.clone(), Value::String("then".to_string()), false)?;
43
+ if is_callable(&then) {
44
+ Ok(Some(then))
45
+ } else {
46
+ Ok(None)
47
+ }
48
+ }
49
+ _ => Ok(None),
50
+ }
51
+ }
52
+
53
+ pub(crate) fn call_promise_setup_callback(
54
+ &mut self,
55
+ target: PromiseKey,
56
+ callback: Value,
57
+ this_arg: Value,
58
+ args: &[Value],
59
+ policy: PromiseSetupPolicy<'_>,
60
+ ) -> MustardResult<()> {
61
+ let result = match callback {
62
+ Value::Closure(closure) => {
63
+ let closure = self
64
+ .closures
65
+ .get(closure)
66
+ .cloned()
67
+ .ok_or_else(|| MustardError::runtime("closure not found"))?;
68
+ let function = self
69
+ .program
70
+ .functions
71
+ .get(closure.function_id)
72
+ .ok_or_else(|| MustardError::runtime("function not found"))?;
73
+ if function.is_async {
74
+ Err(MustardError::runtime(policy.async_message))
75
+ } else {
76
+ let env = self.new_env(Some(closure.env))?;
77
+ let outcome = self.insert_promise(PromiseState::Pending)?;
78
+ let base_depth = self.frames.len();
79
+ self.push_frame(closure.function_id, env, args, this_arg, Some(outcome))?;
80
+ if let Err(error) =
81
+ self.run_until_frame_depth(base_depth, policy.host_suspension_message)
82
+ {
83
+ self.frames.truncate(base_depth);
84
+ self.suspended_host_call = None;
85
+ self.pending_resume_behavior = ResumeBehavior::Value;
86
+ return self.reject_promise_from_setup_error(target, error);
87
+ }
88
+ match self.promise_outcome(outcome)? {
89
+ Some(PromiseOutcome::Rejected(rejection)) => {
90
+ self.reject_promise(target, rejection)
91
+ }
92
+ Some(PromiseOutcome::Fulfilled(_)) | None => Ok(()),
93
+ }
94
+ }
95
+ }
96
+ Value::BuiltinFunction(function) => {
97
+ self.call_builtin(function, this_arg, args).map(|_| ())
98
+ }
99
+ Value::HostFunction(capability) => {
100
+ match self.call_callable(Value::HostFunction(capability), this_arg, args) {
101
+ Ok(RunState::Completed(_) | RunState::StartedAsync(_)) => Ok(()),
102
+ Ok(RunState::PushedFrame) => Err(MustardError::runtime(
103
+ "promise setup callback unexpectedly pushed a frame",
104
+ )),
105
+ Ok(RunState::Suspended { .. }) => {
106
+ Err(MustardError::runtime(policy.host_suspension_message))
107
+ }
108
+ Err(error) => Err(error),
109
+ }
110
+ }
111
+ _ => Err(MustardError::runtime(policy.non_callable_message)),
112
+ };
113
+ match result {
114
+ Ok(()) => Ok(()),
115
+ Err(error) => self.reject_promise_from_setup_error(target, error),
116
+ }
117
+ }
118
+
119
+ pub(crate) fn construct_promise(&mut self, args: &[Value]) -> MustardResult<Value> {
120
+ let executor = args.first().cloned().unwrap_or(Value::Undefined);
121
+ if !is_callable(&executor) {
122
+ return Err(MustardError::runtime(
123
+ "TypeError: Promise constructor expects a callable executor",
124
+ ));
125
+ }
126
+ let target = self.insert_promise(PromiseState::Pending)?;
127
+ let resolve = self.promise_settler(target, false);
128
+ let reject = self.promise_settler(target, true);
129
+ self.call_promise_setup_callback(
130
+ target,
131
+ executor,
132
+ Value::Undefined,
133
+ &[resolve, reject],
134
+ PromiseSetupPolicy {
135
+ non_callable_message: "TypeError: Promise constructor expects a callable executor",
136
+ host_suspension_message:
137
+ "TypeError: Promise executors do not support synchronous host suspensions",
138
+ async_message:
139
+ "TypeError: Promise executors must be synchronous in the supported surface",
140
+ },
141
+ )?;
142
+ Ok(Value::Promise(target))
143
+ }
144
+
145
+ fn promise_receiver(&self, value: Value, method: &str) -> MustardResult<PromiseKey> {
146
+ match value {
147
+ Value::Promise(key) => Ok(key),
148
+ _ => Err(MustardError::runtime(format!(
149
+ "TypeError: Promise.prototype.{method} called on incompatible receiver",
150
+ ))),
151
+ }
152
+ }
153
+
154
+ fn collect_iterable_values(&mut self, iterable: Value) -> MustardResult<Vec<Value>> {
155
+ let iterator = self.create_iterator(iterable)?;
156
+ let mut values = Vec::new();
157
+ loop {
158
+ let (value, done) = self.iterator_next(iterator.clone())?;
159
+ if done {
160
+ break;
161
+ }
162
+ values.push(value);
163
+ }
164
+ Ok(values)
165
+ }
166
+
167
+ pub(crate) fn call_promise_then(
168
+ &mut self,
169
+ this_value: Value,
170
+ args: &[Value],
171
+ ) -> MustardResult<Value> {
172
+ let promise = self.promise_receiver(this_value, "then")?;
173
+ let target = self.insert_promise(PromiseState::Pending)?;
174
+ let on_fulfilled = args.first().cloned().filter(is_callable);
175
+ let on_rejected = args.get(1).cloned().filter(is_callable);
176
+ self.attach_promise_reaction(
177
+ promise,
178
+ PromiseReaction::Then {
179
+ target,
180
+ on_fulfilled,
181
+ on_rejected,
182
+ },
183
+ )?;
184
+ Ok(Value::Promise(target))
185
+ }
186
+
187
+ pub(crate) fn call_promise_catch(
188
+ &mut self,
189
+ this_value: Value,
190
+ args: &[Value],
191
+ ) -> MustardResult<Value> {
192
+ let promise = self.promise_receiver(this_value, "catch")?;
193
+ let target = self.insert_promise(PromiseState::Pending)?;
194
+ let on_rejected = args.first().cloned().filter(is_callable);
195
+ self.attach_promise_reaction(
196
+ promise,
197
+ PromiseReaction::Then {
198
+ target,
199
+ on_fulfilled: None,
200
+ on_rejected,
201
+ },
202
+ )?;
203
+ Ok(Value::Promise(target))
204
+ }
205
+
206
+ pub(crate) fn call_promise_finally(
207
+ &mut self,
208
+ this_value: Value,
209
+ args: &[Value],
210
+ ) -> MustardResult<Value> {
211
+ let promise = self.promise_receiver(this_value, "finally")?;
212
+ let target = self.insert_promise(PromiseState::Pending)?;
213
+ let callback = args.first().cloned().filter(is_callable);
214
+ self.attach_promise_reaction(promise, PromiseReaction::Finally { target, callback })?;
215
+ Ok(Value::Promise(target))
216
+ }
217
+
218
+ pub(crate) fn call_promise_all(&mut self, args: &[Value]) -> MustardResult<Value> {
219
+ let target = self.insert_promise(PromiseState::Pending)?;
220
+ let values =
221
+ self.collect_iterable_values(args.first().cloned().unwrap_or(Value::Undefined))?;
222
+ if values.is_empty() {
223
+ let array = Value::Array(self.insert_array(Vec::new(), IndexMap::new())?);
224
+ self.resolve_promise(target, array)?;
225
+ return Ok(Value::Promise(target));
226
+ }
227
+ self.promises
228
+ .get_mut(target)
229
+ .ok_or_else(|| MustardError::runtime("promise missing"))?
230
+ .driver = Some(PromiseDriver::All {
231
+ remaining: values.len(),
232
+ values: vec![None; values.len()],
233
+ });
234
+ self.refresh_promise_accounting(target)?;
235
+ for (index, value) in values.into_iter().enumerate() {
236
+ let promise = self.coerce_to_promise(value)?;
237
+ self.attach_promise_reaction(
238
+ promise,
239
+ PromiseReaction::Combinator {
240
+ target,
241
+ index,
242
+ kind: PromiseCombinatorKind::All,
243
+ },
244
+ )?;
245
+ }
246
+ Ok(Value::Promise(target))
247
+ }
248
+
249
+ pub(crate) fn call_promise_race(&mut self, args: &[Value]) -> MustardResult<Value> {
250
+ let target = self.insert_promise(PromiseState::Pending)?;
251
+ for value in
252
+ self.collect_iterable_values(args.first().cloned().unwrap_or(Value::Undefined))?
253
+ {
254
+ let promise = self.coerce_to_promise(value)?;
255
+ self.attach_promise_reaction(
256
+ promise,
257
+ PromiseReaction::Combinator {
258
+ target,
259
+ index: 0,
260
+ kind: PromiseCombinatorKind::Race,
261
+ },
262
+ )?;
263
+ }
264
+ Ok(Value::Promise(target))
265
+ }
266
+
267
+ pub(crate) fn call_promise_any(&mut self, args: &[Value]) -> MustardResult<Value> {
268
+ let target = self.insert_promise(PromiseState::Pending)?;
269
+ let values =
270
+ self.collect_iterable_values(args.first().cloned().unwrap_or(Value::Undefined))?;
271
+ if values.is_empty() {
272
+ let error = self.make_aggregate_error(Vec::new())?;
273
+ self.reject_promise(
274
+ target,
275
+ PromiseRejection {
276
+ value: error,
277
+ span: None,
278
+ traceback: self.traceback_snapshots(),
279
+ },
280
+ )?;
281
+ return Ok(Value::Promise(target));
282
+ }
283
+ self.promises
284
+ .get_mut(target)
285
+ .ok_or_else(|| MustardError::runtime("promise missing"))?
286
+ .driver = Some(PromiseDriver::Any {
287
+ remaining: values.len(),
288
+ reasons: vec![None; values.len()],
289
+ });
290
+ self.refresh_promise_accounting(target)?;
291
+ for (index, value) in values.into_iter().enumerate() {
292
+ let promise = self.coerce_to_promise(value)?;
293
+ self.attach_promise_reaction(
294
+ promise,
295
+ PromiseReaction::Combinator {
296
+ target,
297
+ index,
298
+ kind: PromiseCombinatorKind::Any,
299
+ },
300
+ )?;
301
+ }
302
+ Ok(Value::Promise(target))
303
+ }
304
+
305
+ pub(crate) fn call_promise_all_settled(&mut self, args: &[Value]) -> MustardResult<Value> {
306
+ let target = self.insert_promise(PromiseState::Pending)?;
307
+ let values =
308
+ self.collect_iterable_values(args.first().cloned().unwrap_or(Value::Undefined))?;
309
+ if values.is_empty() {
310
+ let array = Value::Array(self.insert_array(Vec::new(), IndexMap::new())?);
311
+ self.resolve_promise(target, array)?;
312
+ return Ok(Value::Promise(target));
313
+ }
314
+ self.promises
315
+ .get_mut(target)
316
+ .ok_or_else(|| MustardError::runtime("promise missing"))?
317
+ .driver = Some(PromiseDriver::AllSettled {
318
+ remaining: values.len(),
319
+ results: vec![None; values.len()],
320
+ });
321
+ self.refresh_promise_accounting(target)?;
322
+ for (index, value) in values.into_iter().enumerate() {
323
+ let promise = self.coerce_to_promise(value)?;
324
+ self.attach_promise_reaction(
325
+ promise,
326
+ PromiseReaction::Combinator {
327
+ target,
328
+ index,
329
+ kind: PromiseCombinatorKind::AllSettled,
330
+ },
331
+ )?;
332
+ }
333
+ Ok(Value::Promise(target))
334
+ }
335
+ }
@@ -0,0 +1,356 @@
1
+ use super::*;
2
+ use regex::{Captures, Regex, RegexBuilder};
3
+
4
+ struct CompiledRegExp {
5
+ flags: RegExpFlagsState,
6
+ regex: Regex,
7
+ }
8
+
9
+ impl Runtime {
10
+ pub(crate) fn construct_regexp(&mut self, args: &[Value]) -> MustardResult<Value> {
11
+ let pattern_arg = args.first().cloned().unwrap_or(Value::Undefined);
12
+ let flags_arg = args.get(1).cloned().unwrap_or(Value::Undefined);
13
+ let (pattern, flags) = match pattern_arg {
14
+ Value::Object(object) if self.is_regexp_object(object) => {
15
+ let regex = self.regexp_object(object)?.clone();
16
+ if matches!(flags_arg, Value::Undefined) {
17
+ (regex.pattern, regex.flags)
18
+ } else {
19
+ (regex.pattern, self.to_string(flags_arg)?)
20
+ }
21
+ }
22
+ value => {
23
+ let pattern = if matches!(value, Value::Undefined) {
24
+ String::new()
25
+ } else {
26
+ self.to_string(value)?
27
+ };
28
+ let flags = if matches!(flags_arg, Value::Undefined) {
29
+ String::new()
30
+ } else {
31
+ self.to_string(flags_arg)?
32
+ };
33
+ (pattern, flags)
34
+ }
35
+ };
36
+ self.make_regexp_value(pattern, flags)
37
+ }
38
+
39
+ fn regexp_receiver(&self, value: Value, method: &str) -> MustardResult<ObjectKey> {
40
+ match value {
41
+ Value::Object(key) if self.is_regexp_object(key) => Ok(key),
42
+ _ => Err(MustardError::runtime(format!(
43
+ "TypeError: RegExp.prototype.{method} called on incompatible receiver",
44
+ ))),
45
+ }
46
+ }
47
+
48
+ pub(crate) fn regexp_match_array_value(
49
+ &mut self,
50
+ input: &str,
51
+ matched: &RegExpMatchData,
52
+ ) -> MustardResult<Value> {
53
+ let mut groups = IndexMap::new();
54
+ for (name, value) in &matched.named_groups {
55
+ groups.insert(
56
+ name.clone(),
57
+ value.clone().map_or(Value::Undefined, Value::String),
58
+ );
59
+ }
60
+ let mut properties = IndexMap::from([
61
+ (
62
+ "index".to_string(),
63
+ Value::Number(matched.start_index as f64),
64
+ ),
65
+ ("input".to_string(), Value::String(input.to_string())),
66
+ ]);
67
+ if !groups.is_empty() {
68
+ properties.insert(
69
+ "groups".to_string(),
70
+ Value::Object(self.insert_object(groups, ObjectKind::Plain)?),
71
+ );
72
+ }
73
+ let mut elements = Vec::with_capacity(matched.captures.len() + 1);
74
+ elements.push(Value::String(
75
+ input[matched.start_byte..matched.end_byte].to_string(),
76
+ ));
77
+ elements.extend(
78
+ matched
79
+ .captures
80
+ .iter()
81
+ .map(|value| value.clone().map_or(Value::Undefined, Value::String)),
82
+ );
83
+ Ok(Value::Array(self.insert_array(elements, properties)?))
84
+ }
85
+
86
+ pub(crate) fn call_regexp_exec(
87
+ &mut self,
88
+ this_value: Value,
89
+ args: &[Value],
90
+ ) -> MustardResult<Value> {
91
+ let regex = self.regexp_receiver(this_value, "exec")?;
92
+ let input = self.to_string(args.first().cloned().unwrap_or(Value::Undefined))?;
93
+ let Some(matched) = self.first_regexp_match(regex, &input)? else {
94
+ return Ok(Value::Null);
95
+ };
96
+ self.regexp_match_array_value(&input, &matched)
97
+ }
98
+
99
+ pub(crate) fn call_regexp_test(
100
+ &mut self,
101
+ this_value: Value,
102
+ args: &[Value],
103
+ ) -> MustardResult<Value> {
104
+ let regex = self.regexp_receiver(this_value, "test")?;
105
+ let input = self.to_string(args.first().cloned().unwrap_or(Value::Undefined))?;
106
+ Ok(Value::Bool(
107
+ self.first_regexp_match(regex, &input)?.is_some(),
108
+ ))
109
+ }
110
+
111
+ pub(crate) fn make_regexp_value(
112
+ &mut self,
113
+ pattern: String,
114
+ flags: String,
115
+ ) -> MustardResult<Value> {
116
+ self.validate_regexp_flags(&flags)?;
117
+ self.compile_regexp(&pattern, &flags)?;
118
+ let object = self.insert_object(
119
+ IndexMap::new(),
120
+ ObjectKind::RegExp(RegExpObject {
121
+ pattern,
122
+ flags,
123
+ last_index: 0,
124
+ }),
125
+ )?;
126
+ Ok(Value::Object(object))
127
+ }
128
+
129
+ fn validate_regexp_flags(&self, flags: &str) -> MustardResult<RegExpFlagsState> {
130
+ let mut state = RegExpFlagsState {
131
+ global: false,
132
+ ignore_case: false,
133
+ multiline: false,
134
+ dot_all: false,
135
+ unicode: false,
136
+ sticky: false,
137
+ };
138
+ let mut seen = HashSet::new();
139
+ for flag in flags.chars() {
140
+ if !seen.insert(flag) {
141
+ return Err(MustardError::runtime(format!(
142
+ "SyntaxError: duplicate regular expression flag `{flag}`",
143
+ )));
144
+ }
145
+ match flag {
146
+ 'g' => state.global = true,
147
+ 'i' => state.ignore_case = true,
148
+ 'm' => state.multiline = true,
149
+ 's' => state.dot_all = true,
150
+ 'u' => state.unicode = true,
151
+ 'y' => state.sticky = true,
152
+ _ => {
153
+ return Err(MustardError::runtime(format!(
154
+ "SyntaxError: unsupported regular expression flag `{flag}`",
155
+ )));
156
+ }
157
+ }
158
+ }
159
+ Ok(state)
160
+ }
161
+
162
+ fn compile_regexp(&self, pattern: &str, flags: &str) -> MustardResult<CompiledRegExp> {
163
+ let flags = self.validate_regexp_flags(flags)?;
164
+ let mut builder = RegexBuilder::new(pattern);
165
+ builder.case_insensitive(flags.ignore_case);
166
+ builder.multi_line(flags.multiline);
167
+ builder.dot_matches_new_line(flags.dot_all);
168
+ // The Rust engine operates over UTF-8 strings, so keep Unicode mode
169
+ // enabled even without the JS `u` flag. This preserves the supported
170
+ // text-regexp subset while avoiding non-UTF-8 byte classes.
171
+ builder.unicode(true);
172
+ let regex = builder.build().map_err(|error| {
173
+ MustardError::runtime(format!("SyntaxError: invalid regular expression: {error}"))
174
+ })?;
175
+ Ok(CompiledRegExp { flags, regex })
176
+ }
177
+
178
+ pub(crate) fn is_regexp_object(&self, key: ObjectKey) -> bool {
179
+ self.objects
180
+ .get(key)
181
+ .is_some_and(|object| matches!(object.kind, ObjectKind::RegExp(_)))
182
+ }
183
+
184
+ pub(crate) fn is_date_object(&self, key: ObjectKey) -> bool {
185
+ self.objects
186
+ .get(key)
187
+ .is_some_and(|object| matches!(object.kind, ObjectKind::Date(_)))
188
+ }
189
+
190
+ pub(crate) fn date_object(&self, key: ObjectKey) -> MustardResult<&DateObject> {
191
+ match &self
192
+ .objects
193
+ .get(key)
194
+ .ok_or_else(|| MustardError::runtime("object missing"))?
195
+ .kind
196
+ {
197
+ ObjectKind::Date(date) => Ok(date),
198
+ _ => Err(MustardError::runtime("date missing")),
199
+ }
200
+ }
201
+
202
+ pub(crate) fn regexp_object(&self, key: ObjectKey) -> MustardResult<&RegExpObject> {
203
+ match &self
204
+ .objects
205
+ .get(key)
206
+ .ok_or_else(|| MustardError::runtime("object missing"))?
207
+ .kind
208
+ {
209
+ ObjectKind::RegExp(regex) => Ok(regex),
210
+ _ => Err(MustardError::runtime("regexp missing")),
211
+ }
212
+ }
213
+
214
+ pub(crate) fn regexp_object_mut(&mut self, key: ObjectKey) -> MustardResult<&mut RegExpObject> {
215
+ match &mut self
216
+ .objects
217
+ .get_mut(key)
218
+ .ok_or_else(|| MustardError::runtime("object missing"))?
219
+ .kind
220
+ {
221
+ ObjectKind::RegExp(regex) => Ok(regex),
222
+ _ => Err(MustardError::runtime("regexp missing")),
223
+ }
224
+ }
225
+
226
+ fn regexp_match_data_from_captures(
227
+ &self,
228
+ compiled: &CompiledRegExp,
229
+ text: &str,
230
+ captures: &Captures<'_>,
231
+ ) -> MustardResult<RegExpMatchData> {
232
+ let matched = captures
233
+ .get(0)
234
+ .ok_or_else(|| MustardError::runtime("regex match missing full capture"))?;
235
+ let named_groups = compiled
236
+ .regex
237
+ .capture_names()
238
+ .enumerate()
239
+ .skip(1)
240
+ .filter_map(|(index, name)| {
241
+ name.map(|name| {
242
+ (
243
+ name.to_string(),
244
+ captures
245
+ .get(index)
246
+ .map(|capture| capture.as_str().to_string()),
247
+ )
248
+ })
249
+ })
250
+ .collect::<IndexMap<_, _>>();
251
+ Ok(RegExpMatchData {
252
+ start_byte: matched.start(),
253
+ end_byte: matched.end(),
254
+ start_index: byte_index_to_char_index(text, matched.start()),
255
+ end_index: byte_index_to_char_index(text, matched.end()),
256
+ captures: (1..captures.len())
257
+ .map(|index| {
258
+ captures
259
+ .get(index)
260
+ .map(|capture| capture.as_str().to_string())
261
+ })
262
+ .collect(),
263
+ named_groups,
264
+ })
265
+ }
266
+
267
+ fn first_regexp_match_with_compiled(
268
+ &self,
269
+ compiled: &CompiledRegExp,
270
+ text: &str,
271
+ start_index: usize,
272
+ ) -> MustardResult<Option<RegExpMatchData>> {
273
+ self.check_cancellation()?;
274
+ let start_byte = char_index_to_byte_index(text, start_index);
275
+ let Some(captures) = compiled.regex.captures_at(text, start_byte) else {
276
+ return Ok(None);
277
+ };
278
+ let matched = captures
279
+ .get(0)
280
+ .ok_or_else(|| MustardError::runtime("regex match missing full capture"))?;
281
+ if compiled.flags.sticky && matched.start() != start_byte {
282
+ return Ok(None);
283
+ }
284
+ self.regexp_match_data_from_captures(compiled, text, &captures)
285
+ .map(Some)
286
+ }
287
+
288
+ pub(crate) fn first_regexp_match_from_state(
289
+ &self,
290
+ regex: &RegExpObject,
291
+ text: &str,
292
+ start_index: usize,
293
+ ) -> MustardResult<Option<RegExpMatchData>> {
294
+ let compiled = self.compile_regexp(&regex.pattern, &regex.flags)?;
295
+ self.first_regexp_match_with_compiled(&compiled, text, start_index)
296
+ }
297
+
298
+ fn first_regexp_match(
299
+ &mut self,
300
+ regex_key: ObjectKey,
301
+ text: &str,
302
+ ) -> MustardResult<Option<RegExpMatchData>> {
303
+ let regex = self.regexp_object(regex_key)?.clone();
304
+ let flags = self.validate_regexp_flags(&regex.flags)?;
305
+ let start_index = if flags.global || flags.sticky {
306
+ regex.last_index
307
+ } else {
308
+ 0
309
+ };
310
+ let matched = self.first_regexp_match_from_state(&regex, text, start_index)?;
311
+ if flags.global || flags.sticky {
312
+ let next_index = matched.as_ref().map_or(0, |matched| {
313
+ if matched.start_byte == matched.end_byte {
314
+ advance_char_index(text, matched.start_index)
315
+ } else {
316
+ matched.end_index
317
+ }
318
+ });
319
+ self.regexp_object_mut(regex_key)?.last_index = next_index;
320
+ self.refresh_object_accounting(regex_key)?;
321
+ }
322
+ Ok(matched)
323
+ }
324
+
325
+ pub(crate) fn collect_regexp_matches_from_state(
326
+ &self,
327
+ regex: &RegExpObject,
328
+ text: &str,
329
+ all: bool,
330
+ ) -> MustardResult<Vec<RegExpMatchData>> {
331
+ let compiled = self.compile_regexp(&regex.pattern, &regex.flags)?;
332
+ let mut matches = Vec::new();
333
+ let mut start_index = 0usize;
334
+ loop {
335
+ let Some(matched) =
336
+ self.first_regexp_match_with_compiled(&compiled, text, start_index)?
337
+ else {
338
+ break;
339
+ };
340
+ let next_index = if matched.start_byte == matched.end_byte {
341
+ advance_char_index(text, matched.start_index)
342
+ } else {
343
+ matched.end_index
344
+ };
345
+ matches.push(matched);
346
+ if !all {
347
+ break;
348
+ }
349
+ if next_index < start_index {
350
+ break;
351
+ }
352
+ start_index = next_index;
353
+ }
354
+ Ok(matches)
355
+ }
356
+ }