packages feed

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 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