packages feed

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

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

import Control.Monad
import Control.Monad.IO.Class
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as T
import Data.Void
import Language.Haskell.Meta.Parse
import Language.Haskell.Meta.Parse as M
import Language.Haskell.TH
import Language.Haskell.TH qualified
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import Mischief.ECS.Components (Component)
import Mischief.ECS.World.Query
import Mischief.ECS.World.Query.Markers hiding (Q)
import Mischief.ECS.World.Query.QueryFilter
import Mischief.ECS.World.Query.Queryable hiding (Q)
import Mischief.ECS.World.Query.TH.Common
-- import Mischief.ECS.World.Query.TH.QD (CompType (..), Parser, pTup, whitespace)
-- import Mischief.ECS.World.Query.TH.QD qualified as QD
import Text.Megaparsec (MonadParsec (eof, lookAhead, notFollowedBy, try), Parsec, choice, many, manyTill, noneOf, optional, parseTest, satisfy, some, (<|>))
import Text.Megaparsec.Char
import Text.Megaparsec.Char.Lexer qualified as L

data Qf = With' [QfType] | Added' [QfType] | Changed' [QfType] | Not' Qf | Tup' [Qf] | Or' Qf Qf | Check' CompType Text 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 "||", string "or", string "OR", string "Or"] <* whitespace
  case or of
    Nothing -> return s
    Just _ -> foldr Or' s <$> pTup'

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

pNot :: Parser Qf
pNot = do
  void $ choice [string "Not", string "not", string "!"]
  whitespace

  Tup' <$> pTup'

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
  Not' . With' <$> pTypes

pAdded :: Parser Qf
pAdded = do
  void $ choice [string "Added", string "added"] <* notFollowedBy alphaNumChar
  whitespace
  Added' <$> pTypes

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

pCheck :: Parser Qf
pCheck = do
  void $ choice [string "Check", string "check"] <* notFollowedBy alphaNumChar
  whitespace

  f <- pF
  whitespace

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

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

  return $ Check' compType f

pF :: Parser Text
pF = try ((char '(' *> whitespace) *> pfLambda "(" <* whitespace) <|> T.pack <$> some alphaNumChar

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 _ -> return $ 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
    return r

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

  return $
    QfType
      { name,
        compType
      }

quoteQf :: Qf -> Q Exp
quoteQf (Tup' qf) = processTup qf
quoteQf (With' x) = AppE (ConE 'With) <$> processTypes x
quoteQf (Changed' x) = AppE (ConE 'Changed) <$> processTypes x
quoteQf (Added' x) = AppE (ConE 'Added) <$> processTypes x
quoteQf (Or' x y) = do
  x <- quoteQf x
  y <- quoteQf y
  return $ AppE (AppE (ConE 'Or) x) y
quoteQf (Not' x) = AppE (ConE 'Not) <$> quoteQf x
quoteQf (Check' c f) = case parseExp (T.unpack f) of
  Left x -> error x
  Right x -> processCheck c x

processCheck :: CompType -> Exp -> Q Exp
processCheck Single f = return $ AppE (ConE 'Check) f
processCheck (Pair e') f = do
  e <- getValueName e'
  return $ AppE (AppE (ConE 'CheckR) (VarE e)) f
processCheck PairAny f = return $ AppE (AppE (ConE 'CheckR) (ConE 'Any)) f

processTup :: [Qf] -> Q Exp
processTup [x] = quoteQf x
processTup t = TupE . map Just <$> forM t quoteQf

processTypes :: [QfType] -> Q Exp
processTypes [x] = processType x
processTypes t = TupE . map Just <$> forM t processType

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