1- use std:: cell:: Cell ;
1+ use std:: { cell:: Cell , fmt } ;
22
33use boa_gc:: { Finalize , Trace , custom_trace} ;
44use fixed_decimal:: { Decimal , FloatPrecision , SignDisplay } ;
@@ -13,24 +13,24 @@ use icu_locale::{Locale, extensions::unicode::Value};
1313use icu_provider:: { DataMarker , DataMarkerAttributes , DynamicDataProvider , buf:: BufferMarker } ;
1414use num_bigint:: BigInt ;
1515use num_traits:: Num ;
16- use writeable:: Writeable ;
16+ use writeable:: { PartsWrite , Writeable , adapters :: CoreWriteAsPartsWrite } ;
1717
1818use super :: {
1919 Service ,
2020 locale:: { canonicalize_locale_list, filter_locales, resolve_locale} ,
2121 options:: { IntlOptions , coerce_options_to_object} ,
2222} ;
2323use 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
200201impl 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