packages feed

snail-0.1.2.0: test/Snail/LexerSpec.hs

{-# LANGUAGE QuasiQuotes #-}

module Snail.LexerSpec (spec) where

import Data.Maybe (isJust, isNothing)
import Data.Text
import Snail
import Test.HUnit (assertBool)
import Test.Hspec
import Text.Megaparsec (parseMaybe, parseTest)
import Text.RawString.QQ

foldLexemes :: SnailAst -> [Text]
foldLexemes = go []
  where
    go :: [Text] -> SnailAst -> [Text]
    go acc (Lexeme (_, t)) = acc ++ [t]
    go acc (TextLiteral (_, t)) = acc ++ [t]
    go acc (SExpression _ _ []) = acc
    go acc (SExpression _ _ (x : xs)) = lgo (go acc x) xs
    lgo :: [Text] -> [SnailAst] -> [Text]
    lgo acc [] = acc
    lgo acc (x : xs) = lgo (go acc x) xs

failAssertion :: String -> Expectation
failAssertion s = assertBool s False

sExpressionShouldBe :: Text -> [Text] -> Expectation
sExpressionShouldBe input output =
    case parseMaybe sExpression input of
        Nothing -> failAssertion $ "sExpressionShouldBe: " <> unpack input
        Just sExpr -> do
            let lexemes = foldLexemes sExpr
            lexemes `shouldBe` output

textLiteralShouldBe :: Text -> [Text] -> Expectation
textLiteralShouldBe input output =
    case parseMaybe textLiteral input of
        Nothing -> failAssertion "textLiteralShouldBe: Nothing"
        Just sExpr -> do
            let lexemes = foldLexemes sExpr
            lexemes `shouldBe` output

spec :: Spec
spec = do
    describe "non quote character" $ do
        it "handles spaces properly" $ do
            let mSExpr = parseMaybe nonQuoteCharacter " "
            mSExpr `shouldBe` Just " "

    describe "parse text literals" $ do
        it "successfully parses a basic text literal" $ do
            [r|"hello \"world"|] `textLiteralShouldBe` [[r|hello \"world|]]

        it "successfully parses a text literal with leading/trailing quotes" $ do
            [r|"\"hello \"world\""|] `textLiteralShouldBe` [[r|\"hello \"world\"|]]

        it "fails to lex text literal with unescaped quote" $ do
            let mSExpr = parseMaybe textLiteral [r|"hello "world"|]
            mSExpr `shouldSatisfy` isNothing

        it "can parse the empty string" $ do
            [r|""|] `textLiteralShouldBe` [""]

        it "can parse whitespace" $ do
            [r|" "|] `textLiteralShouldBe` [" "]

        it "can parse more whitespace" $ do
            [r|"    "|] `textLiteralShouldBe` ["    "]

    describe "parse sExpression" $ do
        it "successfully lex a basic list" $ do
            "(a b c)" `sExpressionShouldBe` ["a", "b", "c"]

        it "successfully parse nil inside parentheses" $ do
            "(nil)" `sExpressionShouldBe` ["nil"]

        it "fail to parse a standalone nil" $ do
            let mSExpr = parseMaybe sExpression "nil"
            mSExpr `shouldSatisfy` isNothing

        it "successfully lex a basic list with number" $ do
            parseTest sExpression "(1 a)"
            "(1 a)" `sExpressionShouldBe` ["1", "a"]

        it "successfully lex a basic list with number and starting character" $ do
            "'(1 a)" `sExpressionShouldBe` ["1", "a"]

        it "successfully lex a single element list" $ do
            "(1a)" `sExpressionShouldBe` ["1a"]

        it "successfully lex a nested s-expression" $ do
            "((1a))" `sExpressionShouldBe` ["1a"]

        it "successfully lex a nested s-expression with different brackets" $ do
            "([1a])" `sExpressionShouldBe` ["1a"]

        it "successfully lex nested s-expressions" $ do
            "(() ())" `sExpressionShouldBe` []

        it "successfully lex nested s-expressions of each bracket" $ do
            "(() [] {})" `sExpressionShouldBe` []

        it "successfully lex internally nested s-expressions of each bracket" $ do
            "([{}])" `sExpressionShouldBe` []

        it "successfully lex a nested s-expressions" $ do
            "((()) (()))" `sExpressionShouldBe` []

        it "successfully lex a nested s-expressions of different brackets" $ do
            "({()} [{}])" `sExpressionShouldBe` []

        it "successfully lex line comment" $ do
            "(-- ...\n)" `sExpressionShouldBe` []

        it "successfully lex line comment followed by token" $ do
            "(-- ...\nabc)" `sExpressionShouldBe` ["abc"]

        it "successfully lex line comment with \r\n followed by token" $ do
            "(-- ...\r\nabc)" `sExpressionShouldBe` ["abc"]

        it "successfully lex line comment with \t followed by token" $ do
            "(-- ...\n\tabc)" `sExpressionShouldBe` ["abc"]

        it "successfully lex line comment with \v followed by token" $ do
            "(-- ...\n\vabc)" `sExpressionShouldBe` ["abc"]

        it "successfully lex block comment" $ do
            "({- ... -})" `sExpressionShouldBe` []

        it "successfully lex block comment followed by token" $ do
            "({- ... -}abc)" `sExpressionShouldBe` ["abc"]

        it "successfully lex token followed by block comments" $ do
            "(abc{- ... -})" `sExpressionShouldBe` ["abc"]

        it "successfully lex block comments sorrounded by tokens" $ do
            "(abc{- ... -}def)" `sExpressionShouldBe` ["abc", "def"]

        it "successfully lex nested block comments" $ do
            "({- ... {- ... -} ... -})" `sExpressionShouldBe` []

        it "fail to lex nested block comments with missing internal start" $ do
            parseMaybe sExpression "({- ... -} ... -})" `shouldBe` Nothing

        it "fail to lex nested block comments with missing internal stop" $ do
            parseMaybe sExpression "({- ... {- ... -})" `shouldBe` Nothing

        it "fail to lex block comment with missing stop" $ do
            parseMaybe sExpression "({- ...)" `shouldBe` Nothing

        it "can handle subsequent s-expressions" $ do
            parseMaybe snailAst "()(nil)()" `shouldSatisfy` isJust

        it "fails to parse nested naked nil" $ do
            parseMaybe snailAst "()nil()" `shouldSatisfy` isNothing

        it "handles successive text literals" $ do
            [r|("hello" " " "world" "!!!")|] `sExpressionShouldBe` ["hello", " ", "world", "!!!"]

        it "fails to parse nested mismatched brackets" $ do
            parseMaybe snailAst "([)]" `shouldSatisfy` isNothing