-- | Symantics for regular grammars.
module Language.Symantic.Grammar.Regular where

import Data.Semigroup (Semigroup(..))
import Data.String (IsString(..))
import qualified Data.Text as Text

import Language.Symantic.Grammar.Meta
import Language.Symantic.Grammar.Fixity
import Language.Symantic.Grammar.EBNF
import Language.Symantic.Grammar.Terminal

-- * Type 'Reg'
-- | Left or right regular grammar.
newtype Reg (lr::Side) g a = Reg { unReg :: g a }
 deriving (IsString, Functor, Gram_Terminal)
deriving instance Gram_Alt g => Gram_Alt (Reg lr g)
deriving instance Gram_Try g => Gram_Try (Reg lr g)
deriving instance Gram_Rule g => Gram_Rule (Reg lr g)
deriving instance Gram_Reader st g => Gram_Reader st (Reg lr g)
deriving instance Gram_State st g => Gram_State st (Reg lr g)
deriving instance Gram_Error err g => Gram_Error err (Reg lr g)
deriving instance (Functor g, Gram_Alt g, Gram_RegL g) => Gram_RegL (RegL g)
deriving instance (Functor g, Gram_Alt g, Gram_RegR g) => Gram_RegR (RegR g)
deriving instance Gram_RegL RuleEBNF
deriving instance Gram_RegR RuleEBNF
deriving instance Gram_RuleEBNF g => Gram_RuleEBNF (RegR g)
deriving instance Gram_RuleEBNF g => Gram_RuleEBNF (RegL g)

reg_of_Terminal :: Terminal g a -> Reg lr g a
reg_of_Terminal (Terminal g) = Reg g

type RegL = Reg 'SideL
type RegR = Reg 'SideR

-- ** Class 'Gram_Alt'
-- | Like 'Alternative' but without the 'Applicative' super-class,
-- because a regular grammar is not closed under 'Applicative'.
class Gram_Alt g where
	empty  :: g a
	(<+>)  :: g a -> g a -> g a
	infixl 3 <+>
	choice :: [g a] -> g a
	choice = foldr (<+>) empty
deriving instance Gram_Alt p => Gram_Alt (Terminal p)
deriving instance Gram_Alt RuleEBNF
instance Gram_Alt EBNF where
	empty = ebnf_const $ "empty"
	EBNF g <+> EBNF q =
		EBNF $ \bo po -> parenInfix po op $
		g bo (op, SideL) <> " | " <> q bo (op, SideR)
		where op = infixB SideL 2
	choice [] = empty
	choice [g] = g
	choice l@(_:_) =
		EBNF $ \bo po -> parenInfix po op $
		Text.intercalate " | " $
		(unEBNF <$> l) <*> pure bo <*> pure (op, SideL)
		where op = infixB SideL 2

-- *** Type 'Gram_Try'
-- | Explicit backtracking.
--
-- To get more accurate error messages,
-- it is helpful to backtrack (put 'try' constructors)
-- only when the grammar actually has another alternative
-- that could match, instead of always backtracking
-- all previous alternatives, as in: 'try'@ a @'<+>'@ b@
class Gram_Try g where
	try :: g a -> g a
instance Gram_Try EBNF where
	try = id
deriving instance Gram_Try RuleEBNF

-- ** Class 'Gram_RegR'
-- | Symantics for right regular grammars.
class (Functor g, Gram_Alt g) => Gram_RegR g where
	(.*>) :: Terminal g (a -> b) -> RegR g a -> RegR g b
	infixl 4 .*>
	manyR :: Terminal g a -> RegR g [a]
	manyR g = (:) <$> g .*> manyR g <+> empty
	someR :: Terminal g a -> RegR g [a]
	someR g = (:) <$> g .*> manyR g
instance Gram_RegR EBNF where
	Terminal f .*> Reg x = Reg $ f <*> x
	manyR (Terminal (EBNF g)) = Reg $ EBNF $ \rm _po -> "{" <> g rm (op, SideL) <> "}"  where op = infixN0
	someR (Terminal (EBNF g)) = Reg $ EBNF $ \rm _po -> "{" <> g rm (op, SideL) <> "}-" where op = infixN0
	 -- NOTE: the suffix "-" symbolizes "minus the empty string".

-- ** Class 'Gram_RegL'
-- | Symantics for left regular grammars.
class (Functor g, Gram_Alt g) => Gram_RegL g where
	(<*.) :: RegL g (a -> b) -> Terminal g a -> RegL g b
	infixl 4 <*.
	manyL :: Terminal g a -> RegL g [a]
	manyL g' = reverse <$> go g'
		where go g = flip (:) <$> go g <*. g <+> empty
	someL :: Terminal g a -> RegL g [a]
	someL g = (\cs c -> cs ++ [c]) <$> manyL g <*. g
instance Gram_RegL EBNF where
	Reg f <*. Terminal x = Reg $ f <*> x
	manyL (Terminal (EBNF g)) = Reg $ EBNF $ \rm _po -> "{" <> g rm (op, SideL) <> "}"  where op = infixN0
	someL (Terminal (EBNF g)) = Reg $ EBNF $ \rm _po -> "{" <> g rm (op, SideL) <> "}-" where op = infixN0