Skip to content

Commit 5c668e0

Browse files
authored
feat: implement Intl.NumberFormat.formatToParts (#5499)
Short and simple IMO
1 parent 6767cf1 commit 5c668e0

1 file changed

Lines changed: 106 additions & 9 deletions

File tree

  • core/engine/src/builtins/intl/number_format

core/engine/src/builtins/intl/number_format/mod.rs

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::cell::Cell;
1+
use std::{cell::Cell, fmt};
22

33
use boa_gc::{Finalize, Trace, custom_trace};
44
use fixed_decimal::{Decimal, FloatPrecision, SignDisplay};
@@ -13,24 +13,24 @@ use icu_locale::{Locale, extensions::unicode::Value};
1313
use icu_provider::{DataMarker, DataMarkerAttributes, DynamicDataProvider, buf::BufferMarker};
1414
use num_bigint::BigInt;
1515
use num_traits::Num;
16-
use writeable::Writeable;
16+
use writeable::{PartsWrite, Writeable, adapters::CoreWriteAsPartsWrite};
1717

1818
use super::{
1919
Service,
2020
locale::{canonicalize_locale_list, filter_locales, resolve_locale},
2121
options::{IntlOptions, coerce_options_to_object},
2222
};
2323
use crate::{
24-
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
25-
NativeFunction,
24+
Context, JsArgs, JsData, JsExpect, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
25+
JsValue, NativeFunction,
2626
builtins::{
27-
BuiltInConstructor, BuiltInObject, IntrinsicObject, builder::BuiltInBuilder,
28-
options::get_option,
27+
BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject,
28+
builder::BuiltInBuilder, options::get_option,
2929
},
3030
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
3131
js_string,
3232
object::{
33-
FunctionObjectBuilder, JsFunction, ObjectInitializer,
33+
FunctionObjectBuilder, JsArray, JsFunction, ObjectInitializer,
3434
internal_methods::get_prototype_from_constructor,
3535
},
3636
property::{Attribute, PropertyDescriptor},
@@ -59,7 +59,7 @@ impl<T: Writeable> Writeable for FormattedNumber<'_, T> {
5959
}
6060
}
6161

62-
fn write_to_parts<S: writeable::PartsWrite + ?Sized>(&self, sink: &mut S) -> core::fmt::Result {
62+
fn write_to_parts<S: PartsWrite + ?Sized>(&self, sink: &mut S) -> core::fmt::Result {
6363
match self {
6464
FormattedNumber::Decimal(d) => d.write_to_parts(sink),
6565
FormattedNumber::Compact(c) => c.write_to_parts(sink),
@@ -185,6 +185,7 @@ impl IntrinsicObject for NumberFormat {
185185
Attribute::CONFIGURABLE,
186186
)
187187
.method(Self::resolved_options, js_string!("resolvedOptions"), 0)
188+
.method(Self::format_to_parts, js_string!("formatToParts"), 1)
188189
.build();
189190
}
190191

@@ -199,7 +200,7 @@ impl BuiltInObject for NumberFormat {
199200

200201
impl BuiltInConstructor for NumberFormat {
201202
const CONSTRUCTOR_ARGUMENTS: usize = 0;
202-
const PROTOTYPE_STORAGE_SLOTS: usize = 4;
203+
const PROTOTYPE_STORAGE_SLOTS: usize = 5;
203204
const CONSTRUCTOR_STORAGE_SLOTS: usize = 1;
204205

205206
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
@@ -635,6 +636,102 @@ impl NumberFormat {
635636
Ok(bound_format.into())
636637
}
637638

639+
/// [`Intl.NumberFormat.prototype.formatToParts ( value )`][spec]
640+
///
641+
/// [spec]: https://tc39.es/ecma402/#sec-intl.numberformat.prototype.formattoparts
642+
fn format_to_parts(
643+
this: &JsValue,
644+
args: &[JsValue],
645+
context: &mut Context,
646+
) -> JsResult<JsValue> {
647+
#[derive(Debug, Clone)]
648+
struct PartsCollector(Vec<(&'static str, String)>);
649+
650+
impl fmt::Write for PartsCollector {
651+
// TODO: is this the correct way to catch literals?
652+
fn write_str(&mut self, s: &str) -> fmt::Result {
653+
self.0.push(("literal", String::from(s)));
654+
Ok(())
655+
}
656+
}
657+
658+
impl PartsWrite for PartsCollector {
659+
type SubPartsWrite = CoreWriteAsPartsWrite<String>;
660+
661+
fn with_part(
662+
&mut self,
663+
part: writeable::Part,
664+
mut f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result,
665+
) -> fmt::Result {
666+
let mut string = CoreWriteAsPartsWrite(String::new());
667+
f(&mut string)?;
668+
if string.0.is_empty() || (part.category != "decimal") {
669+
return Ok(());
670+
}
671+
672+
self.0.push((part.value, string.0));
673+
674+
Ok(())
675+
}
676+
}
677+
678+
// 1. Let nf be the this value.
679+
// 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
680+
let nf = this
681+
.as_object()
682+
.and_then(|o| o.downcast::<Self>().ok())
683+
.ok_or_else(|| {
684+
js_error!(
685+
TypeError:
686+
"value was not an initialized `Intl.NumberFormat` object"
687+
)
688+
})?;
689+
// 3. Let x be ? ToIntlMathematicalValue(value).
690+
let mut x = to_intl_mathematical_value(args.get_or_undefined(0), context)?;
691+
692+
// 4. Return FormatNumericToParts(nf, x).
693+
//
694+
// `FormatNumericToParts ( numberFormat, x )`
695+
// <https://tc39.es/ecma402/#sec-formatnumbertoparts>
696+
//
697+
// 1. Let parts be PartitionNumberPattern(numberFormat, x).
698+
let nf = nf.borrow();
699+
let parts = nf.data().format(&mut x);
700+
let mut collector = PartsCollector(Vec::new());
701+
parts
702+
.write_to_parts(&mut collector)
703+
.map_err(|e| JsNativeError::typ().with_message(e.to_string()))?;
704+
705+
// 2. Let result be ! ArrayCreate(0).
706+
let result = JsArray::new(context)?;
707+
708+
// 3. Let n be 0.
709+
// 4. For each Record { [[Type]], [[Value]] } part of parts, do
710+
// e. Set n to n + 1.
711+
for (n, (typ, value)) in collector.0.into_iter().enumerate() {
712+
// a. Let partObj be OrdinaryObjectCreate(%Object.prototype%).
713+
let part_obj = context
714+
.intrinsics()
715+
.templates()
716+
.ordinary_object()
717+
.create(OrdinaryObject, vec![]);
718+
// b. Perform ! CreateDataPropertyOrThrow(partObj, "type", part.[[Type]]).
719+
part_obj
720+
.create_data_property_or_throw(js_string!("type"), JsString::from(typ), context)
721+
.js_expect("cannot fail to create property on new ordinary object")?;
722+
// c. Perform ! CreateDataPropertyOrThrow(partObj, "value", part.[[Value]]).
723+
part_obj
724+
.create_data_property_or_throw(js_string!("value"), JsString::from(value), context)
725+
.js_expect("cannot fail to create property on new ordinary object")?;
726+
// d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(𝔽(n)), partObj).
727+
result
728+
.create_data_property_or_throw(n, part_obj, context)
729+
.js_expect("cannot fail to push element on array")?;
730+
}
731+
// 5. Return result.
732+
Ok(result.into())
733+
}
734+
638735
/// [`Intl.NumberFormat.prototype.resolvedOptions ( )`][spec].
639736
///
640737
/// Returns a new object with properties reflecting the locale and options computed during the

0 commit comments

Comments
 (0)