on 616f034 help sign in
214 lines 5.9 kB history raw
1-- |
2-- Module : Streamly.Internal.Unicode.Char.Parser
3-- Copyright : (c) 2021 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
8--
9-- To parse a text input, use the decode routines from
10-- "Streamly.Unicode.Stream" module to convert an input byte stream to a
11-- Unicode Char stream and then use these parsers on the Char stream.
13-- XXX Add explicit export list.
14module Streamly.Internal.Unicode.Char.Parser
15 ( alpha
16 , alphaNum
17 , ascii
18 , asciiLower
19 , asciiUpper
20 , char
21 , decimal
22 , digit
23 , double
24 , hexadecimal
25 , hexDigit
26 , latin1
27 , letter
28 , lower
29 , mark
30 , number
31 , octDigit
32 , print
33 , punctuation
34 , separator
35 , signed
36 , space
37 , symbol
38 , upper
39 )
40where
42import Control.Applicative (Alternative(..))
43import Control.Monad.Catch (MonadCatch)
44import Data.Bits (Bits, (.|.), shiftL)
45import Data.Char (ord)
46import Prelude hiding (print)
47import Streamly.Internal.Data.Parser (Parser)
49import qualified Data.Char as Char
50import qualified Streamly.Internal.Data.Parser as Parser
51import qualified Streamly.Internal.Data.Fold as Fold
53--------------------------------------------------------------------------------
54-- Character classification
55--------------------------------------------------------------------------------
57-- XXX It may be possible to implement faster predicates for ASCII byte stream.
58-- We can measure if there is a signficant difference and if so we can add such
59-- predicates to Streamly.Unicode.Char.Parser.Latin1.
60--
61#define CHAR_PARSER_SIG(NAME) NAME :: MonadCatch m => Parser m Char Char
62-- XXX Need to use the predicates from Unicode.Char module/unicode-data package
63#define CHAR_PARSER(NAME, PREDICATE) NAME = Parser.satisfy Char.PREDICATE
65-- XXX Add haddock documentation
66-- XXX Add INLINE pragmas
68CHAR_PARSER_SIG(space)
69CHAR_PARSER(space,isSpace)
71CHAR_PARSER_SIG(lower)
72CHAR_PARSER(lower,isLower)
74CHAR_PARSER_SIG(upper)
75CHAR_PARSER(upper,isUpper)
77CHAR_PARSER_SIG(alpha)
78CHAR_PARSER(alpha,isAlpha)
80CHAR_PARSER_SIG(alphaNum)
81CHAR_PARSER(alphaNum,isAlphaNum)
83CHAR_PARSER_SIG(print)
84CHAR_PARSER(print,isPrint)
86CHAR_PARSER_SIG(digit)
87CHAR_PARSER(digit,isDigit)
89CHAR_PARSER_SIG(octDigit)
90CHAR_PARSER(octDigit,isOctDigit)
92CHAR_PARSER_SIG(hexDigit)
93CHAR_PARSER(hexDigit,isHexDigit)
95CHAR_PARSER_SIG(letter)
96CHAR_PARSER(letter,isLetter)
98CHAR_PARSER_SIG(mark)
99CHAR_PARSER(mark,isMark)
101CHAR_PARSER_SIG(number)
102CHAR_PARSER(number,isNumber)
104CHAR_PARSER_SIG(punctuation)
105CHAR_PARSER(punctuation,isPunctuation)
107CHAR_PARSER_SIG(symbol)
108CHAR_PARSER(symbol,isSymbol)
110CHAR_PARSER_SIG(separator)
111CHAR_PARSER(separator,isSeparator)
113CHAR_PARSER_SIG(ascii)
114CHAR_PARSER(ascii,isAscii)
116CHAR_PARSER_SIG(latin1)
117CHAR_PARSER(latin1,isLatin1)
119CHAR_PARSER_SIG(asciiUpper)
120CHAR_PARSER(asciiUpper,isAsciiUpper)
122CHAR_PARSER_SIG(asciiLower)
123CHAR_PARSER(asciiLower,isAsciiLower)
125--------------------------------------------------------------------------------
126-- Character parsers
127--------------------------------------------------------------------------------
129-- | Match a specific character.
130{-# INLINE char #-}
131char :: MonadCatch m => Char -> Parser m Char Char
132char c = Parser.satisfy (== c)
134--------------------------------------------------------------------------------
135-- Numeric parsers
136--------------------------------------------------------------------------------
138-- XXX It should fail if the number is larger than the size of the type.
139--
140-- | Parse and decode an unsigned integral decimal number.
141{-# INLINE decimal #-}
142decimal :: (MonadCatch m, Integral a) => Parser m Char a
143decimal = Parser.takeWhile1 Char.isDigit (Fold.foldl' step 0)
145 where
147 step a c = a * 10 + fromIntegral (ord c - 48)
149-- | Parse and decode an unsigned integral hexadecimal number. The hex digits
150-- @\'a\'@ through @\'f\'@ may be upper or lower case.
151--
152-- Note: This parser does not accept a leading @\"0x\"@ string.
153{-# INLINE hexadecimal #-}
154hexadecimal :: (MonadCatch m, Integral a, Bits a) => Parser m Char a
155hexadecimal = Parser.takeWhile1 isHexDigit (Fold.foldl' step 0)
157 where
159 isHexDigit c =
160 (c >= '0' && c <= '9')
161 || (c >= 'a' && c <= 'f')
162 || (c >= 'A' && c <= 'F')
164 step a c
165 | w >= 48 && w <= 57 =
166 (a `shiftL` 4) .|. fromIntegral (w - 48)
167 | w >= 97 =
168 (a `shiftL` 4) .|. fromIntegral (w - 87)
169 | otherwise =
170 (a `shiftL` 4) .|. fromIntegral (w - 55)
172 where
174 w = ord c
176-- | Allow an optional leading @\'+\'@ or @\'-\'@ sign character before any
177-- parser.
178{-# INLINE signed #-}
179signed :: (Num a, MonadCatch m) => Parser m Char a -> Parser m Char a
180signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p
182-- | Parse a 'Double'.
183--
184-- This parser accepts an optional leading sign character, followed by
185-- at most one decimal digit. The syntax is similar to that accepted by
186-- the 'read' function, with the exception that a trailing @\'.\'@ is
187-- consumed.
188--
189-- === Examples
190--
191-- Examples with behaviour identical to 'read', if you feed an empty
192-- continuation to the first result:
193--
194-- > IS.parse double (IS.fromList "3") == 3.0
195-- > IS.parse double (IS.fromList "3.1") == 3.1
196-- > IS.parse double (IS.fromList "3e4") == 30000.0
197-- > IS.parse double (IS.fromList "3.1e4") == 31000.0
198-- > IS.parse double (IS.fromList "3e") == 30
199--
200-- Examples with behaviour identical to 'read':
201--
202-- > IS.parse (IS.fromList ".3") == error "Parse failed"
203-- > IS.parse (IS.fromList "e3") == error "Parse failed"
204--
205-- Example of difference from 'read':
206--
207-- > IS.parse double (IS.fromList "3.foo") == 3.0
208--
209-- This function does not accept string representations of \"NaN\" or
210-- \"Infinity\".
211--
212-- /Unimplemented/
213double :: Parser m Char Double
214double = undefined