on fab76f2 help sign in
255 lines 7.2 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.
13module Streamly.Internal.Unicode.Char.Parser
14 (
15 -- * Generic
16 char
17 , charAnyCase
19 -- * Sequences
20 , string
21 , stringAnyCase
22 , dropSpace
23 , dropSpace1
25 -- * Classes
26 , alpha
27 , alphaNum
28 , letter
29 , ascii
30 , asciiLower
31 , asciiUpper
32 , latin1
33 , lower
34 , upper
35 , mark
36 , print
37 , punctuation
38 , separator
39 , space
40 , symbol
42 -- digits
43 , digit
44 , octDigit
45 , hexDigit
46 , number
48 -- * Numeric
49 , signed
50 , double
51 , decimal
52 , hexadecimal
53 )
54where
56import Control.Applicative (Alternative(..))
57import Control.Monad.Catch (MonadCatch)
58import Data.Bits (Bits, (.|.), shiftL)
59import Data.Char (ord)
60import Prelude hiding (print)
61import Streamly.Internal.Data.Parser (Parser)
63import qualified Data.Char as Char
64import qualified Streamly.Internal.Data.Parser as Parser
65import qualified Streamly.Internal.Data.Fold as Fold
67--------------------------------------------------------------------------------
68-- Character classification
69--------------------------------------------------------------------------------
71-- XXX It may be possible to implement faster predicates for ASCII byte stream.
72-- We can measure if there is a signficant difference and if so we can add such
73-- predicates to Streamly.Unicode.Char.Parser.Latin1.
74--
75#define CHAR_PARSER_SIG(NAME) NAME :: MonadCatch m => Parser m Char Char
76-- XXX Need to use the predicates from Unicode.Char module/unicode-data package
77#define CHAR_PARSER(NAME, PREDICATE) NAME = Parser.satisfy Char.PREDICATE
79-- XXX Add haddock documentation
80-- XXX Add INLINE pragmas
82CHAR_PARSER_SIG(space)
83CHAR_PARSER(space,isSpace)
85CHAR_PARSER_SIG(lower)
86CHAR_PARSER(lower,isLower)
88CHAR_PARSER_SIG(upper)
89CHAR_PARSER(upper,isUpper)
91CHAR_PARSER_SIG(alpha)
92CHAR_PARSER(alpha,isAlpha)
94CHAR_PARSER_SIG(alphaNum)
95CHAR_PARSER(alphaNum,isAlphaNum)
97CHAR_PARSER_SIG(print)
98CHAR_PARSER(print,isPrint)
100CHAR_PARSER_SIG(digit)
101CHAR_PARSER(digit,isDigit)
103CHAR_PARSER_SIG(octDigit)
104CHAR_PARSER(octDigit,isOctDigit)
106CHAR_PARSER_SIG(hexDigit)
107CHAR_PARSER(hexDigit,isHexDigit)
109CHAR_PARSER_SIG(letter)
110CHAR_PARSER(letter,isLetter)
112CHAR_PARSER_SIG(mark)
113CHAR_PARSER(mark,isMark)
115CHAR_PARSER_SIG(number)
116CHAR_PARSER(number,isNumber)
118CHAR_PARSER_SIG(punctuation)
119CHAR_PARSER(punctuation,isPunctuation)
121CHAR_PARSER_SIG(symbol)
122CHAR_PARSER(symbol,isSymbol)
124CHAR_PARSER_SIG(separator)
125CHAR_PARSER(separator,isSeparator)
127CHAR_PARSER_SIG(ascii)
128CHAR_PARSER(ascii,isAscii)
130CHAR_PARSER_SIG(latin1)
131CHAR_PARSER(latin1,isLatin1)
133CHAR_PARSER_SIG(asciiUpper)
134CHAR_PARSER(asciiUpper,isAsciiUpper)
136CHAR_PARSER_SIG(asciiLower)
137CHAR_PARSER(asciiLower,isAsciiLower)
139--------------------------------------------------------------------------------
140-- Character parsers
141--------------------------------------------------------------------------------
143-- | Match a specific character.
144{-# INLINE char #-}
145char :: MonadCatch m => Char -> Parser m Char Char
146char c = Parser.satisfy (== c)
148-- XXX Case conversion may lead to change in number of chars
149-- | Match a specific character ignoring case.
150{-# INLINE charAnyCase #-}
151charAnyCase :: MonadCatch m => Char -> Parser m Char Char
152charAnyCase c = Parser.lmap Char.toLower (Parser.satisfy (== Char.toLower c))
154--------------------------------------------------------------------------------
155-- Character sequences
156--------------------------------------------------------------------------------
158-- | Match the input with the supplied string and return it if successful.
159string :: MonadCatch m => String -> Parser m Char String
160string = Parser.list
162-- XXX Not accurate unicode case conversion
163-- | Match the input with the supplied string and return it if successful.
164stringAnyCase :: MonadCatch m => String -> Parser m Char String
165stringAnyCase s = Parser.lmap Char.toLower (Parser.list (map Char.toLower s))
167-- | Drop /zero/ or more white space characters.
168dropSpace :: MonadCatch m => Parser m Char ()
169dropSpace = Parser.dropWhile Char.isSpace
171-- | Drop /one/ or more white space characters.
172dropSpace1 :: MonadCatch m => Parser m Char ()
173dropSpace1 = Parser.takeWhile1 Char.isSpace Fold.drain
175--------------------------------------------------------------------------------
176-- Numeric parsers
177--------------------------------------------------------------------------------
179-- XXX It should fail if the number is larger than the size of the type.
180--
181-- | Parse and decode an unsigned integral decimal number.
182{-# INLINE decimal #-}
183decimal :: (MonadCatch m, Integral a) => Parser m Char a
184decimal = Parser.takeWhile1 Char.isDigit (Fold.foldl' step 0)
186 where
188 step a c = a * 10 + fromIntegral (ord c - 48)
190-- | Parse and decode an unsigned integral hexadecimal number. The hex digits
191-- @\'a\'@ through @\'f\'@ may be upper or lower case.
192--
193-- Note: This parser does not accept a leading @\"0x\"@ string.
194{-# INLINE hexadecimal #-}
195hexadecimal :: (MonadCatch m, Integral a, Bits a) => Parser m Char a
196hexadecimal = Parser.takeWhile1 isHexDigit (Fold.foldl' step 0)
198 where
200 isHexDigit c =
201 (c >= '0' && c <= '9')
202 || (c >= 'a' && c <= 'f')
203 || (c >= 'A' && c <= 'F')
205 step a c
206 | w >= 48 && w <= 57 =
207 (a `shiftL` 4) .|. fromIntegral (w - 48)
208 | w >= 97 =
209 (a `shiftL` 4) .|. fromIntegral (w - 87)
210 | otherwise =
211 (a `shiftL` 4) .|. fromIntegral (w - 55)
213 where
215 w = ord c
217-- | Allow an optional leading @\'+\'@ or @\'-\'@ sign character before any
218-- parser.
219{-# INLINE signed #-}
220signed :: (Num a, MonadCatch m) => Parser m Char a -> Parser m Char a
221signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p
223-- | Parse a 'Double'.
224--
225-- This parser accepts an optional leading sign character, followed by
226-- at most one decimal digit. The syntax is similar to that accepted by
227-- the 'read' function, with the exception that a trailing @\'.\'@ is
228-- consumed.
229--
230-- === Examples
231--
232-- Examples with behaviour identical to 'read', if you feed an empty
233-- continuation to the first result:
234--
235-- > IS.parse double (IS.fromList "3") == 3.0
236-- > IS.parse double (IS.fromList "3.1") == 3.1
237-- > IS.parse double (IS.fromList "3e4") == 30000.0
238-- > IS.parse double (IS.fromList "3.1e4") == 31000.0
239-- > IS.parse double (IS.fromList "3e") == 30
240--
241-- Examples with behaviour identical to 'read':
242--
243-- > IS.parse (IS.fromList ".3") == error "Parse failed"
244-- > IS.parse (IS.fromList "e3") == error "Parse failed"
245--
246-- Example of difference from 'read':
247--
248-- > IS.parse double (IS.fromList "3.foo") == 3.0
249--
250-- This function does not accept string representations of \"NaN\" or
251-- \"Infinity\".
252--
253-- /Unimplemented/
254double :: Parser m Char Double
255double = undefined