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,553 @@
1
+ use super::*;
2
+
3
+ impl Runtime {
4
+ fn normalize_intl_locale(&self, value: Option<Value>) -> MustardResult<String> {
5
+ let Some(value) = value else {
6
+ return Ok("en-US".to_string());
7
+ };
8
+ match value {
9
+ Value::Undefined => Ok("en-US".to_string()),
10
+ Value::String(locale) if locale == "en-US" => Ok(locale),
11
+ Value::Array(array) => {
12
+ let first = self
13
+ .arrays
14
+ .get(array)
15
+ .ok_or_else(|| MustardError::runtime("array missing"))?
16
+ .elements
17
+ .iter()
18
+ .flatten()
19
+ .next()
20
+ .cloned()
21
+ .unwrap_or(Value::String("en-US".to_string()));
22
+ self.normalize_intl_locale(Some(first))
23
+ }
24
+ _ => Err(MustardError::runtime(
25
+ "TypeError: Intl currently supports only the `en-US` locale",
26
+ )),
27
+ }
28
+ }
29
+
30
+ fn intl_options_object(&self, value: Option<Value>) -> MustardResult<Option<ObjectKey>> {
31
+ match value.unwrap_or(Value::Undefined) {
32
+ Value::Undefined | Value::Null => Ok(None),
33
+ Value::Object(object) => Ok(Some(object)),
34
+ _ => Err(MustardError::runtime(
35
+ "TypeError: Intl options must be a plain object in the supported surface",
36
+ )),
37
+ }
38
+ }
39
+
40
+ fn intl_option_value(&self, object: Option<ObjectKey>, key: &str) -> MustardResult<Value> {
41
+ let Some(object) = object else {
42
+ return Ok(Value::Undefined);
43
+ };
44
+ Ok(self
45
+ .objects
46
+ .get(object)
47
+ .ok_or_else(|| MustardError::runtime("object missing"))?
48
+ .properties
49
+ .get(key)
50
+ .cloned()
51
+ .unwrap_or(Value::Undefined))
52
+ }
53
+
54
+ fn intl_option_field_style(
55
+ &self,
56
+ object: Option<ObjectKey>,
57
+ key: &str,
58
+ ) -> MustardResult<Option<IntlFieldStyle>> {
59
+ Ok(match self.intl_option_value(object, key)? {
60
+ Value::Undefined => None,
61
+ Value::String(value) if value == "numeric" => Some(IntlFieldStyle::Numeric),
62
+ Value::String(value) if value == "2-digit" => Some(IntlFieldStyle::TwoDigit),
63
+ _ => {
64
+ return Err(MustardError::runtime(format!(
65
+ "TypeError: Intl.{key} only supports `numeric` or `2-digit`",
66
+ )));
67
+ }
68
+ })
69
+ }
70
+
71
+ fn intl_option_string(
72
+ &self,
73
+ object: Option<ObjectKey>,
74
+ key: &str,
75
+ ) -> MustardResult<Option<String>> {
76
+ Ok(match self.intl_option_value(object, key)? {
77
+ Value::Undefined => None,
78
+ value => Some(self.to_string(value)?),
79
+ })
80
+ }
81
+
82
+ fn intl_option_bool(
83
+ &self,
84
+ object: Option<ObjectKey>,
85
+ key: &str,
86
+ ) -> MustardResult<Option<bool>> {
87
+ Ok(match self.intl_option_value(object, key)? {
88
+ Value::Undefined => None,
89
+ Value::Bool(value) => Some(value),
90
+ _ => {
91
+ return Err(MustardError::runtime(format!(
92
+ "TypeError: Intl `{key}` must be a boolean in the supported surface",
93
+ )));
94
+ }
95
+ })
96
+ }
97
+
98
+ fn intl_option_digits(
99
+ &self,
100
+ object: Option<ObjectKey>,
101
+ key: &str,
102
+ ) -> MustardResult<Option<usize>> {
103
+ match self.intl_option_value(object, key)? {
104
+ Value::Undefined => Ok(None),
105
+ value => {
106
+ let digits = self.to_integer(value)?;
107
+ if !(0..=20).contains(&digits) {
108
+ return Err(MustardError::runtime(format!(
109
+ "RangeError: Intl `{key}` must be between 0 and 20",
110
+ )));
111
+ }
112
+ Ok(Some(digits as usize))
113
+ }
114
+ }
115
+ }
116
+
117
+ fn intl_assert_supported_option_keys(
118
+ &self,
119
+ object: Option<ObjectKey>,
120
+ ctor: &str,
121
+ allowed: &[&str],
122
+ ) -> MustardResult<()> {
123
+ let Some(object) = object else {
124
+ return Ok(());
125
+ };
126
+ for key in self
127
+ .objects
128
+ .get(object)
129
+ .ok_or_else(|| MustardError::runtime("object missing"))?
130
+ .properties
131
+ .keys()
132
+ {
133
+ if !allowed
134
+ .iter()
135
+ .any(|allowed_key| allowed_key == &key.as_str())
136
+ {
137
+ return Err(MustardError::runtime(format!(
138
+ "TypeError: Intl.{ctor} does not support the `{key}` option",
139
+ )));
140
+ }
141
+ }
142
+ Ok(())
143
+ }
144
+
145
+ pub(crate) fn construct_intl_date_time_format(
146
+ &mut self,
147
+ args: &[Value],
148
+ ) -> MustardResult<Value> {
149
+ let locale = self.normalize_intl_locale(args.first().cloned())?;
150
+ let options = self.intl_options_object(args.get(1).cloned())?;
151
+ self.intl_assert_supported_option_keys(
152
+ options,
153
+ "DateTimeFormat",
154
+ &[
155
+ "timeZone", "year", "month", "day", "hour", "minute", "second",
156
+ ],
157
+ )?;
158
+ let time_zone = match self.intl_option_string(options, "timeZone")? {
159
+ None => "UTC".to_string(),
160
+ Some(value) if value == "UTC" => value,
161
+ Some(_) => {
162
+ return Err(MustardError::runtime(
163
+ "TypeError: Intl.DateTimeFormat currently supports only the `UTC` timeZone",
164
+ ));
165
+ }
166
+ };
167
+ let mut year = self.intl_option_field_style(options, "year")?;
168
+ let mut month = self.intl_option_field_style(options, "month")?;
169
+ let mut day = self.intl_option_field_style(options, "day")?;
170
+ let hour = self.intl_option_field_style(options, "hour")?;
171
+ let minute = self.intl_option_field_style(options, "minute")?;
172
+ let second = self.intl_option_field_style(options, "second")?;
173
+ if year.is_none()
174
+ && month.is_none()
175
+ && day.is_none()
176
+ && hour.is_none()
177
+ && minute.is_none()
178
+ && second.is_none()
179
+ {
180
+ year = Some(IntlFieldStyle::Numeric);
181
+ month = Some(IntlFieldStyle::Numeric);
182
+ day = Some(IntlFieldStyle::Numeric);
183
+ }
184
+ Ok(Value::Object(self.insert_object(
185
+ IndexMap::new(),
186
+ ObjectKind::IntlDateTimeFormat(IntlDateTimeFormatObject {
187
+ locale,
188
+ time_zone,
189
+ year,
190
+ month,
191
+ day,
192
+ hour,
193
+ minute,
194
+ second,
195
+ }),
196
+ )?))
197
+ }
198
+
199
+ pub(crate) fn construct_intl_number_format(&mut self, args: &[Value]) -> MustardResult<Value> {
200
+ let locale = self.normalize_intl_locale(args.first().cloned())?;
201
+ let options = self.intl_options_object(args.get(1).cloned())?;
202
+ self.intl_assert_supported_option_keys(
203
+ options,
204
+ "NumberFormat",
205
+ &[
206
+ "style",
207
+ "currency",
208
+ "minimumFractionDigits",
209
+ "maximumFractionDigits",
210
+ "useGrouping",
211
+ ],
212
+ )?;
213
+ let style = match self.intl_option_string(options, "style")? {
214
+ None => IntlNumberStyle::Decimal,
215
+ Some(value) if value == "decimal" => IntlNumberStyle::Decimal,
216
+ Some(value) if value == "percent" => IntlNumberStyle::Percent,
217
+ Some(value) if value == "currency" => IntlNumberStyle::Currency,
218
+ Some(_) => {
219
+ return Err(MustardError::runtime(
220
+ "TypeError: Intl.NumberFormat currently supports `decimal`, `percent`, or `currency` styles",
221
+ ));
222
+ }
223
+ };
224
+ let currency = self.intl_option_string(options, "currency")?;
225
+ if style == IntlNumberStyle::Currency && currency.as_deref() != Some("USD") {
226
+ return Err(MustardError::runtime(
227
+ "TypeError: Intl.NumberFormat currency style currently supports only `USD`",
228
+ ));
229
+ }
230
+ let minimum_fraction_digits = self
231
+ .intl_option_digits(options, "minimumFractionDigits")?
232
+ .unwrap_or(match style {
233
+ IntlNumberStyle::Currency => 2,
234
+ _ => 0,
235
+ });
236
+ let maximum_fraction_digits = self
237
+ .intl_option_digits(options, "maximumFractionDigits")?
238
+ .unwrap_or(match style {
239
+ IntlNumberStyle::Currency => 2,
240
+ IntlNumberStyle::Percent => 0,
241
+ IntlNumberStyle::Decimal => 3,
242
+ });
243
+ if minimum_fraction_digits > maximum_fraction_digits {
244
+ return Err(MustardError::runtime(
245
+ "RangeError: Intl.NumberFormat minimumFractionDigits cannot exceed maximumFractionDigits",
246
+ ));
247
+ }
248
+ let use_grouping = self
249
+ .intl_option_bool(options, "useGrouping")?
250
+ .unwrap_or(true);
251
+ Ok(Value::Object(self.insert_object(
252
+ IndexMap::new(),
253
+ ObjectKind::IntlNumberFormat(IntlNumberFormatObject {
254
+ locale,
255
+ style,
256
+ currency,
257
+ minimum_fraction_digits,
258
+ maximum_fraction_digits,
259
+ use_grouping,
260
+ }),
261
+ )?))
262
+ }
263
+
264
+ fn intl_date_time_format_receiver(
265
+ &self,
266
+ value: Value,
267
+ method: &str,
268
+ ) -> MustardResult<&IntlDateTimeFormatObject> {
269
+ match value {
270
+ Value::Object(object) => match &self
271
+ .objects
272
+ .get(object)
273
+ .ok_or_else(|| MustardError::runtime("object missing"))?
274
+ .kind
275
+ {
276
+ ObjectKind::IntlDateTimeFormat(formatter) => Ok(formatter),
277
+ _ => Err(MustardError::runtime(format!(
278
+ "TypeError: Intl.DateTimeFormat.prototype.{method} called on incompatible receiver",
279
+ ))),
280
+ },
281
+ _ => Err(MustardError::runtime(format!(
282
+ "TypeError: Intl.DateTimeFormat.prototype.{method} called on incompatible receiver",
283
+ ))),
284
+ }
285
+ }
286
+
287
+ fn intl_number_format_receiver(
288
+ &self,
289
+ value: Value,
290
+ method: &str,
291
+ ) -> MustardResult<&IntlNumberFormatObject> {
292
+ match value {
293
+ Value::Object(object) => match &self
294
+ .objects
295
+ .get(object)
296
+ .ok_or_else(|| MustardError::runtime("object missing"))?
297
+ .kind
298
+ {
299
+ ObjectKind::IntlNumberFormat(formatter) => Ok(formatter),
300
+ _ => Err(MustardError::runtime(format!(
301
+ "TypeError: Intl.NumberFormat.prototype.{method} called on incompatible receiver",
302
+ ))),
303
+ },
304
+ _ => Err(MustardError::runtime(format!(
305
+ "TypeError: Intl.NumberFormat.prototype.{method} called on incompatible receiver",
306
+ ))),
307
+ }
308
+ }
309
+
310
+ fn format_intl_field(value: u8, style: IntlFieldStyle) -> String {
311
+ match style {
312
+ IntlFieldStyle::Numeric => value.to_string(),
313
+ IntlFieldStyle::TwoDigit => format!("{value:02}"),
314
+ }
315
+ }
316
+
317
+ pub(crate) fn call_intl_date_time_format_format(
318
+ &self,
319
+ this_value: Value,
320
+ args: &[Value],
321
+ ) -> MustardResult<Value> {
322
+ let formatter = self.intl_date_time_format_receiver(this_value, "format")?;
323
+ let timestamp_ms = match args.first().cloned().unwrap_or(Value::Undefined) {
324
+ Value::Undefined => current_time_millis(),
325
+ value => self.date_timestamp_ms_from_value(value)?,
326
+ };
327
+ let Some(datetime) = date_time_fields_from_timestamp_ms(timestamp_ms) else {
328
+ return Err(MustardError::runtime("RangeError: Invalid time value"));
329
+ };
330
+ let mut date_parts = Vec::new();
331
+ if let Some(month) = formatter.month {
332
+ date_parts.push(Self::format_intl_field(datetime.month, month));
333
+ }
334
+ if let Some(day) = formatter.day {
335
+ date_parts.push(Self::format_intl_field(datetime.day, day));
336
+ }
337
+ if let Some(year) = formatter.year {
338
+ date_parts.push(match year {
339
+ IntlFieldStyle::Numeric => datetime.year.to_string(),
340
+ IntlFieldStyle::TwoDigit => format!("{:02}", datetime.year.rem_euclid(100)),
341
+ });
342
+ }
343
+ let mut rendered = if date_parts.is_empty() {
344
+ String::new()
345
+ } else {
346
+ date_parts.join("/")
347
+ };
348
+ let mut rendered_time = None;
349
+ if let Some(hour_style) = formatter.hour {
350
+ let hour_24 = datetime.hour;
351
+ let meridiem = if hour_24 < 12 { "AM" } else { "PM" };
352
+ let hour_12 = match hour_24 % 12 {
353
+ 0 => 12,
354
+ value => value,
355
+ };
356
+ let mut time_parts = vec![match hour_style {
357
+ IntlFieldStyle::Numeric => hour_12.to_string(),
358
+ IntlFieldStyle::TwoDigit => format!("{hour_12:02}"),
359
+ }];
360
+ if let Some(minute) = formatter.minute {
361
+ time_parts.push(Self::format_intl_field(datetime.minute, minute));
362
+ }
363
+ if let Some(second) = formatter.second {
364
+ time_parts.push(Self::format_intl_field(datetime.second, second));
365
+ }
366
+ rendered_time = Some(format!("{} {meridiem}", time_parts.join(":")));
367
+ } else {
368
+ let mut time_parts = Vec::new();
369
+ if let Some(minute) = formatter.minute {
370
+ time_parts.push(Self::format_intl_field(datetime.minute, minute));
371
+ }
372
+ if let Some(second) = formatter.second {
373
+ time_parts.push(Self::format_intl_field(datetime.second, second));
374
+ }
375
+ if !time_parts.is_empty() {
376
+ rendered_time = Some(time_parts.join(":"));
377
+ }
378
+ }
379
+ if let Some(time) = rendered_time {
380
+ if !rendered.is_empty() {
381
+ rendered.push_str(", ");
382
+ }
383
+ rendered.push_str(&time);
384
+ }
385
+ Ok(Value::String(rendered))
386
+ }
387
+
388
+ pub(crate) fn call_intl_date_time_format_resolved_options(
389
+ &mut self,
390
+ this_value: Value,
391
+ ) -> MustardResult<Value> {
392
+ let formatter = self
393
+ .intl_date_time_format_receiver(this_value, "resolvedOptions")?
394
+ .clone();
395
+ let mut properties = IndexMap::new();
396
+ properties.insert("locale".to_string(), Value::String(formatter.locale));
397
+ properties.insert("timeZone".to_string(), Value::String(formatter.time_zone));
398
+ if let Some(year) = formatter.year {
399
+ properties.insert(
400
+ "year".to_string(),
401
+ Value::String(match year {
402
+ IntlFieldStyle::Numeric => "numeric".to_string(),
403
+ IntlFieldStyle::TwoDigit => "2-digit".to_string(),
404
+ }),
405
+ );
406
+ }
407
+ if let Some(month) = formatter.month {
408
+ properties.insert(
409
+ "month".to_string(),
410
+ Value::String(match month {
411
+ IntlFieldStyle::Numeric => "numeric".to_string(),
412
+ IntlFieldStyle::TwoDigit => "2-digit".to_string(),
413
+ }),
414
+ );
415
+ }
416
+ if let Some(day) = formatter.day {
417
+ properties.insert(
418
+ "day".to_string(),
419
+ Value::String(match day {
420
+ IntlFieldStyle::Numeric => "numeric".to_string(),
421
+ IntlFieldStyle::TwoDigit => "2-digit".to_string(),
422
+ }),
423
+ );
424
+ }
425
+ if let Some(hour) = formatter.hour {
426
+ properties.insert(
427
+ "hour".to_string(),
428
+ Value::String(match hour {
429
+ IntlFieldStyle::Numeric => "numeric".to_string(),
430
+ IntlFieldStyle::TwoDigit => "2-digit".to_string(),
431
+ }),
432
+ );
433
+ }
434
+ if let Some(minute) = formatter.minute {
435
+ properties.insert(
436
+ "minute".to_string(),
437
+ Value::String(match minute {
438
+ IntlFieldStyle::Numeric => "numeric".to_string(),
439
+ IntlFieldStyle::TwoDigit => "2-digit".to_string(),
440
+ }),
441
+ );
442
+ }
443
+ if let Some(second) = formatter.second {
444
+ properties.insert(
445
+ "second".to_string(),
446
+ Value::String(match second {
447
+ IntlFieldStyle::Numeric => "numeric".to_string(),
448
+ IntlFieldStyle::TwoDigit => "2-digit".to_string(),
449
+ }),
450
+ );
451
+ }
452
+ Ok(Value::Object(
453
+ self.insert_object(properties, ObjectKind::Plain)?,
454
+ ))
455
+ }
456
+
457
+ fn format_intl_number(&self, formatter: &IntlNumberFormatObject, number: f64) -> String {
458
+ let value = match formatter.style {
459
+ IntlNumberStyle::Percent => number * 100.0,
460
+ _ => number,
461
+ };
462
+ if !value.is_finite() {
463
+ return value.to_string();
464
+ }
465
+ let rounded = format!("{:.*}", formatter.maximum_fraction_digits, value.abs());
466
+ let mut parts = rounded.split('.').collect::<Vec<_>>();
467
+ let mut integer = parts.remove(0).to_string();
468
+ let mut fraction = parts.first().copied().unwrap_or("").to_string();
469
+ while fraction.len() > formatter.minimum_fraction_digits && fraction.ends_with('0') {
470
+ fraction.pop();
471
+ }
472
+ if formatter.use_grouping {
473
+ integer = format_en_us_number_grouped(&integer);
474
+ }
475
+ let rendered = if fraction.is_empty() {
476
+ integer
477
+ } else {
478
+ format!("{integer}.{fraction}")
479
+ };
480
+ match formatter.style {
481
+ IntlNumberStyle::Decimal => {
482
+ if value.is_sign_negative() {
483
+ format!("-{rendered}")
484
+ } else {
485
+ rendered
486
+ }
487
+ }
488
+ IntlNumberStyle::Percent => {
489
+ let mut rendered = if value.is_sign_negative() {
490
+ format!("-{rendered}")
491
+ } else {
492
+ rendered
493
+ };
494
+ rendered.push('%');
495
+ rendered
496
+ }
497
+ IntlNumberStyle::Currency => {
498
+ if value.is_sign_negative() {
499
+ format!("-${rendered}")
500
+ } else {
501
+ format!("${rendered}")
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ pub(crate) fn call_intl_number_format_format(
508
+ &self,
509
+ this_value: Value,
510
+ args: &[Value],
511
+ ) -> MustardResult<Value> {
512
+ let formatter = self.intl_number_format_receiver(this_value, "format")?;
513
+ let number = self.to_number(args.first().cloned().unwrap_or(Value::Undefined))?;
514
+ Ok(Value::String(self.format_intl_number(formatter, number)))
515
+ }
516
+
517
+ pub(crate) fn call_intl_number_format_resolved_options(
518
+ &mut self,
519
+ this_value: Value,
520
+ ) -> MustardResult<Value> {
521
+ let formatter = self
522
+ .intl_number_format_receiver(this_value, "resolvedOptions")?
523
+ .clone();
524
+ let mut properties = IndexMap::new();
525
+ properties.insert("locale".to_string(), Value::String(formatter.locale));
526
+ properties.insert(
527
+ "style".to_string(),
528
+ Value::String(match formatter.style {
529
+ IntlNumberStyle::Decimal => "decimal".to_string(),
530
+ IntlNumberStyle::Percent => "percent".to_string(),
531
+ IntlNumberStyle::Currency => "currency".to_string(),
532
+ }),
533
+ );
534
+ if let Some(currency) = formatter.currency {
535
+ properties.insert("currency".to_string(), Value::String(currency));
536
+ }
537
+ properties.insert(
538
+ "minimumFractionDigits".to_string(),
539
+ Value::Number(formatter.minimum_fraction_digits as f64),
540
+ );
541
+ properties.insert(
542
+ "maximumFractionDigits".to_string(),
543
+ Value::Number(formatter.maximum_fraction_digits as f64),
544
+ );
545
+ properties.insert(
546
+ "useGrouping".to_string(),
547
+ Value::Bool(formatter.use_grouping),
548
+ );
549
+ Ok(Value::Object(
550
+ self.insert_object(properties, ObjectKind::Plain)?,
551
+ ))
552
+ }
553
+ }
@@ -0,0 +1,25 @@
1
+ use super::*;
2
+
3
+ use indexmap::IndexMap;
4
+
5
+ mod arrays;
6
+ mod collections;
7
+ mod install;
8
+ mod intl;
9
+ mod objects;
10
+ mod primitives;
11
+ mod promises;
12
+ mod regexp;
13
+ mod strings;
14
+ mod support;
15
+
16
+ use self::support::{
17
+ DateTimeFields, RegExpFlagsState, StringSearchPattern, advance_char_index,
18
+ byte_index_to_char_index, char_index_to_byte_index, clamp_index, collect_literal_matches,
19
+ current_time_millis, date_time_fields_from_timestamp_ms, expand_regexp_replacement_template,
20
+ find_string_pattern, format_en_us_number_grouped, format_iso_datetime,
21
+ normalize_relative_bound, normalize_search_index, parse_date_timestamp_ms,
22
+ replace_all_string_matches, replace_first_string_match, split_string_by_pattern, time_clip,
23
+ };
24
+ pub(crate) use promises::PromiseSetupPolicy;
25
+ pub(crate) use support::RegExpMatchData;