looksee 0.4.0 → 0.5.0
raw patch · 3 files changed
+152/−101 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Looksee: ReasonSplitComp :: !SplitComp -> !Int -> !Text -> !Int -> Reason e r
- Looksee: SplitCompEQ :: SplitComp
- Looksee: SplitCompGE :: SplitComp
- Looksee: SplitCompGT :: SplitComp
- Looksee: data SplitComp
- Looksee: greedy1P :: Monad m => ParserT e m a -> ParserT e m (Seq a)
- Looksee: greedyP :: Monad m => ParserT e m a -> ParserT e m (Seq a)
- Looksee: instance GHC.Classes.Eq Looksee.SplitComp
- Looksee: instance GHC.Classes.Ord Looksee.SplitComp
- Looksee: instance GHC.Enum.Bounded Looksee.SplitComp
- Looksee: instance GHC.Enum.Enum Looksee.SplitComp
- Looksee: instance GHC.Show.Show Looksee.SplitComp
- Looksee: splitCompP :: Monad m => SplitComp -> Int -> Text -> ParserT e m a -> ParserT e m (Seq a)
- Looksee: sepBy1P :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)
+ Looksee: sepBy1P :: Monad m => ParserT e m () -> ParserT e m a -> ParserT e m (Seq a)
- Looksee: sepBy2P :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)
+ Looksee: sepBy2P :: Monad m => ParserT e m () -> ParserT e m a -> ParserT e m (Seq a)
- Looksee: sepByP :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)
+ Looksee: sepByP :: Monad m => ParserT e m () -> ParserT e m a -> ParserT e m (Seq a)
Files
- looksee.cabal +1/−1
- src/Looksee.hs +73/−90
- test/Main.hs +78/−10
looksee.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: looksee-version: 0.4.0+version: 0.5.0 synopsis: parser with looksee description: Please see the README on GitHub at <https://github.com/ejconlon/looksee#readme> homepage: https://github.com/ejconlon/looksee#readme
src/Looksee.hs view
@@ -10,7 +10,6 @@ , lookupLineCol , Label (..) , textSpan- , SplitComp (..) , Reason (..) , ErrF (..) , Err (..)@@ -29,8 +28,6 @@ , emptyP , endP , optP- , greedyP- , greedy1P , lookP , labelP , textP@@ -40,7 +37,6 @@ , breakP , someBreakP , splitP- , splitCompP , split1P , split2P , leadP@@ -62,7 +58,11 @@ , takeAll1P , dropAll1P , betweenP+ , repeatP+ , repeat1P , sepByP+ , sepBy1P+ , sepBy2P , spaceP , stripP , stripStartP@@ -80,14 +80,10 @@ , usciP , numP , unumP- , repeatP- , repeat1P , space1P , strip1P , stripStart1P , stripEnd1P- , sepBy1P- , sepBy2P , transP , scopeP , iterP@@ -228,13 +224,9 @@ data InfixPhase = InfixPhaseLeft | InfixPhaseRight | InfixPhaseCont deriving stock (Eq, Ord, Show, Enum, Bounded) -data SplitComp = SplitCompEQ | SplitCompGE | SplitCompGT- deriving stock (Eq, Ord, Show, Enum, Bounded)- -- | Reason for parse failure data Reason e r = ReasonCustom !e- | ReasonSplitComp !SplitComp !Int !Text !Int | ReasonExpect !Text !Text | ReasonDemand !Int !Int | ReasonLeftover !Int@@ -346,8 +338,8 @@ instance Monad m => Alternative (ParserT e m) where empty = emptyP p1 <|> p2 = altP [p1, p2]- many = fmap toList . greedyP- some = fmap toList . greedy1P+ many = fmap toList . repeatP+ some = fmap toList . repeat1P -- | The parser monad type Parser e = ParserT e Identity@@ -482,20 +474,6 @@ emptyP :: Monad m => ParserT e m a emptyP = ParserT (\j -> mkErrT ReasonEmpty >>= j . Left) --- | Parse repeatedly until the parser fails-greedyP :: Monad m => ParserT e m a -> ParserT e m (Seq a)-greedyP p = go Empty- where- go !acc = do- ma <- optP p- case ma of- Nothing -> pure acc- Just a -> go (acc :|> a)---- | Same as 'greedyP' but ensure at least one result-greedy1P :: Monad m => ParserT e m a -> ParserT e m (Seq a)-greedy1P p = liftA2 (:<|) p (greedyP p)- -- | Lookahead - rewinds state if the parser succeeds, otherwise throws error lookP :: Monad m => ParserT e m a -> ParserT e m a lookP (ParserT g) = ParserT $ \j -> do@@ -539,36 +517,39 @@ someBreakP :: Monad m => Text -> ParserT e m a -> ParserT e m a someBreakP tx pa = fmap fst (someInfixRP tx pa (pure ())) --- | Split on the delimiter, parsing segments with a narrowed range, until parsing fails.--- Returns the sequence of successes with state at the delimiter preceding the failure (or end of input),--- Note that this will always succeed, sometimes consuming no input and yielding empty results.-splitP :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)-splitP tx pa = getP >>= go Empty+-- private+splitTailP :: Monad m => Text -> ParserT e m a -> Seq a -> St -> ParserT e m (Seq a)+splitTailP tx pa = go where go !acc !st = do- mz <- ignoreErrorInfixRP tx pa (pure ())+ mz <- optInfixRP tx pa (pure ()) case mz of Nothing -> optP pa >>= maybe (acc <$ putP st) (pure . (acc :|>)) Just (a, st', _) -> go (acc :|> a) st' -splitCompP :: Monad m => SplitComp -> Int -> Text -> ParserT e m a -> ParserT e m (Seq a)-splitCompP comp n tx pa = do- as <- splitP tx pa- let len = Seq.length as- ok = case comp of- SplitCompEQ -> len == n- SplitCompGE -> len >= n- SplitCompGT -> len > n- if ok then pure as else errP (ReasonSplitComp comp n tx len)+-- | Split on the delimiter, parsing segments with a narrowed range, until parsing fails.+-- Returns the sequence of successes with state at the delimiter preceding the failure (or end of input),+-- Note that this will always succeed, sometimes consuming no input and yielding empty results.+splitP :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)+splitP tx pa = getP >>= splitTailP tx pa Empty -- | Like 'splitP' but ensures the sequence is at least length 1. split1P :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)-split1P = splitCompP SplitCompGE 1+split1P tx pa = do+ mz <- optInfixRP tx pa (pure ())+ case mz of+ Nothing -> fmap Seq.singleton pa+ Just (a, st', _) -> splitTailP tx pa (Seq.singleton a) st' -- | Like 'splitP' but ensures the sequence is at least length 2. -- (This ensures there is at least one delimiter included.) split2P :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)-split2P = splitCompP SplitCompGE 2+split2P tx pa = do+ a0 <- someBreakP tx pa+ mz <- optInfixRP tx pa (pure ())+ case mz of+ Nothing -> fmap (Empty :|> a0 :|>) pa+ Just (a1, st', _) -> splitTailP tx pa (Empty :|> a0 :|> a1) st' -- | Like 'splitP' but ensures a leading delimiter leadP :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)@@ -627,22 +608,23 @@ Right c -> pure c -- private-ignoreErrorInfixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (Maybe (a, St, b))-ignoreErrorInfixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (ignoreErrorInfix j) (breakAllRP tx st0))+optInfixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (Maybe (a, St, b))+optInfixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (optInfixFn j) (breakAllRP tx st0)) -ignoreErrorInfix+-- private+optInfixFn :: (Either (Err e) (Maybe (a, St, b)) -> T e m r) -> (Either (Err e) (Maybe (a, St, b)) -> T e m r)-ignoreErrorInfix j e = case e of+optInfixFn j e = case e of Right _ -> j e Left _ -> j (Right Nothing) -- private-requireInfix+requireInfixFn :: Monad m => (Either (Err e) (a, b) -> T e m r) -> (Either (Err e) (Maybe (a, St, b)) -> T e m r)-requireInfix j = \case+requireInfixFn j = \case Right mxab -> case mxab of Nothing -> mkErrT ReasonEmpty >>= j . Left@@ -652,12 +634,12 @@ -- | Right-associative infix parsing. Searches for the operator from START to END of range, -- trying only the first break point. infixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (a, b)-infixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (requireInfix j) (maybeToList (breakRP tx st0)))+infixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (requireInfixFn j) (maybeToList (breakRP tx st0))) -- | Right-associative infix parsing. Searches for the operator from START to END of range, -- trying subsequent break points until success. someInfixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (a, b)-someInfixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (requireInfix j) (breakAllRP tx st0))+someInfixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (requireInfixFn j) (breakAllRP tx st0)) -- | Take the given number of characters from the start of the range, or fewer if empty takeP :: Monad m => Int -> ParserT e m Text@@ -811,23 +793,49 @@ betweenP :: ParserT e m x -> ParserT e m y -> ParserT e m a -> ParserT e m a betweenP px py pa = px *> pa <* py --- | Parse a sequence of items delimited by the first parser-sepByP :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)-sepByP c p = go+repeatTailP :: Monad m => ParserT e m a -> Seq a -> ParserT e m (Seq a)+repeatTailP p = go where- go = do+ go !acc = do ma <- optP p case ma of- Nothing -> pure Empty- Just a -> goNext (Empty :|> a)- goNext !acc = do- mc <- optP c- case mc of Nothing -> pure acc- Just _ -> do- a <- p- goNext (acc :|> a)+ Just a -> go (acc :|> a) +-- | Repeat a parser until it fails, collecting the results.+repeatP :: Monad m => ParserT e m a -> ParserT e m (Seq a)+repeatP p = repeatTailP p Empty++-- | Like 'repeatP' but ensures at least one result.+repeat1P :: Monad m => ParserT e m a -> ParserT e m (Seq a)+repeat1P p = p >>= repeatTailP p . Seq.singleton++-- private+sepByTailP :: Monad m => ParserT e m () -> ParserT e m a -> Seq a -> ParserT e m (Seq a)+sepByTailP pu pa = go+ where+ go !acc = do+ ma <- optP (pu >> pa)+ case ma of+ Nothing -> pure acc+ Just a -> go (acc :|> a)++-- | Parse a sequence of items delimited by the first parser+sepByP :: Monad m => ParserT e m () -> ParserT e m a -> ParserT e m (Seq a)+sepByP pu pa = optP pa >>= maybe (pure Empty) (sepByTailP pu pa . Seq.singleton)++-- | Like 'sepByP' but ensures at least one result.+sepBy1P :: Monad m => ParserT e m () -> ParserT e m a -> ParserT e m (Seq a)+sepBy1P pu pa = pa >>= sepByTailP pu pa . Seq.singleton++-- | Like 'sepByP' but ensures at least two results (and at least one delimiter).+sepBy2P :: Monad m => ParserT e m () -> ParserT e m a -> ParserT e m (Seq a)+sepBy2P pu pa = do+ a0 <- pa+ pu+ a1 <- pa+ sepByTailP pu pa (Empty :|> a0 :|> a1)+ -- | Consumes many spaces at the start of the range spaceP :: Monad m => ParserT e m () spaceP = void (dropWhileP isSpace)@@ -945,20 +953,6 @@ partS = S.scientific frac (ex - places) pure (Right (wholeS + partS)) --- | Repeat a parser until it fails, collecting the results.-repeatP :: Monad m => ParserT e m a -> ParserT e m (Seq a)-repeatP p = go Empty- where- go !acc = do- ma <- optP p- case ma of- Nothing -> pure acc- Just a -> go (acc :|> a)---- | Like 'repeatP' but ensures at least 1-repeat1P :: Monad m => ParserT e m a -> ParserT e m (Seq a)-repeat1P p = liftA2 (:<|) p (repeatP p)- -- | Like 'spaceP' but ensures at least 1 space removed space1P :: Monad m => ParserT e m () space1P = void (dropWhile1P isSpace)@@ -975,14 +969,6 @@ stripEnd1P :: Monad m => ParserT e m a -> ParserT e m a stripEnd1P p = p <* space1P --- | Like 'sepByP' but ensures at least 1 element-sepBy1P :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)-sepBy1P px pa = liftA2 (:<|) pa (fmap (fromMaybe Empty) (optP (px *> sepByP px pa)))---- | Like 'sepBy1P' but ensures at least 2 elements (i.e. there was a delimiter)-sepBy2P :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)-sepBy2P px pa = liftA2 (:<|) (pa <* px) (sepBy1P px pa)- -- | Implement this to format custom errors. The list will be joined with `unlines`. class HasErrMessage e where getErrMessage :: e -> [Text]@@ -1002,9 +988,6 @@ let hd = "Custom error:" tl = indent 1 (getErrMessage e) in hd : tl- ReasonSplitComp comp n tx len ->- let op = case comp of SplitCompEQ -> "=="; SplitCompGE -> ">="; SplitCompGT -> ">"- in ["Split on \"" <> tx <> "\" with length " <> T.pack (show len) <> " not " <> op <> " " <> T.pack (show n)] ReasonExpect expected actual -> ["Expected text: '" <> expected <> "' but found: '" <> actual <> "'"] ReasonDemand expected actual ->
test/Main.hs view
@@ -65,8 +65,8 @@ , ("end", testEnd) , ("expectHead", testExpectHead) , ("expect", testExpect)- , ("greedy", testGreedy)- , ("greedy1", testGreedy1)+ , ("repeat", testRepeat)+ , ("repeat1", testRepeat1) , ("or", testOr) , ("alt", testAlt) , ("opt (empty)", testOptEmpty)@@ -93,6 +93,10 @@ , ("someBreak", testSomeBreak) , ("split", testSplit) , ("split1", testSplit1)+ , ("split2", testSplit2)+ , ("sepBy", testSepBy)+ , ("sepBy1", testSepBy1)+ , ("sepBy2", testSepBy2) ] testEmpty :: [TestTree]@@ -185,10 +189,10 @@ , ParserCase "short" parser "h" (err (Span 1 1) (ReasonExpect "hi" "h")) ] -testGreedy :: [TestTree]-testGreedy = fmap testParserCase cases+testRepeat :: [TestTree]+testRepeat = fmap testParserCase cases where- parser = fmap (T.pack . toList) (greedyP (charP 'h')) :: TestParser Text+ parser = fmap (T.pack . toList) (repeatP (charP 'h')) :: TestParser Text cases = [ ParserCase "empty" parser "" (suc "" 0) , ParserCase "non-empty" parser "hi" (suc "h" 1)@@ -197,10 +201,10 @@ , ParserCase "non-match" parser "bye" (suc "" 3) ] -testGreedy1 :: [TestTree]-testGreedy1 = fmap testParserCase cases+testRepeat1 :: [TestTree]+testRepeat1 = fmap testParserCase cases where- parser = fmap (T.pack . toList) (greedy1P (charP 'h')) :: TestParser Text+ parser = fmap (T.pack . toList) (repeat1P (charP 'h')) :: TestParser Text cases = [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonExpect "h" "")) , ParserCase "non-empty" parser "hi" (suc "h" 1)@@ -515,15 +519,79 @@ where parser = fmap toList (split1P "+" (takeWhile1P (== 'x'))) cases =- [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonSplitComp SplitCompGE 1 "+" 0))+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonTakeNone) , ParserCase "single" parser "x" (suc ["x"] 0) , ParserCase "no delim" parser "xy" (suc ["x"] 1) , ParserCase "double" parser "x+x" (suc ["x", "x"] 0) , ParserCase "triple" parser "x+x+x" (suc ["x", "x", "x"] 0) , ParserCase "two + fail" parser "x+x+y" (suc ["x", "x"] 2) , ParserCase "two fail" parser "x+xy" (suc ["x", "x"] 1)- , ParserCase "fail first" parser "y+x" (err (Span 0 3) (ReasonSplitComp SplitCompGE 1 "+" 0))+ , ParserCase "fail first" parser "y+x" (err (Span 0 3) ReasonTakeNone) , ParserCase "fail second" parser "x+y" (suc ["x"] 2)+ ]++testSplit2 :: [TestTree]+testSplit2 = fmap testParserCase cases+ where+ parser = fmap toList (split2P "+" (takeWhile1P (== 'x')))+ cases =+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)+ , ParserCase "single" parser "x" (err (Span 0 1) ReasonEmpty)+ , ParserCase "no delim" parser "xy" (err (Span 0 2) ReasonEmpty)+ , ParserCase "double" parser "x+x" (suc ["x", "x"] 0)+ , ParserCase "triple" parser "x+x+x" (suc ["x", "x", "x"] 0)+ , ParserCase "two + fail" parser "x+x+y" (suc ["x", "x"] 2)+ , ParserCase "two fail" parser "x+xy" (suc ["x", "x"] 1)+ , ParserCase "fail first" parser "y+x" (errInfix (Span 0 3) [(1, InfixPhaseLeft, Span 0 1, ReasonTakeNone)])+ , ParserCase "fail second" parser "x+y" (errInfix (Span 0 3) [(1, InfixPhaseCont, Span 2 3, ReasonTakeNone)])+ ]++testSepBy :: [TestTree]+testSepBy = fmap testParserCase cases+ where+ parser = fmap toList (sepByP (charP_ '+') (takeWhile1P (== 'x')))+ cases =+ [ ParserCase "empty" parser "" (suc [] 0)+ , ParserCase "single" parser "x" (suc ["x"] 0)+ , ParserCase "no delim" parser "xy" (suc ["x"] 1)+ , ParserCase "double" parser "x+x" (suc ["x", "x"] 0)+ , ParserCase "triple" parser "x+x+x" (suc ["x", "x", "x"] 0)+ , ParserCase "two + fail" parser "x+x+y" (suc ["x", "x"] 2)+ , ParserCase "two fail" parser "x+xy" (suc ["x", "x"] 1)+ , ParserCase "fail first" parser "y+x" (suc [] 3)+ , ParserCase "fail second" parser "x+y" (suc ["x"] 2)+ ]++testSepBy1 :: [TestTree]+testSepBy1 = fmap testParserCase cases+ where+ parser = fmap toList (sepBy1P (charP_ '+') (takeWhile1P (== 'x')))+ cases =+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonTakeNone)+ , ParserCase "single" parser "x" (suc ["x"] 0)+ , ParserCase "no delim" parser "xy" (suc ["x"] 1)+ , ParserCase "double" parser "x+x" (suc ["x", "x"] 0)+ , ParserCase "triple" parser "x+x+x" (suc ["x", "x", "x"] 0)+ , ParserCase "two + fail" parser "x+x+y" (suc ["x", "x"] 2)+ , ParserCase "two fail" parser "x+xy" (suc ["x", "x"] 1)+ , ParserCase "fail first" parser "y+x" (err (Span 0 3) ReasonTakeNone)+ , ParserCase "fail second" parser "x+y" (suc ["x"] 2)+ ]++testSepBy2 :: [TestTree]+testSepBy2 = fmap testParserCase cases+ where+ parser = fmap toList (sepBy2P (charP_ '+') (takeWhile1P (== 'x')))+ cases =+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonTakeNone)+ , ParserCase "single" parser "x" (err (Span 1 1) (ReasonExpect "+" ""))+ , ParserCase "no delim" parser "xy" (err (Span 2 2) (ReasonExpect "+" "y"))+ , ParserCase "double" parser "x+x" (suc ["x", "x"] 0)+ , ParserCase "triple" parser "x+x+x" (suc ["x", "x", "x"] 0)+ , ParserCase "two + fail" parser "x+x+y" (suc ["x", "x"] 2)+ , ParserCase "two fail" parser "x+xy" (suc ["x", "x"] 1)+ , ParserCase "fail first" parser "y+x" (err (Span 0 3) ReasonTakeNone)+ , ParserCase "fail second" parser "x+y" (err (Span 2 3) ReasonTakeNone) ] testSpan :: TestTree