{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}

-- Note GADTs needed by GHC 7.6. In later GHCs is works with just TypeFamilies.

-- | A simple compiler for NanoFeldspar

module NanoFeldsparComp where



import Control.Monad.State
import Control.Monad.Writer

import Language.Syntactic
import Language.Syntactic.Functional
import Language.Syntactic.Functional.Sharing
import Language.Syntactic.Functional.Tuple

import NanoFeldspar hiding ((==))



--------------------------------------------------------------------------------
-- * Imperative programs
--------------------------------------------------------------------------------

type Var = String

varName :: Name -> Var
varName v = 'v' : show v

varNameE :: Name -> Exp
varNameE v = App (varName v) []

data Exp = App String [Exp]
  deriving (Eq, Show)

data Stmt
    = Assign Exp Exp
    | If Exp Prog Prog
    | For Exp Var Prog
  deriving (Eq, Show)

type Prog = [Stmt]

viewOp :: String -> Maybe String
viewOp op@(_:_:_)
    | head op == '(' && last op == ')' = Just $ tail $ init op
    | otherwise = Nothing

renderExp :: Exp -> String
renderExp (App f []) = f
renderExp (App f@(_:_) [a,b])
    | Just op <- viewOp f = concat ["(", renderExp a, " ", op, " ", renderExp b, ")"]
renderExp (App f args) = "(" ++ unwords (f : Prelude.map renderExp args) ++ ")"

indent :: [String] -> [String]
indent = Prelude.map ("    " ++)

renderProg :: Prog -> String
renderProg = unlines . concatMap render
  where
    render (Assign l r) = [unwords [renderExp l, "=", renderExp r]]
    render (If c t f) = concat
        [ [unwords ["if", renderExp c]]
        , indent (concatMap render t)
        , ["else"]
        , indent (concatMap render f)
        ]
    render (For l v body) = concat
        [ [unwords ["for",v,"<", renderExp l]]
        , indent (concatMap render body)
        ]



--------------------------------------------------------------------------------
-- * Code generation
--------------------------------------------------------------------------------

type CodeGen = WriterT Prog (State Name)

type Dom = BindingT
       :+: Let
       :+: Tuple
       :+: Arithmetic
       :+: Parallel
       :+: ForLoop
       :+: Construct

fresh :: CodeGen Exp
fresh = do
    v <- get; put (v+1)
    return (varNameE v)

confiscate :: CodeGen Exp -> CodeGen (Exp,Prog)
confiscate = censor (const mempty) . listen

compileExp :: ASTF Dom a -> CodeGen Exp
compileExp var
    | Just (Var v) <- prj var = return (varNameE v)
compileExp (lett :$ a :$ (lam :$ body))
    | Just (Let _)  <- prj lett
    , Just (LamT v) <- prj lam
    = do
        a' <- compileExp a
        tell [Assign (varNameE v) a']
        compileExp body
compileExp (par :$ len :$ (lam :$ body))
    | Just Parallel <- prj par
    , Just (LamT v) <- prj lam
    = do
        len' <- compileExp len
        (e,body') <- confiscate $ compileExp body
        arr <- fresh
        let arrPos = App "(!)" [arr, varNameE v]
        tell
            [ For len' (varName v)
                (  body'
                ++ [Assign arrPos e]
                )
            ]
        return arr
compileExp (for :$ len :$ init :$ (lam1 :$ (lam2 :$ body)))
    | Just ForLoop  <- prj for
    , Just (LamT i) <- prj lam1
    , Just (LamT s) <- prj lam2
    = do
        len'  <- compileExp len
        init' <- compileExp init
        (e,body') <- confiscate $ compileExp body
        next <- fresh
        tell
            [ Assign (varNameE s) init'
            , For len' (varName i)
                (  body'
                ++ [ Assign next e
                   , Assign (varNameE s) next
                   ]
                )
            ]
        return next
compileExp (cond :$ c :$ t :$ f)
    | Just (Construct "cond" _) <- prj cond
    = do
        c' <- compileExp c
        (t',tProg) <- confiscate $ compileExp t
        (f',fProg) <- confiscate $ compileExp f
        res <- fresh
        tell
            [ If c'
                (  tProg
                ++ [Assign res  t']
                )
                (  fProg
                ++ [Assign res  f']
                )
            ]
        return res
compileExp (arrIx :$ arr :$ ix)
    | Just (Construct "arrIx" _) <- prj arrIx = do
        arr' <- compileExp arr
        ix'  <- compileExp ix
        return $ App "(!)" [arr',ix']
-- Generic case for all other constructs
compileExp a = simpleMatch
  (\s as -> fmap (App (renderSym s)) $ sequence (listArgs compileExp as)) a

compileTop :: ASTF Dom a -> CodeGen ()
compileTop = go 0
  where
    go :: Int -> ASTF Dom a -> CodeGen ()
    go n (lam :$ body)
        | Just (LamT v) <- prj lam = do
            tell [Assign (varNameE v) (App ("inp" ++ show n) [])]
            go (n+1) body
    go _ a = do
        a' <- compileExp a
        tell [Assign (App "out" []) a']



compile :: (Syntactic a, Domain a ~ Typed Dom) => a -> String
compile
    = renderProg
    . fst
    . flip runState 0
    . execWriterT
    . compileTop
    . mapAST (\(Typed s) -> s)
    . codeMotion cmInterface
    . desugar

icompile :: (Syntactic a, Domain a ~ Typed Dom) => a -> IO ()
icompile = putStrLn . compile



--------------------------------------------------------------------------------
-- * Code generation
--------------------------------------------------------------------------------

test_matMul = icompile matMul

