module FrontEnd.Lex.Fixity where

{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
import Control.Monad
import Control.Monad.Identity
import Data.Tree
import System.Environment
import Text.Show

data Fixity = L | R | N | Prefix | Prefixy | Postfix
    deriving(Show,Eq)

type Prec = (Fixity,Int)

-- alternating operators and expressions, always succeeds.

simpleShunt :: (Show e, Show o)
    => (o -> Prec)
    -> e -> [(o,e)]
    -> (o -> e -> e -> e) -> e

simpleShunt getPrec e oes combine = runIdentity $ do
    let ShuntSpec { .. } = shuntSpec
    let lookupToken (Left e) = return $ Left e
        lookupToken (Right o) = return $ Right (getPrec o)
        operator ~(Right t) ~[x,y] = return $ combine t x y
        ss = ShuntSpec { .. }
    shunt ss (Left e:concat [ [Right o, Left e] | (o,e) <- oes])

data S t e = S {
    stack :: [(Maybe t,Prec)],
    output :: [e],
    lastWasOp :: Bool
    } deriving(Show)
initialState = S { stack = [], output = [], lastWasOp = True }

-- lookups must be idempotent.
data ShuntSpec m t e = ShuntSpec {
    appPrec     :: Prec,                   -- Precedence of application.
    lookupToken :: t -> m (Either e Prec), -- A token is either an operator or an expression.
    lookupUnary :: t -> m (Maybe Int),     -- What to do when an unexpected op happens, either can be interpeted as unary or fail.
    application :: e -> e -> m e,          -- what to do on application
    operator    :: t -> [e] -> m e,        -- what to do on operator
    emptyInput  :: m e,                    -- what to do on empty input
    trailingOps :: e -> t -> m e,          -- what to do with trailing operators
    equalFixity :: Prec -> [Maybe t] -> m e-- what to do on nonassociate fixities
}

shuntSpec :: (Monad m, Show t) => ShuntSpec m t e
shuntSpec = ShuntSpec { .. } where
    appPrec          = (L,10)
    lookupToken t    = fail $ "ShuntSpec.lookupToken unspecified: " ++ show t
    lookupUnary t    = fail $ "Operator used in unary location: " ++ show t
    application _ _  = fail $ "ShuntSpec.application unspecified"
    operator t _     = fail $ "ShuntSpec.operator unspecified: " ++ show t
    emptyInput       = fail $ "Empty input to precedence parser"
    trailingOps _ ts = fail $ "Trailing operators: " ++ show ts
    equalFixity p ts = fail $ "Cannot mix operators of same fixity: "
        ++ show p ++ " " ++ show ts

shunt :: (Show t, Show e,Monad m)
    => ShuntSpec m t e   -- specification
    -> [t]               -- token stream
    -> m e

shunt ShuntSpec { .. } xs = f initialState xs where
    f S { output = [e], stack = [] } [] = return e
    f S { output = [], stack = [] } [] = emptyInput

    f s@S { .. } (t:ts) = lookupToken t >>= \x -> case (x,lastWasOp) of
        (Left o,True) ->  f s { output = o:output, lastWasOp = False } ts
        (Left o,False) -> op s { lastWasOp = True } (Nothing,appPrec) Nothing (t:ts)
        (Right p@(Prefix,_),False) -> op s { lastWasOp = True } (Nothing,appPrec) Nothing (t:ts)
        (Right p,False) -> op s { lastWasOp = True } (Just t,p) Nothing ts
        (Right p@(Prefix,_),True) -> op s  (Just t,p) Nothing ts
        (Right p@(_,n),True) -> do
            mn <- lookupUnary t
            let Just n' = mplus mn (Just n)
            op s (Just t,(Prefix,n')) Nothing ts

    f s@S { output = (x:os) , stack = (t,(Prefix,_)):ss } [] = do
        ne <- apop t [x]
        f s { output = ne:os, stack = ss } []
    f s@S { output = (x:y:os) , stack = (t,_):ss } [] = do
        ne <- apop t [y,x]
        f s { output = ne:os, stack = ss } []

    f s@S { output = [e], stack = (Just t,_):ss } [] = do
        ne <- trailingOps e t -- (catMaybes $ map fst stack)
        f s { output = [ne], stack = ss } []
    f s [] = error $ show s

    op s@S { .. } o@(_,prec) me ts = g s where
        g s@S { stack = [] } = f (addOut s { stack = [o] }) ts
        g s@S { stack = (t,prec'@(Prefix,_)):ss, output = (x:rs) }
            | prec' `gt` prec = do
                ne <- apop t [x]
                g s { stack = ss, output = ne:rs }
        g s@S { stack = (t,prec'@(N,_)):ss, output = (x:y:rs) }
            | prec' == prec = do equalFixity prec [t]
        g s@S { stack = (t,prec'):ss, output = (x:y:rs) }
            | prec' `gt` prec = do
                ne <- apop t [y,x]
                g s { stack = ss, output = ne:rs }
        g s@S { .. } = f (addOut s { stack = o:stack }) ts
        addOut s@S { ..} = case me of
            Just e -> s { output = e:output }
            Nothing -> s
    apop t [x,y] = case t of
        Nothing -> application x y
        Just t' -> operator t' [x,y]
    apop t xs = operator t' xs where
        Just t' = t

    _ `gt` (Prefix,_) = False
    (Prefix,x) `gt` (_,y) = x >= y
    (L,x) `gt` (_,y) = x >= y
    (_,x) `gt` (_,y) = x > y

precs = procPrecs
    [prec Prefix ["~"]
    ,prec R ["^"]
    ,prec L ["/","*","%"]
    ,prec R []
    ,prec L ["+","-"]
    ,prec L ["."]
    ,prec N ["=~"]
    ,prec Prefix ["!"]
    ,prec N ["<","<=",">=","!=","=="]
    ,prec L ["&&"]
    ,prec L ["||"]
    ]
prec p ws = (p,ws)
procPrecs ps = concatMap f (zip (reverse ps) [0 :: Int ..]) where
    f ((p,ws),n) = [ (w,(p,n)) | w <- ws ]

main = do
    xs <- getArgs
    let ts = concatMap words xs
        ShuntSpec { .. } = shuntSpec
    let lookupToken x = return $ case lookup x precs of
            Just y -> Right y
            _ -> Left x
        operator t [x,y] = return $ parens (x <+> t <+> y)
        operator t ~[x] = return $ brackets (t <+> x)
        application = \x y -> return $ parens (x <+> y)
        lookupUnary = \_ -> return Nothing
        --trailingOps e t = return $ "<" ++ e ++ " " ++ t ++ ">"
        appPrec = (L,7)
    res <- shunt ShuntSpec { .. } ts
    r2 <- shunt (treeShuntSpec "" (return . (`lookup` precs))) { lookupUnary, appPrec } ts
--    mapM_ print precs
    putStrLn res
    putStrLn $ drawParenTree r2

x <+> y = x ++ " " ++ y
parens x = "(" ++ x ++ ")"
brackets x = "[" ++ x ++ "]"

treeShuntSpec :: (Monad m, Show t)
    => t                     -- token value to use for applications.
    -> (t -> m (Maybe Prec)) -- lookup precedence
    -> ShuntSpec m t (Tree t)
treeShuntSpec app lup = shuntSpec { application, operator, lookupToken } where
    application x y = return $ Node app [x,y]
    operator t xs = return $ Node t xs
    lookupToken t = lup t >>= \x -> case x of
        Just p -> return $ Right p
        Nothing -> return $ Left (Node t [])

drawParenTree :: Tree String -> String
drawParenTree t = f t [] where
    f (Node t []) = showString t
    f (Node "" [x,y]) = showChar '(' . f x . showChar ' ' . f y . showChar ')'
    f (Node t [x,y]) = showChar '(' . f x . showChar ' ' . showString t . showChar ' ' . f y . showChar ')'
    f (Node t [x]) = showChar '(' . showString t . showChar ' ' . f x . showChar ')'
    f (Node t xs) = showString t . showListWith id (map f xs)
