annah-1.0.0: src/Annah/Parser.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecursiveDo #-}
-- | Parsing logic for the Morte language
module Annah.Parser (
-- * Parser
exprFromText,
typesFromText,
-- * Errors
ParseError(..),
ParseMessage(..)
) where
import Annah.Core
import Annah.Lexer (Position, Token, LocatedToken(..))
import Control.Applicative hiding (Const)
import Control.Exception (Exception)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (Except, throwE, runExceptT)
import Control.Monad.Trans.State.Strict (evalState, get)
import Data.Monoid
import Data.Text.Buildable (Buildable(..))
import Data.Text.Lazy (Text)
import Data.Text.Lazy.Builder (toLazyText)
import Data.Typeable (Typeable)
import Morte.Core (Path(..))
import Filesystem.Path.CurrentOS (FilePath)
import Prelude hiding (FilePath)
import Text.Earley
import qualified Annah.Lexer as Lexer
import qualified Pipes.Prelude as Pipes
import qualified Data.Text.Lazy as Text
match :: Token -> Prod r Token LocatedToken Token
match t = fmap Lexer.token (satisfy predicate) <?> t
where
predicate (LocatedToken t' _) = t == t'
label :: Prod r e LocatedToken Text
label = fmap unsafeFromLabel (satisfy isLabel)
where
isLabel (LocatedToken (Lexer.Label _) _) = True
isLabel _ = False
unsafeFromLabel (LocatedToken (Lexer.Label l) _) = l
number :: Prod r e LocatedToken Int
number = fmap unsafeFromNumber (satisfy isNumber)
where
isNumber (LocatedToken (Lexer.Number _) _) = True
isNumber _ = False
unsafeFromNumber (LocatedToken (Lexer.Number n) _) = n
file :: Prod r e LocatedToken FilePath
file = fmap unsafeFromFile (satisfy isFile)
where
isFile (LocatedToken (Lexer.File _) _) = True
isFile _ = False
unsafeFromFile (LocatedToken (Lexer.File n) _) = n
url :: Prod r e LocatedToken Text
url = fmap unsafeFromURL (satisfy isURL)
where
isURL (LocatedToken (Lexer.URL _) _) = True
isURL _ = False
unsafeFromURL (LocatedToken (Lexer.URL n) _) = n
sepBy1 :: Alternative f => f a -> f b -> f [a]
sepBy1 x sep = (:) <$> x <*> many (sep *> x)
sepBy :: Alternative f => f a -> f b -> f [a]
sepBy x sep = sepBy1 x sep <|> pure []
expr
:: Grammar r
(Prod r Token LocatedToken Expr, Prod r Token LocatedToken [Type])
expr = mdo
expr0 <- rule
( Annot <$> expr1 <*> (match Lexer.Colon *> expr0)
<|> expr1
)
expr1 <- rule
( Lam
<$> (match Lexer.Lambda *> match Lexer.OpenParen *> label)
<*> (match Lexer.Colon *> expr1)
<*> (match Lexer.CloseParen *> match Lexer.Arrow *> expr1)
<|> Pi
<$> (match Lexer.Pi *> match Lexer.OpenParen *> label)
<*> (match Lexer.Colon *> expr1)
<*> (match Lexer.CloseParen *> match Lexer.Arrow *> expr1)
<|> Pi "_" <$> expr2 <*> (match Lexer.Arrow *> expr1)
<|> Family <$> types <*> (match Lexer.In *> expr1)
<|> Lets <$> lets <*> (match Lexer.In *> expr1)
<|> expr2
)
vexpr <- rule
( V <$> label <*> (match Lexer.At *> number)
<|> V <$> label <*> pure 0
)
expr2 <- rule
( App <$> expr2 <*> expr3
<|> expr3
)
let makeExpr3 p =
( Var <$> vexpr
<|> match Lexer.Star *> pure (Const Star)
<|> match Lexer.Box *> pure (Const Box )
<|> Embed <$> embed
<|> (Natural . fromIntegral) <$> number
<|> List
<$> (match Lexer.OpenList *> expr0)
<*> (many (match Lexer.Comma *> expr0) <* match Lexer.CloseBracket)
<|> Path
<$> (match Lexer.OpenPath *> expr0)
<*> many ((,) <$> object <*> expr0)
<*> (object <* match Lexer.CloseBracket)
<|> Do
<$> (match Lexer.Do *> expr0)
<*> (match Lexer.OpenBrace *> many bind)
<*> (bind <* match Lexer.CloseBrace)
<|> (match Lexer.OpenParen *> p <* match Lexer.CloseParen)
)
expr3 <- rule (makeExpr3 expr0)
expr3' <- rule (makeExpr3 expr1)
arg <- rule
( Arg
<$> (match Lexer.OpenParen *> label)
<*> (match Lexer.Colon *> expr1 <* match Lexer.CloseParen)
<|> Arg "_" <$> expr3'
)
args <- rule (many arg)
data_ <- rule (Data <$> (match Lexer.Data *> label) <*> args)
datas <- rule (many data_)
type_ <- rule
( Type
<$> (match Lexer.Type *> label)
<*> datas
<*> (match Lexer.Fold *> label)
<|> Type
<$> (match Lexer.Type *> label)
<*> datas
<*> pure "_"
)
types <- rule (some type_)
let_ <- rule
( Let
<$> (match Lexer.Let *> label)
<*> args
<*> (match Lexer.Colon *> expr0)
<*> (match Lexer.Equals *> expr1)
)
lets <- rule (some let_)
object <- rule (match Lexer.OpenBrace *> expr0 <* match Lexer.CloseBrace)
bind <- rule
( (\x y z -> Bind (Arg x y) z)
<$> label
<*> (match Lexer.Colon *> expr0)
<*> (match Lexer.LArrow *> expr0 <* match Lexer.Semicolon)
)
embed <- rule
( File <$> file
<|> URL <$> url
)
return (expr0, types)
-- | The specific parsing error
data ParseMessage
-- | Lexing failed, returning the remainder of the text
= Lexing Text
-- | Parsing failed, returning the invalid token and the expected tokens
| Parsing Token [Token]
deriving (Show)
-- | Structured type for parsing errors
data ParseError = ParseError
{ position :: Position
, parseMessage :: ParseMessage
} deriving (Typeable)
instance Show ParseError where
show = Text.unpack . toLazyText . build
instance Exception ParseError
instance Buildable ParseError where
build (ParseError (Lexer.P l c) e) =
"\n"
<> "Line: " <> build l <> "\n"
<> "Column: " <> build c <> "\n"
<> "\n"
<> case e of
Lexing r ->
"Lexing: \"" <> build remainder <> dots <> "\"\n"
<> "\n"
<> "Error: Lexing failed\n"
where
remainder = Text.takeWhile (/= '\n') (Text.take 64 r)
dots = if Text.length r > 64 then "..." else mempty
Parsing t ts ->
"Parsing : " <> build (show t ) <> "\n"
<> "Expected: " <> build (show ts) <> "\n"
<> "\n"
<> "Error: Parsing failed\n"
runParser
:: (forall r . Grammar r (Prod r Token LocatedToken a))
-> Text
-> Either ParseError a
runParser p text = evalState (runExceptT m) (Lexer.P 1 0)
where
m = do
(locatedTokens, mtxt) <- lift (Pipes.toListM' (Lexer.lexExpr text))
case mtxt of
Nothing -> return ()
Just txt -> do
pos <- lift get
throwE (ParseError pos (Lexing txt))
let (parses, Report _ needed found) =
fullParses (parser p) locatedTokens
case parses of
parse:_ -> return parse
[] -> do
let LocatedToken t pos = case found of
lt:_ -> lt
_ -> LocatedToken Lexer.EOF (Lexer.P 0 0)
throwE (ParseError pos (Parsing t needed))
-- | Parse an `Expr` from `Text` or return a `ParseError` if parsing fails
exprFromText :: Text -> Either ParseError Expr
exprFromText = runParser (fmap fst expr)
{-| Parse a type definition from `Text` or return a `ParseError` if parsing
fails
-}
typesFromText :: Text -> Either ParseError [Type]
typesFromText = runParser (fmap snd expr)