module Language.SystemF.Parser (
  parseExpr,
  parseType
  ) where

import Control.Monad
import Data.Functor
import Prelude hiding (abs)

import Text.Parsec
import Text.Parsec.String

import Language.SystemF.Expression

parseExpr :: String -> Either ParseError (SystemFExpr String String)
parseExpr = parse (whitespace *> expr <* eof) ""

parseType :: String -> Either ParseError (Ty String)
parseType = parse (whitespace *> ty <* eof) ""

-- Parse expressions
expr :: Parser (SystemFExpr String String)
expr = try tyapp <|> try app <|> term

app :: Parser (SystemFExpr String String)
app = chainl1 term (return App)

tyapp :: Parser (SystemFExpr String String)
tyapp = TyApp
      <$> term
      <*> ty'
  where ty' = symbol '[' *> ty <* symbol ']'

term :: Parser (SystemFExpr String String)
term = try abs <|> tyabs <|> var <|> parens expr

var :: Parser (SystemFExpr String String)
var = Var <$> exprId

abs :: Parser (SystemFExpr String String)
abs = curry 
    <$> (symbol '\\' *> many1 args <* symbol '.') 
    <*> expr
  where args = (,) <$> (exprId <* symbol ':') <*> ty
        curry = flip . foldr . uncurry $ Abs

tyabs :: Parser (SystemFExpr String String)
tyabs = curry <$> args <*> expr
  where args = symbol '\\' *> many1 typeId <* symbol '.'
        curry = flip (foldr TyAbs)

-- Parse type expressions
ty :: Parser (Ty String)
ty = try arrow

arrow :: Parser (Ty String)
arrow = chainr1 tyterm (symbol' "->" $> TyArrow)

tyterm :: Parser (Ty String)
tyterm = tyvar <|> parens ty

tyvar :: Parser (Ty String)
tyvar = TyVar <$> typeId

parens :: Parser a -> Parser a
parens p = symbol '(' *> p <* symbol ')'

identifier :: Parser Char -> Parser String
identifier firstChar = lexeme ((:) <$> first <*> many rest)
  where first = firstChar <|> char '_'
        rest = first <|> digit

typeId, exprId :: Parser String
typeId = identifier upper
exprId = identifier lower

whitespace :: Parser ()
whitespace = void . many . oneOf $ " \t"

symbol :: Char -> Parser ()
symbol = void . lexeme . char

symbol' :: String -> Parser ()
symbol' = void . lexeme . string

lexeme :: Parser a -> Parser a
lexeme p = p <* whitespace
