-----------------------------------------------------------------------------
-- |
-- Module      : Hasmin.Parser.Numeric
-- Copyright   : (c) 2017 Cristian Adrián Ontivero
-- License     : BSD3
-- Stability   : experimental
-- Portability : unknown
--
-----------------------------------------------------------------------------
module Hasmin.Parser.Numeric
    ( alphavalue
    , rational
    , int
    , percentage
    , number
    ) where

import Control.Applicative ((<|>))
import Numeric (readSigned, readFloat)
import Data.Attoparsec.Text (Parser)
import qualified Data.Attoparsec.Text as A

import Hasmin.Types.Numeric
import Hasmin.Parser.Utils
import Hasmin.Parser.Primitives

-- | Parser for <https://www.w3.org/TR/css-values-3/#numbers \<number\>>.
number :: Parser Number
number = Number <$> rational

-- | Parser for <https://drafts.csswg.org/css-values-3/#percentages \<percentage\>>.
percentage :: Parser Percentage
percentage = Percentage <$> rational <* A.char '%'

alphavalue :: Parser Alphavalue
alphavalue = mkAlphavalue <$> rational

-- Note that many properties that allow an integer or real number as a value
-- actually restrict the value to some range, often to a non-negative value.
--
-- | Real number parser. \<number\>: <'int' integer> | [0-9]*.[0-9]+
rational :: Parser Rational
rational = do
    sign <- A.option [] (wrapMinus <$> (A.char '-' <|> A.char '+'))
    dgts <- ((++) <$> digits <*> A.option "" fractionalPart)
           <|> ("0"++) <$> fractionalPart -- append a zero for read not to fail
    e <- A.option [] expo
    pure . fst . head $ readSigned readFloat (sign ++ dgts ++ e)
  where fractionalPart = (:) <$> A.char '.' <*> digits
        expo = (:) <$> A.satisfy (\c -> c == 'e' || c == 'E') <*> int'
        wrapMinus x = [x | x == '-'] -- we use this since read fails with leading '+'