packages feed

miso-css-0.0.1: src/Miso/Css/Gen.hs

module Miso.Css.Gen where

import Control.Monad.State ( MonadState(put), runState, State )
import CssParser
import CssParser.Rule.Pseudo as P
import Data.Map.Strict qualified as M
import Data.Text (unpack)
import Language.Haskell.TH.Syntax
import Miso.Css.Escape ( escapeTypeIden, escapeValIden )
import Miso.Css.List ( spanMaybe )
import Miso.Css.Parser ( CssIndex(byClass, hashSet), Selectors )
import Miso.Css.Prelude
import Miso.Css.Segment
    ( MatchScope(NowOrLater, AutoClean, JustNow), SubSeg(A, T, C) )
import Miso.Css.Sibling (SiblingBranch(NilSibBranch))
import Miso.Css.Style

type TsFilter = TagSubSelector -> Bool
type TsTr = (TagSelector, TagRelation)
type TsSibRel = (TagSelector, SibRel)
type TsHierRel = (TagSelector, HierRel)

data SibRel = SibDir | SibGen deriving (Show, Eq, Ord)
data HierRel = HierChild | HierDescendant deriving (Show, Eq, Ord)

cssIdentToTypeDec :: Ident -> Dec
cssIdentToTypeDec (Ident i) = TySynD n [] (AppT (ConT ''ElementId) (LitT (StrTyLit ns)))
  where
    ns = unpack i
    n = mkName $ escapeTypeIden ns

selectorsToDecs :: CssIndex -> Q [ Dec ]
selectorsToDecs cidx =
  ((cssIdentToTypeDec <$> cidx.hashSet ) <>) . concat <$> mapM go (M.toList cidx.byClass)
  where
    go (i, sels) =
      [d|$(pure . VarP $ identToName i) = $(selectorsToExp i sels)|]

-- type of result Exp is :: OrClass p c
selectorsToExp :: Ident -> Selectors -> Q Exp
selectorsToExp i sels =
  [| ($(composeExpsWithDot <$> mapM (selectorToExp i) sels))
        (TopOrClass $(identToSymbol i)) |]

identToName :: Ident -> Name
identToName (Ident i) = mkName . escapeValIden $ unpack i

identToSymbol :: Ident -> Q Exp
identToSymbol (Ident i) =
  [| Proxy :: Proxy $(pure . LitT . StrTyLit $ unpack i) |]

-- type of result exp  is :: OrClass p c -> OrClass p c
selectorToExp :: Ident -> [ (TagRelation, TagSelector) ] -> Q Exp
selectorToExp i s =
  case runState (shiftSelector s) Nothing of
    (tsTrs, Just (lastTs :: TagSelector)) -> do
      [| AddAncestorBranch ($(foldShiftedTsTr iFilter lastTs tsTrs)) |]
    o -> fail $ "Dead code on " <> show i <> " " <> show s <> " due: " <> show o
  where
    iFilter = \case
      AtomicClass c -> c /= i
      _ -> True

-- .a > .b > .c
tagNameToExp :: TagName -> Q [Exp]
tagNameToExp = \case
  TagName i -> fmap (:[]) [| AddSubSegConstraint (Proxy @T) $(identToSymbol i) |]
  AmpersandTag -> pure [] -- todo expand local alias
  _ -> pure []

tagSubSelectorToExp :: TagSubSelector -> Q [Exp]
tagSubSelectorToExp = \case
  AtomicClass c ->
    fmap (:[]) [| AddSubSegConstraint (Proxy @C) $(identToSymbol c) |]
  HasAttr an ->
    fmap (:[]) [| AddSubSegConstraint (Proxy @A) $(identToSymbol an.attrName) |]
  Hash i ->
    fmap (:[]) [| AddSubSegConstraint (Proxy @A) $(identToSymbol i) |]
  AtomicPseudoClass P.Root ->
    fmap (:[]) [| AddRoot |]
  _ -> pure []

-- every Exp represents a function :: AncestorConstraint -> AncestorConstraint
tagSelectorToExp :: (TagSubSelector -> Bool) -> TagSelector -> Q Exp
tagSelectorToExp tsFilter ts = do
  tn <- tagNameToExp ts.tagName
  composeExpsWithArr . (tn <>) . concat <$>
    mapM tagSubSelectorToExp (filter tsFilter ts.tagSubSelectors)

jn :: Proxy JustNow
jn = Proxy @JustNow
nol :: Proxy NowOrLater
nol = Proxy @NowOrLater
acn :: Proxy AutoClean
acn = Proxy @AutoClean

passAll :: b -> Bool
passAll = const True

lastSelectorToExp :: TsFilter -> TagSelector -> Q Exp
lastSelectorToExp tsf lastTs = [| NextAncestor acn >>> $(tagSelectorToExp tsf lastTs) |]

-- returns exp that is :: AncestorConstraint p
foldShiftedTsTr :: TsFilter -> TagSelector -> [ TsTr ] -> Q Exp
foldShiftedTsTr tsFilter lastTs tsTrs =
  case spanSiblings tsTrs of
    ([], []) -> [| (CssOrphan acn & $(tagSelectorToExp tsFilter lastTs)) |]
    ([], [(ts, Child)]) ->
      [| (CssOrphan jn & ($(tagSelectorToExp passAll ts) >>> $(lastSelectorToExp tsFilter lastTs))) |]
    ([], [(ts, Descendant)]) ->
      [| (CssOrphan nol & ($(tagSelectorToExp passAll ts) >>> $(lastSelectorToExp tsFilter lastTs))) |]

    ([], (ts, Child) : tsTrs') ->
      [| (CssOrphan jn & ($(go (tagSelectorToExp passAll ts) tsTrs'))) |]
    ([], (ts, Descendant) : tsTrs') ->
      [| (CssOrphan nol & ($(go (tagSelectorToExp passAll ts) tsTrs'))) |]

    (tsSrs, []) ->
      [| (CssOrphan jn & ($(tsTrsToAddSiblingBranchExp tsSrs) >>> $(lastSelectorToExp tsFilter lastTs))) |]

    (tsSrs, [(ts, Child)]) ->
      [| (CssOrphan jn & (   $(tsTrsToAddSiblingBranchExp tsSrs)
                         >>> NextAncestor jn
                         >>> $(tagSelectorToExp passAll ts))) |]
    (tsSrs, [(ts, Descendant)]) ->
      [| (CssOrphan nol & (   $(tsTrsToAddSiblingBranchExp tsSrs)
                          >>> NextAncestor jn
                          >>> $(tagSelectorToExp passAll ts))) |]

    (tsSrs, (ts, Child) : tsTrs') ->
      [| (CssOrphan jn & (   $(tsTrsToAddSiblingBranchExp tsSrs)
                         >>> NextAncestor jn
                         >>> $(go (tagSelectorToExp passAll ts) tsTrs'))) |]
    (tsSrs, (ts, Descendant) : tsTrs') ->
      [| (CssOrphan nol & (   $(tsTrsToAddSiblingBranchExp tsSrs)
                          >>> NextAncestor jn
                          >>> $(go (tagSelectorToExp passAll ts) tsTrs'))) |]

    o -> fail $ "Unexpected tsTrs" <> show tsTrs <>  " due case: " <> show o
  where
    go b tsTrs' =
      case spanSiblings tsTrs' of
        ([], []) -> [| $b >>> $(lastSelectorToExp tsFilter lastTs) |]
        ([], [(ts, Child)]) ->
          [| $b >>> NextAncestor jn
                >>> $(tagSelectorToExp passAll ts)
                >>> $(lastSelectorToExp tsFilter lastTs) |]
        ([], [(ts, Descendant)]) ->
          [| $b >>> NextAncestor nol
                >>> $(tagSelectorToExp passAll ts)
                >>> $(lastSelectorToExp tsFilter lastTs) |]
        ([], (ts, Child) : tsTrs'') ->
          go [| $b >>> NextAncestor jn >>> $(tagSelectorToExp passAll ts) |] tsTrs''
        ([], (ts, Descendant) : tsTrs'') ->
          go [| $b >>> NextAncestor nol >>> $(tagSelectorToExp passAll ts) |] tsTrs''
        (tsSrs, []) ->
          [| $b >>> $(tsTrsToAddSiblingBranchExp tsSrs) >>> $(lastSelectorToExp tsFilter lastTs) |]
        (tsSrs, tsTrs'') ->
          go [| $b >>> $(tsTrsToAddSiblingBranchExp tsSrs) |] tsTrs''

sibRelToNilSibExp :: SibRel -> Q Exp
sibRelToNilSibExp = \case
  SibDir -> [| NilSib (Proxy @NowOrLater) |]
  SibGen -> [| NilSib (Proxy @JustNow) |]

-- @AddSiblingBranch (... >>> ... $ NilSibBranch)@
tsTrsToAddSiblingBranchExp :: [TsSibRel] -> Q Exp
tsTrsToAddSiblingBranchExp tsSrs =
  [| AddSiblingBranch (($(go)) NilSibBranch) |]
  where
    go = composeExpsWithArr <$> mapM tsTrToAddSegToSibBrExp tsSrs

-- @AddSegToSibBranch (AddSib (Proxy @C) pa $ NilSib nol)@
tsTrToAddSegToSibBrExp :: TsSibRel -> Q Exp
tsTrToAddSegToSibBrExp (ts, sr) =
  [| AddSegToSibBranch (($(tagSelectorToSibBrSeg ts)) $(sibRelToNilSibExp sr)) |]

-- Exp is :: Sibling ms ss -> Sibling ms ss
-- @AddSib (Proxy @C) pa@
tagSelectorToSibBrSeg :: TagSelector -> Q Exp
tagSelectorToSibBrSeg ts = composeExpsWithDot <$> go
  where
    go :: Q [Exp]
    go = (:) <$> tagNameToAddSibExp ts.tagName
             <*> mapM tagSubSelectorToAddSibExp ts.tagSubSelectors

dotOp :: (b -> c) -> (a -> b) -> a -> c
dotOp = (.)

-- compose the list of unary functions Exp
composeExpsWithDot :: [Exp] -> Exp
composeExpsWithDot = foldr go (VarE 'id)
  where
    go aE = UInfixE aE (VarE 'dotOp)

composeExpsWithArr :: [Exp] -> Exp
composeExpsWithArr = foldl' go (VarE 'id)
  where
    go bE = UInfixE bE (VarE 'arr)

arr :: (a -> b) -> (b -> c) -> a -> c
arr = (>>>)

tagNameToAddSibExp :: TagName -> Q Exp
tagNameToAddSibExp = \case
  TagName i ->
    [| AddSib (Proxy @T) $(identToSymbol i) |]
  AmpersandTag -> [| id |] -- todo expand local alias
  _ -> [| id |]

tagSubSelectorToAddSibExp :: TagSubSelector -> Q Exp
tagSubSelectorToAddSibExp = \case
  AtomicClass c ->
    [| AddSib (Proxy @C) $(identToSymbol c) |]
  HasAttr an ->
    [| AddSib (Proxy @A) $(identToSymbol an.attrName) |]
  Hash i ->
    [| AddSib (Proxy @A) $(identToSymbol i) |]
  _ -> [| id |]

-- every Exp represents a function :: AncestorConstraint -> AncestorConstraint
tagRelationToExp :: TagRelation -> Q [Exp]
tagRelationToExp = \case
  Descendant -> (:[]) <$> [| NextAncestor (Proxy @NowOrLater) |]
  Child -> (:[]) <$> [| NextAncestor (Proxy @JustNow) |]
  NextSibling -> (:[]) <$> [| NextAncestor (Proxy @JustNow) |]
  GeneralSibling -> (:[]) <$> [| NextAncestor (Proxy @NowOrLater) |]

isSiblingRel :: TagRelation -> Maybe SibRel
isSiblingRel = \case
  NextSibling -> pure SibDir
  GeneralSibling -> pure SibGen
  _ -> Nothing

isHierRel :: TagRelation -> Maybe HierRel
isHierRel = \case
  Child -> pure HierChild
  Descendant -> pure HierDescendant
  _ -> Nothing

spanSiblings :: [TsTr] -> ([TsSibRel], [TsTr])
spanSiblings = spanMaybe (\(ts, tr) -> isSiblingRel tr <&> (ts,))

spanHierarchy :: [TsTr] -> ([TsHierRel], [TsTr])
spanHierarchy = spanMaybe (\(ts, tr) -> isHierRel tr <&> (ts,))

shiftSelector :: [(TagRelation, TagSelector)] -> State (Maybe TagSelector) [TsTr]
shiftSelector = \case
  [] -> pure []
  [(_, s)] -> put (Just s) >> pure []
  (_, s) : t -> go s t
  where
    go s = \case
      [] -> pure []
      [(r, lastS)] -> put (Just lastS) >> pure [(s, r)]
      (r', s') : t -> ((s, r') :) <$> go s' t