module Graphics.Blank.Parser where

import Control.Applicative hiding (many, optional)

import Data.Char
import Data.Ix
import Data.Functor (void)

import Text.ParserCombinators.ReadP
import Text.ParserCombinators.ReadPrec (ReadPrec, readPrec_to_P)

-- | @maybeRead p@ will either parse @p@ or return 'Nothing' without consuming any
--   input. Compare to 'option' from "Text.ParserCombinators.ReadP".
maybeRead :: ReadP a -> ReadP (Maybe a)
maybeRead p = (Just <$> p) <|> return Nothing

-- | @maybeReadPrec p@ will either parse @p@ or return 'Nothing' without consuming any
--   input. Compare to 'option' from "Text.ParserCombinators.ReadP".
maybeReadPrec :: ReadPrec a -> ReadPrec (Maybe a)
maybeReadPrec p = (Just <$> p) <|> return Nothing

-- | A case-insensitive version of 'string' from "Text.ParserCombinators.ReadP".
stringCI :: String -> ReadP String
stringCI this = look >>= scan this
  where
    scan :: String -> String -> ReadP String
    scan []     _                = return this
    scan (x:xs) (y:ys)
        | toLower x == toLower y = get *> scan xs ys
    scan _      _                = pfail

-- | Convert a 'ReadPrec' to a 'ReadP' (the converse of 'lift').
unlift :: ReadPrec a -> ReadP a
unlift = flip readPrec_to_P 0

-- | Equivalent to the function from @parsec@, but using 'ReadP'.
noneOf :: [Char] -> ReadP Char
noneOf cs = satisfy $ \c -> not $ elem c cs

-------------------------------------------------------------------------------
-- Parser combinators for CSS identifiers. Adapted from the hxt-css package.
-------------------------------------------------------------------------------

-- | Parses a CSS identifier.
cssIdent :: ReadP String
cssIdent = (:) <$> nmstart <*> many nmchar

-- | Parses the beginning character of a CSS identifier.
nmstart :: ReadP Char
nmstart = satisfy p <|> nonascii
  where
    p c = inRange ('a', 'z') c || inRange ('A', 'Z') c || c == '_'

-- | Parses a non-beginning CSS identifier character.
nmchar :: ReadP Char
nmchar = satisfy p <|> nonascii
  where
    p c = inRange ('a', 'z') c || inRange ('A', 'Z') c ||
        isDigit c || elem c "_-"

-- | Parses a CSS string literal.
stringLit :: ReadP String
stringLit = string1 <|> string2
  where
    string1 = char '"'
           *> many (noneOf "\n\r\f\\\"" <|> nl <|> nonascii)
           <* char '*'
    string2 = char '\''
           *> many (noneOf "\n\r\f\\'"  <|> nl <|> nonascii)
           <* char '\''

-- | Parses a non-ASCII CSS character.
nonascii :: ReadP Char
nonascii = satisfy (> '\DEL')

-- | Parses a CSS-style newline.
nl :: ReadP Char
nl = choice
    [ void $ char '\n'
    , char '\r' >> optional (char '\n')
    , void $ char '\f'
    ] >> return '\n'