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,859 @@
1
+ use std::{
2
+ collections::HashSet,
3
+ io::{self, Read, Write},
4
+ };
5
+
6
+ use oxc_syntax::number::ToJsString;
7
+ use rand::random;
8
+
9
+ use super::*;
10
+
11
+ const JSON_HELPER_IO_CHUNK_BYTES: usize = 256;
12
+ const NUMBER_PARSE_HELPER_CHUNK_CHARS: usize = 256;
13
+
14
+ #[derive(Default)]
15
+ struct JsonStringifyTraversalState {
16
+ arrays: HashSet<ArrayKey>,
17
+ objects: HashSet<ObjectKey>,
18
+ }
19
+
20
+ struct BudgetedJsonReader<'runtime, 'source> {
21
+ runtime: &'runtime mut Runtime,
22
+ source: &'source [u8],
23
+ offset: usize,
24
+ failure: Option<MustardError>,
25
+ }
26
+
27
+ impl<'runtime, 'source> BudgetedJsonReader<'runtime, 'source> {
28
+ fn new(runtime: &'runtime mut Runtime, source: &'source [u8]) -> Self {
29
+ Self {
30
+ runtime,
31
+ source,
32
+ offset: 0,
33
+ failure: None,
34
+ }
35
+ }
36
+
37
+ fn into_failure(self) -> Option<MustardError> {
38
+ self.failure
39
+ }
40
+ }
41
+
42
+ impl Read for BudgetedJsonReader<'_, '_> {
43
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
44
+ if self.offset >= self.source.len() {
45
+ return Ok(0);
46
+ }
47
+ let chunk_len = buf
48
+ .len()
49
+ .min(self.source.len() - self.offset)
50
+ .min(JSON_HELPER_IO_CHUNK_BYTES);
51
+ if let Err(error) = self.runtime.charge_native_helper_work(1) {
52
+ self.failure = Some(error);
53
+ return Err(io::Error::other("mustard-json-parse-aborted"));
54
+ }
55
+ buf[..chunk_len].copy_from_slice(&self.source[self.offset..self.offset + chunk_len]);
56
+ self.offset += chunk_len;
57
+ Ok(chunk_len)
58
+ }
59
+ }
60
+
61
+ struct JsonOutputWriter<'runtime, 'output> {
62
+ runtime: &'runtime mut Runtime,
63
+ output: &'output mut String,
64
+ failure: Option<MustardError>,
65
+ }
66
+
67
+ impl<'runtime, 'output> JsonOutputWriter<'runtime, 'output> {
68
+ fn new(runtime: &'runtime mut Runtime, output: &'output mut String) -> Self {
69
+ Self {
70
+ runtime,
71
+ output,
72
+ failure: None,
73
+ }
74
+ }
75
+
76
+ fn into_failure(self) -> Option<MustardError> {
77
+ self.failure
78
+ }
79
+ }
80
+
81
+ impl Write for JsonOutputWriter<'_, '_> {
82
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
83
+ if buf.is_empty() {
84
+ return Ok(0);
85
+ }
86
+ let chunk_len = buf.len().min(JSON_HELPER_IO_CHUNK_BYTES);
87
+ let next_len = self
88
+ .output
89
+ .len()
90
+ .checked_add(chunk_len)
91
+ .ok_or_else(|| io::Error::other("json output overflow"))?;
92
+ if let Err(error) = self.runtime.ensure_heap_capacity(next_len) {
93
+ self.failure = Some(error);
94
+ return Err(io::Error::other("mustard-json-stringify-aborted"));
95
+ }
96
+ if let Err(error) = self.runtime.charge_native_helper_work(1) {
97
+ self.failure = Some(error);
98
+ return Err(io::Error::other("mustard-json-stringify-aborted"));
99
+ }
100
+ let chunk = std::str::from_utf8(&buf[..chunk_len]).map_err(io::Error::other)?;
101
+ self.output.push_str(chunk);
102
+ Ok(chunk_len)
103
+ }
104
+
105
+ fn flush(&mut self) -> io::Result<()> {
106
+ Ok(())
107
+ }
108
+ }
109
+
110
+ impl Runtime {
111
+ fn number_receiver(&self, value: Value, method: &str) -> MustardResult<f64> {
112
+ match value {
113
+ Value::Number(value) => Ok(value),
114
+ Value::Object(object) => match &self
115
+ .objects
116
+ .get(object)
117
+ .ok_or_else(|| MustardError::runtime("object missing"))?
118
+ .kind
119
+ {
120
+ ObjectKind::NumberObject(value) => Ok(*value),
121
+ _ => Err(MustardError::runtime(format!(
122
+ "TypeError: Number.prototype.{method} called on incompatible receiver",
123
+ ))),
124
+ },
125
+ _ => Err(MustardError::runtime(format!(
126
+ "TypeError: Number.prototype.{method} called on incompatible receiver",
127
+ ))),
128
+ }
129
+ }
130
+
131
+ fn boolean_receiver(&self, value: Value, method: &str) -> MustardResult<bool> {
132
+ match value {
133
+ Value::Bool(value) => Ok(value),
134
+ Value::Object(object) => match &self
135
+ .objects
136
+ .get(object)
137
+ .ok_or_else(|| MustardError::runtime("object missing"))?
138
+ .kind
139
+ {
140
+ ObjectKind::BooleanObject(value) => Ok(*value),
141
+ _ => Err(MustardError::runtime(format!(
142
+ "TypeError: Boolean.prototype.{method} called on incompatible receiver",
143
+ ))),
144
+ },
145
+ _ => Err(MustardError::runtime(format!(
146
+ "TypeError: Boolean.prototype.{method} called on incompatible receiver",
147
+ ))),
148
+ }
149
+ }
150
+
151
+ pub(crate) fn construct_date(&mut self, args: &[Value]) -> MustardResult<Value> {
152
+ if args.len() > 1 {
153
+ return Err(MustardError::runtime(
154
+ "TypeError: Date currently supports zero or one constructor argument",
155
+ ));
156
+ }
157
+ let timestamp_ms = match args {
158
+ [] => time_clip(current_time_millis()),
159
+ [value] => self.date_timestamp_ms_from_value(value.clone())?,
160
+ _ => unreachable!(),
161
+ };
162
+ Ok(Value::Object(self.insert_object(
163
+ IndexMap::new(),
164
+ ObjectKind::Date(DateObject { timestamp_ms }),
165
+ )?))
166
+ }
167
+
168
+ fn date_receiver(&self, value: Value, method: &str) -> MustardResult<ObjectKey> {
169
+ match value {
170
+ Value::Object(key) if self.is_date_object(key) => Ok(key),
171
+ _ => Err(MustardError::runtime(format!(
172
+ "TypeError: Date.prototype.{method} called on incompatible receiver",
173
+ ))),
174
+ }
175
+ }
176
+
177
+ pub(crate) fn call_date_get_time(&self, this_value: Value) -> MustardResult<Value> {
178
+ let date = self.date_receiver(this_value, "getTime")?;
179
+ Ok(Value::Number(self.date_object(date)?.timestamp_ms))
180
+ }
181
+
182
+ pub(crate) fn call_date_value_of(&self, this_value: Value) -> MustardResult<Value> {
183
+ let date = self.date_receiver(this_value, "valueOf")?;
184
+ Ok(Value::Number(self.date_object(date)?.timestamp_ms))
185
+ }
186
+
187
+ pub(crate) fn call_date_to_iso_string(&self, this_value: Value) -> MustardResult<Value> {
188
+ let date = self.date_receiver(this_value, "toISOString")?;
189
+ let timestamp_ms = self.date_object(date)?.timestamp_ms;
190
+ let Some(rendered) = format_iso_datetime(timestamp_ms) else {
191
+ return Err(MustardError::runtime("RangeError: Invalid time value"));
192
+ };
193
+ Ok(Value::String(rendered))
194
+ }
195
+
196
+ pub(crate) fn call_date_to_json(&self, this_value: Value) -> MustardResult<Value> {
197
+ let date = self.date_receiver(this_value, "toJSON")?;
198
+ let timestamp_ms = self.date_object(date)?.timestamp_ms;
199
+ Ok(match format_iso_datetime(timestamp_ms) {
200
+ Some(rendered) => Value::String(rendered),
201
+ None => Value::Null,
202
+ })
203
+ }
204
+
205
+ fn date_utc_fields(
206
+ &self,
207
+ this_value: Value,
208
+ method: &str,
209
+ ) -> MustardResult<Option<DateTimeFields>> {
210
+ let date = self.date_receiver(this_value, method)?;
211
+ let timestamp_ms = self.date_object(date)?.timestamp_ms;
212
+ Ok(date_time_fields_from_timestamp_ms(timestamp_ms))
213
+ }
214
+
215
+ pub(crate) fn call_date_get_utc_full_year(&self, this_value: Value) -> MustardResult<Value> {
216
+ Ok(Value::Number(
217
+ self.date_utc_fields(this_value, "getUTCFullYear")?
218
+ .map_or(f64::NAN, |fields| fields.year as f64),
219
+ ))
220
+ }
221
+
222
+ pub(crate) fn call_date_get_utc_month(&self, this_value: Value) -> MustardResult<Value> {
223
+ Ok(Value::Number(
224
+ self.date_utc_fields(this_value, "getUTCMonth")?
225
+ .map_or(f64::NAN, |fields| f64::from(fields.month - 1)),
226
+ ))
227
+ }
228
+
229
+ pub(crate) fn call_date_get_utc_date(&self, this_value: Value) -> MustardResult<Value> {
230
+ Ok(Value::Number(
231
+ self.date_utc_fields(this_value, "getUTCDate")?
232
+ .map_or(f64::NAN, |fields| f64::from(fields.day)),
233
+ ))
234
+ }
235
+
236
+ pub(crate) fn call_date_get_utc_hours(&self, this_value: Value) -> MustardResult<Value> {
237
+ Ok(Value::Number(
238
+ self.date_utc_fields(this_value, "getUTCHours")?
239
+ .map_or(f64::NAN, |fields| f64::from(fields.hour)),
240
+ ))
241
+ }
242
+
243
+ pub(crate) fn call_date_get_utc_minutes(&self, this_value: Value) -> MustardResult<Value> {
244
+ Ok(Value::Number(
245
+ self.date_utc_fields(this_value, "getUTCMinutes")?
246
+ .map_or(f64::NAN, |fields| f64::from(fields.minute)),
247
+ ))
248
+ }
249
+
250
+ pub(crate) fn call_date_get_utc_seconds(&self, this_value: Value) -> MustardResult<Value> {
251
+ Ok(Value::Number(
252
+ self.date_utc_fields(this_value, "getUTCSeconds")?
253
+ .map_or(f64::NAN, |fields| f64::from(fields.second)),
254
+ ))
255
+ }
256
+
257
+ pub(crate) fn date_timestamp_ms_from_value(&self, value: Value) -> MustardResult<f64> {
258
+ let timestamp_ms = match value {
259
+ Value::Number(value) => value,
260
+ Value::String(value) => parse_date_timestamp_ms(&value),
261
+ Value::Object(object) if self.is_date_object(object) => {
262
+ self.date_object(object)?.timestamp_ms
263
+ }
264
+ Value::Undefined => f64::NAN,
265
+ _ => {
266
+ return Err(MustardError::runtime(
267
+ "TypeError: Date currently supports only numeric, string, or Date arguments",
268
+ ));
269
+ }
270
+ };
271
+ Ok(time_clip(timestamp_ms))
272
+ }
273
+
274
+ pub(crate) fn call_error_ctor(&mut self, args: &[Value], name: &str) -> MustardResult<Value> {
275
+ let options = args.get(1).cloned().unwrap_or(Value::Undefined);
276
+ let cause = self.error_options_cause(options)?;
277
+ self.make_error_object(name, args, None, None, cause)
278
+ }
279
+
280
+ pub(crate) fn call_number_ctor(&self, args: &[Value]) -> MustardResult<Value> {
281
+ Ok(Value::Number(self.to_number(
282
+ args.first().cloned().unwrap_or(Value::Undefined),
283
+ )?))
284
+ }
285
+
286
+ pub(crate) fn call_number_parse_int(&mut self, args: &[Value]) -> MustardResult<Value> {
287
+ let input = self.to_string(args.first().cloned().unwrap_or(Value::Undefined))?;
288
+ let trimmed = input.trim_start();
289
+ let radix_value = args.get(1).cloned().unwrap_or(Value::Undefined);
290
+ let radix = if matches!(radix_value, Value::Undefined) {
291
+ None
292
+ } else {
293
+ let parsed = self.to_integer(radix_value)?;
294
+ if !(2..=36).contains(&parsed) {
295
+ return Ok(Value::Number(f64::NAN));
296
+ }
297
+ Some(parsed as u32)
298
+ };
299
+
300
+ let (sign, remainder) = if let Some(stripped) = trimmed.strip_prefix('+') {
301
+ (1.0, stripped)
302
+ } else if let Some(stripped) = trimmed.strip_prefix('-') {
303
+ (-1.0, stripped)
304
+ } else {
305
+ (1.0, trimmed)
306
+ };
307
+ let (radix, digits) =
308
+ if radix.is_none() && (remainder.starts_with("0x") || remainder.starts_with("0X")) {
309
+ (16u32, &remainder[2..])
310
+ } else {
311
+ (radix.unwrap_or(10), remainder)
312
+ };
313
+ let mut end = 0usize;
314
+ let mut saw_digit = false;
315
+ for (index, ch) in digits.char_indices() {
316
+ if index % NUMBER_PARSE_HELPER_CHUNK_CHARS == 0 {
317
+ self.charge_native_helper_work(1)?;
318
+ }
319
+ if ch.to_digit(radix).is_none() {
320
+ break;
321
+ }
322
+ saw_digit = true;
323
+ end = index + ch.len_utf8();
324
+ }
325
+ if !saw_digit {
326
+ return Ok(Value::Number(f64::NAN));
327
+ }
328
+ let parsed = i128::from_str_radix(&digits[..end], radix).unwrap_or(0) as f64 * sign;
329
+ Ok(Value::Number(parsed))
330
+ }
331
+
332
+ pub(crate) fn call_number_parse_float(&mut self, args: &[Value]) -> MustardResult<Value> {
333
+ let input = self.to_string(args.first().cloned().unwrap_or(Value::Undefined))?;
334
+ let trimmed = input.trim_start();
335
+ if trimmed.starts_with("Infinity") || trimmed.starts_with("+Infinity") {
336
+ return Ok(Value::Number(f64::INFINITY));
337
+ }
338
+ if trimmed.starts_with("-Infinity") {
339
+ return Ok(Value::Number(f64::NEG_INFINITY));
340
+ }
341
+ let mut end = 0usize;
342
+ let mut seen_digit = false;
343
+ let mut seen_dot = false;
344
+ let mut seen_exp = false;
345
+ let mut allow_sign = true;
346
+ for (index, ch) in trimmed.char_indices() {
347
+ if index % NUMBER_PARSE_HELPER_CHUNK_CHARS == 0 {
348
+ self.charge_native_helper_work(1)?;
349
+ }
350
+ let accepted = if allow_sign && matches!(ch, '+' | '-') {
351
+ allow_sign = false;
352
+ true
353
+ } else if ch.is_ascii_digit() {
354
+ seen_digit = true;
355
+ allow_sign = false;
356
+ true
357
+ } else if ch == '.' && !seen_dot && !seen_exp {
358
+ seen_dot = true;
359
+ allow_sign = false;
360
+ true
361
+ } else if matches!(ch, 'e' | 'E') && seen_digit && !seen_exp {
362
+ seen_exp = true;
363
+ allow_sign = true;
364
+ true
365
+ } else {
366
+ false
367
+ };
368
+ if !accepted {
369
+ break;
370
+ }
371
+ end = index + ch.len_utf8();
372
+ }
373
+ let parsed = trimmed[..end].parse::<f64>().unwrap_or(f64::NAN);
374
+ Ok(Value::Number(parsed))
375
+ }
376
+
377
+ pub(crate) fn call_number_is_nan(&self, args: &[Value]) -> Value {
378
+ Value::Bool(matches!(args.first(), Some(Value::Number(value)) if value.is_nan()))
379
+ }
380
+
381
+ pub(crate) fn call_number_is_finite(&self, args: &[Value]) -> Value {
382
+ Value::Bool(matches!(args.first(), Some(Value::Number(value)) if value.is_finite()))
383
+ }
384
+
385
+ pub(crate) fn call_number_is_integer(&self, args: &[Value]) -> Value {
386
+ Value::Bool(matches!(
387
+ args.first(),
388
+ Some(Value::Number(value)) if value.is_finite() && value.fract() == 0.0
389
+ ))
390
+ }
391
+
392
+ pub(crate) fn call_number_is_safe_integer(&self, args: &[Value]) -> Value {
393
+ Value::Bool(matches!(
394
+ args.first(),
395
+ Some(Value::Number(value))
396
+ if value.is_finite()
397
+ && value.fract() == 0.0
398
+ && value.abs() <= 9_007_199_254_740_991.0
399
+ ))
400
+ }
401
+
402
+ pub(crate) fn call_string_ctor(&self, args: &[Value]) -> MustardResult<Value> {
403
+ Ok(Value::String(self.to_string(
404
+ args.first().cloned().unwrap_or(Value::Undefined),
405
+ )?))
406
+ }
407
+
408
+ pub(crate) fn call_boolean_ctor(&self, args: &[Value]) -> MustardResult<Value> {
409
+ Ok(Value::Bool(is_truthy(
410
+ args.first().unwrap_or(&Value::Undefined),
411
+ )))
412
+ }
413
+
414
+ pub(crate) fn call_number_to_string(&self, this_value: Value) -> MustardResult<Value> {
415
+ Ok(Value::String(self.to_string(Value::Number(
416
+ self.number_receiver(this_value, "toString")?,
417
+ ))?))
418
+ }
419
+
420
+ pub(crate) fn call_number_value_of(&self, this_value: Value) -> MustardResult<Value> {
421
+ Ok(Value::Number(self.number_receiver(this_value, "valueOf")?))
422
+ }
423
+
424
+ pub(crate) fn call_boolean_to_string(&self, this_value: Value) -> MustardResult<Value> {
425
+ Ok(Value::String(self.to_string(Value::Bool(
426
+ self.boolean_receiver(this_value, "toString")?,
427
+ ))?))
428
+ }
429
+
430
+ pub(crate) fn call_boolean_value_of(&self, this_value: Value) -> MustardResult<Value> {
431
+ Ok(Value::Bool(self.boolean_receiver(this_value, "valueOf")?))
432
+ }
433
+
434
+ pub(crate) fn construct_number(&mut self, args: &[Value]) -> MustardResult<Value> {
435
+ let value = self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?;
436
+ Ok(Value::Object(self.insert_object(
437
+ IndexMap::new(),
438
+ ObjectKind::NumberObject(value),
439
+ )?))
440
+ }
441
+
442
+ pub(crate) fn construct_string(&mut self, args: &[Value]) -> MustardResult<Value> {
443
+ let value = self.to_string(args.first().cloned().unwrap_or(Value::Undefined))?;
444
+ Ok(Value::Object(self.insert_object(
445
+ IndexMap::new(),
446
+ ObjectKind::StringObject(value),
447
+ )?))
448
+ }
449
+
450
+ pub(crate) fn construct_boolean(&mut self, args: &[Value]) -> MustardResult<Value> {
451
+ let value = is_truthy(args.first().unwrap_or(&Value::Undefined));
452
+ Ok(Value::Object(self.insert_object(
453
+ IndexMap::new(),
454
+ ObjectKind::BooleanObject(value),
455
+ )?))
456
+ }
457
+
458
+ pub(crate) fn call_math_abs(&self, args: &[Value]) -> MustardResult<Value> {
459
+ Ok(Value::Number(
460
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
461
+ .abs(),
462
+ ))
463
+ }
464
+
465
+ pub(crate) fn call_math_max(&self, args: &[Value]) -> MustardResult<Value> {
466
+ let mut value = f64::NEG_INFINITY;
467
+ for arg in args {
468
+ value = value.max(self.to_number(arg.clone())?);
469
+ }
470
+ Ok(Value::Number(value))
471
+ }
472
+
473
+ pub(crate) fn call_math_min(&self, args: &[Value]) -> MustardResult<Value> {
474
+ let mut value = f64::INFINITY;
475
+ for arg in args {
476
+ value = value.min(self.to_number(arg.clone())?);
477
+ }
478
+ Ok(Value::Number(value))
479
+ }
480
+
481
+ pub(crate) fn call_math_floor(&self, args: &[Value]) -> MustardResult<Value> {
482
+ Ok(Value::Number(
483
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
484
+ .floor(),
485
+ ))
486
+ }
487
+
488
+ pub(crate) fn call_math_ceil(&self, args: &[Value]) -> MustardResult<Value> {
489
+ Ok(Value::Number(
490
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
491
+ .ceil(),
492
+ ))
493
+ }
494
+
495
+ pub(crate) fn call_math_round(&self, args: &[Value]) -> MustardResult<Value> {
496
+ Ok(Value::Number(
497
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
498
+ .round(),
499
+ ))
500
+ }
501
+
502
+ pub(crate) fn call_math_pow(&self, args: &[Value]) -> MustardResult<Value> {
503
+ Ok(Value::Number(
504
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
505
+ .powf(self.to_number(args.get(1).cloned().unwrap_or(Value::Undefined))?),
506
+ ))
507
+ }
508
+
509
+ pub(crate) fn call_math_sqrt(&self, args: &[Value]) -> MustardResult<Value> {
510
+ Ok(Value::Number(
511
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
512
+ .sqrt(),
513
+ ))
514
+ }
515
+
516
+ pub(crate) fn call_math_trunc(&self, args: &[Value]) -> MustardResult<Value> {
517
+ Ok(Value::Number(
518
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
519
+ .trunc(),
520
+ ))
521
+ }
522
+
523
+ pub(crate) fn call_math_sign(&self, args: &[Value]) -> MustardResult<Value> {
524
+ let value = self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?;
525
+ Ok(Value::Number(if value.is_nan() {
526
+ f64::NAN
527
+ } else if value == 0.0 {
528
+ value
529
+ } else if value.is_sign_positive() {
530
+ 1.0
531
+ } else {
532
+ -1.0
533
+ }))
534
+ }
535
+
536
+ pub(crate) fn call_math_log(&self, args: &[Value]) -> MustardResult<Value> {
537
+ Ok(Value::Number(
538
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
539
+ .ln(),
540
+ ))
541
+ }
542
+
543
+ pub(crate) fn call_math_exp(&self, args: &[Value]) -> MustardResult<Value> {
544
+ Ok(Value::Number(
545
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
546
+ .exp(),
547
+ ))
548
+ }
549
+
550
+ pub(crate) fn call_math_log2(&self, args: &[Value]) -> MustardResult<Value> {
551
+ Ok(Value::Number(
552
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
553
+ .log2(),
554
+ ))
555
+ }
556
+
557
+ pub(crate) fn call_math_log10(&self, args: &[Value]) -> MustardResult<Value> {
558
+ Ok(Value::Number(
559
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
560
+ .log10(),
561
+ ))
562
+ }
563
+
564
+ pub(crate) fn call_math_sin(&self, args: &[Value]) -> MustardResult<Value> {
565
+ Ok(Value::Number(
566
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
567
+ .sin(),
568
+ ))
569
+ }
570
+
571
+ pub(crate) fn call_math_cos(&self, args: &[Value]) -> MustardResult<Value> {
572
+ Ok(Value::Number(
573
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
574
+ .cos(),
575
+ ))
576
+ }
577
+
578
+ pub(crate) fn call_math_atan2(&self, args: &[Value]) -> MustardResult<Value> {
579
+ Ok(Value::Number(
580
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
581
+ .atan2(self.to_number(args.get(1).cloned().unwrap_or(Value::Undefined))?),
582
+ ))
583
+ }
584
+
585
+ pub(crate) fn call_math_hypot(&self, args: &[Value]) -> MustardResult<Value> {
586
+ let mut value: f64 = 0.0;
587
+ for arg in args {
588
+ value = value.hypot(self.to_number(arg.clone())?);
589
+ }
590
+ Ok(Value::Number(value))
591
+ }
592
+
593
+ pub(crate) fn call_math_cbrt(&self, args: &[Value]) -> MustardResult<Value> {
594
+ Ok(Value::Number(
595
+ self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?
596
+ .cbrt(),
597
+ ))
598
+ }
599
+
600
+ pub(crate) fn call_math_random(&self) -> Value {
601
+ Value::Number(random::<f64>())
602
+ }
603
+
604
+ pub(crate) fn call_json_stringify(&mut self, args: &[Value]) -> MustardResult<Value> {
605
+ let value = args.first().cloned().unwrap_or(Value::Undefined);
606
+ let mut traversal = JsonStringifyTraversalState::default();
607
+ let mut output = String::new();
608
+ if self.json_stringify_value(&value, &mut traversal, &mut output)? {
609
+ Ok(Value::String(output))
610
+ } else {
611
+ Ok(Value::Undefined)
612
+ }
613
+ }
614
+
615
+ pub(crate) fn call_json_parse(&mut self, args: &[Value]) -> MustardResult<Value> {
616
+ let source = self.to_string(args.first().cloned().unwrap_or(Value::Undefined))?;
617
+ let mut reader = BudgetedJsonReader::new(self, source.as_bytes());
618
+ let parsed: serde_json::Value = match serde_json::from_reader(&mut reader) {
619
+ Ok(parsed) => parsed,
620
+ Err(error) => {
621
+ if let Some(runtime_error) = reader.into_failure() {
622
+ return Err(runtime_error);
623
+ }
624
+ return Err(MustardError::runtime(error.to_string()));
625
+ }
626
+ };
627
+ drop(reader);
628
+ self.value_from_json(parsed)
629
+ }
630
+
631
+ fn json_stringify_value(
632
+ &mut self,
633
+ value: &Value,
634
+ traversal: &mut JsonStringifyTraversalState,
635
+ output: &mut String,
636
+ ) -> MustardResult<bool> {
637
+ self.charge_native_helper_work(1)?;
638
+ match value {
639
+ Value::Undefined => Ok(false),
640
+ Value::Null => {
641
+ self.push_json_fragment(output, "null")?;
642
+ Ok(true)
643
+ }
644
+ Value::Bool(value) => {
645
+ if *value {
646
+ self.push_json_fragment(output, "true")?;
647
+ } else {
648
+ self.push_json_fragment(output, "false")?;
649
+ }
650
+ Ok(true)
651
+ }
652
+ Value::Number(value) => {
653
+ self.push_json_fragment(output, &json_number_to_string(*value))?;
654
+ Ok(true)
655
+ }
656
+ Value::BigInt(_) => Err(MustardError::runtime(
657
+ "TypeError: Do not know how to serialize a BigInt",
658
+ )),
659
+ Value::String(value) => {
660
+ self.push_json_string(output, value)?;
661
+ Ok(true)
662
+ }
663
+ Value::Closure(_) | Value::BuiltinFunction(_) | Value::HostFunction(_) => Ok(false),
664
+ Value::Array(array) => {
665
+ if !traversal.arrays.insert(*array) {
666
+ return Err(json_stringify_cycle_error());
667
+ }
668
+ let elements = self
669
+ .arrays
670
+ .get(*array)
671
+ .ok_or_else(|| MustardError::runtime("array missing"))?
672
+ .elements
673
+ .clone();
674
+ let result = (|| {
675
+ self.push_json_fragment(output, "[")?;
676
+ for (index, value) in elements.iter().enumerate() {
677
+ if index > 0 {
678
+ self.push_json_fragment(output, ",")?;
679
+ }
680
+ let value = value.as_ref().unwrap_or(&Value::Undefined);
681
+ if !self.json_stringify_value(value, traversal, output)? {
682
+ self.push_json_fragment(output, "null")?;
683
+ }
684
+ }
685
+ self.push_json_fragment(output, "]")?;
686
+ Ok(true)
687
+ })();
688
+ traversal.arrays.remove(array);
689
+ result
690
+ }
691
+ Value::Object(object) => self.json_stringify_object(*object, traversal, output),
692
+ Value::Map(_) | Value::Set(_) | Value::Iterator(_) | Value::Promise(_) => {
693
+ self.push_json_fragment(output, "{}")?;
694
+ Ok(true)
695
+ }
696
+ }
697
+ }
698
+
699
+ fn json_stringify_object(
700
+ &mut self,
701
+ object: ObjectKey,
702
+ traversal: &mut JsonStringifyTraversalState,
703
+ output: &mut String,
704
+ ) -> MustardResult<bool> {
705
+ let date_timestamp_ms = {
706
+ let object_ref = self
707
+ .objects
708
+ .get(object)
709
+ .ok_or_else(|| MustardError::runtime("object missing"))?;
710
+ match &object_ref.kind {
711
+ ObjectKind::Date(date) => Some(date.timestamp_ms),
712
+ _ => None,
713
+ }
714
+ };
715
+ if let Some(timestamp_ms) = date_timestamp_ms {
716
+ self.push_json_fragment(output, &self.json_stringify_date(timestamp_ms)?)?;
717
+ return Ok(true);
718
+ }
719
+
720
+ if !traversal.objects.insert(object) {
721
+ return Err(json_stringify_cycle_error());
722
+ }
723
+
724
+ let keys = {
725
+ let object_ref = self
726
+ .objects
727
+ .get(object)
728
+ .ok_or_else(|| MustardError::runtime("object missing"))?;
729
+ match &object_ref.kind {
730
+ ObjectKind::Error(_) => {
731
+ ordered_own_property_keys_filtered(&object_ref.properties, |key, _| {
732
+ key != "name" && key != "message"
733
+ })
734
+ }
735
+ _ => ordered_own_property_keys(&object_ref.properties),
736
+ }
737
+ };
738
+ let result = (|| {
739
+ self.push_json_fragment(output, "{")?;
740
+ let mut wrote_any = false;
741
+ for key in keys {
742
+ let value = self
743
+ .objects
744
+ .get(object)
745
+ .ok_or_else(|| MustardError::runtime("object missing"))?
746
+ .properties
747
+ .get(&key)
748
+ .cloned()
749
+ .ok_or_else(|| MustardError::runtime("object property missing"))?;
750
+ let rewind = output.len();
751
+ if wrote_any {
752
+ self.push_json_fragment(output, ",")?;
753
+ }
754
+ self.push_json_string(output, &key)?;
755
+ self.push_json_fragment(output, ":")?;
756
+ if !self.json_stringify_value(&value, traversal, output)? {
757
+ output.truncate(rewind);
758
+ continue;
759
+ }
760
+ wrote_any = true;
761
+ }
762
+ self.push_json_fragment(output, "}")?;
763
+ Ok(true)
764
+ })();
765
+
766
+ traversal.objects.remove(&object);
767
+ result
768
+ }
769
+
770
+ fn json_stringify_date(&self, timestamp_ms: f64) -> MustardResult<String> {
771
+ if !timestamp_ms.is_finite() {
772
+ return Ok("null".to_string());
773
+ }
774
+
775
+ let timestamp_nanos = timestamp_ms * 1_000_000.0;
776
+ if !timestamp_nanos.is_finite()
777
+ || timestamp_nanos < i128::MIN as f64
778
+ || timestamp_nanos > i128::MAX as f64
779
+ {
780
+ return Ok("null".to_string());
781
+ }
782
+
783
+ let Some(rendered) = format_iso_datetime(timestamp_ms) else {
784
+ return Ok("null".to_string());
785
+ };
786
+ serde_json::to_string(&rendered).map_err(|error| MustardError::runtime(error.to_string()))
787
+ }
788
+
789
+ fn push_json_fragment(&mut self, output: &mut String, fragment: &str) -> MustardResult<()> {
790
+ let next_len = output
791
+ .len()
792
+ .checked_add(fragment.len())
793
+ .ok_or_else(|| limit_error("heap limit exceeded"))?;
794
+ self.ensure_heap_capacity(next_len)?;
795
+ let units = fragment.len().max(1).div_ceil(JSON_HELPER_IO_CHUNK_BYTES);
796
+ self.charge_native_helper_work(units)?;
797
+ output.push_str(fragment);
798
+ Ok(())
799
+ }
800
+
801
+ fn push_json_string(&mut self, output: &mut String, value: &str) -> MustardResult<()> {
802
+ let mut writer = JsonOutputWriter::new(self, output);
803
+ let result = serde_json::to_writer(&mut writer, value);
804
+ match result {
805
+ Ok(()) => {
806
+ if let Some(runtime_error) = writer.into_failure() {
807
+ Err(runtime_error)
808
+ } else {
809
+ Ok(())
810
+ }
811
+ }
812
+ Err(error) => {
813
+ if let Some(runtime_error) = writer.into_failure() {
814
+ Err(runtime_error)
815
+ } else {
816
+ Err(MustardError::runtime(error.to_string()))
817
+ }
818
+ }
819
+ }
820
+ }
821
+ }
822
+
823
+ impl Runtime {
824
+ fn error_options_cause(&self, options: Value) -> MustardResult<Option<Option<Value>>> {
825
+ match options {
826
+ Value::Undefined | Value::Null => Ok(None),
827
+ Value::Object(object) => {
828
+ let object = self
829
+ .objects
830
+ .get(object)
831
+ .ok_or_else(|| MustardError::runtime("object missing"))?;
832
+ let cause = object.properties.get("cause").cloned();
833
+ Ok(Some(cause))
834
+ }
835
+ Value::Array(array) => {
836
+ let array = self
837
+ .arrays
838
+ .get(array)
839
+ .ok_or_else(|| MustardError::runtime("array missing"))?;
840
+ Ok(Some(array.properties.get("cause").cloned()))
841
+ }
842
+ _ => Err(MustardError::runtime(
843
+ "TypeError: Error options must be an object in the supported surface",
844
+ )),
845
+ }
846
+ }
847
+ }
848
+
849
+ fn json_number_to_string(value: f64) -> String {
850
+ if !value.is_finite() {
851
+ "null".to_string()
852
+ } else {
853
+ value.to_js_string()
854
+ }
855
+ }
856
+
857
+ fn json_stringify_cycle_error() -> MustardError {
858
+ MustardError::runtime("TypeError: Converting circular structure to JSON")
859
+ }