lss-0.1.0.0: src/Language/Lss.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
module Language.Lss ( LssState(..)
, LssFunc(..)
, LssApp(..)
, parseDefs
, parseApp
, apply
, attach
, unIdent) where
import Control.Applicative ((<$>), (<*), (<*>))
import Control.Arrow ((&&&))
import qualified Data.Attoparsec.Text as A
import Data.List (partition)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import qualified Language.Css.Parse as P
import qualified Language.Css.Pretty as C
import qualified Language.Css.Syntax as C
import Prelude hiding ((++))
import qualified Text.XmlHtml as X
(++) :: Monoid d => d -> d -> d
(++) = mappend
type ConstMap = Map C.Ident C.Expr
type Symbol = String
deriving instance Ord C.Ident
unIdent :: C.Ident -> Text
unIdent (C.Ident s) = T.pack s
data LssState = LssState { sConstants :: ConstMap, sFunctions :: Map C.Ident LssFunc} deriving (Show, Eq)
data LssFunc = LssFunc { fParams :: [C.Ident], fLocalConstants :: ConstMap, fBody :: [C.RuleSet] } deriving (Show, Eq)
data LssApp = LssApp { aIdent :: C.Ident, cArgs :: [C.Expr]} deriving (Show, Eq)
instance Monoid LssState where
mempty = LssState M.empty M.empty
mappend (LssState c1 f1) (LssState c2 f2) = LssState (M.union c2 c1) (M.union f2 f1)
data FunOrConstOrRule = Fun { unFun :: (C.Ident, LssFunc) }
| Const { unConst :: (C.Ident, C.Expr) }
| Rule { unRule :: C.RuleSet }
deriving Show
isConst (Const _) = True
isConst _ = False
parseDefs :: Text -> Either String LssState
parseDefs = A.parseOnly (lssGrammar <* A.endOfInput)
where lssGrammar = do res <- A.many' (A.choice [A.skipSpace >> fun, A.skipSpace >> con])
let (cons, funs) = partition isConst res
return (LssState (M.fromList $ map unConst cons) (M.fromList $ map unFun funs))
fun = do ident <- P.identp
params <- A.option [] $ do
A.char '('
ps <- A.option [] (do param <- P.identp
rest <- A.many' $ A.char ',' >> A.skipSpace >> P.identp
return (param:rest))
A.char ')'
return ps
A.skipSpace
A.char '{'
res <- A.many' (A.choice [Rule <$> (A.skipSpace >> P.rulesetp), A.skipSpace >> con])
let (cons, rules) = partition isConst res
A.skipSpace
A.char '}'
return $ Fun (ident, LssFunc params (M.fromList $ map unConst cons) (map unRule rules))
con = do ident <- P.identp
A.skipSpace
A.char '='
A.skipSpace
expr <- P.exprp
return $ Const (ident, expr)
parseApp = A.parseOnly (lssApplication <* A.endOfInput)
where lssApplication = do id <- P.identp
args <- A.option [] $ do
A.char '('
as <- A.option [] (do arg <- P.exprp
rest <- A.many' $ A.char ',' >> A.skipSpace >> P.exprp
return (arg:rest))
A.char ')'
return as
return $ LssApp id args
apply :: LssState -> C.Ident -> [C.Expr] -> Either Text [C.RuleSet]
apply state ident args = case M.lookup ident (sFunctions state) of
Nothing -> Left $ "Unknown function: " ++ unIdent ident ++ "."
Just (LssFunc params locals body) | length params == length args ->
let localSubst = map (uncurry subst) (M.assocs locals)
in Right $ map (\b -> foldr ($) b (zipWith subst params args ++ localSubst)) body
Just (LssFunc params _ _) ->
Left $ "Invalid number of arguments to " ++ unIdent ident
++ ": expected " ++ T.pack (show $ length params)
++ ", but got " ++ T.pack (show $ length args)
++ " instead."
where subst param arg (C.RuleSet sels decls) = C.RuleSet sels (map (substDecl param arg) decls)
substDecl param arg (C.Decl prio prop expr) = C.Decl prio prop (substExpr param arg expr)
substExpr param arg expr =
case expr of
C.EVal val -> case val of
C.VIdent ide | ide == param -> arg
_ -> expr
C.SlashSep exp1 exp2 -> C.SlashSep (substExpr param arg exp1) (substExpr param arg exp2)
C.CommaSep exp1 exp2 -> C.CommaSep (substExpr param arg exp1) (substExpr param arg exp2)
C.SpaceSep exp1 exp2 -> C.SpaceSep (substExpr param arg exp1) (substExpr param arg exp2)
attach :: Symbol -> [C.RuleSet] -> [X.Node] -> [X.Node]
attach sym rules nodes = styleNode : map (addClass (T.pack sym)) nodes
where styleNode = X.Element "style" [("type", "text/css")] [X.TextNode $ T.pack $ C.prettyPrint $ C.StyleSheet Nothing [] (map (C.SRuleSet . addSels) rules)]
addSels (C.RuleSet sels decls) = C.RuleSet (concatMap addSel sels) decls
addSel sel = let desc = C.DescendSel (C.SSel (C.UnivSel [C.ClassSel sym])) sel
adj = addAdj sel
in [desc, adj]
addAdj sel = case sel of
C.SSel sim -> addSSel sim
C.DescendSel sel1 sel2 -> C.DescendSel (addAdj sel1) sel2
C.ChildSel sel1 sel2 -> C.ChildSel (addAdj sel1) sel2
C.AdjSel sel1 sel2 -> C.AdjSel (addAdj sel1) sel2
addSSel sim = case sim of
C.UnivSel sels -> C.SSel (C.UnivSel (C.ClassSel sym : sels))
C.TypeSel el sels -> C.SSel (C.TypeSel el (C.ClassSel sym : sels))
addClass classSym node =
case node of
X.Element _ attrs _ ->
let classAttr = case lookup "class" attrs of
Nothing -> ("class", classSym)
Just cls -> ("class", classSym ++ " " ++ cls)
in node { X.elementAttrs = classAttr : filter ((/= "class").fst) attrs}
_ -> node