language-qux-0.2.0.0: src/Language/Qux/Annotated/Parser.hs
{-|
Module : Language.Qux.Annotated.Parser
Description : A Parsec indentation-based parser for generating a program.
Copyright : (c) Henry J. Wylde, 2015
License : BSD3
Maintainer : public@hjwylde.com
A "Text.Parsec" indentation-based parser for generating a 'Program'.
-}
module Language.Qux.Annotated.Parser (
-- * Parser type
Parser, ParseError,
parse,
-- * Source position type
SourcePos,
sourceName, sourceLine, sourceColumn,
-- * Parsers
id_, program, decl, stmt, expr, value, type_
) where
import Control.Monad.State
import Control.Monad.Trans.Except
import Language.Qux.Annotated.Syntax
import Language.Qux.Lexer
import Text.Parsec hiding (State, parse)
import Text.Parsec.Expr
import Text.Parsec.Indent
-- | A 'ParsecT' that retains indentation information.
type Parser a = ParsecT String () (State SourcePos) a
-- | @parse parser sourceName input@ parses @input@ using @parser@.
-- Returns either a 'ParseError' or @a@.
-- This method wraps 'runParserT' by running the indentation resolver over the parser's state.
parse :: Parser a -> SourceName -> String -> Except ParseError a
parse parser sourceName input = except $ runIndent sourceName (runParserT parser () sourceName input)
-- | 'Id' parser.
id_ :: Parser (Id SourcePos)
id_ = Id <$> getPosition <*> identifier <?> "identifier"
-- | 'Program' parser.
program :: Parser (Program SourcePos)
program = do
pos <- getPosition
whiteSpace
checkIndent
reserved "module"
module_ <- sepBy1 id_ dot
checkIndent
decls <- block decl
eof
return $ Program pos module_ decls
<?> "program"
-- | 'Decl' parser.
decl :: Parser (Decl SourcePos)
decl = do
pos <- getPosition
name <- id_
symbol_ "::"
parameters <- (try $ (,) <$> type_ <*> id_) `endBy` rightArrow
returnType <- type_
colon
indented
stmts <- block stmt
return $ FunctionDecl pos name (parameters ++ [(returnType, Id pos "@")]) stmts
<?> "function declaration"
-- | 'Stmt' parser.
stmt :: Parser (Stmt SourcePos)
stmt = choice [ifStmt, returnStmt, whileStmt] <?> "statement"
where
ifStmt = do
pos <- getPosition
reserved "if"
condition <- expr
colon
indented
trueStmts <- block stmt
falseStmts <- option [] (checkIndent >> withBlock' (do { reserved "else"; colon }) stmt)
return $ IfStmt pos condition trueStmts falseStmts
returnStmt = ReturnStmt <$> getPosition <* reserved "return" <*> expr
whileStmt = do
pos <- getPosition
withBlock (WhileStmt pos) (reserved "while" *> expr <* colon) stmt
-- | 'Expr' parser.
expr :: Parser (Expr SourcePos)
expr = buildExpressionParser table (try application <|> term) <?> "expression"
application :: Parser (Expr SourcePos)
application = ApplicationExpr <$> getPosition <*> id_ <*> many (sameOrIndented >> term)
term :: Parser (Expr SourcePos)
term = getPosition >>= \pos -> choice [
parens expr,
ApplicationExpr pos <$> id_ <*> return [],
ListExpr pos <$> brackets (expr `sepEndBy` comma),
UnaryExpr pos Len <$> pipes expr,
ValueExpr pos <$> value
]
table :: OperatorTable String () (State SourcePos) (Expr SourcePos)
table = [
[
Prefix (unaryExpr Neg "-")
],
[
Infix (binaryExpr Acc "!!") AssocLeft
],
[
Infix (binaryExpr Mul "*") AssocLeft,
Infix (binaryExpr Div "/") AssocLeft,
Infix (binaryExpr Mod "%") AssocLeft
],
[
Infix (binaryExpr Add "+") AssocLeft,
Infix (binaryExpr Sub "-") AssocLeft
],
[
Infix (binaryExpr Lte "<=") AssocLeft,
Infix (binaryExpr Lt "<") AssocLeft,
Infix (binaryExpr Gte ">=") AssocLeft,
Infix (binaryExpr Gt ">") AssocLeft
],
[
Infix (binaryExpr Eq "==") AssocLeft,
Infix (binaryExpr Neq "!=") AssocLeft
]
]
binaryExpr :: BinaryOp -> String -> Parser ((Expr SourcePos) -> (Expr SourcePos) -> (Expr SourcePos))
binaryExpr op sym = getPosition >>= \pos -> BinaryExpr pos op <$ operator sym
unaryExpr :: UnaryOp -> String -> Parser ((Expr SourcePos) -> (Expr SourcePos))
unaryExpr op sym = getPosition >>= \pos -> UnaryExpr pos op <$ operator sym
-- | 'Value' parser.
-- A value doesn't have a source position attached as this can be retrieved from a 'ValueExpr'.
value :: Parser Value
value = choice [
BoolValue False <$ reserved "false",
BoolValue True <$ reserved "true",
IntValue <$> natural,
ListValue <$> brackets (value `sepEndBy` comma),
NilValue <$ reserved "nil"
] <?> "value"
-- | 'Type' parser.
type_ :: Parser (Type SourcePos)
type_ = getPosition >>= \pos -> choice [
BoolType pos <$ reserved "Bool",
IntType pos <$ reserved "Int",
ListType pos <$> brackets type_,
NilType pos <$ reserved "Nil"
] <?> "type"