-----------------------------------------------------------------------------
-- |
-- Module      :  Language.Haskell.Exts.Fixity
-- Copyright   :  (c) Niklas Broberg 2009
-- License     :  BSD-style (see the file LICENSE.txt)
--
-- Maintainer  :  Niklas Broberg, d00nibro@chalmers.se
-- Stability   :  stable
-- Portability :  portable
--
-- Fixity information to give the parser so that infix operators can
-- be parsed properly.
--
-----------------------------------------------------------------------------
module Language.Haskell.Exts.Fixity
    (
    -- * Fixity representation
      Fixity(..)
    -- | The following three functions all create lists of
    --   fixities from textual representations of operators.
    --   The intended usage is e.g.
    --
    -- > fixs = infixr_ 0  ["$","$!","`seq`"]
    --
    --   Note that the operators are expected as you would
    --   write them infix, i.e. with ` characters surrounding
    --   /varid/ operators, and /varsym/ operators written as is.
    , infix_, infixl_, infixr_
    -- ** Collections of fixities
    , preludeFixities, baseFixities

    -- * Applying fixities to an AST
    , AppFixity(..)
    ) where

import Language.Haskell.Exts.Syntax

import Data.Char (isUpper)

-- | Operator fixities are represented by their associativity
--   (left, right or none) and their precedence (0-9).
data Fixity = Fixity Assoc Int Op

-- | All AST elements that may include expressions which in turn may
--   need fixity tweaking will be instances of this class.
class AppFixity ast where
  -- | Tweak any expressions in the element to account for the
  --   fixities given. Assumes that all operator expressions are
  --   fully left associative chains to begin with.
  applyFixities :: [Fixity] -- ^ The fixities to account for.
                    -> ast  -- ^ The element to tweak.
                    -> ast  -- ^ The same element, but with operator expressions updated.


instance AppFixity Exp where
  applyFixities fixs = infFix fixs . leafFix fixs
    where -- This is the real meat case. We can assume a left-associative list to begin with.
          infFix fixs (InfixApp a op2 z) =
              let e = infFix fixs a
               in case e of
                   InfixApp x op1 y ->
                      let (a1,p1) = askFixity fixs op1
                          (a2,p2) = askFixity fixs op2
                       in if (p1 == p2 && (a1 /= a2 || a1 == AssocNone)) -- Ambiguous infix expression!
                              || (p1 > p2 || p1 == p2 && (a1 == AssocLeft || a2 == AssocNone)) -- Already right order
                           then InfixApp e op2 z
                           else InfixApp x op1 (infFix fixs $ InfixApp y op2 z)
                   _  -> InfixApp e op2 z

          infFix _ e = e

instance AppFixity Pat where
  applyFixities fixs = infFix fixs . leafFixP fixs
    where -- Same for patterns
          infFix fixs (PInfixApp a op2 z) =
              let p = infFix fixs a
               in case p of
                   PInfixApp x op1 y ->
                      let (a1,p1) = askFixityP fixs op1
                          (a2,p2) = askFixityP fixs op2
                       in if (p1 == p2 && (a1 /= a2 || a1 == AssocNone)) -- Ambiguous infix expression!
                              || (p1 > p2 || p1 == p2 && (a1 == AssocLeft || a2 == AssocNone)) -- Already right order
                           then PInfixApp p op2 z
                           else PInfixApp x op1 (infFix fixs $ PInfixApp y op2 z)
                   _  -> PInfixApp p op2 z

          infFix _ p = p


-- Internal: lookup associativity and precedence of an operator
askFixity :: [Fixity] -> QOp -> (Assoc, Int)
askFixity xs k = askFix xs (f k) -- undefined -- \k -> askFixityP xs (f k) -- lookupWithDefault (AssocLeft, 9) (f k) mp
    where
        f (QVarOp x) = VarOp (g x)
        f (QConOp x) = ConOp (g x)

        g (Qual _ x) = x
        g (UnQual x) = x
        g (Special Cons) = Symbol ":"

-- Same using patterns
askFixityP :: [Fixity] -> QName -> (Assoc, Int)
askFixityP xs qn = askFix xs (ConOp $ g qn)
    where
        g (Qual _ x) = x
        g (UnQual x) = x
        g (Special Cons) = Symbol ":"
        
askFix :: [Fixity] -> Op -> (Assoc, Int)
askFix xs = \k -> lookupWithDefault (AssocLeft, 9) k mp
    where
        lookupWithDefault def k mp = case lookup k mp of
            Nothing -> def
            Just x  -> x

        mp = [(x,(a,p)) | Fixity a p x <- xs]


-- | All fixities defined in the Prelude.
preludeFixities :: [Fixity]
preludeFixities = concat
    [infixr_ 9  ["."]
    ,infixl_ 9  ["!!"]
    ,infixr_ 8  ["^","^^","**"]
    ,infixl_ 7  ["*","/","`quot`","`rem`","`div`","`mod`",":%","%"]
    ,infixl_ 6  ["+","-"]
    ,infixr_ 5  [":","++"]
    ,infix_  4  ["==","/=","<","<=",">=",">","`elem`","`notElem`"]
    ,infixr_ 3  ["&&"]
    ,infixr_ 2  ["||"]
    ,infixl_ 1  [">>",">>="]
    ,infixr_ 1  ["=<<"]
    ,infixr_ 0  ["$","$!","`seq`"]
    ]

-- | All fixities defined in the base package.
--
--   Note that the @+++@ operator appears in both Control.Arrows and
--   Text.ParserCombinators.ReadP. The listed precedence for @+++@ in
--   this list is that of Control.Arrows.
baseFixities :: [Fixity]
baseFixities = preludeFixities ++ concat
    [infixl_ 9 ["!","//","!:"]
    ,infixl_ 8 ["`shift`","`rotate`","`shiftL`","`shiftR`","`rotateL`","`rotateR`"]
    ,infixl_ 7 [".&."]
    ,infixl_ 6 ["`xor`"]
    ,infix_  6 [":+"]
    ,infixl_ 5 [".|."]
    ,infixr_ 5 ["+:+","<++","<+>"] -- fixity conflict for +++ between ReadP and Arrow
    ,infix_  5 ["\\\\"]
    ,infixl_ 4 ["<$>","<$","<*>","<*","*>","<**>"]
    ,infix_  4 ["`elemP`","`notElemP`"]
    ,infixl_ 3 ["<|>"]
    ,infixr_ 3 ["&&&","***"]
    ,infixr_ 2 ["+++","|||"]
    ,infixr_ 1 ["<=<",">=>",">>>","<<<","^<<","<<^","^>>",">>^"]
    ,infixl_ 0 ["`on`"]
    ,infixr_ 0 ["`par`","`pseq`"]
    ]

infixr_, infixl_, infix_ :: Int -> [String] -> [Fixity]
infixr_ = fixity AssocRight
infixl_ = fixity AssocLeft
infix_  = fixity AssocNone

-- Internal: help function for the above definitions.
fixity :: Assoc -> Int -> [String] -> [Fixity]
fixity a p = map (Fixity a p . op)
    where
        op ('`':xs) = (if isUpper (head xs) then ConOp else VarOp) $ Ident $ init xs
        op xs = (if head xs == ':' then ConOp else VarOp) $ Symbol xs






-------------------------------------------------------------------
-- Boilerplate - yuck!! Everything below here is internal stuff

instance AppFixity Module where
    applyFixities fixs (Module loc n prs mwt ext imp decls) =
        Module loc n prs mwt ext imp $ appFixDecls fixs decls

instance AppFixity Decl where
    applyFixities fixs decl = case decl of
        ClassDecl loc ctxt n vars deps cdecls   -> ClassDecl loc ctxt n vars deps $ map fix cdecls
        InstDecl loc ctxt n ts idecls           -> InstDecl loc ctxt n ts $ map fix idecls
        SpliceDecl loc spl      -> SpliceDecl loc $ fix spl
        FunBind matches         -> FunBind $ map fix matches
        PatBind loc p mt rhs bs -> PatBind loc (fix p) mt (fix rhs) (fix bs)
        _                       -> decl
      where fix x = applyFixities fixs x

appFixDecls :: [Fixity] -> [Decl] -> [Decl]
appFixDecls fixs decls =
    let extraFixs = getFixities decls
     in map (applyFixities (fixs++extraFixs)) decls
  where getFixities = concatMap getFixity
        getFixity (InfixDecl _ a p ops) = map (Fixity a p) ops
        getFixity _ = []

instance AppFixity ClassDecl where
    applyFixities fixs (ClsDecl decl) = ClsDecl $ applyFixities fixs decl
    applyFixities _ cdecl = cdecl

instance AppFixity InstDecl where
    applyFixities fixs (InsDecl decl) = InsDecl $ applyFixities fixs decl
    applyFixities _ idecl = idecl

instance AppFixity Match where
    applyFixities fixs (Match loc n ps mt rhs bs) = Match loc n (map fix ps) mt (fix rhs) (fix bs)
      where fix x = applyFixities fixs x

instance AppFixity Rhs where
    applyFixities fixs rhs = case rhs of
        UnGuardedRhs e      -> UnGuardedRhs $ fix e
        GuardedRhss grhss   -> GuardedRhss $ map fix grhss
      where fix x = applyFixities fixs x

instance AppFixity GuardedRhs where
    applyFixities fixs (GuardedRhs loc stmts e) = GuardedRhs loc (map fix stmts) $ fix e
      where fix x = applyFixities fixs x

instance AppFixity PatField where
    applyFixities fixs (PFieldPat n p) = PFieldPat n $ applyFixities fixs p
    applyFixities _ pf = pf

instance AppFixity RPat where
    applyFixities fixs rp = case rp of
        RPOp rp op          -> RPOp (fix rp) op
        RPEither a b        -> RPEither (fix a) (fix b)
        RPSeq rps           -> RPSeq $ map fix rps
        RPGuard p stmts     -> RPGuard (fix p) $ map fix stmts
        RPCAs n rp          -> RPCAs n $ fix rp
        RPAs n rp           -> RPAs n $ fix rp
        RPParen rp          -> RPParen $ fix rp
        RPPat p             -> RPPat $ fix p
      where fix x = applyFixities fixs x

instance AppFixity PXAttr where
    applyFixities fixs (PXAttr n p) = PXAttr n $ applyFixities fixs p

instance AppFixity Stmt where
    applyFixities fixs stmt = case stmt of
        Generator loc p e   -> Generator loc (fix p) (fix e)
        Qualifier e         -> Qualifier $ fix e
        LetStmt bs          -> LetStmt $ fix bs    -- special behavior
        RecStmt stmts       -> RecStmt $ map fix stmts
      where fix x = applyFixities fixs x

instance AppFixity Binds where
    applyFixities fixs bs = case bs of
        BDecls decls        -> BDecls $ appFixDecls fixs decls  -- special behavior
        IPBinds ips         -> IPBinds $ map fix ips
      where fix x = applyFixities fixs x


instance AppFixity IPBind where
    applyFixities fixs (IPBind loc n e) = IPBind loc n $ applyFixities fixs e

instance AppFixity FieldUpdate where
    applyFixities fixs (FieldUpdate n e) = FieldUpdate n $ applyFixities fixs e
    applyFixities _ fup = fup

instance AppFixity Alt where
    applyFixities fixs (Alt loc p galts bs) = Alt loc (fix p) (fix galts) (fix bs)
      where fix x = applyFixities fixs x

instance AppFixity GuardedAlts where
    applyFixities fixs galts = case galts of
        UnGuardedAlt e      -> UnGuardedAlt $ fix e
        GuardedAlts  galts  -> GuardedAlts $ map fix galts
      where fix x = applyFixities fixs x

instance AppFixity GuardedAlt where
    applyFixities fixs (GuardedAlt loc stmts e) = GuardedAlt loc (map fix stmts) (fix e)
      where fix x = applyFixities fixs x

instance AppFixity QualStmt where
    applyFixities fixs qstmt = case qstmt of
        QualStmt     s      -> QualStmt $ fix s
        ThenTrans    e      -> ThenTrans $ fix e
        ThenBy       e1 e2  -> ThenBy (fix e1) (fix e2)
        GroupBy      e      -> GroupBy (fix e)
        GroupUsing   e      -> GroupUsing (fix e)
        GroupByUsing e1 e2  -> GroupByUsing (fix e1) (fix e2)
      where fix x = applyFixities fixs x

instance AppFixity Bracket where
    applyFixities fixs br = case br of
        ExpBracket e    -> ExpBracket $ fix e
        PatBracket p    -> PatBracket $ fix p
        DeclBracket ds  -> DeclBracket $ map fix ds
        _               -> br
      where fix x = applyFixities fixs x

instance AppFixity Splice where
    applyFixities fixs (ParenSplice e) = ParenSplice $ applyFixities fixs e
    applyFixities _ s = s

instance AppFixity XAttr where
    applyFixities fixs (XAttr n e) = XAttr n $ applyFixities fixs e


-- the boring boilerplate stuff for expressions too
-- Recursively fixes the "leaves" of the infix chains,
-- without yet touching the chain itself. We assume all chains are
-- left-associate to begin with.
leafFix fixs e = case e of
    InfixApp e1 op e2       -> InfixApp (leafFix fixs e1) op (fix e2)
    App e1 e2               -> App (fix e1) (fix e2)
    NegApp e                -> NegApp $ fix e
    Lambda loc pats e       -> Lambda loc (map fix pats) $ fix e
    Let bs e                -> Let (fix bs) $ fix e
    If e a b                -> If (fix e) (fix a) (fix b)
    Case e alts             -> Case (fix e) $ map fix alts
    Do stmts                -> Do $ map fix stmts
    MDo stmts               -> MDo $ map fix stmts
    Tuple exps              -> Tuple $ map fix exps
    List exps               -> List $ map fix  exps
    Paren e                 -> Paren $ fix e
    LeftSection e op        -> LeftSection (fix e) op
    RightSection op e       -> RightSection op $ fix e
    RecConstr n fups        -> RecConstr n $ map fix fups
    RecUpdate e fups        -> RecUpdate (fix e) $ map fix fups
    EnumFrom e              -> EnumFrom $ fix e
    EnumFromTo e1 e2        -> EnumFromTo (fix e1) (fix e2)
    EnumFromThen e1 e2      -> EnumFromThen (fix e1) (fix e2)
    EnumFromThenTo e1 e2 e3 -> EnumFromThenTo (fix e1) (fix e2) (fix e3)
    ListComp e quals        -> ListComp (fix e) $ map fix quals
    ParComp  e qualss       -> ParComp (fix e) $ map (map fix) qualss
    ExpTypeSig loc e t      -> ExpTypeSig loc (fix e) t
    BracketExp b            -> BracketExp $ fix b
    SpliceExp s             -> SpliceExp $ fix s
    XTag loc n ats mexp cs  -> XTag loc n (map fix ats) (fmap fix mexp) (map fix cs)
    XETag loc n ats mexp    -> XETag loc n (map fix ats) (fmap fix mexp)
    XExpTag e               -> XExpTag $ fix e
    Proc loc p e            -> Proc loc (fix p) (fix e)
    LeftArrApp e1 e2        -> LeftArrApp (fix e1) (fix e2)
    RightArrApp e1 e2       -> RightArrApp (fix e1) (fix e2)
    LeftArrHighApp e1 e2    -> LeftArrHighApp (fix e1) (fix e2)
    RightArrHighApp e1 e2   -> RightArrHighApp (fix e1) (fix e2)
    CorePragma s e          -> CorePragma s (fix e)
    SCCPragma s e           -> SCCPragma s (fix e)
    GenPragma s ab cd e     -> GenPragma s ab cd (fix e)

    _                       -> e
  where
    fix x = applyFixities fixs x

leafFixP fixs p = case p of
        PNeg p                -> PNeg $ fix p
        PApp n ps             -> PApp n $ map fix ps
        PTuple ps             -> PTuple $ map fix ps
        PList ps              -> PList $ map fix ps
        PParen p              -> PParen $ fix p
        PRec n pfs            -> PRec n $ map fix pfs
        PAsPat n p            -> PAsPat n $ fix p
        PIrrPat p             -> PIrrPat $ fix p
        PatTypeSig loc p t    -> PatTypeSig loc (fix p) t
        PViewPat e p          -> PViewPat (fix e) (fix p)
        PRPat rps             -> PRPat $ map fix rps
        PXTag loc n ats mp ps -> PXTag loc n (map fix ats) (fmap fix mp) (map fix ps)
        PXETag loc n ats mp   -> PXETag loc n (map fix ats) (fmap fix mp)
        PXPatTag p            -> PXPatTag $ fix p
        PXRPats rps           -> PXRPats $ map fix rps
        PBangPat p            -> PBangPat $ fix p
        _                     -> p
      where fix x = applyFixities fixs x