{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
{-|
Module      : Parsley.Char
Description : The input consuming combinators
License     : BSD-3-Clause
Maintainer  : Jamie Willis
Stability   : stable

This module contains combinators that deal with input consumption..

@since 2.0.0.0
-}
module Parsley.Char (
    satisfy, char, item,
    string, token,
    oneOf, noneOf,
    digit, letter, letterOrDigit
  ) where

import Prelude hiding           (traverse)
import Data.Char                (isAlpha, isAlphaNum)
import Data.List                (sort)
import Parsley.Applicative      (($>), traverse)
import Parsley.Defunctionalized (Defunc(LIFTED, RANGES))
import Parsley.Internal         (Parser, makeQ)
import Parsley.ParserOps        (satisfy)

import qualified Parsley.Internal as Internal (try)

{-|
This combinator will attempt match a given string. If the parser fails midway through, this
combinator will fail having consumed input. On success, the string itself is returned and input
will be consumed.

@since 2.0.0.0
-}
string :: String -> Parser String
string :: String -> Parser String
string = (Char -> Parser Char) -> String -> Parser String
forall a b. (a -> Parser b) -> [a] -> Parser [b]
traverse Char -> Parser Char
char

{-|
This combinator will attempt to match any one of the provided list of characters. If one of those
characters is found, it will be returned and the input consumed. If not, the combinator will fail
having consumed no input.

@since 2.0.0.0
-}
oneOf :: [Char] -> Parser Char
oneOf :: String -> Parser Char
oneOf = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy (Defunc (Char -> Bool) -> Parser Char)
-> (String -> Defunc (Char -> Bool)) -> String -> Parser Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> [(Char, Char)] -> Defunc (Char -> Bool)
RANGES Bool
True ([(Char, Char)] -> Defunc (Char -> Bool))
-> (String -> [(Char, Char)]) -> String -> Defunc (Char -> Bool)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [(Char, Char)]
ranges

{-|
This combinator will attempt to not match any one of the provided list of characters. If one of those
characters is found, the combinator will fail having consumed no input. If not, it will return
the character that was not an element of the provided list.

@since 2.0.0.0
-}
noneOf :: [Char] -> Parser Char
noneOf :: String -> Parser Char
noneOf = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy (Defunc (Char -> Bool) -> Parser Char)
-> (String -> Defunc (Char -> Bool)) -> String -> Parser Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> [(Char, Char)] -> Defunc (Char -> Bool)
RANGES Bool
False ([(Char, Char)] -> Defunc (Char -> Bool))
-> (String -> [(Char, Char)]) -> String -> Defunc (Char -> Bool)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [(Char, Char)]
ranges

ranges :: [Char] -> [(Char, Char)]
ranges :: String -> [(Char, Char)]
ranges [] = []
ranges (String -> String
forall a. Ord a => [a] -> [a]
sort -> Char
c:String
cs) = Char -> Int -> String -> [(Char, Char)]
go Char
c (Char -> Int
forall a. Enum a => a -> Int
fromEnum Char
c) String
cs
  where
    go :: Char -> Int -> [Char] -> [(Char, Char)]
    go :: Char -> Int -> String -> [(Char, Char)]
go Char
lower Int
prev [] = [(Char
lower, Int -> Char
forall a. Enum a => Int -> a
toEnum Int
prev)]
    go Char
lower Int
prev (Char
c:String
cs)
      | Int
i <- Char -> Int
forall a. Enum a => a -> Int
fromEnum Char
c, Int
i Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
prev Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 = Char -> Int -> String -> [(Char, Char)]
go Char
lower Int
i String
cs
      | Bool
otherwise = (Char
lower, Int -> Char
forall a. Enum a => Int -> a
toEnum Int
prev) (Char, Char) -> [(Char, Char)] -> [(Char, Char)]
forall a. a -> [a] -> [a]
: Char -> Int -> String -> [(Char, Char)]
go Char
c (Char -> Int
forall a. Enum a => a -> Int
fromEnum Char
c) String
cs

{-|
Like `string`, excepts parses the given string atomically using `try`. Never consumes input on
failure.

@since 2.0.0.0
-}
token :: String -> Parser String
token :: String -> Parser String
token = Parser String -> Parser String
forall a. Parser a -> Parser a
Internal.try (Parser String -> Parser String)
-> (String -> Parser String) -> String -> Parser String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Parser String
string

{-|
This combinator will attempt to match a given character. If that character is the next input token,
the parser succeeds and the character is returned. Otherwise, the combinator will fail having not
consumed any input.

@since 2.0.0.0
-}
char :: Char -> Parser Char
char :: Char -> Parser Char
char Char
c = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy (Bool -> [(Char, Char)] -> Defunc (Char -> Bool)
RANGES Bool
True [(Char
c, Char
c)]) Parser Char -> Defunc Char -> Parser Char
forall (rep :: Type -> Type) a b.
ParserOps rep =>
Parser a -> rep b -> Parser b
$> Char -> Defunc Char
forall a. (Show a, Lift a, Typeable a) => a -> Defunc a
LIFTED Char
c

{-|
Reads any single character. This combinator will only fail if there is no more input remaining.
The parsed character is returned.

@since 2.0.0.0
-}
item :: Parser Char
item :: Parser Char
item = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy (Bool -> [(Char, Char)] -> Defunc (Char -> Bool)
RANGES Bool
False [])

isDigit :: Defunc (Char -> Bool)
isDigit :: Defunc (Char -> Bool)
isDigit = Bool -> [(Char, Char)] -> Defunc (Char -> Bool)
RANGES Bool
True [(Char
'0', Char
'9')]

{-|
Reads a digit (0 through 9).

@since 2.0.0.0
-}
digit :: Parser Char
digit :: Parser Char
digit = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy Defunc (Char -> Bool)
isDigit

{-|
Uses `isAlpha` to parse a letter, this includes
unicode letters.

@since 2.0.0.0
-}
letter :: Parser Char
letter :: Parser Char
letter = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy ((Char -> Bool) -> Code (Char -> Bool) -> Defunc (Char -> Bool)
forall (q :: Type -> Type) a. Quapplicative q => a -> Code a -> q a
makeQ Char -> Bool
isAlpha [||isAlpha||])

{-|
Uses `isAlphaNum` to parse a letter, this includes
unicode letters, or a digit.

@since 2.0.0.0
-}
letterOrDigit :: Parser Char
letterOrDigit :: Parser Char
letterOrDigit = Defunc (Char -> Bool) -> Parser Char
forall (rep :: Type -> Type).
ParserOps rep =>
rep (Char -> Bool) -> Parser Char
satisfy ((Char -> Bool) -> Code (Char -> Bool) -> Defunc (Char -> Bool)
forall (q :: Type -> Type) a. Quapplicative q => a -> Code a -> q a
makeQ Char -> Bool
isAlphaNum [||isAlphaNum||])