module Laika.Parser where
import Laika.Prelude hiding (lex)
import qualified Laika.Lexer as Lexer
import qualified Data.Text as T
import qualified Filesystem.Path.CurrentOS as FS
import qualified Filesystem as FS
type Parser =
StateT [Lexer.Token] (EitherT String IO)
run :: Parser a -> IO (Either String a)
run =
runEitherT .
flip evalStateT []
lex :: Text -> Parser ()
lex =
join . fmap (\l -> modify (l <>)) .
lift . hoistEither . Lexer.run (Lexer.complete Lexer.tokens)
load :: FilePath -> Parser ()
load p =
liftIO (FS.isFile p) >>= \case
False -> lift $ left $ showString "File not found: " $ show p
True -> liftIO (FS.readTextFile p) >>= lex
parseTemplate :: Parser Template
parseTemplate =
do phrase <- Just <$> parsePhrase <|> pure Nothing
maybe (return []) (\x -> (x :) <$> parseTemplate) phrase
parsePhrase :: Parser Phrase
parsePhrase =
getToken >>= \case
Lexer.BlockOpening path ->
do template <- parseTemplate
getToken >>= \case
Lexer.BlockClosing -> return ()
_ -> lift $ left $ "Unclosed block"
return $
let path' = convertPath path
in Block [r|{ path = path', template = template }|]
Lexer.BlockClosing ->
lift $ left $ "Unexpected block closing"
Lexer.Include path ->
do load path
parsePhrase
Lexer.Reference ref ->
return $ Reference $ convertReference ref
Lexer.Text t ->
return $ Text t
getToken :: Parser Lexer.Token
getToken =
StateT $ \case
h : t -> return (h, t)
_ -> left $ "No tokens left"
shouldFail :: Parser a -> Parser ()
shouldFail p =
optional p >>= maybe (return ()) (const empty)
endOfInput :: Parser ()
endOfInput =
optional getToken >>=
maybe (return ())
(\t -> lift $ left $ "Not all tokens got parsed. Stopped at: " <> show t)
convertPath :: Lexer.Path -> Path
convertPath lp =
let absolute = view [l|absolute|] lp
segments = map convertPathSegment $ view [l|segments|] lp
in [r|{ absolute = absolute, segments = segments }|]
where
convertPathSegment =
\case
Lexer.Dot -> Dot
Lexer.DoubleDot -> DoubleDot
Lexer.Identifier t -> Identifier t
convertReference :: Lexer.Reference -> Reference
convertReference lr =
let escaped = view [l|escaped|] lr
path = convertPath $ view [l|path|] lr
in [r|{ escaped = escaped, path = path }|]
type Template =
[Phrase]
data Phrase =
Text Text |
Reference Reference |
Block Block
deriving (Show)
type Reference =
[r|{ escaped :: Bool, path :: Path }|]
type Path =
[r|{ absolute :: Bool, segments :: [PathSegment] }|]
data PathSegment =
Dot | DoubleDot | Identifier Text
deriving (Show)
type Block =
[r|{ path :: Path, template :: Template }|]