@@ -18,58 +18,112 @@ module KDL.Decoder.Internal.DecodeM (
1818) where
1919
2020import Control.Applicative (Alternative (.. ))
21+ import Data.Bifunctor (first )
22+ import Data.List.NonEmpty (NonEmpty (.. ))
23+ import Data.List.NonEmpty qualified as NonEmpty
2124import Data.Text (Text )
2225import KDL.Decoder.Internal.Error
2326
2427-- | The monad that returns either a 'DecodeError' or a result of type @a@.
2528--
26- -- To a first approximation, this monad is equivalent to the @Either DecodeError@
27- -- monad, with the following changes:
29+ -- The odd structure here is because of our backtracking semantics. We want to
30+ -- collect all errors that may appear (even if a value is successfully parsed)
31+ -- so that if we get a failure later on, we can return the deepest error, even
32+ -- if it was in a successful branch.
2833--
29- -- * Uses continuation-passing style for performance
30- -- * Collects as many errors as possible, within an Applicative context
34+ -- Take this motivating example: a node takes an arbitrary number of string
35+ -- args. If you pass some strings then a number, it'll successfully parse up to
36+ -- the number and return success, only for the node to fail later with
37+ -- "unexpected argument: 123". But the true error was
38+ -- "unexpected number, expected string".
3139data DecodeM a
32- = DecodeM
33- ( forall r .
34- (DecodeError -> r ) ->
35- (a -> r ) ->
36- r
37- )
40+ = DecodeM_Found a [BaseDecodeError ]
41+ | DecodeM_Fail (NonEmpty BaseDecodeError )
3842
3943instance Functor DecodeM where
40- fmap f (DecodeM k) = DecodeM $ \ onFail onSuccess -> k onFail (onSuccess . f)
44+ fmap f = \ case
45+ DecodeM_Found a es -> DecodeM_Found (f a) es
46+ DecodeM_Fail es -> DecodeM_Fail es
4147instance Applicative DecodeM where
42- pure x = DecodeM $ \ _ onSuccess -> onSuccess x
43- DecodeM kf <*> DecodeM ka = DecodeM $ \ onFail onSuccess ->
44- -- Collect all errors
45- kf
46- (\ e1 -> ka (\ e2 -> onFail $ e1 <> e2) (\ _ -> onFail e1))
47- (\ f -> ka onFail (onSuccess . f))
48+ pure x = DecodeM_Found x []
49+ l <*> r =
50+ case (l, r) of
51+ (DecodeM_Found f es1, DecodeM_Found a es2) -> DecodeM_Found (f a) (mergeErrorsLR es1 es2)
52+ (DecodeM_Found _ es1, DecodeM_Fail es2) -> DecodeM_Fail (mergeErrorsL es1 es2)
53+ (DecodeM_Fail es1, DecodeM_Found _ es2) -> DecodeM_Fail (mergeErrorsR es1 es2)
54+ (DecodeM_Fail es1, DecodeM_Fail es2) -> DecodeM_Fail (mergeErrors es1 es2)
4855instance Monad DecodeM where
4956 (>>) = (*>)
50- DecodeM ka >>= k = DecodeM $ \ onFail onSuccess ->
51- ka onFail $ \ a -> let DecodeM kb = k a in kb onFail onSuccess
57+ m >>= k =
58+ case m of
59+ DecodeM_Fail es1 -> DecodeM_Fail es1
60+ DecodeM_Found a es1 ->
61+ case k a of
62+ DecodeM_Found b es2 -> DecodeM_Found b (mergeErrorsLR es1 es2)
63+ DecodeM_Fail es2 -> DecodeM_Fail (mergeErrorsL es1 es2)
5264instance Alternative DecodeM where
53- empty = DecodeM $ \ onFail _ -> onFail mempty
54- DecodeM k1 <|> DecodeM k2 = DecodeM $ \ onFail onSuccess ->
55- k1
56- (\ e1 -> k2 (\ e2 -> onFail $ e1 <> e2) onSuccess)
57- onSuccess
65+ empty = failM " <empty>"
66+ l <|> r =
67+ case l of
68+ DecodeM_Found a es1 -> DecodeM_Found a es1
69+ DecodeM_Fail es1 ->
70+ case r of
71+ DecodeM_Found a es2 -> DecodeM_Found a (NonEmpty. toList $ mergeErrorsR es1 es2)
72+ DecodeM_Fail es2 -> DecodeM_Fail (mergeErrors es1 es2)
5873
59- -- | Run a 'DecodeM' action and return the result or the error.
74+ -- | Run a 'DecodeM' action and return the result or the deepest error found .
6075runDecodeM :: DecodeM a -> Either DecodeError a
61- runDecodeM (DecodeM f) = f Left Right
76+ runDecodeM = \ case
77+ DecodeM_Found a _ -> Right a
78+ DecodeM_Fail errors -> Left DecodeError {filepath = Nothing , errors}
79+
80+ mergeErrors ::
81+ NonEmpty BaseDecodeError ->
82+ NonEmpty BaseDecodeError ->
83+ NonEmpty BaseDecodeError
84+ mergeErrors es1 es2 =
85+ case compare (key es1) (key es2) of
86+ LT -> es2
87+ EQ -> es1 <> es2
88+ GT -> es1
89+ where
90+ key = length . fst . NonEmpty. head
91+
92+ mergeErrorsL ::
93+ [BaseDecodeError ] ->
94+ NonEmpty BaseDecodeError ->
95+ NonEmpty BaseDecodeError
96+ mergeErrorsL l r = maybe r (\ l' -> mergeErrors l' r) (NonEmpty. nonEmpty l)
97+
98+ mergeErrorsR ::
99+ NonEmpty BaseDecodeError ->
100+ [BaseDecodeError ] ->
101+ NonEmpty BaseDecodeError
102+ mergeErrorsR l r = maybe l (\ r' -> mergeErrors l r') (NonEmpty. nonEmpty r)
103+
104+ mergeErrorsLR ::
105+ [BaseDecodeError ] ->
106+ [BaseDecodeError ] ->
107+ [BaseDecodeError ]
108+ mergeErrorsLR l r =
109+ case (l, r) of
110+ ([] , _) -> r
111+ (_, [] ) -> l
112+ (x : xs, y : ys) -> NonEmpty. toList $ mergeErrors (x :| xs) (y :| ys)
113+
114+ mapErrors :: (BaseDecodeError -> BaseDecodeError ) -> DecodeM a -> DecodeM a
115+ mapErrors f = \ case
116+ DecodeM_Found a es -> DecodeM_Found a (fmap f es)
117+ DecodeM_Fail es -> DecodeM_Fail (fmap f es)
62118
63119-- | Throw an error.
64120decodeThrow :: DecodeErrorKind -> DecodeM a
65- decodeThrow e = DecodeM $ \ onFail _ -> onFail $ DecodeError Nothing [ ([] , e)]
121+ decodeThrow e = DecodeM_Fail . NonEmpty. singleton $ ([] , e)
66122
67123-- | Throw a 'DecodeError_Custom' error.
68124failM :: Text -> DecodeM a
69125failM = decodeThrow . DecodeError_Custom
70126
71127-- | Add context to all errors that occur in the given action.
72128addContext :: ContextItem -> DecodeM a -> DecodeM a
73- addContext ctxItem (DecodeM f) = DecodeM $ \ onFail onSuccess -> f (onFail . addCtx) onSuccess
74- where
75- addCtx e = e{errors = [(ctxItem : ctx, msg) | (ctx, msg) <- e. errors]}
129+ addContext ctxItem = mapErrors (first (ctxItem : ))
0 commit comments