packages feed

axel-0.0.13: test/Axel/Test/ParseSpec.hs

{- HLINT ignore "Redundant do" -}
{-# LANGUAGE QuasiQuotes #-}

module Axel.Test.ParseSpec where

import Axel.Prelude

import Axel.Eff.Error
import Axel.Parse
import Axel.Parse.AST
import Axel.Utils.Text

import Control.Monad

import qualified Effectful as Eff
import qualified Effectful.Error.Static as Eff

import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

import Test.Hspec

import TestUtils

parseSingle :: Text -> Expression ()
parseSingle = (() <$) . unsafeParseSingle Nothing

spec_Parse :: Spec
spec_Parse = do
  describe "parseSingle" $ do
    it "can parse a character literal" $ do
      parseSingle "#\\a" `shouldBe` LiteralChar () 'a'
      parseSingle "#\\\\x1000" `shouldBe` LiteralChar () '\x1000'
    it "can parse an integer literal" $ do
      parseSingle "-123" `shouldBe` LiteralInt () (-123)
      parseSingle "456" `shouldBe` LiteralInt () 456
    it "can parse a float literal" $ do
      parseSingle "-12.3" `shouldBe` LiteralFloat () (-12.3)
      parseSingle "4.56" `shouldBe` LiteralFloat () 4.56
    it "can parse a list literal" $ do
      let result =
            SExpression
              ()
              [Symbol () "list", LiteralInt () 1, LiteralChar () 'a']
      parseSingle "[1 #\\a]" `shouldBe` result
    it "can parse a string literal" $ do
      let result = LiteralString () "a \x1000 \"b"
      parseSingle "\"a \x1000 \\\"b\"" `shouldBe` result
    it
      "can parse string literals with escaped double quotes at the boundaries (regression: #79)" $ do
      let result = LiteralString () "a \x1000 \""
      parseSingle "\"a \x1000 \\\"\"" `shouldBe` result
      let result = LiteralString () "a \x1000 \"\""
      parseSingle "\"a \x1000 \\\"\\\"\"" `shouldBe` result
      let result = LiteralString () "\""
      parseSingle "\"\\\"\"" `shouldBe` result
      let result = LiteralString () "\"\""
      parseSingle "\"\\\"\\\"\"" `shouldBe` result
      let result = LiteralString () "\"\" foo"
      parseSingle "\"\\\"\\\" foo\"" `shouldBe` result
      let result = LiteralString () "\"\"\""
      parseSingle "\"\\\"\\\"\\\"\"" `shouldBe` result
    it
      "can parse a string literal with a double quote at the end (regression: #79)" $ do
      let result = LiteralString () "a \x1000 \""
      parseSingle "\"a \x1000 \\\"\"" `shouldBe` result
    it "can parse a string literal with escaped unprintables" $ do
      let result = LiteralString () "\0"
      parseSingle "\"\\0\"" `shouldBe` result
      let result = LiteralString () "\NUL"
      parseSingle "\"\\NUL\"" `shouldBe` result
    it "can parse a quasiquoted expression" $ do
      let result =
            SExpression
              ()
              [ Symbol () "quasiquote"
              , SExpression () [Symbol () "foo", Symbol () "bar"]
              ]
      parseSingle "`(foo bar)" `shouldBe` result
    it "can parse an s-expression" $ do
      let result = SExpression () [Symbol () "foo", Symbol () "bar"]
      parseSingle "(foo bar)" `shouldBe` result
    it "can parse a splice-unquoted expression" $ do
      let result =
            SExpression
              ()
              [ Symbol () "unquoteSplicing"
              , SExpression () [Symbol () "foo", Symbol () "bar"]
              ]
      parseSingle "~@(foo bar)" `shouldBe` result
    it "can parse a symbol" $ do
      let result = Symbol () "abc123'''"
      parseSingle "abc123'''" `shouldBe` result
    it
      "can parse a value-level name that starts with a Haskell-restricted character" $ do
      let result =
            Symbol () "aXEL_VALUE_aXEL_SYMBOL_COMMA_aXEL_SYMBOL_SEMICOLON_foo"
      parseSingle ",;foo" `shouldBe` result
    it "can parse an unquoted expression" $ do
      let result =
            SExpression
              ()
              [ Symbol () "unquote"
              , SExpression () [Symbol () "foo", Symbol () "bar"]
              ]
      parseSingle "~(foo bar)" `shouldBe` result
  describe "parseMultiple" $ do
    it "can parse multiple expressions" $ do
      let input =
            [s|
(foo 1 2 3)

(bar
 x
 y
 z)
|]
      let result =
            [ SExpression
                ()
                [ Symbol () "foo"
                , LiteralInt () 1
                , LiteralInt () 2
                , LiteralInt () 3
                ]
            , SExpression
                ()
                [Symbol () "bar", Symbol () "x", Symbol () "y", Symbol () "z"]
            ]
      case Eff.runPureEff . Eff.runErrorNoCallStack $
           parseMultiple Nothing input of
        Left err -> failSpec $ renderError err
        Right x -> map (() <$) x `shouldBe` result
  describe "parseSource" $ do
    it "can parse a source file" $ do
      let input =
            [s|
(foo 1 2 3) -- This is a comment
-- Another comment! (bar x y z)
(end of line comment) --
(butThis-->IsASingleSymbol)
|]
      let result =
            SExpression
              ()
              [ Symbol () "begin"
              , SExpression
                  ()
                  [ Symbol () "foo"
                  , LiteralInt () 1
                  , LiteralInt () 2
                  , LiteralInt () 3
                  ]
              , SExpression
                  ()
                  [ Symbol () "end"
                  , Symbol () "of"
                  , Symbol () "line"
                  , Symbol () "comment"
                  ]
              , SExpression
                  ()
                  [ Symbol
                      ()
                      "butThisaXEL_SYMBOL_HYPHEN_aXEL_SYMBOL_HYPHEN_aXEL_SYMBOL_GREATERTHAN_IsASingleSymbol"
                  ]
              ]
      case Eff.runPureEff . Eff.runErrorNoCallStack $ parseSource Nothing input of
        Left err -> failSpec $ renderError err
        Right x -> void x `shouldBe` result

hprop_can_parse_string_literals :: Property
hprop_can_parse_string_literals =
  property $ do
    string <- forAll $ Gen.string (Range.linear 0 5) Gen.unicode
    let result = parseSingle $ showText string
    result === LiteralString () string