{-# LANGUAGE TemplateHaskell #-}

-- |
-- Module      : Data.Binding.Hobbits.PatternParser
-- Copyright   : (c) 2011 Edwin Westbrook, Nicolas Frisby, and Paul Brauner
--
-- License     : BSD3
--
-- Maintainer  : emw4@rice.edu
-- Stability   : experimental
-- Portability : GHC
--
-- A simple parser for Haskell patterns. Currently does not handle:
--
-- - Record patterns @Pt { pointx = x }@
--
-- - Signature patterns @p :: t@
--
-- - View patterns @e -> p@

module Data.Binding.Hobbits.PatternParser (parsePattern, parseVar) where

import Text.ParserCombinators.Parsec
import Language.Haskell.TH
import Data.Char


varStartChars = ['a'..'z']
ctorStartChars = ['A'..'Z']

identChars = varStartChars ++ ctorStartChars ++ ['0'..'9'] ++ "'_"
infixChars = "!#$%&*+./<=>?@"

varParser :: GenParser Char st String
varParser =
    do char1 <- oneOf varStartChars
       char_rest <- many (oneOf identChars)
       return (char1 : char_rest)

ctorParser :: GenParser Char st String
ctorParser =
    do char1 <- oneOf ctorStartChars
       char_rest <- many (oneOf identChars)
       return (char1 : char_rest)

infixParser :: GenParser Char st String
infixParser =
    do char1 <- char ':'
       char_rest <- many (oneOf infixChars)
       return (char1 : char_rest)

stringParser :: GenParser Char st String
stringParser =
    do char '"'
       res <- stringContentsParser
       return res

stringContentsParser =
    many (noneOf "\\\"") >>= \prefix ->
        (char '"' >> return prefix)
        <|>
        (char '\\' >> do c <- anyChar
                         rest <- stringContentsParser
                         return $ prefix ++ [c] ++ rest)

charParser :: GenParser Char st Char
charParser =
    do char '\''
       c <- ((char '\\' >> anyChar) <|> anyChar)
       char '\''
       return c

digitsToInt digits = helper digits 0
    where helper [] accum = accum
          helper (digit:digits) accum =
              helper digits (accum * 10 + (digitToInt digit))

intToRational :: Int -> Rational
intToRational = fromIntegral

digitsToFrac digits = helper digits
    where helper [] = 0.0
          helper (digit:digits) = ((helper digits) + (intToRational $ digitToInt digit)) / 10

numParser :: GenParser Char st Lit
numParser =
    do base_digits <- many1 (oneOf ['0'..'9'])
       ((do char '.'
            frac_digits <- many1 (oneOf ['0'..'9'])
            return (RationalL $ (intToRational $ digitsToInt base_digits) + digitsToFrac frac_digits))
        <|> return (IntegerL $ fromIntegral $ digitsToInt base_digits))

litParser :: GenParser Char st Lit
litParser = (charParser >>= return . CharL) <|>
            (stringParser >>= return . StringL) <|>
            numParser

commaSepParser :: GenParser Char st [Pat]
commaSepParser =
    (do first <- pattParser 0
        rest <- (char ',' >> commaSepParser) <|> (return [])
        return (first:rest)) <|> (return [])


-- the int gives the "level":
--   0 = parse anything
--   1 = parse ctor args but not infix ops
--   2 = do not parse ctor args or infix ops
tokenParser :: Int -> GenParser Char st Pat
tokenParser i =
    -- literals
    (litParser >>= return . LitP) <|>

    -- wildcards
    (char '_' >> return WildP) <|>

    -- bangs
    (do char '!'
        patt <- pattParser i
        return $ BangP patt) <|>

    -- tildes
    (do char '~'
        patt <- pattParser i
        return $ TildeP patt) <|>

    -- as-patterns
    (try (do var <- varParser
             wsParser
             char '@'
             patt <- pattParser i
             return $ AsP (mkName var) patt)) <|>

    -- vars
    (varParser >>= return . VarP . mkName) <|>

    -- tuples; NOTE: we parse any parenthesized expression as a tuple,
    -- and remove the TupP constructor when there are no commas
    (do char '('
        tup <- commaSepParser
        char ')'
        return (case tup of
                  [] -> ConP '() []
                  [patt] -> patt
                  _ -> TupP tup)) <|>

    -- constructor applications
    (do ctor <- ctorParser
        args <- if i < 2 then many (try $ pattParser 2) else return []
        return $ ConP (mkName ctor) args) <|>

    -- lists
    (do char '['
        elems <- commaSepParser
        char ']'
        return $ ListP elems)



wsParser :: GenParser Char st ()
wsParser = many (oneOf " \t\n\r") >> return ()

pattParser :: Int -> GenParser Char st Pat
pattParser i =
    do wsParser
       res <- if i == 0 then
                  -- infix constructor applications
                  try (do lhs <- pattParser 1
                          op <- infixParser
                          rhs <- pattParser 0
                          return $ ConP (mkName op) [lhs, rhs]) <|>
                  tokenParser i
              else
                  tokenParser i
       wsParser
       return res

varOnlyParser :: GenParser Char st String
varOnlyParser = do wsParser
                   res <- varParser
                   wsParser
                   eof
                   return res


----------------------------------------
-- Finally, the external interface... --
----------------------------------------

-- | Parse a string into a Template Haskell pattern.
parsePattern str = case parse (pattParser 0) "" str of
                  Left err -> error $ show err
                  Right patt -> patt

{-|
  Parse a string for a Haskell variable; return the string on success
  (if the string is a valid Haskell variable) and signal an error
  otherwise.
-}
parseVar str = case parse varOnlyParser "" str of
                 Left err -> error $ show err
                 Right str -> str