looksee 0.1.0 → 0.2.0
raw patch · 3 files changed
+184/−126 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Looksee: Range :: !Int -> !Int -> Range
- Looksee: [efRange] :: ErrF e r -> !Range
- Looksee: [rangeEnd] :: Range -> !Int
- Looksee: [rangeStart] :: Range -> !Int
- Looksee: data Range
- Looksee: errRange :: Err e -> Range
- Looksee: instance GHC.Classes.Eq Looksee.Range
- Looksee: instance GHC.Classes.Ord Looksee.Range
- Looksee: instance GHC.Show.Show Looksee.Range
- Looksee: range :: Text -> Range
+ Looksee: Span :: !a -> !a -> Span a
+ Looksee: [efSpan] :: ErrF e r -> !Span Int
+ Looksee: [spanEnd] :: Span a -> !a
+ Looksee: [spanStart] :: Span a -> !a
+ Looksee: calculateLineCol :: Text -> LineColLookup
+ Looksee: data Span a
+ Looksee: errSpan :: Err e -> Span Int
+ Looksee: instance Data.Foldable.Foldable Looksee.Span
+ Looksee: instance Data.Traversable.Traversable Looksee.Span
+ Looksee: instance GHC.Base.Functor Looksee.Span
+ Looksee: instance GHC.Classes.Eq a => GHC.Classes.Eq (Looksee.Span a)
+ Looksee: instance GHC.Classes.Ord a => GHC.Classes.Ord (Looksee.Span a)
+ Looksee: instance GHC.Show.Show a => GHC.Show.Show (Looksee.Span a)
+ Looksee: lookupLineCol :: Int -> LineColLookup -> (Int, Int)
+ Looksee: spanP :: Monad m => ParserT e m (Span Int)
+ Looksee: textSpan :: Text -> Span Int
+ Looksee: type LineColLookup = Vector (Int, Int)
- Looksee: ErrF :: !Range -> !Reason e r -> ErrF e r
+ Looksee: ErrF :: !Span Int -> !Reason e r -> ErrF e r
Files
- looksee.cabal +1/−1
- src/Looksee.hs +88/−64
- test/Main.hs +95/−61
looksee.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: looksee-version: 0.1.0+version: 0.2.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
@@ -4,14 +4,17 @@ -- | A simple text parser with decent errors module Looksee- ( Label (..)- , Range (..)- , range+ ( Span (..)+ , LineColLookup ()+ , calculateLineCol+ , lookupLineCol+ , Label (..)+ , textSpan , SplitComp (..) , Reason (..) , ErrF (..) , Err (..)- , errRange+ , errSpan , errReason , AltPhase (..) , InfixPhase (..)@@ -20,6 +23,7 @@ , parseT , parse , parseI+ , spanP , throwP , altP , emptyP@@ -130,36 +134,47 @@ import Errata.Types qualified as E import System.IO (stderr) --- private-type OffsetVec = Vector (Int, Int)+-- | A generic span, used for tracking ranges of offsets or (line, col)+data Span a = Span {spanStart :: !a, spanEnd :: !a}+ deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable) --- private-mkOffsetVec :: Text -> OffsetVec-mkOffsetVec t = V.unfoldrN (T.length t) go ((0, 0), T.unpack t)+-- | Auxiliary data structure to translate offsets to (line, col)+type LineColLookup = Vector (Int, Int)++-- | Construct an offset lookup from a document+calculateLineCol :: Text -> LineColLookup+calculateLineCol t = V.unfoldrN (T.length t) go ((0, 0), T.unpack t) where go (p@(!line, !col), xs) = case xs of [] -> Nothing x : xs' -> Just (p, if x == '\n' then ((line + 1, 0), xs') else ((line, col + 1), xs')) +-- | Returns 0-based (line, col) for the given offset.+-- Clamps to the valid range of offsets, returning (0, 0) for+-- empty text. Note that the valid range is from before the first+-- character to before the last, so a 3 character string has+-- three valid offsets (0, 1, and 2).+lookupLineCol :: Int -> LineColLookup -> (Int, Int)+lookupLineCol i v =+ if V.null v+ then (0, 0)+ else v V.! max 0 (min i (V.length v - 1))+ -- | A parser label (for error reporting) newtype Label = Label {unLabel :: Text} deriving stock (Show) deriving newtype (Eq, Ord, IsString) --- | Range in text character offset-data Range = Range {rangeStart :: !Int, rangeEnd :: !Int}- deriving stock (Eq, Ord, Show)---- | Create a range from the given text-range :: Text -> Range-range t = Range 0 (T.length t)+-- | Create a span from the given text+textSpan :: Text -> Span Int+textSpan t = Span 0 (T.length t) -- private -- Parser state data St = St { stHay :: !Text- , stRange :: !Range+ , stSpan :: !(Span Int) , stLabels :: !(Seq Label) } deriving stock (Eq, Ord, Show)@@ -168,38 +183,38 @@ -- Returns list of possible break points with positions -- (startStream, breakPt) breakPt (breakPt + needLen, endStream) breakAllRP :: Text -> St -> [(St, Int, St)]-breakAllRP needle (St hay (Range r0 r1) labs) = fmap go (T.breakOnAll needle hay)+breakAllRP needle (St hay (Span r0 r1) labs) = fmap go (T.breakOnAll needle hay) where go (hay1, hay2) = let end1 = r0 + T.length hay1 needLen = T.length needle- rng1 = Range r0 end1- rng2 = Range (end1 + needLen) r1+ rng1 = Span r0 end1+ rng2 = Span (end1 + needLen) r1 st1 = St hay1 rng1 labs st2 = St (T.drop needLen hay2) rng2 labs in (st1, end1, st2) -- private breakRP :: Text -> St -> Maybe (St, Int, St)-breakRP needle (St hay (Range r0 r1) labs) =+breakRP needle (St hay (Span r0 r1) labs) = let (hay1, hay2) = T.breakOn needle hay in if T.null hay2 then Nothing else let end1 = r0 + T.length hay1 needLen = T.length needle- rng1 = Range r0 end1- rng2 = Range (end1 + needLen) r1+ rng1 = Span r0 end1+ rng2 = Span (end1 + needLen) r1 st1 = St hay1 rng1 labs st2 = St (T.drop needLen hay2) rng2 labs in Just (st1, end1, st2) -- private splitRP :: Text -> St -> [(St, Int)]-splitRP needle (St hay (Range r0 _) labs) = goHead (T.splitOn needle hay)+splitRP needle (St hay (Span r0 _) labs) = goHead (T.splitOn needle hay) where needLen = T.length needle- mkSt start end hayN = St hayN (Range start end) labs+ mkSt start end hayN = St hayN (Span start end) labs goHead = \case [] -> [] hay0 : hays ->@@ -245,7 +260,7 @@ -- | Base functor for 'Err' containing the range and reason for the error data ErrF e r = ErrF- { efRange :: !Range+ { efSpan :: !(Span Int) , efReason :: !(Reason e r) } deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)@@ -283,9 +298,9 @@ instance Corecursive (Err e) where embed = Err --- | Range of a parse error-errRange :: Err e -> Range-errRange = efRange . unErr+-- | Span of a parse error+errSpan :: Err e -> Span Int+errSpan = efSpan . unErr -- | Reason for a parse error errReason :: Err e -> Reason e (Err e)@@ -311,7 +326,7 @@ -- private mkErrT :: Monad m => Reason e (Err e) -> T e m (Err e)-mkErrT re = gets (\st -> Err (ErrF (stRange st) re))+mkErrT re = gets (\st -> Err (ErrF (stSpan st) re)) -- private -- errT :: Monad m => Reason e (Err e) -> T e m a@@ -396,13 +411,13 @@ -- private leftoverP :: Monad m => ParserT e m Int-leftoverP = getsP (\st -> let Range s e = stRange st in e - s)+leftoverP = getsP (\st -> let Span s e = stSpan st in e - s) -- | Run a parser transformer. You must consume all input or this will error! -- If you really don't care about the rest of the input, you can always -- discard it with 'dropAllP'. parseT :: Monad m => ParserT e m a -> Text -> m (Either (Err e) a)-parseT p h = fmap fst (finishParserT (p <* endP) (St h (range h) Empty))+parseT p h = fmap fst (finishParserT (p <* endP) (St h (textSpan h) Empty)) -- | Run a parser (see 'parseT') parse :: Parser e a -> Text -> Either (Err e) a@@ -417,6 +432,15 @@ Right _ -> pure () pure ea +-- | Get the span (in character offset) at the current point representing+-- the entire parseable range. At the start of parsing this will be `Span 0 n` for+-- an `n`-character document. The start offset will increase as input is consumed,+-- and the end offset will decrease as lookahead delimits the range. To evaluate+-- the "real" range of characters consumed by a parser, construct a span with the+-- starting offsets before and after executing a subparser.+spanP :: Monad m => ParserT e m (Span Int)+spanP = getsP stSpan+ -- | Throw a custom parse error throwP :: Monad m => e -> ParserT e m a throwP = errP . ReasonCustom@@ -483,7 +507,7 @@ lookP :: Monad m => ParserT e m a -> ParserT e m a lookP (ParserT g) = ParserT $ \j -> do st0 <- get- g (\ea -> put st0 >> j (first (Err . ErrF (stRange st0) . ReasonLook) ea))+ g (\ea -> put st0 >> j (first (Err . ErrF (stSpan st0) . ReasonLook) ea)) -- | Labels parse errors labelP :: Monad m => Label -> ParserT e m a -> ParserT e m a@@ -538,11 +562,11 @@ put st unParserT (pa <* endP) $ \case Left _ -> do- let rng = stRange st0- start = rangeStart rng+ let rng = stSpan st0+ start = spanStart rng hay' = T.drop (start' - start) (stHay st0)- range' = rng {rangeStart = start'}- st' = st0 {stHay = hay', stRange = range'}+ range' = rng {spanStart = start'}+ st' = st0 {stHay = hay', stSpan = range'} put st' j (Right (acc, False)) Right a -> go (acc :|> a) sts@@ -630,9 +654,9 @@ let h = stHay st (o, h') = T.splitAt i h l = T.length o- r = stRange st- r' = r {rangeStart = rangeStart r + l}- st' = st {stHay = h', stRange = r'}+ r = stSpan st+ r' = r {spanStart = spanStart r + l}+ st' = st {stHay = h', stSpan = r'} in (o, st') -- | Take exactly the given number of characters from the start of the range, or error@@ -642,9 +666,9 @@ let h = stHay st (o, h') = T.splitAt i h l = T.length o- r = stRange st- r' = r {rangeStart = rangeStart r + T.length o}- st' = st {stHay = h', stRange = r'}+ r = stSpan st+ r' = r {spanStart = spanStart r + T.length o}+ st' = st {stHay = h', stSpan = r'} in if l == i then (Right o, st') else (Left l, st) case et of Left l -> errP (ReasonDemand i l)@@ -665,9 +689,9 @@ o = T.takeWhile f h l = T.length o h' = T.drop l h- r = stRange st- r' = r {rangeStart = rangeStart r + l}- st' = st {stHay = h', stRange = r'}+ r = stSpan st+ r' = r {spanStart = spanStart r + l}+ st' = st {stHay = h', stSpan = r'} in (o, st') -- | Like 'takeWhileP' but ensures at least 1 character has been taken@@ -678,9 +702,9 @@ o = T.takeWhile f h l = T.length o h' = T.drop l h- r = stRange st- r' = r {rangeStart = rangeStart r + l}- st' = st {stHay = h', stRange = r'}+ r = stSpan st+ r' = r {spanStart = spanStart r + l}+ st' = st {stHay = h', stSpan = r'} in if l == 0 then (Nothing, st) else (Just o, st') case mt of Nothing -> errP ReasonTakeNone@@ -698,9 +722,9 @@ takeAllP :: Monad m => ParserT e m Text takeAllP = stateP $ \st -> let h = stHay st- r = stRange st- r' = r {rangeStart = rangeEnd r}- st' = st {stHay = T.empty, stRange = r'}+ r = stSpan st+ r' = r {spanStart = spanEnd r}+ st' = st {stHay = T.empty, stSpan = r'} in (h, st') -- | Like 'takeAllP' but ensures at least 1 character has been taken@@ -708,9 +732,9 @@ takeAll1P = do mt <- stateP $ \st -> let h = stHay st- r = stRange st- r' = r {rangeStart = rangeEnd r}- st' = st {stHay = T.empty, stRange = r'}+ r = stSpan st+ r' = r {spanStart = spanEnd r}+ st' = st {stHay = T.empty, stSpan = r'} in if T.null h then (Nothing, st) else (Just h, st') case mt of Nothing -> errP (ReasonDemand 1 0)@@ -812,9 +836,9 @@ -- | Parses and returns the length of the consumed input along with the result measureP :: Monad m => ParserT e m a -> ParserT e m (a, Int) measureP p = do- start <- getsP (rangeStart . stRange)+ start <- getsP (spanStart . stSpan) a <- p- end <- getsP (rangeStart . stRange)+ end <- getsP (spanStart . stSpan) pure (a, end - start) -- | Takes exactly 1 character from the start of the range, returning Nothing@@ -826,9 +850,9 @@ in case mxy of Nothing -> (Nothing, st) Just (x, y) ->- let r = stRange st- r' = r {rangeStart = rangeStart r + 1}- st' = st {stHay = y, stRange = r'}+ let r = stSpan st+ r' = r {spanStart = spanStart r + 1}+ st' = st {stHay = y, stSpan = r'} in (Just x, st') -- | Takes exactly 1 character from the start of the range, throwing error@@ -960,7 +984,7 @@ indent i = let s = T.replicate (2 * i) " " in fmap (s <>) instance HasErrMessage e => HasErrMessage (Err e) where- getErrMessage (Err (ErrF (Range start end) re)) =+ getErrMessage (Err (ErrF (Span start end) re)) = let pos = "Error in range: (" <> T.pack (show start) <> ", " <> T.pack (show end) <> ")" body = case re of ReasonCustom e ->@@ -1005,7 +1029,7 @@ -- | Create 'Errata' formatting a parse error errataE :: HasErrMessage e => FilePath -> (Int -> (E.Line, E.Column)) -> Err e -> [E.Errata] errataE fp mkP e =- let (line, col) = mkP (rangeStart (errRange e))+ let (line, col) = mkP (spanStart (errSpan e)) msg = getErrMessage e block = E.blockSimple E.basicStyle E.basicPointer fp Nothing (line, col, col + 1, Nothing) (Just (T.unlines msg)) in [E.Errata Nothing [block] Nothing]@@ -1013,8 +1037,8 @@ -- | Render a formatted error to text renderE :: HasErrMessage e => FilePath -> Text -> Err e -> Text renderE fp h e =- let ov = mkOffsetVec h- mkP = if V.null ov then const (1, 1) else \i -> let (!l, !c) = ov V.! min i (V.length ov - 1) in (l + 1, c + 1)+ let v = calculateLineCol h+ mkP i = let (l, c) = lookupLineCol i v in (l + 1, c + 1) in TL.toStrict (E.prettyErrors h (errataE fp mkP e)) -- | Print a formatted error to stderr
test/Main.hs view
@@ -4,6 +4,7 @@ module Main (main) where import Control.Applicative (Alternative (..), liftA2)+import Control.Exception (throwIO) import Data.Bifunctor (first) import Data.Foldable (toList) import Data.Sequence (Seq (..))@@ -11,6 +12,7 @@ import Data.String (IsString) import Data.Text (Text) import Data.Text qualified as T+import Data.Void (Void) import Looksee import Looksee.Examples import Test.Tasty (TestName, TestTree, defaultMain, testGroup)@@ -24,20 +26,20 @@ data ParserCase a = ParserCase !TestName !(TestParser a) !Text !(TestResult (a, Int)) -err :: Range -> Reason Error (Err Error) -> TestResult (a, Int)+err :: Span Int -> Reason Error (Err Error) -> TestResult (a, Int) err ra re = Left (Err (ErrF ra re)) -errAlt :: Range -> [(AltPhase, Range, Reason Error (Err Error))] -> TestResult (a, Int)+errAlt :: Span Int -> [(AltPhase, Span Int, Reason Error (Err Error))] -> TestResult (a, Int) errAlt ra tups = Left (Err (ErrF ra (ReasonAlt (Seq.fromList (fmap f tups))))) where f (ap, ra', re) = (ap, Err (ErrF ra' re)) -errInfix :: Range -> [(Int, InfixPhase, Range, Reason Error (Err Error))] -> TestResult (a, Int)+errInfix :: Span Int -> [(Int, InfixPhase, Span Int, Reason Error (Err Error))] -> TestResult (a, Int) errInfix ra tups = Left (Err (ErrF ra (ReasonInfix (Seq.fromList (fmap f tups))))) where f (ix, ip, ra', re) = (ix, ip, Err (ErrF ra' re)) -errLook :: Range -> Range -> Reason Error (Err Error) -> TestResult (a, Int)+errLook :: Span Int -> Span Int -> Reason Error (Err Error) -> TestResult (a, Int) errLook ra1 ra2 re = Left (Err (ErrF ra1 (ReasonLook (Err (ErrF ra2 re))))) suc :: a -> Int -> TestResult (a, Int)@@ -98,8 +100,8 @@ where parser = emptyP :: TestParser Int cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonEmpty)- , ParserCase "non-empty" parser "hi" (err (Range 0 2) ReasonEmpty)+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)+ , ParserCase "non-empty" parser "hi" (err (Span 0 2) ReasonEmpty) ] testPure :: [TestTree]@@ -116,8 +118,8 @@ where parser = fail "i give up" :: TestParser Int cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonFail "i give up"))- , ParserCase "non-empty" parser "hi" (err (Range 0 2) (ReasonFail "i give up"))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonFail "i give up"))+ , ParserCase "non-empty" parser "hi" (err (Span 0 2) (ReasonFail "i give up")) ] testHead :: [TestTree]@@ -125,7 +127,7 @@ where parser = headP cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonDemand 1 0))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonDemand 1 0)) , ParserCase "non-empty" parser "hi" (suc 'h' 1) ] @@ -157,7 +159,7 @@ parser = endP cases = [ ParserCase "empty" parser "" (suc () 0)- , ParserCase "non-empty" parser "hi" (err (Range 0 2) (ReasonLeftover 2))+ , ParserCase "non-empty" parser "hi" (err (Span 0 2) (ReasonLeftover 2)) ] testExpectHead :: [TestTree]@@ -165,9 +167,9 @@ where parser = charP 'h' cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonExpect "h" ""))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonExpect "h" "")) , ParserCase "non-empty" parser "hi" (suc 'h' 1)- , ParserCase "non-match" parser "bye" (err (Range 1 3) (ReasonExpect "h" "b"))+ , ParserCase "non-match" parser "bye" (err (Span 1 3) (ReasonExpect "h" "b")) ] testExpect :: [TestTree]@@ -175,12 +177,12 @@ where parser = textP "hi" cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonExpect "hi" ""))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonExpect "hi" "")) , ParserCase "non-empty" parser "hi" (suc "hi" 0) , ParserCase "prefix" parser "hiya" (suc "hi" 2)- , ParserCase "partial" parser "hey" (err (Range 2 3) (ReasonExpect "hi" "he"))- , ParserCase "non-match" parser "bye" (err (Range 2 3) (ReasonExpect "hi" "by"))- , ParserCase "short" parser "h" (err (Range 1 1) (ReasonExpect "hi" "h"))+ , ParserCase "partial" parser "hey" (err (Span 2 3) (ReasonExpect "hi" "he"))+ , ParserCase "non-match" parser "bye" (err (Span 2 3) (ReasonExpect "hi" "by"))+ , ParserCase "short" parser "h" (err (Span 1 1) (ReasonExpect "hi" "h")) ] testGreedy :: [TestTree]@@ -200,11 +202,11 @@ where parser = fmap (T.pack . toList) (greedy1P (charP 'h')) :: TestParser Text cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonExpect "h" ""))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonExpect "h" "")) , ParserCase "non-empty" parser "hi" (suc "h" 1) , ParserCase "repeat" parser "hhi" (suc "hh" 1) , ParserCase "full" parser "hhh" (suc "hhh" 0)- , ParserCase "non-match" parser "bye" (err (Range 1 3) (ReasonExpect "h" "b"))+ , ParserCase "non-match" parser "bye" (err (Span 1 3) (ReasonExpect "h" "b")) ] testOr :: [TestTree]@@ -214,17 +216,17 @@ cases = [ ParserCase "empty" parser "" $ errAlt- (Range 0 0)- [ (AltPhaseBranch, Range 0 0, ReasonExpect "h" "")- , (AltPhaseBranch, Range 0 0, ReasonExpect "xi" "")+ (Span 0 0)+ [ (AltPhaseBranch, Span 0 0, ReasonExpect "h" "")+ , (AltPhaseBranch, Span 0 0, ReasonExpect "xi" "") ] , ParserCase "first" parser "hi" (suc "h" 1) , ParserCase "second" parser "xi" (suc "xi" 0) , ParserCase "non-match" parser "bye" $ errAlt- (Range 0 3)- [ (AltPhaseBranch, Range 1 3, ReasonExpect "h" "b")- , (AltPhaseBranch, Range 2 3, ReasonExpect "xi" "by")+ (Span 0 3)+ [ (AltPhaseBranch, Span 1 3, ReasonExpect "h" "b")+ , (AltPhaseBranch, Span 2 3, ReasonExpect "xi" "by") ] ] @@ -235,10 +237,10 @@ cases = [ ParserCase "empty" parser "" $ errAlt- (Range 0 0)- [ (AltPhaseBranch, Range 0 0, ReasonExpect "h" "")- , (AltPhaseBranch, Range 0 0, ReasonDemand 1 0)- , (AltPhaseBranch, Range 0 0, ReasonExpect "xi" "")+ (Span 0 0)+ [ (AltPhaseBranch, Span 0 0, ReasonExpect "h" "")+ , (AltPhaseBranch, Span 0 0, ReasonDemand 1 0)+ , (AltPhaseBranch, Span 0 0, ReasonExpect "xi" "") ] , ParserCase "first" parser "hi" (suc "h" 1) , ParserCase "middle" parser "zi" (suc "y" 1)@@ -269,8 +271,8 @@ where parser = charP 'x' >>= \c -> pure [c, c] cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonExpect "x" ""))- , ParserCase "first" parser "hi" (err (Range 1 2) (ReasonExpect "x" "h"))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonExpect "x" ""))+ , ParserCase "first" parser "hi" (err (Span 1 2) (ReasonExpect "x" "h")) , ParserCase "second" parser "xi" (suc "xx" 1) ] @@ -279,8 +281,8 @@ where parser = headP >>= \x -> if x == 'x' then pure 'y' else emptyP cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonDemand 1 0))- , ParserCase "first" parser "hi" (err (Range 1 2) ReasonEmpty)+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonDemand 1 0))+ , ParserCase "first" parser "hi" (err (Span 1 2) ReasonEmpty) , ParserCase "second" parser "xi" (suc 'y' 1) ] @@ -290,8 +292,8 @@ cust = Error "boo" parser = throwP cust :: TestParser Int cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonCustom cust))- , ParserCase "non-empty" parser "hi" (err (Range 0 2) (ReasonCustom cust))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonCustom cust))+ , ParserCase "non-empty" parser "hi" (err (Span 0 2) (ReasonCustom cust)) ] testConsumeThrow :: [TestTree]@@ -300,8 +302,8 @@ cust = Error "boo" parser = headP *> throwP cust :: TestParser Int cases =- [ ParserCase "empty" parser "" (err (Range 0 0) (ReasonDemand 1 0))- , ParserCase "non-empty" parser "hi" (err (Range 1 2) (ReasonCustom cust))+ [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonDemand 1 0))+ , ParserCase "non-empty" parser "hi" (err (Span 1 2) (ReasonCustom cust)) ] testOptThrow :: [TestTree]@@ -364,7 +366,7 @@ where parser = lookP headP cases =- [ ParserCase "non-match empty" parser "" (errLook (Range 0 0) (Range 0 0) (ReasonDemand 1 0))+ [ ParserCase "non-match empty" parser "" (errLook (Span 0 0) (Span 0 0) (ReasonDemand 1 0)) , ParserCase "non-empty" parser "hi" (suc 'h' 2) ] @@ -374,8 +376,8 @@ cust = Error "boo" parser = lookP (headP *> throwP cust) :: TestParser Char cases =- [ ParserCase "non-match empty" parser "" (errLook (Range 0 0) (Range 0 0) (ReasonDemand 1 0))- , ParserCase "non-empty" parser "hi" (errLook (Range 0 2) (Range 1 2) (ReasonCustom cust))+ [ ParserCase "non-match empty" parser "" (errLook (Span 0 0) (Span 0 0) (ReasonDemand 1 0))+ , ParserCase "non-empty" parser "hi" (errLook (Span 0 2) (Span 1 2) (ReasonCustom cust)) ] testTakeWhile :: [TestTree]@@ -395,8 +397,8 @@ where parser = takeWhile1P (== 'h') :: TestParser Text cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonTakeNone)- , ParserCase "non-match" parser "i" (err (Range 0 1) ReasonTakeNone)+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonTakeNone)+ , ParserCase "non-match" parser "i" (err (Span 0 1) ReasonTakeNone) , ParserCase "match" parser "hi" (suc "h" 1) , ParserCase "match 2" parser "hhi" (suc "hh" 1) , ParserCase "match end" parser "hh" (suc "hh" 0)@@ -419,8 +421,8 @@ where parser = dropWhile1P (== 'h') :: TestParser Int cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonTakeNone)- , ParserCase "non-match" parser "i" (err (Range 0 1) ReasonTakeNone)+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonTakeNone)+ , ParserCase "non-match" parser "i" (err (Span 0 1) ReasonTakeNone) , ParserCase "match" parser "hi" (suc 1 1) , ParserCase "match 2" parser "hhi" (suc 2 1) , ParserCase "match end" parser "hh" (suc 2 0)@@ -434,14 +436,14 @@ parserR = infixRP "+" (textP "x") (textP "x+x") :: TestParser (Text, Text) parserL = infixRP "+" (textP "x+x") (textP "x") :: TestParser (Text, Text) cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonEmpty)- , ParserCase "fail delim" parser "xy" (err (Range 0 2) ReasonEmpty)- , ParserCase "fail first" parser "+y" (errInfix (Range 0 2) [(0, InfixPhaseLeft, Range 0 0, ReasonTakeNone)])- , ParserCase "fail second" parser "x+" (errInfix (Range 0 2) [(1, InfixPhaseRight, Range 2 2, ReasonTakeNone)])+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)+ , ParserCase "fail delim" parser "xy" (err (Span 0 2) ReasonEmpty)+ , ParserCase "fail first" parser "+y" (errInfix (Span 0 2) [(0, InfixPhaseLeft, Span 0 0, ReasonTakeNone)])+ , ParserCase "fail second" parser "x+" (errInfix (Span 0 2) [(1, InfixPhaseRight, Span 2 2, ReasonTakeNone)]) , ParserCase "match" parser "x+y" (suc ("x", "y") 0) , ParserCase "match multi" parser "x++y" (suc ("x", "+y") 0) , ParserCase "match rassoc" parserR "x+x+x" (suc ("x", "x+x") 0)- , ParserCase "fail lassoc" parserL "x+x+x" (errInfix (Range 0 5) [(1, InfixPhaseLeft, Range 1 1, ReasonExpect "x+x" "x")])+ , ParserCase "fail lassoc" parserL "x+x+x" (errInfix (Span 0 5) [(1, InfixPhaseLeft, Span 1 1, ReasonExpect "x+x" "x")]) ] testSomeInfixR :: [TestTree]@@ -452,10 +454,10 @@ parserR = someInfixRP "+" (textP "x") (textP "x+x") :: TestParser (Text, Text) parserL = someInfixRP "+" (textP "x+x") (textP "x") :: TestParser (Text, Text) cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonEmpty)- , ParserCase "fail delim" parser "xy" (err (Range 0 2) ReasonEmpty)- , ParserCase "fail first" parser "+y" (errInfix (Range 0 2) [(0, InfixPhaseLeft, Range 0 0, ReasonTakeNone)])- , ParserCase "fail second" parser "x+" (errInfix (Range 0 2) [(1, InfixPhaseRight, Range 2 2, ReasonTakeNone)])+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)+ , ParserCase "fail delim" parser "xy" (err (Span 0 2) ReasonEmpty)+ , ParserCase "fail first" parser "+y" (errInfix (Span 0 2) [(0, InfixPhaseLeft, Span 0 0, ReasonTakeNone)])+ , ParserCase "fail second" parser "x+" (errInfix (Span 0 2) [(1, InfixPhaseRight, Span 2 2, ReasonTakeNone)]) , ParserCase "match" parser "x+y" (suc ("x", "y") 0) , ParserCase "match multi" parser "x++y" (suc ("x", "+y") 0) , ParserCase "match rassoc" parserR "x+x+x" (suc ("x", "x+x") 0)@@ -469,12 +471,12 @@ parserR = breakP "+" (textP "x") parserL = breakP "+" (textP "x+x") cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonEmpty)- , ParserCase "fail delim" parser "x" (err (Range 0 1) ReasonEmpty)- , ParserCase "fail first" parser "y+" (errInfix (Range 0 2) [(1, InfixPhaseLeft, Range 0 1, ReasonTakeNone)])+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)+ , ParserCase "fail delim" parser "x" (err (Span 0 1) ReasonEmpty)+ , ParserCase "fail first" parser "y+" (errInfix (Span 0 2) [(1, InfixPhaseLeft, Span 0 1, ReasonTakeNone)]) , ParserCase "match" parser "x+x+y" (suc "x" 3) , ParserCase "match rassoc" parserR "x+x+x" (suc "x" 3)- , ParserCase "fail lassoc" parserL "x+x+x" (errInfix (Range 0 5) [(1, InfixPhaseLeft, Range 1 1, ReasonExpect "x+x" "x")])+ , ParserCase "fail lassoc" parserL "x+x+x" (errInfix (Span 0 5) [(1, InfixPhaseLeft, Span 1 1, ReasonExpect "x+x" "x")]) ] testSomeBreak :: [TestTree]@@ -484,9 +486,9 @@ parserR = someBreakP "+" (textP "x") parserL = someBreakP "+" (textP "x+x") cases =- [ ParserCase "empty" parser "" (err (Range 0 0) ReasonEmpty)- , ParserCase "fail delim" parser "x" (err (Range 0 1) ReasonEmpty)- , ParserCase "fail first" parser "y+" (errInfix (Range 0 2) [(1, InfixPhaseLeft, Range 0 1, ReasonTakeNone)])+ [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)+ , ParserCase "fail delim" parser "x" (err (Span 0 1) ReasonEmpty)+ , ParserCase "fail first" parser "y+" (errInfix (Span 0 2) [(1, InfixPhaseLeft, Span 0 1, ReasonTakeNone)]) , ParserCase "match" parser "x+x+y" (suc "x" 3) , ParserCase "match rassoc" parserR "x+x+x" (suc "x" 3) , ParserCase "match lassoc" parserL "x+x+x" (suc "x+x" 1)@@ -513,13 +515,44 @@ cases = [ ParserCase "empty" parser "" (suc ([""], True) 0) , ParserCase "single" parser "x" (suc (["x"], True) 0)- , ParserCase "fail" parser "xy" (err (Range 0 2) (ReasonSplitComp SplitCompGE 1 "+" 0))+ , ParserCase "fail" parser "xy" (err (Span 0 2) (ReasonSplitComp SplitCompGE 1 "+" 0)) , ParserCase "double" parser "x+x" (suc (["x", "x"], True) 0) , ParserCase "triple" parser "x+x+x" (suc (["x", "x", "x"], True) 0)- , ParserCase "fail first" parser "y+x" (err (Range 0 3) (ReasonSplitComp SplitCompGE 1 "+" 0))+ , ParserCase "fail first" parser "y+x" (err (Span 0 3) (ReasonSplitComp SplitCompGE 1 "+" 0)) , ParserCase "fail second" parser "x+y" (suc (["x"], False) 2) ] +testSpan :: TestTree+testSpan = testCase "span" $ do+ let p :: Parser Void (Span Int, Span Int) = do+ charP_ 'x'+ r1 <- spanP+ charP_ 'y'+ r2 <- spanP+ charP_ 'z'+ pure (r1, r2)+ let doc = "xyz"+ case parse p doc of+ Left e -> throwIO e+ Right (r1, r2) -> do+ r1 @?= Span 1 3+ r2 @?= Span 2 3+ let v1 = calculateLineCol doc+ lookupLineCol (-1) v1 @?= (0, 0)+ lookupLineCol 0 v1 @?= (0, 0)+ lookupLineCol 1 v1 @?= (0, 1)+ lookupLineCol 2 v1 @?= (0, 2)+ lookupLineCol 3 v1 @?= (0, 2)+ let v2 = calculateLineCol "a\nbc\nd"+ lookupLineCol (-1) v2 @?= (0, 0)+ lookupLineCol 0 v2 @?= (0, 0)+ lookupLineCol 1 v2 @?= (0, 1)+ lookupLineCol 2 v2 @?= (1, 0)+ lookupLineCol 3 v2 @?= (1, 1)+ lookupLineCol 4 v2 @?= (1, 2)+ lookupLineCol 5 v2 @?= (2, 0)+ lookupLineCol 6 v2 @?= (2, 0)+ testJson :: TestTree testJson = testGroup "json" (fmap test cases) where@@ -610,6 +643,7 @@ testGroup "Looksee" [ testBasic+ , testSpan , testJson , testSexp , testArith