{-# LANGUAGE NegativeLiterals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Main (main) where import Control.Monad (void) import Data.Foldable (asum) import Data.Functor (($>)) import Data.Sequence (Seq (..)) import qualified Data.Sequence as Seq import qualified Data.Sequence.NonEmpty as NESeq import Data.String (IsString) import Data.Text (Text) import qualified Data.Text as T import SimpleParser import SimpleParser.Examples.Common.Sexp (Atom (..), Sexp (..), SexpF (..)) import SimpleParser.Examples.Direct.Json (Json (..), JsonF (..), jsonParser) import qualified SimpleParser.Examples.Direct.Sexp as SexpDirect import qualified SimpleParser.Examples.Lexed.Sexp as SexpLexed import Test.Tasty (TestName, TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) import Test.Tasty.TH (defaultMainGenerator) newtype Label = Label { unLabel :: String } deriving (Eq, Show, IsString) newtype Error = Error { unError :: String } deriving (Eq, Show, IsString) type TestState = OffsetStream Text type TestBlock a b = PureMatchBlock Label TestState Error a b type TestParser a = Parser Label TestState Error a type TestResult a = ParseResult Label TestState Error a type TestRawError = RawError Text Char type TestParseError = ParseError Label TestState Error data ParserCase a = ParserCase !TestName !(TestParser a) !Text !(Maybe (TestResult a)) data ExamineCase a b = ExamineCase !TestName !(TestBlock a b) !Text !(LookAheadTestResult Label) fwd :: Int -> TestState -> TestState fwd n (OffsetStream (Offset i) t) = let m = min n (T.length t) in OffsetStream (Offset (i + m)) (T.drop m t) sucRes :: TestState -> a -> Maybe (TestResult a) sucRes st = Just . ParseResultSuccess . ParseSuccess st errRes :: [TestParseError] -> Maybe (TestResult a) errRes es = Just (ParseResultError (ParseErrorBundle (NESeq.unsafeFromSeq (Seq.fromList es)))) custErr :: TestState -> Error -> TestParseError custErr endSt = ParseError emptyStack endSt . CompoundErrorCustom stmErr :: TestState -> TestRawError -> TestParseError stmErr endSt = ParseError emptyStack endSt . CompoundErrorStream . StreamError failErr :: TestState -> Text -> TestParseError failErr endSt = ParseError emptyStack endSt . CompoundErrorFail markWith :: TestState -> TestParseError -> TestParseError markWith s = markParseError (Mark Nothing s) anyTokErr :: TestState -> TestParseError anyTokErr s = markWith s (stmErr s RawErrorAnyToken) anyChunkErr :: TestState -> TestParseError anyChunkErr s = markWith s (stmErr s RawErrorAnyChunk) matchTokErr :: TestState -> Char -> Maybe Char -> TestParseError matchTokErr s x my = markWith s (stmErr (fwd 1 s) (RawErrorMatchToken x my)) matchChunkErr :: TestState -> Text -> Maybe Text -> TestParseError matchChunkErr s x my = markWith s (stmErr (fwd (T.length x) s) (RawErrorMatchChunk x my)) matchEndErr :: TestState -> Char -> TestParseError matchEndErr s x = markWith s (stmErr (fwd 1 s) (RawErrorMatchEnd x)) takeTokErr :: TestState -> Int -> Maybe Char -> TestParseError takeTokErr s n my = markWith s (stmErr (fwd n s) (RawErrorTakeTokensWhile1 my)) dropTokErr :: TestState -> Int -> Maybe Char -> TestParseError dropTokErr s n my = markWith s (stmErr (fwd n s) (RawErrorDropTokensWhile1 my)) testParserCase :: (Show a, Eq a) => ParserCase a -> TestTree testParserCase (ParserCase name parser input expected) = testCase name $ do let actual = runParser parser (newOffsetStream input) actual @?= expected testExamineCase :: ExamineCase a b -> TestTree testExamineCase (ExamineCase name block input expected) = testCase name $ do let actual = pureLookAheadTest block (newOffsetStream input) actual @?= expected test_empty :: [TestTree] test_empty = let parser = emptyParser :: TestParser Int cases = [ ParserCase "empty" parser "" Nothing , ParserCase "non-empty" parser "hi" Nothing ] in fmap testParserCase cases test_pure :: [TestTree] test_pure = let parser = pure (1 :: Int) cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 1) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 1) ] in fmap testParserCase cases test_fail :: [TestTree] test_fail = let parser = fail "i give up" :: TestParser Int cases = [ ParserCase "empty" parser "" (errRes [failErr (OffsetStream 0 "") "i give up"]) , ParserCase "non-empty" parser "hi" (errRes [failErr (OffsetStream 0 "hi") "i give up"]) ] in fmap testParserCase cases test_peek_token :: [TestTree] test_peek_token = let parser = peekToken cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") Nothing) , ParserCase "match" parser "hi" (sucRes (OffsetStream 0 "hi") (Just 'h')) ] in fmap testParserCase cases test_pop_token :: [TestTree] test_pop_token = let parser = popToken cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") Nothing) , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") (Just 'h')) ] in fmap testParserCase cases test_peek_chunk :: [TestTree] test_peek_chunk = let parser = peekChunk 2 cases = [ ParserCase "len 0" parser "" (sucRes (OffsetStream 0 "") Nothing) , ParserCase "len 1" parser "h" (sucRes (OffsetStream 0 "h") (Just "h")) , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 0 "hi") (Just "hi")) , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 0 "hii") (Just "hi")) ] in fmap testParserCase cases test_pop_chunk :: [TestTree] test_pop_chunk = let parser = popChunk 2 cases = [ ParserCase "len 0" parser "" (sucRes (OffsetStream 0 "") Nothing) , ParserCase "len 1" parser "h" (sucRes (OffsetStream 1 "") (Just "h")) , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 2 "") (Just "hi")) , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 2 "i") (Just "hi")) ] in fmap testParserCase cases test_drop_chunk :: [TestTree] test_drop_chunk = let parser = dropChunk 2 cases = [ ParserCase "len 0" parser "" (sucRes (OffsetStream 0 "") Nothing) , ParserCase "len 1" parser "h" (sucRes (OffsetStream 1 "") (Just 1)) , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 2 "") (Just 2)) , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 2 "i") (Just 2)) ] in fmap testParserCase cases test_is_end :: [TestTree] test_is_end = let parser = isEnd cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") True) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") False) ] in fmap testParserCase cases test_match_end :: [TestTree] test_match_end = let parser = matchEnd cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") ()) , ParserCase "non-empty" parser "hi" (errRes [matchEndErr (OffsetStream 0 "hi") 'h']) ] in fmap testParserCase cases test_any_token :: [TestTree] test_any_token = let parser = anyToken cases = [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")]) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") 'h') ] in fmap testParserCase cases test_any_chunk :: [TestTree] test_any_chunk = let parser = anyChunk 2 :: TestParser Text cases = [ ParserCase "len 0" parser "" (errRes [anyChunkErr (OffsetStream 0 "")]) , ParserCase "len 1" parser "h" (sucRes (OffsetStream 1 "") "h") , ParserCase "len 2" parser "hi" (sucRes (OffsetStream 2 "") "hi") , ParserCase "len 3" parser "hii" (sucRes (OffsetStream 2 "i") "hi") ] in fmap testParserCase cases test_match_token :: [TestTree] test_match_token = let parser = matchToken 'h' cases = [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'h' Nothing]) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") 'h') , ParserCase "non-match" parser "bye" (errRes [matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')]) ] in fmap testParserCase cases test_match_chunk :: [TestTree] test_match_chunk = let parser = matchChunk "hi" cases = [ ParserCase "empty" parser "" (errRes [matchChunkErr (OffsetStream 0 "") "hi" Nothing]) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 2 "") "hi") , ParserCase "prefix" parser "hiya" (sucRes (OffsetStream 2 "ya") "hi") , ParserCase "partial" parser "hey" (errRes [matchChunkErr (OffsetStream 0 "hey") "hi" (Just "he")]) , ParserCase "non-match" parser "bye" (errRes [matchChunkErr (OffsetStream 0 "bye") "hi" (Just "by")]) , ParserCase "short" parser "h" (errRes [matchChunkErr (OffsetStream 0 "h") "hi" (Just "h")]) ] in fmap testParserCase cases test_greedy_star :: [TestTree] test_greedy_star = let parser = greedyStarParser (matchToken 'h') :: TestParser String cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") "") , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") "h") , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") "hh") , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") "hhh") , ParserCase "non-match" parser "bye" (sucRes (OffsetStream 0 "bye") "") ] in fmap testParserCase cases test_greedy_star_unit :: [TestTree] test_greedy_star_unit = let parser = greedyStarParser_ (matchToken 'h') cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") ()) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") ()) , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") ()) , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") ()) , ParserCase "non-match" parser "bye" (sucRes (OffsetStream 0 "bye") ()) ] in fmap testParserCase cases test_greedy_plus :: [TestTree] test_greedy_plus = let parser = greedyPlusParser (matchToken 'h') :: TestParser String cases = [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'h' Nothing]) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") "h") , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") "hh") , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") "hhh") , ParserCase "non-match" parser "bye" (errRes [matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')]) ] in fmap testParserCase cases test_greedy_plus_unit :: [TestTree] test_greedy_plus_unit = let parser = greedyPlusParser_ (matchToken 'h') cases = [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'h' Nothing]) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 1 "i") ()) , ParserCase "repeat" parser "hhi" (sucRes (OffsetStream 2 "i") ()) , ParserCase "full" parser "hhh" (sucRes (OffsetStream 3 "") ()) , ParserCase "non-match" parser "bye" (errRes [matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b')]) ] in fmap testParserCase cases test_or :: [TestTree] test_or = let parser = orParser (matchToken 'h') (matchToken 'x') cases = [ ParserCase "empty" parser "" $ errRes [ matchTokErr (OffsetStream 0 "") 'h' Nothing , matchTokErr (OffsetStream 0 "") 'x' Nothing ] , ParserCase "first" parser "hi" (sucRes (OffsetStream 1 "i") 'h') , ParserCase "second" parser "xi" (sucRes (OffsetStream 1 "i") 'x') , ParserCase "non-match" parser "bye" $ errRes [ matchTokErr (OffsetStream 0 "bye") 'h' (Just 'b') , matchTokErr (OffsetStream 0 "bye") 'x' (Just 'b') ] ] in fmap testParserCase cases test_asum :: [TestTree] test_asum = let state = OffsetStream 1 "i" parser = asum [matchToken 'h', 'y' <$ anyToken, matchToken 'x'] cases = [ ParserCase "empty" parser "" $ errRes [ matchTokErr (OffsetStream 0 "") 'h' Nothing , anyTokErr (OffsetStream 0 "") , matchTokErr (OffsetStream 0 "") 'x' Nothing ] , ParserCase "first" parser "hi" (sucRes state 'h') , ParserCase "middle" parser "zi" (sucRes state 'y') , ParserCase "last" parser "xi" (sucRes state 'y') ] in fmap testParserCase cases test_default_empty :: [TestTree] test_default_empty = let parser = defaultParser 'z' emptyParser cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 'z') , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'z') ] in fmap testParserCase cases test_default :: [TestTree] test_default = let parser = defaultParser 'z' (matchToken 'h') cases = [ ParserCase "non-match empty" parser "" (sucRes (OffsetStream 0 "") 'z') , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") 'h') , ParserCase "non-match" parser "bye" (sucRes (OffsetStream 0 "bye") 'z') ] in fmap testParserCase cases test_bind_1 :: [TestTree] test_bind_1 = let state = OffsetStream 1 "i" parser = matchToken 'x' >>= \c -> pure [c, c] cases = [ ParserCase "empty" parser "" (errRes [matchTokErr (OffsetStream 0 "") 'x' Nothing]) , ParserCase "first" parser "hi" (errRes [matchTokErr (OffsetStream 0 "hi") 'x' (Just 'h')]) , ParserCase "second" parser "xi" (sucRes state "xx") ] in fmap testParserCase cases test_bind_2 :: [TestTree] test_bind_2 = let state = OffsetStream 1 "i" parser = anyToken >>= \x -> if x == 'x' then pure 'y' else emptyParser cases = [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")]) , ParserCase "first" parser "hi" Nothing , ParserCase "second" parser "xi" (sucRes state 'y') ] in fmap testParserCase cases test_throw :: [TestTree] test_throw = let err = Error "boo" parser = throwParser err :: TestParser Int cases = [ ParserCase "empty" parser "" (errRes [custErr (OffsetStream 0 "") err]) , ParserCase "non-empty" parser "hi" (errRes [custErr (OffsetStream 0 "hi") err]) ] in fmap testParserCase cases test_consume_throw :: [TestTree] test_consume_throw = let err = Error "boo" parser = anyToken *> throwParser err :: TestParser Int cases = [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")]) , ParserCase "non-empty" parser "hi" (errRes [custErr (OffsetStream 1 "i") err]) ] in fmap testParserCase cases test_default_throw :: [TestTree] test_default_throw = let err = Error "boo" parser = defaultParser 'z' (throwParser err) cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 'z') , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'z') ] in fmap testParserCase cases test_default_consume_throw :: [TestTree] test_default_consume_throw = let err = Error "boo" parser = defaultParser 'z' (anyToken *> throwParser err) cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 'z') , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'z') ] in fmap testParserCase cases test_throw_mixed :: [TestTree] test_throw_mixed = let state = OffsetStream 0 "hi" err = Error "boo" parser = orParser (throwParser err) (pure 1) :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" (sucRes state 1) ] in fmap testParserCase cases test_throw_mixed_flip :: [TestTree] test_throw_mixed_flip = let state = OffsetStream 0 "hi" err = Error "boo" parser = orParser (pure 1) (throwParser err) :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" (sucRes state 1) ] in fmap testParserCase cases test_catch :: [TestTree] test_catch = let state = OffsetStream 0 "hi" err = Error "boo" parser = catchParser (throwParser err) (\(Error m) -> pure (if m == "boo" then 2 else 3)) :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" (sucRes state 2) ] in fmap testParserCase cases test_catch_recur :: [TestTree] test_catch_recur = let state = OffsetStream 0 "hi" err1 = Error "boo" err2 = Error "two" parser = catchParser (throwParser err1) (const (throwParser err2)) :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" (errRes [custErr state err2]) ] in fmap testParserCase cases test_silence_success :: [TestTree] test_silence_success = let state = OffsetStream 0 "hi" parser = silenceParser (pure 1) :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" (sucRes state 1) ] in fmap testParserCase cases test_silence_fail :: [TestTree] test_silence_fail = let err = Error "boo" parser = silenceParser (throwParser err) :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" Nothing ] in fmap testParserCase cases test_silence_empty :: [TestTree] test_silence_empty = let parser = silenceParser emptyParser :: TestParser Int cases = [ ParserCase "non-empty" parser "hi" Nothing ] in fmap testParserCase cases test_look_ahead_pure :: [TestTree] test_look_ahead_pure = let parser = lookAheadParser (pure 1) :: TestParser Int cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 1) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 1) ] in fmap testParserCase cases test_look_ahead_success :: [TestTree] test_look_ahead_success = let parser = lookAheadParser anyToken cases = [ ParserCase "non-match empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")]) , ParserCase "non-empty" parser "hi" (sucRes (OffsetStream 0 "hi") 'h') ] in fmap testParserCase cases test_look_ahead_failure :: [TestTree] test_look_ahead_failure = let err = Error "boo" parser = lookAheadParser (anyToken *> throwParser err) :: TestParser Char cases = [ ParserCase "non-match empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")]) , ParserCase "non-empty" parser "hi" (errRes [custErr (OffsetStream 1 "i") err]) ] in fmap testParserCase cases test_commit :: [TestTree] test_commit = let parser = commitParser (void (matchToken 'h')) (matchChunk "hi") :: TestParser Text cases = [ ParserCase "non-match empty" parser "" Nothing , ParserCase "non-match non-empty" parser "ho" (errRes [matchChunkErr (OffsetStream 0 "ho") "hi" (Just "ho")]) , ParserCase "match" parser "hi" (sucRes (OffsetStream 2 "") "hi") ] in fmap testParserCase cases test_take_while :: [TestTree] test_take_while = let parser = takeTokensWhile (=='h') :: TestParser Text cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") "") , ParserCase "non-match" parser "i" (sucRes (OffsetStream 0 "i") "") , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") "h") , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") "hh") , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") "hh") ] in fmap testParserCase cases test_take_while_1 :: [TestTree] test_take_while_1 = let parser = takeTokensWhile1 Nothing (=='h') :: TestParser Text cases = [ ParserCase "empty" parser "" (errRes [takeTokErr(OffsetStream 0 "") 0 Nothing]) , ParserCase "non-match" parser "i" (errRes [takeTokErr (OffsetStream 0 "i") 0 (Just 'i')]) , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") "h") , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") "hh") , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") "hh") ] in fmap testParserCase cases test_drop_while :: [TestTree] test_drop_while = let parser = dropTokensWhile (=='h') :: TestParser Int cases = [ ParserCase "empty" parser "" (sucRes (OffsetStream 0 "") 0) , ParserCase "non-match" parser "i" (sucRes (OffsetStream 0 "i") 0) , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") 1) , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") 2) , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") 2) ] in fmap testParserCase cases test_drop_while_1 :: [TestTree] test_drop_while_1 = let parser = dropTokensWhile1 Nothing (=='h') :: TestParser Int cases = [ ParserCase "empty" parser "" (errRes [dropTokErr (OffsetStream 0 "") 0 Nothing]) , ParserCase "non-match" parser "i" (errRes [dropTokErr (OffsetStream 0 "i") 0 (Just 'i')]) , ParserCase "match" parser "hi" (sucRes (OffsetStream 1 "i") 1) , ParserCase "match 2" parser "hhi" (sucRes (OffsetStream 2 "i") 2) , ParserCase "match end" parser "hh" (sucRes (OffsetStream 2 "") 2) ] in fmap testParserCase cases simpleBlock :: TestBlock Char Text simpleBlock = MatchBlock anyToken (pure "default") [ MatchCase (Just (Label "match x")) (== 'x') (anyToken $> "found x - consuming") , MatchCase (Just (Label "match x dupe")) (== 'x') (pure "dupe x - leaving") , MatchCase (Just (Label "match y")) (== 'y') (pure "found y - leaving") ] test_look_ahead_match :: [TestTree] test_look_ahead_match = let parser = lookAheadMatch simpleBlock cases = [ ParserCase "empty" parser "" (errRes [anyTokErr (OffsetStream 0 "")]) , ParserCase "non-match" parser "wz" (sucRes (OffsetStream 0 "wz") "default") , ParserCase "match x" parser "xz" (sucRes (OffsetStream 1 "z") "found x - consuming") , ParserCase "match y" parser "yz" (sucRes (OffsetStream 0 "yz") "found y - leaving") ] in fmap testParserCase cases test_look_ahead_examine :: [TestTree] test_look_ahead_examine = let xpositions = [MatchPos 0 (Just (Label "match x")), MatchPos 1 (Just (Label "match x dupe"))] ypositions = [MatchPos 2 (Just (Label "match y"))] cases = [ ExamineCase "empty" simpleBlock "" LookAheadTestEmpty , ExamineCase "non-match" simpleBlock "wz" LookAheadTestDefault , ExamineCase "match x" simpleBlock "xz" (LookAheadTestMatches (NESeq.unsafeFromSeq (Seq.fromList xpositions))) , ExamineCase "match y" simpleBlock "yz" (LookAheadTestMatches (NESeq.unsafeFromSeq (Seq.fromList ypositions))) ] in fmap testExamineCase cases type JsonResult = Maybe Json testJsonCase :: TestName -> Text -> JsonResult -> TestTree testJsonCase name str expected = testCase ("json " <> name) $ do let actual = parseJson str actual @?= expected testJsonTrees :: [(TestName, Text, JsonResult)] -> [TestTree] testJsonTrees = fmap (\(n, s, e) -> testJsonCase n s e) parseJson :: Text -> JsonResult parseJson str = let p = jsonParser <* matchEnd in case runParser p str of Just (ParseResultSuccess (ParseSuccess _ a)) -> Just a _ -> Nothing test_json :: [TestTree] test_json = let nullVal = Json JsonNull trueVal = Json (JsonBool True) falseVal = Json (JsonBool False) arrVal = Json . JsonArray . Seq.fromList strVal = Json . JsonString objVal = Json . JsonObject . Seq.fromList numVal = Json . JsonNum cases = [ ("empty", "", Nothing) , ("bad", "bad", Nothing) , ("null", "null", Just nullVal) , ("true", "true", Just trueVal) , ("false", "false", Just falseVal) , ("arr0", "[]", Just (arrVal [])) , ("arr1", "[null]", Just (arrVal [nullVal])) , ("arr2", "[null, false]", Just (arrVal [nullVal, falseVal])) , ("arr3", "[null, false, true]", Just (arrVal [nullVal, falseVal, trueVal])) , ("arrx", "[null,]", Nothing) , ("str0", "\"\"", Just (strVal "")) , ("str1", "\"x\"", Just (strVal "x")) , ("str2", "\"xy\"", Just (strVal "xy")) , ("str3", "\"xyz\"", Just (strVal "xyz")) , ("str4", "\"xy\\\"z\"", Just (strVal "xy\"z")) , ("obj0", "{}", Just (objVal [])) , ("obj1", "{\"x\": true}", Just (objVal [("x", trueVal)])) , ("obj2", "{\"x\": true, \"y\": false}", Just (objVal [("x", trueVal), ("y", falseVal)])) , ("num0", "0", Just (numVal 0)) , ("num1", "123", Just (numVal 123)) , ("num2", "123.45", Just (numVal 123.45)) , ("num3", "1e100", Just (numVal (read "1e100"))) , ("num4", "{\"x\": 1e100, \"y\": 123.45}", Just (objVal [("x", numVal (read "1e100")), ("y", numVal 123.45)])) ] in testJsonTrees cases type SexpResult = Maybe Sexp testSexpCase :: TestName -> Text -> SexpResult -> TestTree testSexpCase name str expected = testCase ("sexp " <> name) $ do let actualDirect = SexpDirect.runSexpParser str actualDirect @?= expected let actualLexed = SexpLexed.runSexpParser (newOffsetStream str) actualLexed @?= expected testSexpTrees :: [(TestName, Text, SexpResult)] -> [TestTree] testSexpTrees = fmap (\(n, s, e) -> testSexpCase n s e) test_sexp :: [TestTree] test_sexp = let numSexp = Sexp (SexpAtom (AtomInt 1)) sciExpSexp = Sexp (SexpAtom (AtomSci 1)) identSexp = Sexp (SexpAtom (AtomIdent "abc")) stringSexp = Sexp (SexpAtom (AtomString "xyz")) sciSexp = Sexp (SexpAtom (AtomSci 3.14)) emptyList = Sexp (SexpList Empty) singletonList = Sexp (SexpList (Seq.singleton numSexp)) pairList = Sexp (SexpList (Seq.fromList [numSexp, numSexp])) cases = [ ("empty", "", Nothing) , ("empty list", "()", Just emptyList) , ("singleton list", "(1)", Just singletonList) , ("singleton empty list", "(())", Just (Sexp (SexpList (Seq.fromList [emptyList])))) , ("singleton nested list", "((1))", Just (Sexp (SexpList (Seq.fromList [singletonList])))) , ("num", "1", Just numSexp) , ("num pos", "+1", Just numSexp) , ("num neg", "-1", Just (Sexp (SexpAtom (AtomInt -1)))) , ("ident", "abc", Just identSexp) , ("string", "\"xyz\"", Just stringSexp) , ("sci", "3.14", Just sciSexp) , ("sci pos", "+3.14", Just sciSexp) , ("sci neg", "-3.14", Just (Sexp (SexpAtom (AtomSci -3.14)))) , ("sci exp", "1e0", Just sciExpSexp) , ("sci pos exp", "+1e0", Just sciExpSexp) , ("sci dec exp", "1.0", Just sciExpSexp) , ("sci dec exp 2", "1.0e0", Just sciExpSexp) , ("plus", "+", Just (Sexp (SexpAtom (AtomIdent "+")))) , ("minus", "-", Just (Sexp (SexpAtom (AtomIdent "-")))) , ("multi list", "(1 abc \"xyz\" 3.14)", Just (Sexp (SexpList (Seq.fromList [numSexp, identSexp, stringSexp, sciSexp])))) , ("pair nested list", "((1 1) (1 1))", Just (Sexp (SexpList (Seq.fromList [pairList, pairList])))) ] in testSexpTrees cases main :: IO () main = $(defaultMainGenerator)