{-# LANGUAGE OverloadedStrings #-}


module Scheme.TestParser (spec) where

import qualified Data.Text.IO as TI

import Control.Monad.IO.Class
import Scheme.Parser
import Scheme.IR

import Data.Text (Text)
import Test.Hspec.Attoparsec
import Test.Hspec


spec :: Spec
spec = do
  describe "programs" $ do
    it "single expression" $ do
      file <- TI.readFile "./examples/single_expr.scm" 
      file ~> programParser `shouldParse` 
        Program [ap "+" [num 2, num 2]]

    it "without spaces" $ do
      file <- TI.readFile "./examples/without_spaces.scm" 
      file ~> programParser `shouldParse` 
        Program [ap "+" [num 2, num 2] 
               , ap "+" [num 2, num 2]]

    it "without comments" $ do
      file <- TI.readFile "./examples/without_comments.scm" 
      file ~> programParser `shouldParse` 
        Program [ap "+" [num 2, num 2] 
               , ap "+" [num 2, num 2]]

    it "with comments" $ do
      file <- TI.readFile "./examples/with_comments.scm" 
      file ~> programParser `shouldParse` 
        Program [ap "+" [num 2, num 2] 
               , ap "+" [num 2, num 2]]

    it "more interesting" $ do
      file <- TI.readFile "./examples/test.scm" 
      file ~> programParser `shouldParse` 
        Program [
          def "plus" (lambda ["a", "b"] 
            (ap "+" [ref "a", ref "b"]))
        , ap "log" [ap "=" [ap "plus" [num 2, num 2], num 4]]]

    it "inner functions" $ do
      file <- TI.readFile "./examples/iter.scm"
      file ~> programParser `shouldParse`
        Program [
          def "factorial" (lambda ["n"] (Block
            [def "iter" $ lambda ["i", "acc"]
              (ifexpr (ap "=" [ref "i", num 0])
                (ref "acc")
                (ap "iter" [ap "-" [ref "i", num 1],
                            ap "*" [ref "i", ref "acc"]]))
            ,ap "iter" [ref "n", num 1]]))
          ]

    it "werid coments" $ do
      file <- TI.readFile "./examples/weird_comments.scm"
      file ~> programParser `shouldParse`
        Program [ap "+" [num 2, num 2]]
        


  describe "expressions" $ do
    describe "numbers" $ do
      it "parse `2` `-2`" $ do
        ("2"  :: Text) ~> expressionParser `shouldParse` num 2
        ("-2" :: Text) ~> expressionParser `shouldParse` num (-2)
          
      it "parse `2.1` `-2.1`" $ do
        ("2.1"  :: Text) ~> expressionParser `shouldParse` num 2.1
        ("-2.1" :: Text) ~> expressionParser `shouldParse` num (-2.1)

    describe "if expressions" $ do
      it "false" $
        ("#f" :: Text) ~> expressionParser `shouldParse`
          Bool (Boolish False)

      it "true" $
        ("#t" :: Text) ~> expressionParser `shouldParse`
         Bool (Boolish True)

    describe "references" $ do
      it "parse words" $ do
        ("hello" :: Text) ~> expressionParser `shouldParse` ref "hello"
        ("h3110" :: Text) ~> expressionParser `shouldParse` ref "h3110"

      it "operators" $ do
        ("+" :: Text) ~> expressionParser `shouldParse` ref "+"
        ("-" :: Text) ~> expressionParser `shouldParse` ref "-"

    describe "sexpressions" $ do
      it "parse `(+ 2 2)`" $ do
        ("(+ 2 2)" :: Text) ~> expressionParser `shouldParse` ap "+" [num 2, num 2]
        ("(+ 2 2 )" :: Text) ~> expressionParser `shouldParse` ap "+" [num 2, num 2]
        ("( + 2 2)" :: Text) ~> expressionParser `shouldParse` ap "+" [num 2, num 2]

      it "argument less functions" $
        ("(exit)" :: Text) ~> expressionParser `shouldParse` ap "exit" []

    describe "lambdas" $ do
      it "parse `(lambda (a) (+ a a))`" $ do

        ("(lambda (a) (+ a a))" :: Text) ~> expressionParser `shouldParse` 
          lambda ["a"] (ap "+" [ref "a", ref "a"])

        ("( lambda (a)\n  (+ a a) ) " :: Text) ~> expressionParser `shouldParse` 
          lambda ["a"] (ap "+" [ref "a", ref "a"])

      it "multi statement `(lambda (a) (+ a a) (+ a a))`" $
        ("(lambda (a) (+ a a) (+ a a))" :: Text) ~> expressionParser `shouldParse`
          lambda ["a"] (Block 
            [ ap "+" [ref "a", ref "a"]
            , ap "+" [ref "a", ref "a"]])

    describe "if expressions" $
    	it "simple parse" $
        ("(if a b c)" :: Text) ~> expressionParser `shouldParse`
          If (IfExpr (ref "a") (ref "b") (ref "c"))

    describe "definitions" $ do
      it "simple" $
        ("(define a 2)" :: Text) ~> expressionParser `shouldParse`
          Def (Definition (Declaration "a") (Number (Num 2)))

      it "function definition" $
        ("(define (add a b) (+ a b))" :: Text) ~> expressionParser `shouldParse`
          def "add" (lambda ["a", "b"] (ap "+" [ref "a", ref "b"]))


