-----------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Flow.Internal.JSNum
-- License     :  BSD3-style (see the file LICENSE)
--
-- JavaScript-compatible rendering of 'Double' values, following the
-- ECMA-262 @Number::toString@ algorithm. The SVG path strings produced by
-- @\@xyflow\/system@ interpolate JS numbers, so the Haskell port has to
-- print numbers identically (e.g. @150@ rather than @150.0@, @0.01@
-- rather than @1.0e-2@) for paths to be byte-for-byte compatible.
----------------------------------------------------------------------------
module Miso.Flow.Internal.JSNum
  ( jsShow
  ) where
-----------------------------------------------------------------------------
import           Numeric (floatToDigits)
import           Prelude
-----------------------------------------------------------------------------
import           Miso.String (MisoString, ms)
-----------------------------------------------------------------------------
-- | Render a 'Double' exactly like JavaScript string interpolation does.
jsShow :: Double -> MisoString
jsShow :: Double -> MisoString
jsShow Double
x
  | Double -> Bool
forall a. RealFloat a => a -> Bool
isNaN Double
x = MisoString
"NaN"
  | Double -> Bool
forall a. RealFloat a => a -> Bool
isInfinite Double
x = if Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
> Double
0 then MisoString
"Infinity" else MisoString
"-Infinity"
  | Double
x Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0 = MisoString
"0" -- both zeroes print as "0" in JS
  | Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
< Double
0 = MisoString
"-" MisoString -> MisoString -> MisoString
forall a. Semigroup a => a -> a -> a
<> Double -> MisoString
jsShow (Double -> Double
forall a. Num a => a -> a
negate Double
x)
  | Bool
otherwise = String -> MisoString
forall str. ToMisoString str => str -> MisoString
ms (Double -> String
positive Double
x)
-----------------------------------------------------------------------------
-- | ECMA-262 6.1.6.1.20 Number::toString for a positive, finite double.
-- @floatToDigits@ yields the same shortest uniquely-identifying digit
-- string that the spec requires.
positive :: Double -> String
positive :: Double -> String
positive Double
x =
  let ([Int]
digits, Int
n) = Integer -> Double -> ([Int], Int)
forall a. RealFloat a => Integer -> a -> ([Int], Int)
floatToDigits Integer
10 Double
x
      ds :: String
ds = (Int -> String) -> [Int] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Int -> String
forall a. Show a => a -> String
show [Int]
digits
      k :: Int
k = String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
ds
  in if Int
k Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
n Bool -> Bool -> Bool
&& Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
21
       then String
ds String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> Char -> String
forall a. Int -> a -> [a]
replicate (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
k) Char
'0'
     else if Int
0 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
n Bool -> Bool -> Bool
&& Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
21
       then Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
n String
ds String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"." String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
n String
ds
     else if -Int
6 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
n Bool -> Bool -> Bool
&& Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0
       then String
"0." String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> Char -> String
forall a. Int -> a -> [a]
replicate (Int -> Int
forall a. Num a => a -> a
negate Int
n) Char
'0' String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
ds
     else -- exponential notation
       let mantissa :: String
mantissa = case String
ds of
             [Char
d] -> [Char
d]
             (Char
d:String
rest) -> Char
d Char -> String -> String
forall a. a -> [a] -> [a]
: Char
'.' Char -> String -> String
forall a. a -> [a] -> [a]
: String
rest
             [] -> String
"0"
           e :: Int
e = Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
           sign :: String
sign = if Int
e Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0 then String
"+" else String
"-"
       in String
mantissa String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"e" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
sign String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show (Int -> Int
forall a. Num a => a -> a
abs Int
e)
-----------------------------------------------------------------------------