@@ -25,7 +25,7 @@ use crate::{
2525
2626use super :: { ConstantEvaluation , ConstantEvaluationCtx , ConstantValue } ;
2727
28- fn try_fold_url_related_function < ' a > (
28+ fn try_fold_global_functions < ' a > (
2929 ident : & IdentifierReference < ' a > ,
3030 arguments : & Vec < ' a , Argument < ' a > > ,
3131 ctx : & impl ConstantEvaluationCtx < ' a > ,
@@ -39,6 +39,12 @@ fn try_fold_url_related_function<'a>(
3939 "decodeURIComponent" if ctx. is_global_reference ( ident) => {
4040 try_fold_decode_uri_component ( arguments, ctx)
4141 }
42+ "isNaN" if ctx. is_global_reference ( ident) => try_fold_global_is_nan ( arguments, ctx) ,
43+ "isFinite" if ctx. is_global_reference ( ident) => try_fold_global_is_finite ( arguments, ctx) ,
44+ "parseFloat" if ctx. is_global_reference ( ident) => {
45+ try_fold_global_parse_float ( arguments, ctx)
46+ }
47+ "parseInt" if ctx. is_global_reference ( ident) => try_fold_global_parse_int ( arguments, ctx) ,
4248 _ => None ,
4349 }
4450}
@@ -49,7 +55,7 @@ pub fn try_fold_known_global_methods<'a>(
4955 ctx : & impl ConstantEvaluationCtx < ' a > ,
5056) -> Option < ConstantValue < ' a > > {
5157 if let Expression :: Identifier ( ident) = callee {
52- if let Some ( result) = try_fold_url_related_function ( ident, arguments, ctx) {
58+ if let Some ( result) = try_fold_global_functions ( ident, arguments, ctx) {
5359 return Some ( result) ;
5460 }
5561 return None ;
@@ -617,3 +623,259 @@ fn try_fold_decode_uri_component<'a>(
617623 ) ?;
618624 Some ( ConstantValue :: String ( decoded) )
619625}
626+
627+ fn try_fold_global_is_nan < ' a > (
628+ args : & Vec < ' a , Argument < ' a > > ,
629+ ctx : & impl ConstantEvaluationCtx < ' a > ,
630+ ) -> Option < ConstantValue < ' a > > {
631+ if args. is_empty ( ) {
632+ return Some ( ConstantValue :: Boolean ( true ) ) ;
633+ }
634+ if args. len ( ) != 1 {
635+ return None ;
636+ }
637+ let arg = args. first ( ) . unwrap ( ) ;
638+ let expr = arg. as_expression ( ) ?;
639+ let num = expr. get_side_free_number_value ( ctx) ?;
640+ Some ( ConstantValue :: Boolean ( num. is_nan ( ) ) )
641+ }
642+
643+ fn try_fold_global_is_finite < ' a > (
644+ args : & Vec < ' a , Argument < ' a > > ,
645+ ctx : & impl ConstantEvaluationCtx < ' a > ,
646+ ) -> Option < ConstantValue < ' a > > {
647+ if args. is_empty ( ) {
648+ return Some ( ConstantValue :: Boolean ( false ) ) ;
649+ }
650+ if args. len ( ) != 1 {
651+ return None ;
652+ }
653+ let arg = args. first ( ) . unwrap ( ) ;
654+ let expr = arg. as_expression ( ) ?;
655+ let num = expr. get_side_free_number_value ( ctx) ?;
656+ Some ( ConstantValue :: Boolean ( num. is_finite ( ) ) )
657+ }
658+
659+ fn try_fold_global_parse_float < ' a > (
660+ args : & Vec < ' a , Argument < ' a > > ,
661+ ctx : & impl ConstantEvaluationCtx < ' a > ,
662+ ) -> Option < ConstantValue < ' a > > {
663+ if args. is_empty ( ) {
664+ return Some ( ConstantValue :: Number ( f64:: NAN ) ) ;
665+ }
666+ if args. len ( ) != 1 {
667+ return None ;
668+ }
669+ let arg = args. first ( ) . unwrap ( ) ;
670+ let expr = arg. as_expression ( ) ?;
671+ let input_string = expr. get_side_free_string_value ( ctx) ?;
672+ let trimmed = input_string. trim_start ( ) ;
673+ let Some ( trimmed_prefix) = find_str_decimal_literal_prefix ( trimmed) else {
674+ return Some ( ConstantValue :: Number ( f64:: NAN ) ) ;
675+ } ;
676+
677+ let parsed = trimmed_prefix. cow_replace ( '_' , "" ) . parse :: < f64 > ( ) . unwrap_or_else ( |_| {
678+ unreachable ! (
679+ "StrDecimalLiteral should be parse-able with Rust FromStr for f64: {trimmed_prefix}"
680+ )
681+ } ) ;
682+ Some ( ConstantValue :: Number ( parsed) )
683+ }
684+
685+ /// Find the longest prefix of a string that satisfies the syntax of a `StrDecimalLiteral`.
686+ /// Returns None when not found.
687+ ///
688+ /// This function implements step 4 of `parseFloat`.
689+ /// <https://tc39.es/ecma262/2025/multipage/global-object.html#sec-parsefloat-string>
690+ fn find_str_decimal_literal_prefix ( input : & str ) -> Option < & str > {
691+ fn match_decimal_digits ( s : & str ) -> Option < usize > {
692+ let bytes = s. as_bytes ( ) ;
693+ if bytes. first ( ) . is_none_or ( |b| !b. is_ascii_digit ( ) ) {
694+ // must have at least one digit
695+ return None ;
696+ }
697+ let mut iter = bytes. iter ( ) . enumerate ( ) . skip ( 1 ) ;
698+ while let Some ( ( i, & b) ) = iter. next ( ) {
699+ match b {
700+ b'0' ..=b'9' => { }
701+ b'_' => {
702+ let Some ( ( i, & b) ) = iter. next ( ) else {
703+ // must have at least one digit after _
704+ return Some ( i) ; // without _
705+ } ;
706+ if !b. is_ascii_digit ( ) {
707+ // must have at least one digit after _
708+ return Some ( i) ; // without _
709+ }
710+ }
711+ _ => return Some ( i) ,
712+ }
713+ }
714+ Some ( s. len ( ) )
715+ }
716+ fn match_exponent_part ( mut s : & str ) -> Option < usize > {
717+ if !s. starts_with ( [ 'e' , 'E' ] ) {
718+ return None ;
719+ }
720+ let mut last_index = 1 ;
721+ s = & s[ 1 ..] ;
722+ if s. starts_with ( [ '+' , '-' ] ) {
723+ last_index += 1 ;
724+ s = & s[ 1 ..] ;
725+ }
726+ let end_of_decimal_digits = match_decimal_digits ( s) ?;
727+ last_index += end_of_decimal_digits;
728+ Some ( last_index)
729+ }
730+
731+ let mut s = input;
732+ let mut last_index: usize = 0 ;
733+ if s. starts_with ( [ '+' , '-' ] ) {
734+ s = & s[ 1 ..] ;
735+ last_index += 1 ;
736+ }
737+ if s. starts_with ( "Infinity" ) {
738+ last_index += "Infinity" . len ( ) ;
739+ return Some ( & input[ ..last_index] ) ;
740+ }
741+ // . DecimalDigits ExponentPart
742+ if s. starts_with ( '.' ) {
743+ last_index += 1 ;
744+ s = & s[ 1 ..] ;
745+ let end_of_decimal_digits = match_decimal_digits ( s) ?;
746+ last_index += end_of_decimal_digits;
747+ s = & s[ end_of_decimal_digits..] ;
748+ let Some ( end_of_exponent_part) = match_exponent_part ( s) else {
749+ return Some ( & input[ ..last_index] ) ;
750+ } ;
751+ last_index += end_of_exponent_part;
752+ return Some ( & input[ ..last_index] ) ;
753+ }
754+
755+ let end_of_decimal_digits = match_decimal_digits ( s) ?;
756+ last_index += end_of_decimal_digits;
757+ s = & s[ end_of_decimal_digits..] ;
758+
759+ // DecimalDigits . DecimalDigits ExponentPart
760+ if s. starts_with ( '.' ) {
761+ last_index += 1 ;
762+ s = & s[ 1 ..] ;
763+ let Some ( end_of_decimal_digits) = match_decimal_digits ( s) else {
764+ return Some ( & input[ ..last_index - 1 ] ) ; // without .
765+ } ;
766+ last_index += end_of_decimal_digits;
767+ s = & s[ end_of_decimal_digits..] ;
768+ let Some ( end_of_exponent_part) = match_exponent_part ( s) else {
769+ return Some ( & input[ ..last_index] ) ;
770+ } ;
771+ last_index += end_of_exponent_part;
772+ return Some ( & input[ ..last_index] ) ;
773+ }
774+
775+ // DecimalDigits ExponentPart
776+ let Some ( end_of_exponent_part) = match_exponent_part ( s) else {
777+ return Some ( & input[ ..last_index] ) ;
778+ } ;
779+ last_index += end_of_exponent_part;
780+ Some ( & input[ ..last_index] )
781+ }
782+
783+ fn try_fold_global_parse_int < ' a > (
784+ args : & Vec < ' a , Argument < ' a > > ,
785+ ctx : & impl ConstantEvaluationCtx < ' a > ,
786+ ) -> Option < ConstantValue < ' a > > {
787+ if args. is_empty ( ) {
788+ return Some ( ConstantValue :: Number ( f64:: NAN ) ) ;
789+ }
790+ if args. len ( ) > 2
791+ || args
792+ . iter ( )
793+ . any ( |arg| arg. as_expression ( ) . is_none_or ( |arg| arg. may_have_side_effects ( ctx) ) )
794+ {
795+ return None ;
796+ }
797+ let string_arg = args. first ( ) . unwrap ( ) ;
798+ let string_expr = string_arg. as_expression ( ) ?;
799+ let string_value = string_expr. evaluate_value_to_string ( ctx) ?;
800+ let mut string_value = string_value. trim_start ( ) ;
801+
802+ let mut sign = 1 ;
803+ if string_value. starts_with ( '-' ) {
804+ sign = -1 ;
805+ }
806+ if string_value. starts_with ( [ '+' , '-' ] ) {
807+ string_value = & string_value[ 1 ..] ;
808+ }
809+
810+ let mut strip_prefix = true ;
811+ let mut radix = if let Some ( arg) = args. get ( 1 ) {
812+ let expr = arg. as_expression ( ) ?;
813+ let mut radix = expr. evaluate_value_to_number ( ctx) ?. to_int_32 ( ) ;
814+ if radix == 0 {
815+ radix = 10 ;
816+ } else if !( 2 ..=36 ) . contains ( & radix) {
817+ return Some ( ConstantValue :: Number ( f64:: NAN ) ) ;
818+ } else if radix != 16 {
819+ strip_prefix = false ;
820+ }
821+ radix as u32
822+ } else {
823+ 10
824+ } ;
825+
826+ if !matches ! ( radix, 2 | 4 | 8 | 10 | 16 | 32 ) {
827+ // implementation can approximate the values. bail out to be safe
828+ return None ;
829+ }
830+
831+ if strip_prefix && ( string_value. starts_with ( "0x" ) || string_value. starts_with ( "0X" ) ) {
832+ string_value = & string_value[ 2 ..] ;
833+ radix = 16 ;
834+ }
835+
836+ if let Some ( non_radix_digit_pos) = string_value. chars ( ) . position ( |c| !c. is_digit ( radix) ) {
837+ string_value = & string_value[ ..non_radix_digit_pos] ;
838+ }
839+
840+ if string_value. is_empty ( ) {
841+ return Some ( ConstantValue :: Number ( f64:: NAN ) ) ;
842+ }
843+
844+ if radix == 10 && string_value. len ( ) > 20 {
845+ // implementation can approximate the values. bail out to be safe
846+ return None ;
847+ }
848+
849+ let Ok ( math_int) = i32:: from_str_radix ( string_value, radix) else {
850+ // ignore values that cannot be represented as i32 to avoid precision issues
851+ return None ;
852+ } ;
853+ if math_int == 0 {
854+ return Some ( ConstantValue :: Number ( if sign == -1 { -0.0 } else { 0.0 } ) ) ;
855+ }
856+ Some ( ConstantValue :: Number ( ( math_int as f64 ) * sign as f64 ) )
857+ }
858+
859+ #[ test]
860+ fn test_find_str_decimal_literal_prefix ( ) {
861+ assert_eq ! ( find_str_decimal_literal_prefix( "Infinitya" ) , Some ( "Infinity" ) ) ;
862+ assert_eq ! ( find_str_decimal_literal_prefix( "+Infinitya" ) , Some ( "+Infinity" ) ) ;
863+ assert_eq ! ( find_str_decimal_literal_prefix( "-Infinitya" ) , Some ( "-Infinity" ) ) ;
864+ assert_eq ! ( find_str_decimal_literal_prefix( "0a" ) , Some ( "0" ) ) ;
865+ assert_eq ! ( find_str_decimal_literal_prefix( "+0a" ) , Some ( "+0" ) ) ;
866+ assert_eq ! ( find_str_decimal_literal_prefix( "-0a" ) , Some ( "-0" ) ) ;
867+ assert_eq ! ( find_str_decimal_literal_prefix( "0." ) , Some ( "0" ) ) ;
868+ assert_eq ! ( find_str_decimal_literal_prefix( "0.e" ) , Some ( "0" ) ) ;
869+ assert_eq ! ( find_str_decimal_literal_prefix( "0.e1" ) , Some ( "0" ) ) ;
870+ assert_eq ! ( find_str_decimal_literal_prefix( "0.1" ) , Some ( "0.1" ) ) ;
871+ assert_eq ! ( find_str_decimal_literal_prefix( "0.1." ) , Some ( "0.1" ) ) ;
872+ assert_eq ! ( find_str_decimal_literal_prefix( "0.1e" ) , Some ( "0.1" ) ) ;
873+ assert_eq ! ( find_str_decimal_literal_prefix( "0.1e1" ) , Some ( "0.1e1" ) ) ;
874+ assert_eq ! ( find_str_decimal_literal_prefix( ".1" ) , Some ( ".1" ) ) ;
875+ assert_eq ! ( find_str_decimal_literal_prefix( ".1." ) , Some ( ".1" ) ) ;
876+ assert_eq ! ( find_str_decimal_literal_prefix( ".1e" ) , Some ( ".1" ) ) ;
877+ assert_eq ! ( find_str_decimal_literal_prefix( ".1e1" ) , Some ( ".1e1" ) ) ;
878+ assert_eq ! ( find_str_decimal_literal_prefix( "1_" ) , Some ( "1" ) ) ;
879+ assert_eq ! ( find_str_decimal_literal_prefix( "1_1" ) , Some ( "1_1" ) ) ;
880+ assert_eq ! ( find_str_decimal_literal_prefix( "1_1_" ) , Some ( "1_1" ) ) ;
881+ }
0 commit comments