ghcjs-vdom-0.2.0.0: src/GHCJS/VDOM/QQ.hs
{-# LANGUAGE QuasiQuotes, DeriveDataTypeable, TemplateHaskell #-}
{-
More efficient JavaScript literals with QuasiQuoters
mostly experimental, might not stay
-}
module GHCJS.VDOM.QQ (ch, children, att, attributes) where
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import GHCJS.VDOM.Internal.Types
import GHCJS.Types
import GHCJS.Marshal
import Control.Applicative
import Data.Char
import qualified Data.List as L
import Data.List.Split
import Data.Typeable
import System.IO.Unsafe
att :: QuasiQuoter
att = attributes
ch :: QuasiQuoter
ch = children
-- example: [props|a:1, b: null, c: x, d: y |]
-- every value is either a literal or a variable referring to a convertible Haskell name
-- fixme, this does not have a proper parser
attributes :: QuasiQuoter
attributes = QuasiQuoter { quoteExp = quoteProps }
quoteProps :: String -> Q Exp
quoteProps pat = jsExpQQ ('{':ffiPat++"}") (map mkName names)
(\x -> AppE (VarE 'unsafePerformIO) (AppE (VarE 'toJSVal) x))
(AppE (ConE 'Attributes'))
where
(names, ffiPat) = genpat 1 $ map (break (==':') . trim) (linesBy (==',') pat)
isName [] = False
isName (x:xs) = isAlpha x && all isAlphaNum xs
genpat :: Int -> [(String,String)] -> ([String], String)
genpat _ [] = ([], "")
genpat k ((x,':':n):xs)
| isName n' = (n':ns, x ++ ": $" ++ (show k) ++ p)
| otherwise = (ns, x ++ ':' : n ++ sep ++ p)
where
n' = trim n
~(ns, p) = genpat (k+1) xs
sep = if null xs then "" else ","
genpat _ _ = error "invalid pattern"
-- example: [children|x,y,z|] for haskell names x,y,z :: VNode
children :: QuasiQuoter
children = QuasiQuoter { quoteExp = quoteChildren }
quoteChildren :: String -> Q Exp
quoteChildren pat = jsExpQQ ffiPat names (AppE (VarE 'unVNode)) (AppE (ConE 'Children'))
where
names = map (mkName.trim) (linesBy (==',') pat)
ffiPat = '[' : L.intercalate "," (map (('$':).show) (take (length names) [(1::Int)..])) ++ "]"
trim :: String -> String
trim = let f = reverse . dropWhile isSpace in f . f
newtype QQCounter = QQCounter { getCount :: Int } deriving (Typeable, Show)
jsExpQQ :: String -> [Name] -> (Exp -> Exp) -> (Exp -> Exp) -> Q Exp
jsExpQQ pat args unwrap wrap = do
c <- maybe 0 getCount <$> qGetQ
n <- newName ("__ghcjs_vdom_qq_spliced_" ++ show c)
let ffiDecl = ForeignD (ImportF CCall Unsafe pat' n (ty $ length args))
ty :: Int -> Type
ty 0 = ref
ty n = AppT (AppT ArrowT ref) (ty (n-1))
ref = ConT ''JSVal
ffiCall [] = (VarE n)
ffiCall (y:ys) = AppE (ffiCall ys) (unwrap (VarE y))
pat' = "__ghcjs_javascript_" ++ L.intercalate "_" (map (show . ord) pat)
qAddTopDecls [ffiDecl]
qPutQ (QQCounter (c+1))
return $ wrap (ffiCall $ reverse args)