hakyllbars-1.0.0.0: src/Hakyllbars/Source/Parser.hs
module Hakyllbars.Source.Parser where
import Data.Functor
import Data.List.NonEmpty as NEL
import Data.Maybe
import Data.String
import Hakyllbars.Ast
import Hakyllbars.Source.Lexer
import Hakyllbars.Source.Util
import Text.Parsec hiding (parse, runParser, token, tokens, (<?>))
import qualified Text.Parsec as P
import Prelude hiding (lex)
type Parser a = Parsec [Token] ParserState a
parse :: SourceName -> String -> Either ParseError Template
parse origin input = do
tokens <- lex origin input
blocks <- runParser (many block <* eof) origin tokens
return $ Template blocks origin
block :: Parser Block
block =
tryOne
[ offBlock,
commentBlock,
chromeBlock,
altBlock,
expressionBlock,
textBlock
]
commentBlock :: Parser Block
commentBlock = labeled "CommentBlock" $
withPosition do
-- {{!
withTag CommentBlockToken
-- ...text...
value <- withToken \case
TextToken value _ -> Just value
_ -> Nothing
-- }}
closeBlock
return $ CommentBlock value
offBlock :: Parser Block
offBlock = labeled "OffBlock" $
withPosition do
-- {{*
withTag TurnOffToken
-- ...text...
value <- withToken \case
TextToken value _ -> Just value
_ -> Nothing
-- }}
withTag CloseBlockToken
return $ TextBlock value
expressionBlock :: Parser Block
expressionBlock = labeled "ExpressionBlock" $
withPosition do
-- {{
withTag ExpressionBlockToken
-- ...1 + 2...
e <- expression
-- }}
closeBlock
return $ ExpressionBlock e
chromeBlock :: Parser Block
chromeBlock = labeled "ChromeBlock" $
withPosition do
-- {{@
withTag ChromeBlockToken
-- ...layout "body"...
e <- expression
-- }}
closeBlock
-- {{...}}+
bs <- many block
return $ ChromeBlock e bs
altBlock :: Parser Block
altBlock = labeled "AltBlock" $ withPosition do
-- {{#if doThis}} <- one of these
startAlt <- labeled "StartAlt" $ withPosition do
e <- between altToken closeBlock expression
bs <- manyTill block closeAlt
return $ ApplyBlock e bs
-- {{#else doThat}} <- zero or more of these
elseAlts <- flip manyTill closeElses $
labeled "ElseAlt" $ withPosition do
e <- between (altToken *> elseToken) closeBlock expression
bs <- manyTill block closeAlt
return $ ApplyBlock e bs
-- {{#else}} <- zero or one of these
defaultAlt <- optionMaybe . try $
labeled "DefaultAlt" $ withPosition do
defaultBlock
bs <- manyTill block closeDefault
return $ DefaultBlock bs
-- {{#end}}
endBlock
return $ AltBlock (startAlt :| elseAlts) defaultAlt
where
closeAlt = lookAhead $ tryOne [endBlock, defaultBlock, elseBlock]
closeElses = lookAhead $ tryOne [endBlock, defaultBlock]
closeDefault = lookAhead $ try endBlock
--
endBlock = altToken *> endToken *> closeBlock
defaultBlock = altToken *> elseToken *> closeBlock
elseBlock = altToken *> elseToken
--
altToken = withTag AltBlockToken
elseToken = withTag ElseToken
endToken = withTag EndToken
textBlock :: Parser Block
textBlock = labeled "TextBlock" $
withToken \case
TextToken x pos -> Just $ TextBlock x pos
_ -> Nothing
expression :: Parser Expression
expression = filterExpression <?> "Expression"
{-# INLINE expression #-}
filterExpression :: Parser Expression
filterExpression = applyExpression `chainl1` filtered <?> "FilterExpression"
where
filtered = withPosition do
withTag PipeToken
return f
f pos x y = FilterExpression x y pos
applyExpression :: Parser Expression
applyExpression = chain . NEL.fromList <$> many1 accessExpression <?> "ApplyExpression"
where
chain (fn :| args) = chain' fn args
chain' fn (arg : rest) = chain' (ApplyExpression fn arg (getExpressionPos arg)) rest
chain' done [] = done
accessExpression :: Parser Expression
accessExpression = simpleExpression `chainl1` try accessed <?> "AccessExpression"
where
accessed = withPosition do
withTag DotToken
return f
f pos x (NameExpression id' pos') = f pos x (StringExpression id' pos')
f pos x y = AccessExpression x y pos
simpleExpression :: Parser Expression
simpleExpression =
tryOne
[ stringExpression,
intExpression,
doubleExpression,
boolExpression,
nameExpression,
parensExpression,
contextExpression,
listExpression
]
stringExpression :: Parser Expression
stringExpression = labeled "StringLiteral" $
withToken \case
StringToken value pos -> Just (StringExpression value pos)
_ -> Nothing
intExpression :: Parser Expression
intExpression = labeled "IntLiteral" $
withToken \case
IntToken value pos -> Just (IntExpression value pos)
_ -> Nothing
doubleExpression :: Parser Expression
doubleExpression = labeled "DoubleLiteral" $
withToken \case
DoubleToken value pos -> Just (DoubleExpression value pos)
_ -> Nothing
boolExpression :: Parser Expression
boolExpression = labeled "BoolLiteral" $
withToken \case
BoolToken value pos -> Just (BoolExpression value pos)
_ -> Nothing
nameExpression :: Parser Expression
nameExpression = labeled "Name" $ withPosition do
n <- NameExpression <$> withName
try . notFollowedBy $ withTag ColonToken
return n
parensExpression :: Parser Expression
parensExpression = do
withTag OpenParenToken
expression' <- expression
withTag CloseParenToken
return expression'
contextExpression :: Parser Expression
contextExpression =
(braced <?> "BracedContext")
<|> (unbraced <?> "UnbracedContext")
where
braced = withPosition do
withTag OpenBraceToken
pairs <- contextKeyValue `sepEndBy` comma
withTag CloseBraceToken
return $ ContextExpression pairs
unbraced = withPosition do
pairs <- contextKeyValue `sepBy1` comma
return $ ContextExpression pairs
contextKeyValue :: Parser (String, Expression)
contextKeyValue = labeled "ContextLiteralPair" do
key <- withName
withTag ColonToken
value <- expression
return (key, value)
listExpression :: Parser Expression
listExpression = labeled "ListLiteral" do
withPosition do
withTag OpenBracketToken
values <- expression `sepEndBy` comma
withTag CloseBracketToken
return $ ListExpression values
comma :: Parser ()
comma =
labeled "Comma" $
withTag CommaToken
closeBlock :: Parser ()
closeBlock =
labeled "CloseBlock" $
withTag CloseBlockToken
withToken :: (Token -> Maybe a) -> Parser a
withToken = P.token show getTokenPos
withTag :: TokenTag -> Parser ()
withTag tag = P.token show getTokenPos f
where
f = \case
TaggedToken t _ | t == tag -> Just ()
_ -> Nothing
withName :: Parser String
withName = withToken \case
NameToken n _ -> Just n
_ -> Nothing