Skip to content

Commit 53eadf5

Browse files
authored
Merge pull request #949 from haskell/issue-852-monadfix-parser
Resolve #927: Add MonadFix instance
2 parents 853d277 + bef903b commit 53eadf5

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ For the latest version of this document, please see [https://github.com/haskell/
77
- Export `Key` type also from `Data.Aeson.KeyMap` module.
88
- Export `mapWithKey` from `Data.Aeson.KeyMap` module.
99
- Export `ifromJSON` and `iparse` from `Data.Aeson.Types`. Add `iparseEither`.
10+
- Add `MonadFix Parser` instance.
1011

1112
### 2.0.3.0
1213

src/Data/Aeson/Types/Internal.hs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import Prelude.Compat
9090
import Control.Applicative (Alternative(..))
9191
import Control.DeepSeq (NFData(..))
9292
import Control.Monad (MonadPlus(..), ap)
93+
import Control.Monad.Fix (MonadFix (..))
9394
import Data.Char (isLower, isUpper, toLower, isAlpha, isAlphaNum)
9495
import Data.Aeson.Key (Key)
9596
import Data.Data (Data)
@@ -307,6 +308,19 @@ instance Monad.Monad Parser where
307308
{-# INLINE fail #-}
308309
#endif
309310

311+
-- |
312+
--
313+
-- @since 2.1.0.0
314+
instance MonadFix Parser where
315+
mfix f = Parser $ \path kf ks -> let x = runParser (f (fromISuccess x)) path IError ISuccess in
316+
case x of
317+
IError p e -> kf p e
318+
ISuccess y -> ks y
319+
where
320+
fromISuccess :: IResult a -> a
321+
fromISuccess (ISuccess x) = x
322+
fromISuccess (IError path msg) = error $ "mfix @Aeson.Parser: " ++ formatPath path ++ ": " ++ msg
323+
310324
instance Fail.MonadFail Parser where
311325
fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
312326
{-# INLINE fail #-}

tests/UnitTests.hs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{-# LANGUAGE TemplateHaskell #-}
88
{-# LANGUAGE TypeFamilies #-}
99
{-# LANGUAGE QuasiQuotes #-}
10+
{-# LANGUAGE RecursiveDo #-}
1011

1112
-- For Data.Aeson.Types.camelTo
1213
{-# OPTIONS_GHC -fno-warn-deprecations #-}
@@ -37,7 +38,9 @@ import Data.Aeson.Parser
3738
import Data.Aeson.Types
3839
( Options(..), Result(Success, Error), ToJSON(..)
3940
, Value(Array, Bool, Null, Number, Object, String), camelTo, camelTo2
40-
, defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse)
41+
, explicitParseField, liftParseJSON, listParser
42+
, defaultOptions, formatPath, formatRelativePath, omitNothingFields, parse, parseMaybe)
43+
import qualified Data.Aeson.Types
4144
import qualified Data.Aeson.KeyMap as KM
4245
import Data.Attoparsec.ByteString (Parser, parseOnly)
4346
import Data.Char (toUpper, GeneralCategory(Control,Surrogate), generalCategory)
@@ -71,6 +74,7 @@ import qualified Data.Text.Lazy.Encoding as TLE
7174
import qualified Data.Vector as Vector
7275
import qualified ErrorMessages
7376
import qualified SerializationFormatSpec
77+
import qualified Data.Map as Map -- Lazy!
7478

7579
roundTripCamel :: String -> Assertion
7680
roundTripCamel name = assertEqual "" name (camelFrom '_' $ camelTo '_' name)
@@ -717,6 +721,106 @@ type instance Fam757 = Maybe
717721
newtype Newtype757 a = MkNewtype757 (Fam757 a)
718722
deriveToJSON1 defaultOptions ''Newtype757
719723

724+
-------------------------------------------------------------------------------
725+
-- MonadFix
726+
-------------------------------------------------------------------------------
727+
728+
monadFixDecoding1 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion
729+
monadFixDecoding1 p = do
730+
fmap (take 10) (parseMaybe p value) @?= Just "xyzxyzxyzx"
731+
where
732+
value = object
733+
[ "foo" .= ('x', "bar" :: String)
734+
, "bar" .= ('y', "quu" :: String)
735+
, "quu" .= ('z', "foo" :: String)
736+
]
737+
738+
monadFixDecoding2 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion
739+
monadFixDecoding2 p = do
740+
fmap (take 10) (parseMaybe p value) @?= Nothing
741+
where
742+
value = object
743+
[ "foo" .= ('x', "bar" :: String)
744+
, "bar" .= ('y', "???" :: String)
745+
, "quu" .= ('z', "foo" :: String)
746+
]
747+
748+
monadFixDecoding3 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion
749+
monadFixDecoding3 p =
750+
fmap (take 10) (parseMaybe p value) @?= Nothing
751+
where
752+
value = object
753+
[ "foo" .= ('x', "bar" :: String)
754+
, "bar" .= Null
755+
, "quu" .= ('z', "foo" :: String)
756+
]
757+
758+
monadFixDecoding4 :: (Value -> Data.Aeson.Types.Parser [Char]) -> Assertion
759+
monadFixDecoding4 p =
760+
fmap (take 10) (parseMaybe p value) @?= Nothing
761+
where
762+
value = object
763+
[ "els" .= ('x', "bar" :: String)
764+
, "bar" .= Null
765+
, "quu" .= ('z', "foo" :: String)
766+
]
767+
768+
-- Parser with explicit references
769+
monadFixParserA :: Value -> Data.Aeson.Types.Parser [Char]
770+
monadFixParserA = withObject "Rec" $ \obj -> mdo
771+
let p'' :: Value -> Data.Aeson.Types.Parser String
772+
p'' "foo" = return foo
773+
p'' "bar" = return bar
774+
p'' "quu" = return quu
775+
p'' _ = fail "Invalid reference"
776+
777+
let p' :: Value -> Data.Aeson.Types.Parser [Char]
778+
p' v = do
779+
(c, cs) <- liftParseJSON p'' (listParser p'') v
780+
return (c : cs)
781+
782+
foo <- explicitParseField p' obj "foo"
783+
bar <- explicitParseField p' obj "bar"
784+
quu <- explicitParseField p' obj "quu"
785+
return foo
786+
787+
-- Parser with arbitrary references!
788+
monadFixParserB :: Value -> Data.Aeson.Types.Parser [Char]
789+
monadFixParserB = withObject "Rec" $ \obj -> mdo
790+
let p'' :: Value -> Data.Aeson.Types.Parser String
791+
p'' key' = do
792+
key <- parseJSON key'
793+
-- this is ugly: we look whether key is in original obj
794+
-- but then query from refs.
795+
--
796+
-- This way we are lazier. Map.traverse isn't lazy enough.
797+
case KM.lookup key obj of
798+
Just _ -> return (refs Map.! key)
799+
Nothing -> fail "Invalid reference"
800+
801+
let p' :: Value -> Data.Aeson.Types.Parser [Char]
802+
p' v = do
803+
(c, cs) <- liftParseJSON p'' (listParser p'') v
804+
return (c : cs)
805+
806+
refs <- traverse p' (KM.toMap obj)
807+
case Map.lookup "foo" refs of
808+
Nothing -> fail "No foo node"
809+
Just root -> return root
810+
811+
monadFixTests :: TestTree
812+
monadFixTests = testGroup "MonadFix"
813+
[ testCase "Example1a" $ monadFixDecoding1 monadFixParserA
814+
, testCase "Example2a" $ monadFixDecoding2 monadFixParserA
815+
, testCase "Example3a" $ monadFixDecoding3 monadFixParserA
816+
, testCase "Example4a" $ monadFixDecoding4 monadFixParserA
817+
818+
, testCase "Example1b" $ monadFixDecoding1 monadFixParserB
819+
, testCase "Example2b" $ monadFixDecoding2 monadFixParserB
820+
, testCase "Example3b" $ monadFixDecoding3 monadFixParserB
821+
, testCase "Example4b" $ monadFixDecoding4 monadFixParserB
822+
]
823+
720824
-------------------------------------------------------------------------------
721825
-- Tests trees
722826
-------------------------------------------------------------------------------
@@ -779,4 +883,5 @@ tests = testGroup "unit" [
779883
[ testCase "example" $
780884
assertEqual "" (object ["foo" .= True]) [aesonQQ| {"foo": true } |]
781885
]
886+
, monadFixTests
782887
]

0 commit comments

Comments
 (0)