module Stg.Parser.Parser (
parse,
StgParser,
program,
binds,
lambdaForm,
expr,
alts,
nonDefaultAlts,
algebraicAlt,
primitiveAlt,
defaultAlt,
literal,
primOp,
atom,
var,
con,
) where
import Control.Applicative
import Control.Monad
import Data.Char (isSpace)
import Data.List as L
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import Text.Parser.Token.Highlight
import Text.PrettyPrint.ANSI.Leijen (Doc)
import Text.Trifecta as Trifecta
import Stg.Language
parse :: StgParser ast -> Text -> Either Doc ast
parse (StgParser p) input = case parseString (whiteSpace *> p <* eof) mempty (T.unpack input) of
Success a -> Right a
Failure e -> Left e
skipToken :: TokenParsing parser => parser a -> parser ()
skipToken = void . token
newtype StgParser ast = StgParser (Trifecta.Parser ast)
deriving (CharParsing, Parsing, Alternative, Applicative, Functor, Monad)
instance TokenParsing StgParser where
someSpace = skipMany (void (satisfy isSpace) <|> comment)
varId :: TokenParsing parser => IdentifierStyle parser
varId = IdentifierStyle
{ _styleName = "variable"
, _styleStart = lower <|> char '_'
, _styleLetter = alphaNum <|> oneOf "_'"
, _styleReserved = ["let", "letrec", "in", "case", "of", "default"]
, _styleHighlight = Identifier
, _styleReservedHighlight = ReservedIdentifier }
var :: (Monad parser, TokenParsing parser) => parser Var
var = ident varId
reserved :: (Monad parser, TokenParsing parser) => Text -> parser ()
reserved = reserveText varId
con :: (Monad parser, TokenParsing parser) => parser Constr
con = highlight Constructor constructor <?> "constructor"
where
constructor = token (do
start <- upper
body <- many (alphaNum <|> oneOf "_'")
end <- string "#" <|> pure ""
(pure . Constr . T.pack) (start : body <> end) )
program :: (Monad parser, TokenParsing parser) => parser Program
program = someSpace *> fmap Program binds <* eof <?> "STG program"
binds :: (Monad parser, TokenParsing parser) => parser Binds
binds = bindings <?> "non-empty list of bindings"
where
bindings = fmap (Binds . M.fromList) (sepBy1 binding semi)
binding = (,) <$> var <* symbol "=" <*> lambdaForm
comment :: TokenParsing parser => parser ()
comment = skipToken (highlight Comment (lineComment <|> blockComment)) <?> ""
where
lineComment = try (symbol "--") *> manyTill anyChar (char '\n')
blockComment = try (symbol "{-") *> manyTill anyChar (try (symbol "-}"))
lambdaForm :: (Monad parser, TokenParsing parser) => parser LambdaForm
lambdaForm = lf >>= validateLambda <?> "lambda form"
where
lf :: (Monad parser, TokenParsing parser) => parser LambdaForm
lf = (\free bound upd body -> LambdaForm free upd bound body)
<$ token (char '\\')
<*> (parens (some var) <|> pure [])
<*> many var
<*> updateArrow
<*> expr
validateLambda = \case
LambdaForm _ Update [] AppC{} ->
fail "Standard constructors are never updatable"
LambdaForm _ Update (_:_) _ ->
fail "Lambda forms with non-empty argument lists are never updatable"
LambdaForm _ _ _ Lit{} ->
fail "No lambda form has primitive type like 1#;\
\ primitives must be boxed, e.g. Int# (1#)"
LambdaForm _ _ _ AppP{} ->
fail "No lambda form has primitive type like \"+# a b\";\
\ only \"case\" can evaluate them"
x -> pure x
updateArrow :: (Monad parser, TokenParsing parser) => parser UpdateFlag
updateArrow = token (symbol "->" *> pure NoUpdate
<|> symbol "=>" *> pure Update
<?> "update arrow" )
arrow :: TokenParsing parser => parser ()
arrow = skipToken (symbol "->")
expr :: (Monad parser, TokenParsing parser) => parser Expr
expr = choice [let', case', appF, appC, appP, lit] <?> "expression"
where
let', case', appF, appC, appP, lit
:: (Monad parser, TokenParsing parser)
=> parser Expr
let' = Let
<$> (Recursive <$ reserved "letrec" <|> NonRecursive <$ reserved "let")
<*> binds
<* reserved "in"
<*> expr
<?> "let(rec)"
case' = Case
<$ reserved "case"
<*> (expr <?> "expression (as case scrutinee)")
<* reserved "of"
<*> alts
<?> "case expression"
appF = AppF <$> var <*> many atom <?> "function application"
appC = AppC <$> con <*> many atom <?> "constructor application"
appP = AppP <$> primOp <*> atom <*> atom <?> "primitive function application"
lit = Lit <$> literal <?> "literal expression"
alts :: (Monad parser, TokenParsing parser) => parser Alts
alts = Alts
<$> nonDefaultAlts
<*> defaultAlt
<?> "case alternatives"
atom :: (Monad parser, TokenParsing parser) => parser Atom
atom = AtomVar <$> var
<|> AtomLit <$> literal
<?> "atom (variable or literal)"
primOp :: TokenParsing parser => parser PrimOp
primOp = choice ops <?> "primitive function"
where
ops = [ "+" ~> Add
, "-" ~> Sub
, "*" ~> Mul
, "/" ~> Div
, "%" ~> Mod
, "<" ~> Lt
, "<=" ~> Leq
, "==" ~> Eq
, "/=" ~> Neq
, ">=" ~> Geq
, ">" ~> Gt ]
op ~> val = token (try (string op <* char '#')) *> pure val
literal :: TokenParsing parser => parser Literal
literal = token (Literal <$> integer' <* char '#') <?> "integer literal"
nonDefaultAlts :: (Monad parser, TokenParsing parser) => parser NonDefaultAlts
nonDefaultAlts = AlgebraicAlts . NonEmpty.fromList <$> some algebraicAlt
<|> PrimitiveAlts . NonEmpty.fromList <$> some primitiveAlt
<|> pure NoNonDefaultAlts
<?> "non-default case alternatives"
algebraicAlt :: (Monad parser, TokenParsing parser) => parser AlgebraicAlt
algebraicAlt = try (AlgebraicAlt <$> con)
<*> (many var >>= disallowDuplicates)
<* arrow
<*> expr
<* semi
<?> "algebraic case alternative"
where
disallowDuplicates vars = case duplicates vars of
[] -> pure vars
dups ->
let plural = case dups of [_] -> ""; _ -> "s"
errMsg = "Duplicate variable" <> plural <> " in binding: "
<> L.intercalate ", " varNames
varNames = map (\(Var v) -> T.unpack v) dups
in fail errMsg
duplicates = mapMaybe (\case (x:_:_) -> Just x; _ -> Nothing) . group . sort
primitiveAlt :: (Monad parser, TokenParsing parser) => parser PrimitiveAlt
primitiveAlt = try (PrimitiveAlt <$> literal) <* arrow <*> expr <* semi
<?> "primitive case alternative"
defaultAlt :: (Monad parser, TokenParsing parser) => parser DefaultAlt
defaultAlt = DefaultNotBound <$ reserved "default" <* arrow <*> expr
<|> DefaultBound <$> var <* arrow <*> expr
<?> "default alternative"