{-# 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 ]