packages feed

mischief-ecs-0.2.0.0: src/Mischief/ECS/World/Query/TH/QF.hs

{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

module Mischief.ECS.World.Query.TH.QF where

import Control.Monad
import Data.Text (Text)
import Data.Text qualified as T
import Language.Haskell.Meta.Parse
import Language.Haskell.TH
import Mischief.ECS.World.Query.QueryFilter
import Mischief.ECS.World.Query.TH.Common
import Text.Megaparsec (MonadParsec (notFollowedBy, try), choice, many, optional, satisfy, some, (<|>))
import Text.Megaparsec.Char

data Qf = With' [QfType] | Without' [QfType] | Changed' [QfType] Text Text | Tup' [Qf] | And' Qf Qf | Or' Qf Qf deriving (Show)

data QfType = QfType {name :: Text, compType :: CompType} deriving (Show)

pQf :: Parser Qf
pQf = Tup' . concat <$> pTup pTup'

pTup' :: Parser [Qf]
pTup' = try ((char '(' *> whitespace) *> (concat <$> pTup pTup') <* (char ')' *> whitespace)) <|> (: []) <$> pOr

pOr :: Parser Qf
pOr = do
  !s <- pSingle
  whitespace

  !or <- optional $ choice [string "||", string "or", string "OR", string "Or"] <* whitespace
  case or of
    Nothing -> pure s
    Just _ -> foldr Or' s <$> pTup'

pAnd :: Parser Qf
pAnd = do
  !s <- pSingle
  whitespace

  !and <- optional $ choice [string "&&", string "and", string "AND", string "And"] <* whitespace
  case and of
    Nothing -> return s
    Just _ -> foldr And' s <$> pTup'

pSingle :: Parser Qf
pSingle = try pWith <|> pWithout <|> try pChanged

pWith :: Parser Qf
pWith = do
  void $ choice [string "With", string "with"] <* notFollowedBy alphaNumChar
  whitespace
  With' <$> pTypes

pWithout :: Parser Qf
pWithout = do
  void $ choice [string "Without", string "without"] <* notFollowedBy alphaNumChar
  whitespace
  Without' <$> pTypes

pChanged :: Parser Qf
pChanged = do
  void $ choice [string "Changed", string "changed"] <* notFollowedBy alphaNumChar
  whitespace
  types <- pTypes

  lambdas <- optional $ do
    void $ char '('
    l1 <- pfLambda "("
    whitespace

    void $ char '('
    l2 <- pfLambda "("
    pure (l1, l2)

  case lambdas of
    Nothing -> pure $ Changed' types "(>=)" "(<)"
    Just (f1, f2) -> pure $ Changed' types f1 f2

pfLambda :: String -> Parser Text
pfLambda !str = do
  !x <- many (satisfy (/= ')'))
  void $ char ')'
  let !str' = str ++ x ++ [')']
  case parseExp str' of
    Left _ -> do
      pfLambda str'
    Right _ -> pure $ T.pack str'

pTypes :: Parser [QfType]
pTypes = try ((char '(' *> whitespace) *> (concat <$> pTup pTypes) <* (char ')' *> whitespace)) <|> (: []) <$> pType

pType :: Parser QfType
pType = do
  !name <- pNameTup <|> T.pack <$> some alphaNumChar
  whitespace

  !target <- optional $ do
    void $ string "->"
    whitespace
    !r <- string "*" <|> T.pack <$> some alphaNumChar
    whitespace
    pure r

  let !compType = case target of
        Nothing -> Single
        Just "*" -> PairAny
        Just e -> Pair e

  pure $
    QfType
      { name,
        compType
      }

quoteQf :: Qf -> Q Exp
quoteQf (Tup' !qf) = processTup qf
quoteQf (With' !x) = processTypes (ConE 'With) x
quoteQf (Without' !x) = processTypes (ConE 'Without) x
quoteQf (Changed' !x f1 f2) = do
  let (Right f1') = parseExp (T.unpack f1)
  let (Right f2') = parseExp (T.unpack f2)
  processChanged (ConE 'Changed) x f1' f2'
quoteQf (Or' !x !y) = do
  !x <- quoteQf x
  !y <- quoteQf y
  pure $ AppE (AppE (ConE 'Or) x) y
quoteQf (And' !x !y) = do
  !x <- quoteQf x
  !y <- quoteQf y
  pure $ AppE (AppE (ConE 'And) x) y

processTup :: [Qf] -> Q Exp
processTup [] = pure $ VarE '()
processTup [!x] = quoteQf x
processTup (!x : !xs) = do
  !x' <- quoteQf x
  AppE (AppE (ConE 'And) x') <$> processTup xs

processTypes :: Exp -> [QfType] -> Q Exp
processTypes _ [] = pure $ VarE '()
processTypes !exp [!x] = AppE exp <$> processType x
processTypes !exp (!x : !xs) = do
  !x' <- processType x
  AppE (AppE (ConE 'And) (AppE exp x')) <$> processTypes exp xs

processChanged :: Exp -> [QfType] -> Exp -> Exp -> Q Exp
processChanged _ [] _ _ = pure $ VarE '()
processChanged !exp [!x] f1 f2 = do
  r <- processType x
  pure $ AppE (AppE (AppE exp r) f1) f2
processChanged !exp (!x : !xs) f1 f2 = do
  !x' <- processType x
  AppE (AppE (ConE 'And) (AppE (AppE (AppE exp x') f1) f2)) <$> processTypes exp xs

processType :: QfType -> Q Exp
processType (QfType {name, compType = Single}) = processC name
processType (QfType {name, compType}) = processR name =<< relExp compType