module Sifflet.Language.Parser
    (parseExpr
    , parseValue
    , parseLiteral
    , parseTest
    , parseSuccFail
    , parseTypedInput2, parseTypedInputs2
    , parseTypedInput3, parseTypedInputs3
    , nothingBut
    , expr, list
    , value, typedValue
    , bool, qchar, qstring, integer, double
    , number
    )
where
import Text.ParserCombinators.Parsec
import Data.Number.Sifflet
import Sifflet.Language.Expr
import Sifflet.Util
parseExpr :: String -> SuccFail Expr
parseExpr = parseSuccFail expr
parseValue :: String -> SuccFail Value
parseValue s =
    
    
    
    
    parseLiteral s >>= exprToValue
parseLiteral :: String -> SuccFail Expr
parseLiteral s = 
    
    case parseExpr s of
      Succ e -> if exprIsLiteral e
                   then Succ e
                   else Fail $ 
                     "parseLiteral: expr is non-literal" ++ show e
      Fail errmsg -> Fail errmsg
parseSuccFail :: Parser a -> String -> SuccFail a
parseSuccFail p s =
    case parse p "user input" s of
      Left perr -> Fail (show perr)
      Right v -> Succ v
parseTypedInput2 :: (String, VpType) -> SuccFail Value
parseTypedInput2 (str, vartype) =
    parseSuccFail (nothingBut (typedValue vartype)) str
parseTypedInputs2 :: [String]   
                  -> [VpType]   
                  -> SuccFail [Value]
parseTypedInputs2 strs vartypes = 
    mapM parseTypedInput2 (zip strs vartypes)
parseTypedInput3 :: (String, String, VpType) -> SuccFail Value
parseTypedInput3 (s, varname, vartype) =
    case parseSuccFail (nothingBut (typedValue vartype)) s of
      Fail msg -> Fail ("For variable " ++ varname ++ ":\n" ++ msg)
      Succ v -> Succ v
parseTypedInputs3 :: [String]   
                  -> [String]   
                  -> [VpType]   
                  -> SuccFail [Value]
parseTypedInputs3 strs varnames vartypes =
    mapM parseTypedInput3 (zip3 strs varnames vartypes)
input :: Parser Expr
input = nothingBut expr
nothingBut :: Parser a -> Parser a
nothingBut p = (many space >> p) `prog1` (many space >> eof)
prog1 :: (Monad m) => m a -> m b -> m a
prog1 m1 m2 = m1 >>= (\ r -> m2 >> return r)
expr :: Parser Expr
expr = 
       (bool >>= return . EBool) <|>
       (qchar >>= return . EChar) <|>
       (qstring >>= return . EString) <|>
       try (double >>= return . ENumber . Inexact) <|>
       (integer >>= return . ENumber . Exact) <|>
       (list expr >>= return . EList)
       
list :: Parser a -> Parser [a]
list element = 
    let sep = try (skipMany space >> char ',' >> skipMany space)
    in (char '[' >> many space >> sepBy element sep)
       `prog1`
       (many space >> char ']')
       
       <?> "list"               
          
value :: Parser Value
value = (bool >>= return . VBool) <|>
        (qchar >>= return .VChar) <|>
        (qstring >>= return . VString) <|>
        try (double >>= return . VNumber . Inexact) <|>
        (integer >>= return . VNumber . Exact) <|>
        (list value >>= return . VList)
typedValue :: VpType -> Parser Value
typedValue t = 
    (case t of
       VpTypeBool -> bool >>= return . VBool
       VpTypeChar -> qchar >>= return . VChar
       VpTypeString -> qstring >>= return . VString
       VpTypeNum -> number >>= return . VNumber
       VpTypeList e -> list (typedValue e) >>= return . VList
       VpTypeVar _ -> value 
       VpTypeFunction _ _ -> 
           error "typedValue: not implemented for VpTypeFunction"
    )
    <?> typeName t
typeName :: VpType -> String
typeName t =
    case t of 
      VpTypeBool -> "boolean" 
      VpTypeChar -> "character" 
      VpTypeNum -> "number"
      VpTypeString -> "string" 
      VpTypeList e -> "list" ++ 
                      case e of
                        VpTypeVar _ -> ""
                        _ -> " of " ++ typeName e
      VpTypeVar _ -> "anything" 
      VpTypeFunction _ _ -> "function" 
bool :: Parser Bool
bool = (try (string "True" >> return True) <|>
        (string "False" >> return False))
       <?> typeName VpTypeBool
qchar :: Parser Char
qchar = 
    let sq = '\''           
    in (((char sq <?> "opening single quote") >> 
         (try escapedChar <|> noneOf [sq])) 
        `prog1`
        (char sq <?> "closing single quote")
       )
       <?> typeName VpTypeChar
                      
qstring :: Parser String
qstring = 
    let dq = '\"'         
    in (char dq >> 
        many (escapedChar <|> noneOf [dq] <?> "")) 
       `prog1` 
       (char dq <?> "close of quotation")
       
       <?> typeName VpTypeString
escapedChar :: Parser Char
escapedChar = 
    let bs = '\\'       
    in char bs >> 
       (oneOf "ntr\\" <?> "n, t, r, or \\ to follow \\") >>=
       (\ c ->
            return (case c of
                      'n' -> '\n'
                      't' -> '\t'
                      'r' -> '\r'
                      '\\' -> '\\'
                      _ -> error "escapedChar: c MUST be n, t, r, or \\"
                   )
       )
data Sign = Minus | Plus
integer :: Parser Integer 
integer = do { s <- optSign;
               u <- unsignedInteger;
               return (applySign s u)
             }
          <?> "integer"
unsignedInteger :: Parser Integer
unsignedInteger = many1 digit >>= return . read
optSign :: Parser Sign           
optSign = try ( char '-' >> return Minus ) <|>
          try ( char '+' >> return Plus ) <|>
          return Plus
applySign :: (Num n) => Sign -> n -> n
applySign s x =
    case s of 
      Minus -> ( x)
      Plus -> x
double :: Parser Double
double = 
    let digits1 = many1 digit
        point = char '.'
        
        wpf = do { dd <- digits1;
                   dd' <- point >> digits1;
                   return (dd, dd')
                 }
        
        wp = do { dd <- digits1 `prog1` point;
                  return (dd, "0")
                }
        
        pf = do { dd' <- point >> digits1;
                  return ("0", dd')
                }
        
        scale = do { i <- oneOf "eE" >> integer;
                     return (10 ** fromIntegral i)
                   }
                <|> return 1
    in do { sign <- optSign
          ; (whole, frac) <- (try wpf <|>
                              try wp <|>
                              try pf)
          ; m <- scale;
          ; let w = read (whole ++ ".0") 
                f = read ("0." ++ frac)  
          ; return (m * applySign sign (w + f))
          }
       <?> "real number"
number :: Parser Number
number = (try (double >>= return . Inexact) <|> 
          (integer >>= return . Exact))
         <?> typeName VpTypeNum