@@ -11,11 +11,15 @@
1111--
1212module Streamly.Internal.Unicode.Char.Parser where
1313
14+import Control.Applicative (Alternative(..))
1415import Control.Monad.Catch (MonadCatch)
16+import Data.Bits (Bits, (.|.), shiftL)
17+import Data.Char (ord)
1518import Streamly.Internal.Data.Parser (Parser)
1619
1720import qualified Data.Char as Char
1821import qualified Streamly.Internal.Data.Parser as Parser
22+import qualified Streamly.Internal.Data.Fold as Fold
1923
2024--------------------------------------------------------------------------------
2125-- Character classification
@@ -80,3 +84,88 @@ CHAR_PARSER(asciiUpper,isAsciiUpper)
8084
8185CHAR_PARSER_SIG(asciiLower)
8286CHAR_PARSER(asciiLower,isAsciiLower)
87+
88+--------------------------------------------------------------------------------
89+-- Character parsers
90+--------------------------------------------------------------------------------
91+
92+-- | Match a specific character.
93+char :: MonadCatch m => Char -> Parser m Char Char
94+char c = Parser.satisfy (== c)
95+
96+--------------------------------------------------------------------------------
97+-- Numeric parsers
98+--------------------------------------------------------------------------------
99+
100+-- | Parse and decode an unsigned decimal number.
101+{-# INLINE decimal #-}
102+decimal :: (MonadCatch m, Integral a) => Parser m Char a
103+decimal = Parser.takeWhile1 Char.isDigit (Fold.foldl' step 0)
104+
105+ where
106+
107+ step a c = a * 10 + fromIntegral (ord c - 48)
108+
109+
110+-- | Parse and decode an unsigned hexadecimal number. The hex digits
111+-- @\'a\'@ through @\'f\'@ may be upper or lower case.
112+--
113+-- This parser does not accept a leading @\"0x\"@ string.
114+{-# INLINE hexadecimal #-}
115+hexadecimal :: (MonadCatch m, Integral a, Bits a) => Parser m Char a
116+hexadecimal = Parser.takeWhile1 isHexDigit (Fold.foldl' step 0)
117+
118+ where
119+
120+ isHexDigit c =
121+ (c >= '0' && c <= '9')
122+ || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
123+
124+ step a c
125+ | w >= 48 && w <= 57 = (a `shiftL` 4) .|. fromIntegral (w - 48)
126+ | w >= 97 = (a `shiftL` 4) .|. fromIntegral (w - 87)
127+ | otherwise = (a `shiftL` 4) .|. fromIntegral (w - 55)
128+
129+ where
130+
131+ w = ord c
132+
133+-- | Parse a number with an optional leading @\'+\'@ or @\'-\'@ sign
134+-- character.
135+{-# INLINE signed #-}
136+signed :: (Num a, MonadCatch m) => Parser m Char a -> Parser m Char a
137+signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p
138+
139+-- | Parse a 'Double'.
140+--
141+-- This parser accepts an optional leading sign character, followed by
142+-- at most one decimal digit. The syntax is similar to that accepted by
143+-- the 'read' function, with the exception that a trailing @\'.\'@ is
144+-- consumed.
145+--
146+-- === Examples
147+--
148+-- These examples use this helper:
149+--
150+-- Examples with behaviour identical to 'read', if you feed an empty
151+-- continuation to the first result:
152+--
153+-- > IS.parse double (IS.fromList "3") == 3.0
154+-- > IS.parse double (IS.fromList "3.1") == 3.1
155+-- > IS.parse double (IS.fromList "3e4") == 30000.0
156+-- > IS.parse double (IS.fromList "3.1e4") == 31000.0
157+-- > IS.parse double (IS.fromList "3e") == 30
158+--
159+-- Examples with behaviour identical to 'read':
160+--
161+-- > IS.parse (IS.fromList ".3") == error "Parse failed"
162+-- > IS.parse (IS.fromList "e3") == error "Parse failed"
163+--
164+-- Example of difference from 'read':
165+--
166+-- > IS.parse double (IS.fromList "3.foo") == 3.0
167+--
168+-- This function does not accept string representations of \"NaN\" or
169+-- \"Infinity\".
170+double :: Parser m Char Double
171+double = undefined