1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE Rank2Types #-}
3 {-# LANGUAGE ScopedTypeVariables #-}
4 {-# OPTIONS_GHC -fno-warn-orphans #-}
5 module Hcompta.Lib.Parsec where
7 import Control.Monad (void)
8 import Control.Monad.Trans.Class (lift)
9 import Control.Monad.Trans.State (StateT(..), get, put)
11 import Data.Char (Char)
12 import qualified Data.Char
14 import Data.Eq (Eq(..))
15 import Data.Foldable (elem, notElem, foldl')
16 import Data.Functor (Functor(..))
17 import Data.Functor.Identity (Identity(..))
18 import qualified Data.List
19 import Data.Maybe (Maybe(..))
20 import Data.Ord (Ord(..))
21 import Data.String (String)
22 import Prelude (($), (.), Integer, Integral(..), Monad(..), Num(..), Show(..), const, seq)
23 import Text.Parsec (Stream, ParsecT, (<|>), (<?>))
24 import qualified Text.Parsec as R hiding (satisfy, string, char, space, newline, crlf)
25 import qualified Text.Parsec.Pos as R
29 -- | Like 'Text.Parsec.choice' but with 'Text.Parsec.try' on each case.
30 choice_try :: Stream s m t => [ParsecT s u m a] -> ParsecT s u m a
31 choice_try = Data.List.foldr ((<|>) . R.try) R.parserZero
32 -- choice_try = R.choice . Data.List.map R.try
34 -- | Like 'Text.Parsec.sepBy' but without parsing an ending separator.
40 many_separated p sep = do
43 xs <- R.many (R.try (sep >> p))
46 -- | Like 'Text.Parsec.sepBy1' but without parsing an ending separator.
52 many1_separated p sep = do
54 xs <- R.many (R.try (sep >> p))
56 -- (:) <$> p <*> R.many (R.try (sep >> p))
58 -- | Make a 'Text.Parsec.ParsecT' also return its user state.
62 -> ParsecT s u m (a, u)
68 -- ** Fixed 'R.satisfy'
70 -- | Like 'R.updatePosChar' but without '\t' being special.
71 updatePosChar :: R.SourcePos -> Char -> R.SourcePos
74 '\n' -> R.newPos (R.sourceName pos) (R.sourceLine pos + 1) 1
75 _ -> R.newPos (R.sourceName pos) (R.sourceLine pos) (R.sourceColumn pos + 1)
77 -- | Like 'R.updatePosString' but using fixed 'updatePosChar'.
78 updatePosString :: R.SourcePos -> String -> R.SourcePos
79 updatePosString = foldl' updatePosChar
81 -- | Like 'R.satisfy' but using fixed 'updatePosChar'.
82 satisfy :: Stream s m Char => (Char -> Bool) -> ParsecT s u m Char
83 satisfy f = R.tokenPrim (\c -> show [c])
84 (\pos c _cs -> updatePosChar pos c)
85 (\c -> if f c then Just c else Nothing)
87 -- | Like 'R.string' but using fixed 'updatePosString'.
88 string :: Stream s m Char => String -> ParsecT s u m String
89 string = R.tokens show updatePosString
91 -- | Like 'R.char' but using fixed 'satisfy'.
92 char :: Stream s m Char => Char -> ParsecT s u m Char
93 char c = satisfy (==c) <?> show [c]
95 -- | Like 'R.anyChar' but using fixed 'satisfy'.
96 anyChar :: Stream s m Char => ParsecT s u m Char
97 anyChar = satisfy (const True)
99 -- | Like 'R.oneOf' but using fixed 'satisfy'.
100 oneOf :: Stream s m Char => [Char] -> ParsecT s u m Char
101 oneOf cs = satisfy (`elem` cs)
103 -- | Like 'R.noneOf' but using fixed 'satisfy'.
104 noneOf :: Stream s m Char => [Char] -> ParsecT s u m Char
105 noneOf cs = satisfy (`notElem` cs)
107 -- ** Custom 'ParsecT' errors
109 -- | Use the 'StateT' monad transformer
110 -- to attach custom errors to the 'ParsecT' monad.
112 -- NOTE: this is a poor man's hack to overcome 'Parsec'’s limitation.
113 type Error_State e = StateT (R.SourcePos, [Error e])
115 = Error_Parser R.ParseError -- ^ 'Error' raised by 'R.fail'.
116 | Error_Custom R.SourcePos e -- ^ 'Error' raised by 'fail_with'.
117 | Error_At R.SourcePos [Error e] -- ^ 'Error' raised by 'runParserT_with_Error_fail'.
120 instance Functor Error where
121 fmap _ (Error_Parser e) = Error_Parser e
122 fmap f (Error_Custom pos e) = Error_Custom pos (f e)
123 fmap f (Error_At pos es) = Error_At pos $ fmap (fmap f) es
125 -- | Like 'R.parserFail'
126 -- but fail with given custom error.
127 fail_with :: (Stream s (Error_State e m) Char, Monad m)
128 => String -> e -> ParsecT s u (Error_State e m) r
129 fail_with msg err = do
131 rp <- R.getPosition -- NOTE: reported position
132 _ <- (void R.anyChar <|> R.eof)
133 -- NOTE: somehow commits that this character has an error
134 p <- R.getPosition -- NOTE: compared position
136 _ | R.sourceLine p > R.sourceLine sp ||
137 (R.sourceLine p == R.sourceLine sp &&
138 R.sourceColumn p > R.sourceColumn sp)
139 -> lift $ put (p, [Error_Custom rp err])
140 _ | R.sourceLine p == R.sourceLine sp &&
141 R.sourceColumn p == R.sourceColumn sp
142 -> lift $ put (p, Error_Custom rp err:se)
146 -- | Like 'R.runParserT' but return as error an 'Error' list
147 runParserT_with_Error
148 :: (Stream s (Error_State e m) Char, Monad m)
149 => ParsecT s u (Error_State e m) r
150 -> u -> R.SourceName -> s
151 -> m (Either [Error e] r)
152 runParserT_with_Error p u sn s = do
153 (r, (sp, se)) <- runStateT
154 (R.runParserT p u sn s)
155 (R.initialPos sn, [])
157 Left pe | R.sourceLine (R.errorPos pe) < R.sourceLine sp ||
158 (R.sourceLine (R.errorPos pe) == R.sourceLine sp &&
159 R.sourceColumn (R.errorPos pe) < R.sourceColumn sp)
160 -> return $ Left $ se -- NOTE: custom errors detected at a greater position
161 Left pe | R.sourceLine (R.errorPos pe) == R.sourceLine sp &&
162 R.sourceColumn (R.errorPos pe) == R.sourceColumn sp
163 -> return $ Left $ se -- NOTE: prefer custom errors
164 Left pe -> return $ Left $ [Error_Parser pe]
165 Right x -> return $ Right x
167 -- | Like 'R.runParserT_with_Error'
168 -- but applied on the 'Identity' monad.
170 :: (Stream s (Error_State e Identity) Char)
171 => ParsecT s u (Error_State e Identity) r
172 -> u -> R.SourceName -> s
173 -> Either [Error e] r
174 runParser_with_Error p u sn s =
176 runParserT_with_Error p u sn s
178 -- | Like 'R.runParserT_with_Error'
179 -- but propagate any failure to a calling 'ParsecT' monad.
180 runParserT_with_Error_fail ::
181 ( Stream s1 (Error_State ee m) Char
182 , Stream s (Error_State e (ParsecT s1 u1 (Error_State ee m))) Char
185 -> (Error e -> Error ee)
186 -> ParsecT s u (Error_State e (ParsecT s1 u1 (Error_State ee m))) r
187 -> u -> R.SourceName -> s
188 -> ParsecT s1 u1 (Error_State ee m) r
189 runParserT_with_Error_fail msg map_ko p u sn s = do
190 r <- runParserT_with_Error p u sn s
192 Right ok -> return ok
194 rpos <- R.getPosition
197 lift $ put (pos, [Error_At rpos (fmap map_ko ko)])
199 where commit_position = (void R.anyChar) <|> R.eof
201 -- ** Mapping inner monad
203 -- | Like an instance Control.Functor.Morph.'MFunctor' of @R.ParsecT s u@
204 -- but also requiring a second 'Monad' constraint
205 -- on the returned base container.
207 -- NOTE: This code is no longer used by Hcompta, still is left here
208 -- because it was not trivial to write it,
209 -- so eventually it may help others.
210 hoist :: (Monad m, Monad n) => (forall a. m a -> n a) -> ParsecT s u m b -> ParsecT s u n b
211 hoist nat m = R.mkPT $ \s -> do
212 c <- nat $ R.runParsecT m s
214 R.Consumed mrep -> R.Consumed $ nat mrep
215 R.Empty mrep -> R.Empty $ nat mrep
217 -- | Map the type of a 'StateT'.
219 -- NOTE: This code is no longer used by Hcompta, still is left here
220 -- because it was not trivial to write it,
221 -- so eventually it may help others.
222 smap :: Monad m => (s1 -> s0) -> (s0 -> s1) -> StateT s0 m a -> StateT s1 m a
223 smap s1_to_s0 s0_to_s1 st =
224 StateT (\s1_begin -> do
225 (a, s0_end) <- runStateT st (s1_to_s0 s1_begin)
226 return (a, s0_to_s1 s0_end))
230 -- | Return the 'Integer' obtained by multiplying the given digits
231 -- with the power of the given base respective to their rank.
233 :: Integer -- ^ Base.
234 -> [Char] -- ^ Digits (MUST be recognised by 'Data.Char.digitToInt').
236 integer_of_digits base =
238 base*x + toInteger (Data.Char.digitToInt d)) 0
240 decimal :: Stream s m Char => ParsecT s u m Integer
241 decimal = integer 10 R.digit
242 hexadecimal :: Stream s m Char => ParsecT s u m Integer
243 hexadecimal = R.oneOf "xX" >> integer 16 R.hexDigit
244 octal :: Stream s m Char => ParsecT s u m Integer
245 octal = R.oneOf "oO" >> integer 8 R.octDigit
247 -- | Parse an 'Integer'.
248 integer :: Stream s m t
250 -> ParsecT s u m Char
251 -> ParsecT s u m Integer
252 integer base digit = do
253 digits <- R.many1 digit
254 let n = integer_of_digits base digits
259 -- | Return 'True' if and only if the given 'Char' is an horizontal space.
260 is_space_horizontal :: Char -> Bool
261 is_space_horizontal c = c /= '\n' && c /= '\r' && Data.Char.isSpace c
263 -- | Like 'R.space' but using fixed 'satisfy'.
264 space :: Stream s m Char => ParsecT s u m Char
265 {-# INLINEABLE space #-}
266 space = satisfy Data.Char.isSpace <?> "space"
268 -- | Like 'R.spaces' but using fixed 'satisfy'.
269 spaces :: Stream s m Char => ParsecT s u m ()
270 {-# INLINEABLE spaces #-}
271 spaces = R.skipMany space <?> "spaces"
273 -- | Like 'R.tab' but using fixed 'satisfy'.
274 tab :: (Stream s m Char) => ParsecT s u m Char
275 {-# INLINEABLE tab #-}
276 tab = char '\t' <?> "tab"
278 -- | Parse only a 'Char' which passes 'satisfy' 'is_space_horizontal'.
279 space_horizontal :: Stream s m Char => ParsecT s u m Char
280 {-# INLINEABLE space_horizontal #-}
281 space_horizontal = satisfy is_space_horizontal <?> "horizontal-space"
283 new_line :: Stream s m Char => ParsecT s u m ()
284 {-# INLINEABLE new_line #-}
285 new_line = (void (R.try (string "\r\n") <|> R.try (string "\n"))) <?> "newline"