-- Copyright 2016 Google Inc. All Rights Reserved.
--
-- Use of this source code is governed by a BSD-style
-- license that can be found in the LICENSE file or at
-- https://developers.google.com/open-source/licenses/bsd

{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Some utility functions, classes and instances for nicer code generation
-- with haskell-src-exts.
--
-- In particular, we define orphan instances of IsString for various syntax
-- datatypes, with some intelligence about Haskell names.  For example, @"foo"
-- :: Exp@ is treated as a variable and @"Foo" :: Exp@ is treated as a
-- constructor.
module Data.ProtoLens.Compiler.Combinators where

import Data.Char (isAlphaNum, isUpper)
import Data.String (IsString(..))
import Language.Haskell.Exts.SrcLoc (noLoc)
import Language.Haskell.Exts.Syntax as Syntax

-- | Application of a Haskell type or expression to an argument.
-- For example, to represent @f x y@, you can write
--
-- > "f" @@ "x" @@ "y"
class App a where
    (@@) :: a -> a -> a
    infixl 2 @@

instance App Type where
    (@@) = TyApp

instance App Exp where
    (@@) = App

instance IsString Name where
    fromString s
        -- TODO: better handle the case of mixed ident and symbol characters.
        | all isIdentChar s = Ident s
        | otherwise = Symbol s

-- | Whether this character belongs to an Ident (e.g., "foo") or a symbol
-- (e.g., "<$>").
isIdentChar :: Char -> Bool
isIdentChar c = isAlphaNum c || c `elem` "_'"

instance IsString ModuleName where
    fromString = ModuleName

instance IsString QName where
    fromString f
      -- TODO: support qualified operators (e.g., "Control.Applicative.<$>")
      -- Currently we ignore them due to edge-cases
      -- like the composition operator "Prelude.."
      | isIdentChar (last f), '.' `elem` f
      -- Split "Foo.Bar.baz" into ("Foo.Bar", "baz")
      , (f', '.':f'') <- span (/='.') (reverse f)
            = Qual (fromString $ reverse f'') (fromString $ reverse f')
      | otherwise = UnQual $ fromString f

instance IsString Type where
  fromString fs@(f:_)
      | isUpper f = TyCon $ fromString fs
  fromString fs = TyVar $ fromString fs

instance IsString Exp where
    fromString fs@(f:_)
        | isUpper f = Con $ fromString fs
    fromString fs = Var $ fromString fs

instance IsString Pat where
    fromString = PVar . fromString

instance IsString TyVarBind where
    fromString = UnkindedVar . fromString


-- Helper functions for literal numbers, since haskell-src-exts doesn't
-- put parentheses around negative numbers automatically.

litInt :: Integer -> Exp
litInt n
    | n >= 0 = Lit $ Int n
    | otherwise = NegApp $ Lit $ Int $ negate n

litFrac :: Rational -> Exp
litFrac x
    | x >= 0 = Lit $ Frac x
    | otherwise = NegApp $ Lit $ Frac $ negate x

pLitInt :: Integer -> Pat
pLitInt n
    | n >= 0 = PLit Signless $ Int n
    | otherwise = PLit Negative $ Int $ negate n

-- | A simple clause of a function binding.
match :: Name -> [Pat] -> Exp -> Match
match n ps e = Match noLoc n ps Nothing (UnGuardedRhs e) Nothing