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
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
56import Control.Applicative (Alternative(..))
57import Control.Monad.Catch (MonadCatch)
58import Data.Bits (Bits, (.|.), shiftL)
60import Prelude hiding (print)
61import Streamly.Internal.Data.Parser (Parser)
63import qualified Data.Char as Char
64import qualified Streamly.Data.Fold as Fold
65import qualified Streamly.Internal.Data.Parser as Parser
74--------------------------------------------------------------------------------
75-- Character classification
76--------------------------------------------------------------------------------
78-- XXX It may be possible to implement faster predicates for ASCII byte stream.
79-- We can measure if there is a signficant difference and if so we can add such
80-- predicates to Streamly.Unicode.Char.Parser.Latin1.
82#define CHAR_PARSER_SIG(NAME) NAME :: MonadCatch m => Parser m Char Char
83-- XXX Need to use the predicates from Unicode.Char module/unicode-data package
84#define CHAR_PARSER(NAME, PREDICATE) NAME = Parser.satisfy Char.PREDICATE
86-- XXX Add haddock documentation
87-- XXX Add INLINE pragmas
90CHAR_PARSER(space,isSpace)
93CHAR_PARSER(lower,isLower)
96CHAR_PARSER(upper,isUpper)
99CHAR_PARSER(alpha,isAlpha)
101CHAR_PARSER_SIG(alphaNum)
102CHAR_PARSER(alphaNum,isAlphaNum)
104CHAR_PARSER_SIG(print)
105CHAR_PARSER(print,isPrint)
107CHAR_PARSER_SIG(digit)
108CHAR_PARSER(digit,isDigit)
110CHAR_PARSER_SIG(octDigit)
111CHAR_PARSER(octDigit,isOctDigit)
113CHAR_PARSER_SIG(hexDigit)
114CHAR_PARSER(hexDigit,isHexDigit)
116CHAR_PARSER_SIG(letter)
117CHAR_PARSER(letter,isLetter)
120CHAR_PARSER(mark,isMark)
122CHAR_PARSER_SIG(number)
123CHAR_PARSER(number,isNumber)
125CHAR_PARSER_SIG(punctuation)
126CHAR_PARSER(punctuation,isPunctuation)
128CHAR_PARSER_SIG(symbol)
129CHAR_PARSER(symbol,isSymbol)
131CHAR_PARSER_SIG(separator)
132CHAR_PARSER(separator,isSeparator)
134CHAR_PARSER_SIG(ascii)
135CHAR_PARSER(ascii,isAscii)
137CHAR_PARSER_SIG(latin1)
138CHAR_PARSER(latin1,isLatin1)
140CHAR_PARSER_SIG(asciiUpper)
141CHAR_PARSER(asciiUpper,isAsciiUpper)
143CHAR_PARSER_SIG(asciiLower)
144CHAR_PARSER(asciiLower,isAsciiLower)
146--------------------------------------------------------------------------------
148--------------------------------------------------------------------------------
150-- | Match a specific character.
152char :: MonadCatch m => Char -> Parser m Char Char
153char c = Parser.satisfy (== c)
155-- XXX Case conversion may lead to change in number of chars
156-- | Match a specific character ignoring case.
157{-# INLINE charAnyCase #-}
158charAnyCase :: MonadCatch m => Char -> Parser m Char Char
159charAnyCase c = Parser.lmap Char.toLower (Parser.satisfy (== Char.toLower c))
161--------------------------------------------------------------------------------
162-- Character sequences
163--------------------------------------------------------------------------------
165-- | Match the input with the supplied string and return it if successful.
166string :: MonadCatch m => String -> Parser m Char String
169-- XXX Not accurate unicode case conversion
170-- | Match the input with the supplied string and return it if successful.
171stringAnyCase :: MonadCatch m => String -> Parser m Char String
172stringAnyCase s = Parser.lmap Char.toLower (Parser.list (map Char.toLower s))
174-- | Drop /zero/ or more white space characters.
175dropSpace :: MonadCatch m => Parser m Char ()
176dropSpace = Parser.dropWhile Char.isSpace
178-- | Drop /one/ or more white space characters.
179dropSpace1 :: MonadCatch m => Parser m Char ()
180dropSpace1 = Parser.takeWhile1 Char.isSpace Fold.drain
182--------------------------------------------------------------------------------
184--------------------------------------------------------------------------------
186-- XXX It should fail if the number is larger than the size of the type.
188-- | Parse and decode an unsigned integral decimal number.
189{-# INLINE decimal #-}
190decimal :: (MonadCatch m, Integral a) => Parser m Char a
191decimal = Parser.takeWhile1 Char.isDigit (Fold.foldl' step 0)
195 step a c = a * 10 + fromIntegral (ord c - 48)
197-- | Parse and decode an unsigned integral hexadecimal number. The hex digits
198-- @\'a\'@ through @\'f\'@ may be upper or lower case.
200-- Note: This parser does not accept a leading @\"0x\"@ string.
201{-# INLINE hexadecimal #-}
202hexadecimal :: (MonadCatch m, Integral a, Bits a) => Parser m Char a
203hexadecimal = Parser.takeWhile1 isHexDigit (Fold.foldl' step 0)
208 (c >= '0' && c <= '9')
209 || (c >= 'a' && c <= 'f')
210 || (c >= 'A' && c <= 'F')
213 | w >= 48 && w <= 57 =
214 (a `shiftL` 4) .|. fromIntegral (w - 48)
216 (a `shiftL` 4) .|. fromIntegral (w - 87)
218 (a `shiftL` 4) .|. fromIntegral (w - 55)
224-- | Allow an optional leading @\'+\'@ or @\'-\'@ sign character before any
227signed :: (Num a, MonadCatch m) => Parser m Char a -> Parser m Char a
228signed p = (negate <$> (char '-' *> p)) <|> (char '+' *> p) <|> p
230-- | Parse a 'Double'.
232-- This parser accepts an optional leading sign character, followed by
233-- at most one decimal digit. The syntax is similar to that accepted by
234-- the 'read' function, with the exception that a trailing @\'.\'@ is
239-- Examples with behaviour identical to 'read', if you feed an empty
240-- continuation to the first result:
242-- > IS.parse double (IS.fromList "3") == 3.0
243-- > IS.parse double (IS.fromList "3.1") == 3.1
244-- > IS.parse double (IS.fromList "3e4") == 30000.0
245-- > IS.parse double (IS.fromList "3.1e4") == 31000.0
246-- > IS.parse double (IS.fromList "3e") == 30
248-- Examples with behaviour identical to 'read':
250-- > IS.parse (IS.fromList ".3") == error "Parse failed"
251-- > IS.parse (IS.fromList "e3") == error "Parse failed"
253-- Example of difference from 'read':
255-- > IS.parse double (IS.fromList "3.foo") == 3.0
257-- This function does not accept string representations of \"NaN\" or
261double :: Parser m Char Double
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list