-- | a 'Boomerang' library for working with a 'String' {-# LANGUAGE DeriveDataTypeable, FlexibleContexts, FlexibleInstances, TemplateHaskell, TypeFamilies, TypeSynonymInstances, TypeOperators #-} module Text.Boomerang.String ( -- * Types StringBoomerang, StringPrinterParser, StringError -- * Combinators , alpha, anyChar, char, digit, int , integer, lit, satisfy, space -- * Running the 'Boomerang' , isComplete, parseString, unparseString ) where import Prelude hiding ((.), id, (/)) import Control.Category (Category((.), id)) import Data.Char (isAlpha, isDigit, isSpace) import Data.Data (Data, Typeable) import Data.List (stripPrefix) import Data.String (IsString(..)) import Text.Boomerang.Combinators (opt, rCons, rList1) import Text.Boomerang.Error (ParserError(..),ErrorMsg(..), (), condenseErrors, mkParserError) import Text.Boomerang.HStack ((:-)(..)) import Text.Boomerang.Pos (InitialPosition(..), MajorMinorPos(..), incMajor, incMinor) import Text.Boomerang.Prim (Parser(..), Boomerang(..), parse1, xmaph, unparse1, val) type StringError = ParserError MajorMinorPos type StringBoomerang = Boomerang StringError String type StringPrinterParser = StringBoomerang {-# DEPRECATED StringPrinterParser "Use StringBoomerang instead" #-} instance InitialPosition StringError where initialPos _ = MajorMinorPos 0 0 -- | a constant string lit :: String -> StringBoomerang r r lit l = Boomerang pf sf where pf = Parser $ \tok pos -> case tok of [] -> mkParserError pos [EOI "input", Expect (show l)] _ -> parseLit l tok pos sf b = [ (\string -> (l ++ string), b)] parseLit :: String -> String -> MajorMinorPos -> [Either StringError ((r -> r, String), MajorMinorPos)] parseLit [] ss pos = [Right ((id, ss), pos)] parseLit (l:_) [] pos = mkParserError pos [EOI "input", Expect (show l)] parseLit (l:ls) (s:ss) pos | l /= s = mkParserError pos [UnExpect (show s), Expect (show l)] | otherwise = parseLit ls ss (if l == '\n' then incMajor 1 pos else incMinor 1 pos) instance a ~ b => IsString (Boomerang StringError String a b) where fromString = lit -- | statisfy a 'Char' predicate satisfy :: (Char -> Bool) -> StringBoomerang r (Char :- r) satisfy p = val (Parser $ \tok pos -> case tok of [] -> mkParserError pos [EOI "input"] (c:cs) | p c -> do [Right ((c, cs), if (c == '\n') then incMajor 1 pos else incMinor 1 pos)] | otherwise -> do mkParserError pos [SysUnExpect $ show c] ) (\c -> [ \paths -> (c:paths) | p c ]) -- | ascii digits @\'0\'..\'9\'@ digit :: StringBoomerang r (Char :- r) digit = satisfy isDigit "a digit 0-9" -- | matches alphabetic Unicode characters (lower-case, upper-case and title-case letters, -- plus letters of caseless scripts and modifiers letters). (Uses 'isAlpha') alpha :: StringBoomerang r (Char :- r) alpha = satisfy isAlpha "an alphabetic Unicode character" -- | matches white-space characters in the Latin-1 range. (Uses 'isSpace') space :: StringBoomerang r (Char :- r) space = satisfy isSpace "a white-space character" -- | any character anyChar :: StringBoomerang r (Char :- r) anyChar = satisfy (const True) -- | matches the specified character char :: Char -> StringBoomerang r (Char :- r) char c = satisfy (== c) show [c] readIntegral :: (Read a, Eq a, Num a) => String -> a readIntegral s = case reads s of [(x, [])] -> x [] -> error "readIntegral: no parse" _ -> error "readIntegral: ambiguous parse" -- | matches an 'Int' int :: StringBoomerang r (Int :- r) int = xmaph readIntegral (Just . show) (opt (rCons . char '-') . (rList1 digit)) -- | matches an 'Integer' integer :: StringBoomerang r (Integer :- r) integer = xmaph readIntegral (Just . show) (opt (rCons . char '-') . (rList1 digit)) -- | Predicate to test if we have parsed all the strings. -- Typically used as argument to 'parse1' -- -- see also: 'parseStrings' isComplete :: String -> Bool isComplete = null -- | run the parser -- -- Returns the first complete parse or a parse error. -- -- > parseString (rUnit . lit "foo") ["foo"] parseString :: StringBoomerang () (r :- ()) -> String -> Either StringError r parseString pp strs = either (Left . condenseErrors) Right $ parse1 isComplete pp strs -- | run the printer -- -- > unparseString (rUnit . lit "foo") () unparseString :: StringBoomerang () (r :- ()) -> r -> Maybe String unparseString pp r = unparse1 [] pp r