{-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-}
module Parse.Type where

import Data.List (intercalate)
import Text.Parsec ((<|>), (<?>), char, choice, many, optionMaybe, string, try)

import qualified AST.Type as Type
import qualified AST.Variable as Var
import Parse.Helpers
import qualified Reporting.Annotation as A
import qualified Reporting.Region as R


tvar :: IParser Type.Raw
tvar =
  addLocation
    (Type.RVar <$> lowVar <?> "a type variable")


tuple :: IParser Type.Raw
tuple =
  do  (start, types, end) <- located (parens (commaSep expr))
      case types of
        [t] -> return t
        _   -> return (Type.tuple (R.Region start end) types)


record :: IParser Type.Raw
record =
  addLocation $
  do  char '{'
      whitespace
      rcrd <- extended <|> normal
      dumbWhitespace
      char '}'
      return rcrd
  where
    normal = flip Type.RRecord Nothing <$> commaSep field

    -- extended record types require at least one field
    extended =
      do  ext <- try (addLocation lowVar <* (whitespace >> string "|"))
          whitespace
          fields <- commaSep1 field
          return (Type.RRecord fields (Just (A.map Type.RVar ext)))

    field =
      do  lbl <- rLabel
          whitespace >> hasType >> whitespace
          (,) lbl <$> expr


capTypeVar :: IParser String
capTypeVar =
  intercalate "." <$> dotSep1 capVar


constructor0 :: IParser Type.Raw
constructor0 =
  addLocation $
  do  name <- capTypeVar
      return (Type.RType (Var.Raw name))


term :: IParser Type.Raw
term =
  choice [ tuple, record, tvar, constructor0 ]


app :: IParser Type.Raw
app =
  do  start <- getMyPosition
      f <- constructor0 <|> try tupleCtor <?> "a type constructor"
      args <- spacePrefix term
      end <- getMyPosition
      case args of
        [] -> return f
        _  -> return (A.A (R.Region start end) (Type.RApp f args))
  where
    tupleCtor =
      addLocation $
      do  n <- length <$> parens (many (char ','))
          let ctor = "_Tuple" ++ show (if n == 0 then 0 else n+1)
          return (Type.RType (Var.Raw ctor))


expr :: IParser Type.Raw
expr =
  do  start <- getMyPosition
      t1 <- app <|> term
      arr <- optionMaybe $ try (whitespace >> rightArrow)
      case arr of
        Nothing ->
            return t1
        Just _ ->
            do  whitespace
                t2 <- expr
                end <- getMyPosition
                return (A.A (R.Region start end) (Type.RLambda t1 t2))


constructor :: IParser (String, [Type.Raw])
constructor =
  (,) <$> (capTypeVar <?> "another type constructor")
      <*> spacePrefix term
