packages feed

looksee-0.5.3: test/Main.hs

{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Applicative (Alternative (..))
import Control.Exception (throwIO)
import Control.Monad (join)
import Data.Foldable (for_, toList)
import Data.Sequence (Seq (..))
import Data.Sequence qualified as Seq
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)
import Test.Tasty.HUnit (testCase, (@?=))

newtype Error = Error {unError :: String} deriving (Eq, Show, IsString)

type TestParser = Parser Error

type TestResult = Either (Err Error)

data ParserCase a = ParserCase !TestName !(TestParser a) !Text !(TestResult (a, Int))

err :: Span Int -> Reason Error (Err Error) -> TestResult (a, Int)
err ra re = Left (Err (ErrF ra re))

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 :: 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 :: 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)
suc a i = Right (a, i)

testParserCase :: (Show a, Eq a) => ParserCase a -> TestTree
testParserCase (ParserCase name parser input expected) = testCase name $ do
  let parser' = liftA2 (,) parser dropAllP
      actual = parse parser' input
  actual @?= expected

testBasic :: TestTree
testBasic =
  testGroup "basic" $
    fmap
      (uncurry testGroup)
      [ ("empty", testEmpty)
      , ("pure", testPure)
      , ("fail", testFail)
      , ("head", testHead)
      , ("take", testTake)
      , ("drop", testDrop)
      , ("end", testEnd)
      , ("expectHead", testExpectHead)
      , ("expect", testExpect)
      , ("repeat", testRepeat)
      , ("repeat1", testRepeat1)
      , ("or", testOr)
      , ("alt", testAlt)
      , ("branch", testBranch)
      , ("commit", testCommit)
      , ("opt (empty)", testOptEmpty)
      , ("opt", testOpt)
      , ("bind (1)", testBind1)
      , ("bind (2)", testBind2)
      , ("throw", testThrow)
      , ("throw (consume)", testConsumeThrow)
      , ("throw (opt)", testOptThrow)
      , ("throw (opt consume)", testOptConsumeThrow)
      , ("throw (mixed)", testThrowMixed)
      , ("throw (mixed flip)", testThrowMixedFlip)
      , ("backtrack", testBacktrack)
      , ("look (pure)", testLookPure)
      , ("look (success)", testLookSuccess)
      , ("look (failure)", testLookFailure)
      , ("takeWhile", testTakeWhile)
      , ("takeWhile1", testTakeWhile1)
      , ("dropWhile", testDropWhile)
      , ("dropWhile1", testDropWhile1)
      , ("infixR", testInfixR)
      , ("someInfixR", testSomeInfixR)
      , ("break", testBreak)
      , ("someBreak", testSomeBreak)
      , ("split", testSplit)
      , ("split1", testSplit1)
      , ("split2", testSplit2)
      , ("sepBy", testSepBy)
      , ("sepBy1", testSepBy1)
      , ("sepBy2", testSepBy2)
      ]

testEmpty :: [TestTree]
testEmpty = fmap testParserCase cases
 where
  parser = emptyP :: TestParser Int
  cases =
    [ ParserCase "empty" parser "" (err (Span 0 0) ReasonEmpty)
    , ParserCase "non-empty" parser "hi" (err (Span 0 2) ReasonEmpty)
    ]

testPure :: [TestTree]
testPure = fmap testParserCase cases
 where
  parser = pure 'x'
  cases =
    [ ParserCase "empty" parser "" (suc 'x' 0)
    , ParserCase "non-empty" parser "hi" (suc 'x' 2)
    ]

testFail :: [TestTree]
testFail = fmap testParserCase cases
 where
  parser = fail "i give up" :: TestParser Int
  cases =
    [ 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]
testHead = fmap testParserCase cases
 where
  parser = headP
  cases =
    [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonDemand 1 0))
    , ParserCase "non-empty" parser "hi" (suc 'h' 1)
    ]

testTake :: [TestTree]
testTake = fmap testParserCase cases
 where
  parser = takeP 2
  cases =
    [ ParserCase "len 0" parser "" (suc "" 0)
    , ParserCase "len 1" parser "h" (suc "h" 0)
    , ParserCase "len 2" parser "hi" (suc "hi" 0)
    , ParserCase "len 3" parser "hii" (suc "hi" 1)
    ]

testDrop :: [TestTree]
testDrop = fmap testParserCase cases
 where
  parser = dropP 2
  cases =
    [ ParserCase "len 0" parser "" (suc 0 0)
    , ParserCase "len 1" parser "h" (suc 1 0)
    , ParserCase "len 2" parser "hi" (suc 2 0)
    , ParserCase "len 3" parser "hii" (suc 2 1)
    ]

testEnd :: [TestTree]
testEnd = fmap testParserCase cases
 where
  parser = endP
  cases =
    [ ParserCase "empty" parser "" (suc () 0)
    , ParserCase "non-empty" parser "hi" (err (Span 0 2) (ReasonLeftover 2))
    ]

testExpectHead :: [TestTree]
testExpectHead = fmap testParserCase cases
 where
  parser = charP 'h'
  cases =
    [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonExpect "h" ""))
    , ParserCase "non-empty" parser "hi" (suc 'h' 1)
    , ParserCase "non-match" parser "bye" (err (Span 1 3) (ReasonExpect "h" "b"))
    ]

testExpect :: [TestTree]
testExpect = fmap testParserCase cases
 where
  parser = textP "hi"
  cases =
    [ 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 (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"))
    ]

testRepeat :: [TestTree]
testRepeat = fmap testParserCase cases
 where
  parser = fmap (T.pack . toList) (repeatP (charP 'h')) :: TestParser Text
  cases =
    [ ParserCase "empty" parser "" (suc "" 0)
    , 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" (suc "" 3)
    ]

testRepeat1 :: [TestTree]
testRepeat1 = fmap testParserCase cases
 where
  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)
    , ParserCase "repeat" parser "hhi" (suc "hh" 1)
    , ParserCase "full" parser "hhh" (suc "hhh" 0)
    , ParserCase "non-match" parser "bye" (err (Span 1 3) (ReasonExpect "h" "b"))
    ]

testOr :: [TestTree]
testOr = fmap testParserCase cases
 where
  parser = textP "h" <|> textP "xi" :: TestParser Text
  cases =
    [ ParserCase "empty" parser "" $
        errAlt
          (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
          (Span 0 3)
          [ (AltPhaseBranch, Span 1 3, ReasonExpect "h" "b")
          , (AltPhaseBranch, Span 2 3, ReasonExpect "xi" "by")
          ]
    ]

testAlt :: [TestTree]
testAlt = fmap testParserCase cases
 where
  parser = altP [textP "h", "y" <$ headP, textP "xi"] :: TestParser Text
  cases =
    [ ParserCase "empty" parser "" $
        errAlt
          (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)
    , ParserCase "last" parser "xi" (suc "y" 1)
    ]

testBranch :: [TestTree]
testBranch = fmap testParserCase cases
 where
  parser =
    branchP
      [ (charP_ 'h', textP "hi")
      , (charP_ 'y', textP "yi")
      , (charP_ 'y', textP "ya")
      ]
      :: TestParser Text
  cases =
    [ ParserCase "empty" parser "" $
        err (Span 0 0) ReasonEmpty
    , ParserCase "first" parser "hi" (suc "hi" 0)
    , ParserCase "fail first" parser "ho" $
        errAlt
          (Span 0 2)
          [ (AltPhaseBranch, Span 2 2, ReasonExpect "hi" "ho")
          ]
    , ParserCase "middle" parser "yi" (suc "yi" 0)
    , ParserCase "last" parser "ya" (suc "ya" 0)
    , ParserCase "fail rest" parser "yo" $
        errAlt
          (Span 0 2)
          [ (AltPhaseBranch, Span 2 2, ReasonExpect "yi" "yo")
          , (AltPhaseBranch, Span 2 2, ReasonExpect "ya" "yo")
          ]
    ]

testCommit :: [TestTree]
testCommit = fmap testParserCase cases
 where
  parser =
    commitP
      [ (charP_ 'h', textP "hi")
      , (charP_ 'y', textP "yi")
      , (charP_ 'y', textP "ya")
      ]
      :: TestParser Text
  cases =
    [ ParserCase "empty" parser "" $
        err (Span 0 0) ReasonEmpty
    , ParserCase "first" parser "hi" (suc "hi" 0)
    , ParserCase "fail first" parser "ho" $
        err (Span 2 2) (ReasonExpect "hi" "ho")
    , ParserCase "middle" parser "yi" (suc "yi" 0)
    , ParserCase "fail last" parser "ya" $
        err (Span 2 2) (ReasonExpect "yi" "ya")
    , ParserCase "fail rest" parser "yo" $
        err (Span 2 2) (ReasonExpect "yi" "yo")
    ]

testOptEmpty :: [TestTree]
testOptEmpty = fmap testParserCase cases
 where
  parser = optP emptyP :: TestParser (Maybe ())
  cases =
    [ ParserCase "empty" parser "" (suc Nothing 0)
    , ParserCase "non-empty" parser "hi" (suc Nothing 2)
    ]

testOpt :: [TestTree]
testOpt = fmap testParserCase cases
 where
  parser = optP (charP 'h') :: TestParser (Maybe Char)
  cases =
    [ ParserCase "non-match empty" parser "" (suc Nothing 0)
    , ParserCase "match" parser "hi" (suc (Just 'h') 1)
    , ParserCase "non-match" parser "bye" (suc Nothing 3)
    ]

testBind1 :: [TestTree]
testBind1 = fmap testParserCase cases
 where
  parser = charP 'x' >>= \c -> pure [c, c]
  cases =
    [ 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)
    ]

testBind2 :: [TestTree]
testBind2 = fmap testParserCase cases
 where
  parser = headP >>= \x -> if x == 'x' then pure 'y' else emptyP
  cases =
    [ 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)
    ]

testThrow :: [TestTree]
testThrow = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = throwP cust :: TestParser Int
  cases =
    [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonCustom cust))
    , ParserCase "non-empty" parser "hi" (err (Span 0 2) (ReasonCustom cust))
    ]

testConsumeThrow :: [TestTree]
testConsumeThrow = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = headP *> throwP cust :: TestParser Int
  cases =
    [ ParserCase "empty" parser "" (err (Span 0 0) (ReasonDemand 1 0))
    , ParserCase "non-empty" parser "hi" (err (Span 1 2) (ReasonCustom cust))
    ]

testOptThrow :: [TestTree]
testOptThrow = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = optP (throwP cust) :: TestParser (Maybe Int)
  cases =
    [ ParserCase "empty" parser "" (suc Nothing 0)
    , ParserCase "non-empty" parser "hi" (suc Nothing 2)
    ]

testOptConsumeThrow :: [TestTree]
testOptConsumeThrow = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = optP (headP *> throwP cust) :: TestParser (Maybe Int)
  cases =
    [ ParserCase "empty" parser "" (suc Nothing 0)
    , ParserCase "non-empty" parser "hi" (suc Nothing 2)
    ]

testThrowMixed :: [TestTree]
testThrowMixed = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = throwP cust <|> pure 1 :: TestParser Int
  cases =
    [ ParserCase "non-empty" parser "hi" (suc 1 2)
    ]

testThrowMixedFlip :: [TestTree]
testThrowMixedFlip = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = pure 1 <|> throwP cust :: TestParser Int
  cases =
    [ ParserCase "non-empty" parser "hi" (suc 1 2)
    ]

testBacktrack :: [TestTree]
testBacktrack = fmap testParserCase cases
 where
  parser = (textP "x" <|> textP "xz") <* (textP_ "z" *> endP)
  cases =
    [ ParserCase "non-empty" parser "xzz" (suc "xz" 0)
    ]

testLookPure :: [TestTree]
testLookPure = fmap testParserCase cases
 where
  parser = lookP (pure 1) :: TestParser Int
  cases =
    [ ParserCase "empty" parser "" (suc 1 0)
    , ParserCase "non-empty" parser "hi" (suc 1 2)
    ]

testLookSuccess :: [TestTree]
testLookSuccess = fmap testParserCase cases
 where
  parser = lookP headP
  cases =
    [ ParserCase "non-match empty" parser "" (errLook (Span 0 0) (Span 0 0) (ReasonDemand 1 0))
    , ParserCase "non-empty" parser "hi" (suc 'h' 2)
    ]

testLookFailure :: [TestTree]
testLookFailure = fmap testParserCase cases
 where
  cust = Error "boo"
  parser = lookP (headP *> throwP cust) :: TestParser Char
  cases =
    [ 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]
testTakeWhile = fmap testParserCase cases
 where
  parser = takeWhileP (== 'h') :: TestParser Text
  cases =
    [ ParserCase "empty" parser "" (suc "" 0)
    , ParserCase "non-match" parser "i" (suc "" 1)
    , ParserCase "match" parser "hi" (suc "h" 1)
    , ParserCase "match 2" parser "hhi" (suc "hh" 1)
    , ParserCase "match end" parser "hh" (suc "hh" 0)
    ]

testTakeWhile1 :: [TestTree]
testTakeWhile1 = fmap testParserCase cases
 where
  parser = takeWhile1P (== 'h') :: TestParser Text
  cases =
    [ 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)
    ]

testDropWhile :: [TestTree]
testDropWhile = fmap testParserCase cases
 where
  parser = dropWhileP (== 'h') :: TestParser Int
  cases =
    [ ParserCase "empty" parser "" (suc 0 0)
    , ParserCase "non-match" parser "i" (suc 0 1)
    , ParserCase "match" parser "hi" (suc 1 1)
    , ParserCase "match 2" parser "hhi" (suc 2 1)
    , ParserCase "match end" parser "hh" (suc 2 0)
    ]

testDropWhile1 :: [TestTree]
testDropWhile1 = fmap testParserCase cases
 where
  parser = dropWhile1P (== 'h') :: TestParser Int
  cases =
    [ 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)
    ]

testInfixR :: [TestTree]
testInfixR = fmap testParserCase cases
 where
  sub d = takeWhile1P (\c -> c == d || c == '+')
  parser = infixRP "+" (sub 'x') (sub 'y') :: TestParser (Text, Text)
  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 (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 (Span 0 5) [(1, InfixPhaseLeft, Span 1 1, ReasonExpect "x+x" "x")])
    ]

testSomeInfixR :: [TestTree]
testSomeInfixR = fmap testParserCase cases
 where
  sub d = takeWhile1P (\c -> c == d || c == '+')
  parser = someInfixRP "+" (sub 'x') (sub 'y') :: TestParser (Text, Text)
  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 (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 "match lassoc" parserL "x+x+x" (suc ("x+x", "x") 0)
    ]

testBreak :: [TestTree]
testBreak = fmap testParserCase cases
 where
  parser = breakP "+" (takeWhile1P (== 'x'))
  parserR = breakP "+" (textP "x")
  parserL = breakP "+" (textP "x+x")
  cases =
    [ 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 (Span 0 5) [(1, InfixPhaseLeft, Span 1 1, ReasonExpect "x+x" "x")])
    ]

testSomeBreak :: [TestTree]
testSomeBreak = fmap testParserCase cases
 where
  parser = someBreakP "+" (takeWhile1P (== 'x'))
  parserR = someBreakP "+" (textP "x")
  parserL = someBreakP "+" (textP "x+x")
  cases =
    [ 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)
    ]

testSplit :: [TestTree]
testSplit = fmap testParserCase cases
 where
  parser = fmap toList (splitP "+" (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)
    ]

testSplit1 :: [TestTree]
testSplit1 = fmap testParserCase cases
 where
  parser = fmap toList (split1P "+" (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)
    ]

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

splitBindP :: Parser Void (Seq Char)
splitBindP =
  fmap join $
    betweenP (charP '{') (charP '}') $
      splitP "," $
        labelP "split" splitBindP <|> labelP "pure" (fmap pure (charP 'x'))

testSplitBind :: TestTree
testSplitBind = testCase "split bind" $ do
  let docs =
        [ "{x,{x,x}}"
        , "{{x,x},x}"
        , "{{x},x,x}"
        , "{x,{x},x}"
        , "{x,{{x}},x}"
        ]
  for_ docs $ \doc -> do
    res <- parseI splitBindP doc
    case res of
      Left e -> throwIO e
      Right xs -> do
        xs @?= Seq.fromList "xxx"

testJson :: TestTree
testJson = testGroup "json" (fmap test cases)
 where
  test (name, str, expected) = testCase name $ do
    let actual = either (const Nothing) Just (parse jsonParser str)
    actual @?= expected
  trueVal = JsonBool True
  falseVal = JsonBool False
  arrVal = JsonArray . Seq.fromList
  objVal = JsonObject . Seq.fromList
  cases =
    [ ("empty", "", Nothing)
    , ("bad", "bad", Nothing)
    , ("null", "null", Just JsonNull)
    , ("true", "true", Just trueVal)
    , ("false", "false", Just falseVal)
    , ("arr0", "[]", Just (arrVal []))
    , ("arr1", "[null]", Just (arrVal [JsonNull]))
    , ("arr2", "[null, false]", Just (arrVal [JsonNull, falseVal]))
    , ("arr3", "[null, false, true]", Just (arrVal [JsonNull, falseVal, trueVal]))
    , ("arrx", "[null,]", Nothing)
    , ("str0", "\"\"", Just (JsonString ""))
    , ("str1", "\"x\"", Just (JsonString "x"))
    , ("str2", "\"xy\"", Just (JsonString "xy"))
    , ("str3", "\"xyz\"", Just (JsonString "xyz"))
    , ("str4", "\"xy\\\"z\"", Just (JsonString "xy\"z"))
    , ("obj0", "{}", Just (objVal []))
    , ("obj1", "{\"x\": true}", Just (objVal [("x", trueVal)]))
    , ("obj2", "{\"x\": true, \"y\": false}", Just (objVal [("x", trueVal), ("y", falseVal)]))
    , ("obj3", "{\"x\": {\"y\": false}}", Just (objVal [("x", objVal [("y", falseVal)])]))
    , ("num0", "0", Just (JsonNum 0))
    , ("num1", "123", Just (JsonNum 123))
    , ("num2", "123.45", Just (JsonNum 123.45))
    , ("num3", "1e100", Just (JsonNum (read "1e100")))
    , ("num4", "{\"x\": 1e100, \"y\": 123.45}", Just (objVal [("x", JsonNum (read "1e100")), ("y", JsonNum 123.45)]))
    ]

testSexp :: TestTree
testSexp = testGroup "sexp" (fmap test cases)
 where
  test (name, str, expected) = testCase name $ do
    let actual = either (const Nothing) Just (parse sexpParser str)
    actual @?= expected
  numSexp = SexpAtom (AtomInt 1)
  sciExpSexp = SexpAtom (AtomSci 1)
  identSexp = SexpAtom (AtomIdent "abc")
  stringSexp = SexpAtom (AtomString "xyz")
  sciSexp = SexpAtom (AtomSci 3.14)
  emptyList = SexpList Empty
  singletonList = SexpList (Seq.singleton numSexp)
  pairList = SexpList (Seq.fromList [numSexp, numSexp])
  cases =
    [ ("empty", "", Nothing)
    , ("empty list", "()", Just emptyList)
    , ("singleton list", "(1)", Just singletonList)
    , ("singleton empty list", "(())", Just (SexpList (Seq.fromList [emptyList])))
    , ("singleton nested list", "((1))", Just (SexpList (Seq.fromList [singletonList])))
    , ("num", "1", Just numSexp)
    , ("num neg", "-1", Just (SexpAtom (AtomInt -1)))
    , ("ident", "abc", Just identSexp)
    , ("string", "\"xyz\"", Just stringSexp)
    , ("sci", "3.14", Just sciSexp)
    , ("sci neg", "-3.14", Just (SexpAtom (AtomSci -3.14)))
    , ("sci neg exp", "314e-2", Just sciSexp)
    , ("sci neg exp 2", "31.4e-1", Just sciSexp)
    , ("sci pos exp 3", "0.314e1", Just sciSexp)
    , ("sci dec exp", "1.0", Just sciExpSexp)
    , ("sci exp", "1e0", Just sciExpSexp)
    , ("sci dec exp 2", "1.0e0", Just sciExpSexp)
    , ("multi list", "(1 abc \"xyz\" 3.14)", Just (SexpList (Seq.fromList [numSexp, identSexp, stringSexp, sciSexp])))
    , ("pair nested list", "((1 1) (1 1))", Just (SexpList (Seq.fromList [pairList, pairList])))
    ]

testArith :: TestTree
testArith = testGroup "arith" (fmap test cases)
 where
  test (name, str, expected) = testCase name $ do
    let actual = either (const Nothing) Just (parse arithParser str)
    actual @?= expected
  cases =
    [ ("plus", "1 +x+ 2", Just (ArithAdd (ArithNum 1) (ArithAdd (ArithVar "x") (ArithNum 2))))
    , ("prec1", "1 + 2 * 3", Just (ArithAdd (ArithNum 1) (ArithMul (ArithNum 2) (ArithNum 3))))
    , ("prec2", "1 * 2 + 3", Just (ArithAdd (ArithMul (ArithNum 1) (ArithNum 2)) (ArithNum 3)))
    ]

main :: IO ()
main =
  defaultMain $
    testGroup
      "Looksee"
      [ testBasic
      , testSpan
      , testSplitBind
      , testJson
      , testSexp
      , testArith
      ]