module Language.Symantic.Grammar.Operators where

import Control.Applicative (Applicative(..))
import Control.Monad
import Data.Foldable hiding (any)
import Prelude hiding (any)

import Language.Symantic.Grammar.EBNF
import Language.Symantic.Grammar.Terminal
import Language.Symantic.Grammar.Regular
import Language.Symantic.Grammar.ContextFree

-- * Class 'Gram_Op'
class
 ( Alt g
 , Alter g
 , App g
 , Try g
 , Gram_CF g
 , Gram_Rule g
 , Gram_Terminal g
 ) => Gram_Op g where
	operators
	 :: CF g a -- ^ expression
	 -> CF g (Unifix, a -> a) -- ^ prefix operator
	 -> CF g (Infix , a -> a -> a) -- ^ infix operator
	 -> CF g (Unifix, a -> a) -- ^ postfix operator
	 -> CF g (Either Error_Fixity a)
	operators g prG iG poG =
		(evalOpTree <$>)
		 <$> go g prG iG poG
		where
		go
		 :: CF g a
		 -> CF g (Unifix, a -> a)
		 -> CF g (Infix , a -> a -> a)
		 -> CF g (Unifix, a -> a)
		 -> CF g (Either Error_Fixity (OpTree a))
		go = rule4 "operators" $ \aG preG inG postG ->
			(\pres a posts ->
				let nod_a =
					foldr insert_unifix
					 (foldl' (flip insert_unifix) (OpNode0 a) posts)
					 pres
				in \case
				 Just (in_, b) -> insert_infix nod_a in_ b
				 Nothing -> Right nod_a)
			 <$> many (try preG)
			 <*> aG
			 <*> many (try postG)
			 <*> option Nothing (curry Just <$> try inG <*> go aG preG inG postG)
		
		insert_unifix :: (Unifix, a -> a) -> OpTree a -> OpTree a
		insert_unifix a@(uni_a@(Prefix prece_a), op_a) nod_b =
			case nod_b of
			 OpNode0{} -> OpNode1 uni_a op_a nod_b
			 OpNode1 Prefix{} _op_b _nod -> OpNode1 uni_a op_a nod_b
			 OpNode1 uni_b@(Postfix prece_b) op_b nod ->
				case prece_b `compare` prece_a of
				 GT -> OpNode1 uni_a op_a nod_b
				 EQ -> OpNode1 uni_a op_a nod_b
				 LT -> OpNode1 uni_b op_b $ insert_unifix a nod
			 OpNode2 inf_b op_b l r ->
				case infix_prece inf_b `compare` prece_a of
				 GT -> OpNode1 uni_a op_a nod_b
				 EQ -> OpNode1 uni_a op_a nod_b
				 LT -> OpNode2 inf_b op_b (insert_unifix a l) r
		insert_unifix a@(uni_a@(Postfix prece_a), op_a) nod_b =
			case nod_b of
			 OpNode0{} -> OpNode1 uni_a op_a nod_b
			 OpNode1 uni_b@(Prefix prece_b) op_b nod ->
				case prece_b `compare` prece_a of
				 GT -> OpNode1 uni_a op_a nod_b
				 EQ -> OpNode1 uni_a op_a nod_b
				 LT -> OpNode1 uni_b op_b $ insert_unifix a nod
			 OpNode1 Postfix{} _op_b _nod -> OpNode1 uni_a op_a nod_b
			 OpNode2 inf_b op_b l r ->
				case infix_prece inf_b `compare` prece_a of
				 GT -> OpNode1 uni_a op_a nod_b
				 EQ -> OpNode1 uni_a op_a nod_b
				 LT -> OpNode2 inf_b op_b l (insert_unifix a r)
		
		insert_infix
		 :: OpTree a
		 -> (Infix, a -> a -> a)
		 -> Either Error_Fixity (OpTree a)
		 -> Either Error_Fixity (OpTree a)
		insert_infix nod_a in_@(inf_a, op_a) e_nod_b = do
			nod_b <- e_nod_b
			case nod_b of
			 OpNode0{} -> Right $ OpNode2 inf_a op_a nod_a nod_b
			 OpNode1 uni_b op_b nod ->
				case unifix_prece uni_b `compare` infix_prece inf_a of
				 EQ -> Right $ OpNode2 inf_a op_a nod_a nod_b
				 GT -> Right $ OpNode2 inf_a op_a nod_a nod_b
				 LT -> do
					n <- insert_infix nod_a in_ (Right nod)
					Right $ OpNode1 uni_b op_b n
			 OpNode2 inf_b op_b l r ->
				case infix_prece inf_b `compare` infix_prece inf_a of
				 GT -> Right $ OpNode2 inf_a op_a nod_a nod_b
				 LT -> do
					n <- insert_infix nod_a in_ (Right l)
					Right $ OpNode2 inf_b op_b n r
				 EQ ->
					let ass = \case
						 AssocL -> L
						 AssocR -> R
						 AssocB lr -> lr in
					case (ass <$> infix_assoc inf_b, ass <$> infix_assoc inf_a) of
					 (Just L, Just L) -> do
						n <- insert_infix nod_a in_ (Right l)
						Right $ OpNode2 inf_b op_b n r
					 (Just R, Just R) ->
						Right $ OpNode2 inf_a op_a nod_a nod_b
					 _ -> Left $ Error_Fixity_Infix_not_combinable inf_a inf_b
						 -- NOTE: non-associating infix ops
						 -- of the same precedence cannot be mixed.
	infixrG :: CF g a -> CF g (a -> a -> a) -> CF g a
	infixrG = rule2 "infixr" $ \g opG ->
		(\a -> \case
		 Just (op, b) -> a `op` b
		 Nothing -> a)
		 <$> g
		 <*> option Nothing (try $ curry Just <$> opG <*> infixrG g opG)
	infixlG :: CF g a -> CF g (a -> a -> a) -> CF g a
	infixlG = rule2 "infixl" $ \g opG ->
		-- NOTE: infixl uses the same grammar than infixr,
		-- but build the parsed value by applying
		-- the operator in the opposite way.
		($ id) <$> go g opG
		where
		go :: CF g a -> CF g (a -> a -> a) -> CF g ((a -> a) -> a)
		go g opG =
			(\a -> \case
			 Just (op, kb) -> \k -> kb (k a `op`)
			 Nothing -> ($ a))
			 <$> g
			 <*> option Nothing (try $ curry Just <$> opG <*> go g opG)
deriving instance Gram_Op g => Gram_Op (CF g)
instance Gram_Op RuleDef
instance Gram_Op EBNF

-- ** Type 'Error_Fixity'
data Error_Fixity
 =   Error_Fixity_Infix_not_combinable Infix Infix
 |   Error_Fixity_NeedPostfixOrInfix
 |   Error_Fixity_NeedPrefix
 |   Error_Fixity_NeedPostfix
 |   Error_Fixity_NeedInfix
 deriving (Eq, Show)

-- ** Type 'NeedFixity'
data NeedFixity
 =   NeedPrefix
 |   NeedPostfix
 |   NeedPostfixOrInfix
 deriving (Eq, Ord, Show)

-- ** Type 'Fixity'
data Fixity a
 =   FixityPrefix  Unifix (a -> a)
 |   FixityPostfix Unifix (a -> a)
 |   FixityInfix   Infix  (a -> a -> a)

-- ** Type 'Unifix'
data Unifix
 =   Prefix  { unifix_prece :: Precedence }
 |   Postfix { unifix_prece :: Precedence }
 deriving (Eq, Show)

-- ** Type 'OpTree'
data OpTree a
 =   OpNode0 a
 |   OpNode1 Unifix (a -> a)      (OpTree a)
 |   OpNode2 Infix  (a -> a -> a) (OpTree a) (OpTree a)

-- | Collapse an 'OpTree'.
evalOpTree :: OpTree a -> a
evalOpTree (OpNode0 a) = a
evalOpTree (OpNode1 _uni op n) = op $ evalOpTree n
evalOpTree (OpNode2 _inf op l r) = evalOpTree l `op` evalOpTree r

gram_operators :: (Gram_Op g, Gram_RuleDef g) => [CF g ()]
gram_operators =
 [ void $ operators (rule_arg "expr") (rule_arg "prefix") (rule_arg "infix") (rule_arg "postfix")
 ]