{-| The Parser for the reflex-jsx language

    Given a "String", @parseJsx@ outputs the AST for the language. Note that at
    this point, we capture spliced expressions from the meta-language as
    Strings, and parse them during the quasiquoting phase in @ReflexJsx.QQ@
-}
module ReflexJsx.Parser
       ( parseJsx
       , Node(..)
       , Attrs(..)
       , AttrValue(..)
       ) where

import Text.Parsec (runParser, Parsec, try, eof, many, many1, between)
import Text.Parsec.Char (char, letter, noneOf, string, alphaNum, spaces)

import Control.Applicative ((<|>))


data AttrValue = TextVal String
               | ExprVal String


data Attrs = SplicedAttrs String
           | StaticAttrs [(String, AttrValue)]


data Node = Node String Attrs [Node]
          | Text String
          | SplicedNode String


parseJsx :: Monad m => String -> m Node
parseJsx s =
  case runParser p () "" s of
    Left err -> fail $ show err
    Right e -> return e
  where p = do
          spaces
          node <- jsxElement
          spaces
          eof
          return node


jsxElement :: Parsec String u Node
jsxElement = do
  try jsxSelfClosingElement <|> jsxNormalElement


jsxSelfClosingElement :: Parsec String u Node
jsxSelfClosingElement = do
  _ <- char '<'
  name <- jsxElementName
  attrs <- jsxNodeAttrs
  _ <- string "/>"
  return (Node name attrs [])


jsxNormalElement :: Parsec String u Node
jsxNormalElement = do
  (name, attrs) <- jsxOpeningElement
  children <- many jsxChild
  jsxClosingElement name
  return (Node name attrs children)


jsxOpeningElement :: Parsec String u (String, Attrs)
jsxOpeningElement = do
  _ <- char '<'
  name <- jsxElementName
  attrs <- jsxNodeAttrs
  _ <- char '>'
  return (name, attrs)


jsxNodeAttrs :: Parsec String u Attrs
jsxNodeAttrs = do
  try jsxSplicedAttrMap <|> (StaticAttrs <$> many jsxNodeAttr)


jsxSplicedAttrMap :: Parsec String u Attrs
jsxSplicedAttrMap = do
  name <- between (string "{...") (string "}") $ many (noneOf "}")
  return $ SplicedAttrs name


jsxNodeAttr :: Parsec String u (String, AttrValue)
jsxNodeAttr = do
  key <- jsxAttributeName
  spaces
  _ <- char '='
  spaces
  value <- jsxQuotedValue <|> jsxSplicedValue
  spaces
  return (key, value)


jsxAttributeName :: Parsec String u String
jsxAttributeName = do
  many $ letter <|> char '-'


jsxQuotedValue :: Parsec String u AttrValue
jsxQuotedValue = do
  contents <- between (char '"') (char '"') $ many (noneOf "\"")
  return $ TextVal contents


jsxSplicedValue :: Parsec String u AttrValue
jsxSplicedValue = do
  name <- between (char '{') (char '}') $ many (noneOf "}")
  return $ ExprVal name


jsxClosingElement :: String -> Parsec String u ()
jsxClosingElement ele = do
  _ <- string "</" *> string ele *> char '>'
  return ()


jsxChild :: Parsec String u Node
jsxChild = do
  try jsxText <|> try jsxSplicedNode <|> try jsxElement


jsxText :: Parsec String u Node
jsxText = do
  contents <- many1 $ noneOf "{<>}"
  return $ Text contents


jsxSplicedNode :: Parsec String u Node
jsxSplicedNode = do
  exprString <- between (char '{') (char '}') $ many (noneOf "}")
  return $ SplicedNode exprString


jsxElementName :: Parsec String u String
jsxElementName = jsxIdentifier


jsxIdentifier :: Parsec String u String
jsxIdentifier = do
  name <- many1 alphaNum
  spaces
  return name
