module Text.EDE.Internal.Parser where
import Control.Applicative
import Control.Lens hiding (both, noneOf)
import Control.Monad.State.Strict
import Data.Aeson.Types (Array, Object, Value(..))
import Data.Bifunctor
import Data.ByteString (ByteString)
import Data.Char (isSpace)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as Map
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Scientific
import Data.Semigroup
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.Vector as Vector
import Text.EDE.Internal.Syntax
import Text.EDE.Internal.Types
import Text.Parser.Expression
import Text.Parser.LookAhead
import Text.Parser.Token.Style (buildSomeSpaceParser)
import qualified Text.Trifecta as Tri
import Text.Trifecta hiding (Parser, Result(..), spaces)
import Text.Trifecta.Delta
data Env = Env
{ _settings :: !Syntax
, _includes :: HashMap Text (NonEmpty Delta)
}
makeLenses ''Env
instance HasSyntax Env where
syntax = settings
type Parser m =
( Monad m
, MonadState Env m
, TokenParsing m
, DeltaParsing m
, LookAheadParsing m
, Errable m
)
newtype EDE a = EDE { runEDE :: Tri.Parser a }
deriving
( Functor
, Applicative
, Alternative
, Monad
, MonadPlus
, Parsing
, CharParsing
, DeltaParsing
, LookAheadParsing
, Errable
)
instance TokenParsing EDE where
nesting (EDE m) = EDE (nesting m)
someSpace = skipMany (satisfy $ \c -> c /= '\n' && isSpace c)
semi = EDE semi
highlight h (EDE m) = EDE (highlight h m)
instance Errable (StateT Env EDE) where
raiseErr = lift . raiseErr
runParser :: Syntax
-> Text
-> ByteString
-> Result (Exp, HashMap Text (NonEmpty Delta))
runParser o n = res . parseByteString (runEDE run) pos
where
run = runStateT (pragma *> document <* eof) (Env o mempty)
pos = Directed (Text.encodeUtf8 n) 0 0 0 0
res (Tri.Success x) = Success (_includes `second` x)
res (Tri.Failure e) = Failure e
pragma :: Parser m => m ()
pragma = void . many $ do
!xs <- pragmal *> symbol "EDE_SYNTAX" *> sepBy field spaces <* trimr pragmar
mapM_ (uncurry assign) xs
where
field = (,) <$> setter <* symbol "=" <*> parens delim
delim = (,) <$> stringLiteral <* symbol "," <*> stringLiteral
setter = pragmak "pragma" *> pure delimPragma
<|> pragmak "inline" *> pure delimInline
<|> pragmak "comment" *> pure delimComment
<|> pragmak "block" *> pure delimBlock
document :: Parser m => m Exp
document = eapp <$> position <*> many (statement <|> inline <|> fragment)
inline :: Parser m => m Exp
inline = between inlinel inliner term
fragment :: Parser m => m Exp
fragment = ELit <$> position <*> pack (notFollowedBy end0 >> try line0 <|> line1)
where
line0 = manyTill1 (noneOf "\n") (try (lookAhead end0) <|> eof)
line1 = manyEndBy1 anyChar newline
end0 = void (inlinel <|> blockl <|> try end1)
end1 = multiLine (pure ()) (manyTill1 anyChar (lookAhead blockr))
statement :: Parser m => m Exp
statement = choice
[ ifelif
, cases
, loop
, include
, binding
, raw
, comment
]
block :: Parser m => String -> m a -> m a
block k p = try (multiLine (keyword k) p) <|> singleLine (keyword k) p
multiLine :: Parser m => m b -> m a -> m a
multiLine s = between (try (triml blockl *> s)) (trimr blockr)
singleLine :: Parser m => m b -> m a -> m a
singleLine s = between (try (blockl *> s)) blockr
ifelif :: Parser m => m Exp
ifelif = eif
<$> branch "if"
<*> many (branch "elif")
<*> else'
<* exit "endif"
where
branch k = (,) <$> block k term <*> document
cases :: Parser m => m Exp
cases = ecase
<$> block "case" term
<*> many
((,) <$> block "when" pattern
<*> document)
<*> else'
<* exit "endcase"
loop :: Parser m => m Exp
loop = do
d <- position
(i, v) <- block "for"
((,) <$> identifier
<*> (keyword "in" *> collection))
eempty d v
<$> (ELoop d i v <$> document)
<*> else'
<* exit "endfor"
include :: Parser m => m Exp
include = do
d <- position
block "include" (incl d)
where
incl d = do
k <- stringLiteral
includes %= Map.insertWith (<>) k (d:|[])
elet d (EIncl d k) <$> scope
scope = optional $
(,) <$> (keyword "with" *> identifier)
<*> (symbol "=" *> term)
binding :: Parser m => m Exp
binding = do
d <- position
uncurry (ELet d)
<$> block "let"
((,) <$> identifier
<*> (symbol "=" *> term))
<*> document
<* exit "endlet"
raw :: Parser m => m Exp
raw = ELit
<$> position
<*> (start *> pack (manyTill anyChar (lookAhead end)) <* end)
where
start = block "raw" (pure ())
end = exit "endraw"
comment :: Parser m => m Exp
comment = ELit
<$> position
<*> pure (String mempty)
<* (try (triml (trimr go)) <|> go)
where
go = (commentStyle <$> commentl <*> commentr) >>=
buildSomeSpaceParser (fail "whitespace significant")
else' :: Parser m => m (Maybe Exp)
else' = optional (block "else" (pure ()) *> document)
exit :: Parser m => String -> m ()
exit k = block k (pure ())
term :: Parser m => m Exp
term = buildExpressionParser table expr
where
table =
[ [prefix "!"]
, [infix' "*", infix' "/"]
, [infix' "-", infix' "+"]
, [infix' "==", infix' "!=", infix' ">", infix' ">=", infix' "<", infix' "<="]
, [infix' "&&"]
, [infix' "||"]
, [filter' "|"]
]
prefix n = Prefix (efun <$> operator n <*> pure n)
infix' n = Infix (do
d <- operator n
return $ \l r ->
EApp d (efun d n l) r) AssocLeft
filter' n = Infix (do
d <- operator n
i <- try (lookAhead identifier)
return $ \l _ ->
efun d i l) AssocLeft
expr = parens term <|> apply EVar variable <|> apply ELit literal
pattern :: Parser m => m Pat
pattern = PWild <$ char '_' <|> PVar <$> variable <|> PLit <$> literal
collection :: Parser m => m Exp
collection = EVar <$> position <*> variable <|> ELit <$> position <*> col
where
col = Object <$> object
<|> Array <$> array
<|> String <$> stringLiteral
literal :: Parser m => m Value
literal = Bool <$> bool
<|> Number <$> number
<|> String <$> stringLiteral
<|> Object <$> object
<|> Array <$> array
number :: Parser m => m Scientific
number = either fromIntegral fromFloatDigits <$> integerOrDouble
bool :: Parser m => m Bool
bool = symbol "true" *> return True <|> symbol "false" *> return False
object :: Parser m => m Object
object = Map.fromList <$> braces (commaSep pair)
where
pair = (,)
<$> (stringLiteral <* spaces)
<*> (char ':' *> spaces *> literal)
array :: Parser m => m Array
array = Vector.fromList <$> brackets (commaSep literal)
operator :: Parser m => Text -> m Delta
operator n = position <* reserveText operatorStyle n
keyword :: Parser m => String -> m Delta
keyword k = position <* try (reserve keywordStyle k)
variable :: Parser m => m Var
variable = Var <$> (NonEmpty.fromList <$> sepBy1 identifier (char '.'))
identifier :: Parser m => m Id
identifier = ident variableStyle
spaces :: Parser m => m ()
spaces = skipMany (oneOf "\t ")
apply :: Parser m => (Delta -> a -> b) -> m a -> m b
apply f p = f <$> position <*> p
manyTill1 :: Alternative m => m a -> m b -> m [a]
manyTill1 p end = (:) <$> p <*> manyTill p end
manyEndBy1 :: Alternative m => m a -> m a -> m [a]
manyEndBy1 p end = go
where
go = (:[]) <$> end <|> (:) <$> p <*> go
pack :: Functor f => f String -> f Value
pack = fmap (String . Text.pack)
triml :: Parser m => m a -> m a
triml p = do
c <- column <$> position
if c == 0
then spaces *> p
else fail "left whitespace removal failed"
trimr :: Parser m => m a -> m a
trimr p = p <* spaces <* newline
pragmak :: Parser m => String -> m ()
pragmak = reserve pragmaStyle
pragmal, pragmar :: Parser m => m String
pragmal = left delimPragma >>= symbol
pragmar = right delimPragma >>= string
commentl, commentr :: MonadState Env m => m String
commentl = left delimComment
commentr = right delimComment
inlinel, inliner :: Parser m => m String
inlinel = left delimInline >>= symbol
inliner = right delimInline >>= string
blockl, blockr :: Parser m => m String
blockl = left delimBlock >>= symbol
blockr = right delimBlock >>= string
left, right :: MonadState s m => Getter s Delim -> m String
left d = gets (fst . view d)
right d = gets (snd . view d)