polyglot-0.1.1.0: src/Parsers/Haskell/FnDef.hs
module Parsers.Haskell.FnDef where
import Lexers.Haskell.Layout (lexeme)
import Parsers.Haskell.Common (literal, nonTokenQVar, qCtor, qCtorOp, qVar,
qVarOp, token, var, varOp)
import Parsers.Haskell.Pattern (pattern')
import Parsers.Haskell.Type (type')
import SyntaxTrees.Haskell.FnDef (Associativity (LAssoc, RAssoc),
CaseBinding (..), DoStep (..), FnBody (..),
FnDef (FnDef), FnDefOrSig (..), FnOp (..),
FnSig (..), FnVar (..), Guard (..),
GuardedFnBody (..),
InfixFnAnnotation (InfixFnAnnotation),
MaybeGuardedFnBody (..), PatternGuard (..))
import Bookhound.Parser (Parser, andThen, check, withError)
import Bookhound.ParserCombinators (IsMatch (is), someSepBy, (->>-), (<|>),
(|*), (|+), (|?))
import Bookhound.Parsers.Char (comma, dot)
import Bookhound.Parsers.Collections (listOf, tupleOf)
import Bookhound.Parsers.Number (int)
import Bookhound.Parsers.String (spacing, string, withinCurlyBrackets,
withinParens, withinSquareBrackets)
import Data.Foldable (Foldable (fold))
import Data.Maybe (maybeToList)
import Utils.String (wrapCurly)
fnSig :: Parser FnSig
fnSig = withError "Function signature" $
FnSig <$> (var <* is "::")
<*> type'
fnDef :: Parser FnDef
fnDef = withError "Function definition" $
FnDef <$> (tupleOf var <|> pure <$> var)
<*> (pattern' |*)
<*> maybeGuardedFnBody (is "=")
infixAnnotation :: Parser InfixFnAnnotation
infixAnnotation = withError "Infix annotation" $
InfixFnAnnotation <$> token (LAssoc <$ is "infixl" <|>
RAssoc <$ is "infixr")
<*> token int
<*> varOp
fnDefOrSig :: Parser FnDefOrSig
fnDefOrSig = Def <$> fnDef <|>
Sig <$> fnSig
fnBody :: Parser FnBody
fnBody = topLevelFnApply <|> openForm
where
topLevelFnApply = FnApply <$> delimitedForm
<*> (pure <$> lambdaExpr)
fnApply = FnApply <$> delimitedForm
<*> (delimitedForm |+)
infixFnApply = uncurry InfixFnApply <$>
sepByOps fnOp (infixArgForm <|> withinParens typeAnnotation)
leftOpSection = uncurry LeftOpSection
<$> withinParens ((,) <$> fnOp <*> openForm)
rightOpSection = uncurry RightOpSection
<$> withinParens ((,) <$> openForm <*> fnOp)
opSection = leftOpSection <|> rightOpSection
lambdaExpr = LambdaExpr <$> (is '\\' *> (pattern' |*))
<*> (is "->" *> openForm)
letExpr = LetExpr <$> (is "let" *> withinContext fnDefOrSig)
<*> (is "in" *> openForm)
whereExpr = WhereExpr <$> withinCurlyBrackets openForm <* is "where"
<*> withinContext fnDefOrSig
ifExpr = IfExpr <$> (is "if" *> openForm)
<*> (is "then" *> openForm)
<*> (is "else" *> openForm)
multiWayIfExpr = MultiWayIfExpr <$>
(is "if" *> (withinContext $ guardedFnBody $ is "->"))
doExpr = DoExpr <$> (is "do" *> withinContext doStep)
caseOfExpr = CaseOfExpr <$> (is "case" *> openForm <* is "of")
<*> withinContext caseBinding
lambdaCaseExpr = LambdaCaseExpr <$> (is "\\case" *> withinContext caseBinding)
listRange = withinSquareBrackets $
ListRange <$> (openForm <* is "..")
<*> (openForm |?)
typeAnnotation = TypeAnnotation <$> (infixArgForm <* is "::")
<*> type'
tuple = Tuple <$> tupleOf openForm
list = List <$> listOf openForm
fnOp = CtorOp' <$> qCtorOp
<|> VarOp' <$> qVarOp
fnOp' = FnOp' <$> withinParens fnOp
fnVar = FnVar' . Selector <$> withinParens (dot *> var)
<|> FnVar' <$>
(Selection <$> nonTokenQVar <* dot <*> someSepBy dot var)
<|> FnVar' . Var' <$> qVar
<|> FnVar' . Ctor' <$> qCtor
literal' = Literal' <$> literal
recordCreate = RecordCreate <$> delimitedForm <*> recordFields
recordUpdate = RecordUpdate <$> delimitedForm <*> recordFields
recordFields = withinCurlyBrackets (someSepBy comma recordField)
recordField = (,) <$> var <*> (is "=" *> openForm)
infixArgForm = complexInfixForm <|> withinParens complexInfixForm
<|> singleForm
openForm = complexForm <|> singleForm
<|> withinParens (complexForm <|> singleForm)
delimitedForm = singleForm <|> withinParens complexForm
<|> withinParens singleForm
singleForm = fnOp' <|> fnVar <|> literal' <|> tuple <|>
listRange <|> list <|> opSection
complexForm = infixFnApply <|> complexInfixForm <|> typeAnnotation
complexInfixForm = fnApply <|> lambdaCaseExpr <|>
lambdaExpr <|> letExpr <|> whereExpr <|>
ifExpr <|> multiWayIfExpr <|> doExpr <|>
caseOfExpr <|> withinParens infixFnApply <|>
recordCreate <|> recordUpdate
doStep :: Parser DoStep
doStep = DoBinding <$> (tupleOf var <|> pure <$> var) <* is "<-"
<*> (adaptFnBody `andThen` fnBody) <|>
LetBinding <$> (is "let" *> withinContext fnDefOrSig) <|>
Body <$> (adaptFnBody `andThen` fnBody)
caseBinding :: Parser CaseBinding
caseBinding = CaseBinding <$> pattern' <*> maybeGuardedFnBody (is "->")
maybeGuardedFnBody :: Parser a -> Parser MaybeGuardedFnBody
maybeGuardedFnBody sep = Guarded <$> (guardedFnBody sep |+) <|>
Standard <$> (sep *> (adaptFnBody `andThen` fnBody))
guardedFnBody :: Parser a -> Parser GuardedFnBody
guardedFnBody sep = GuardedFnBody <$> guard <* sep <*> (adaptFnBody `andThen` fnBody)
guard :: Parser Guard
guard = Otherwise <$ (is "|" *> token (is "otherwise")) <|>
Guard <$> (is "|" *> someSepBy comma patternGuard)
patternGuard :: Parser PatternGuard
patternGuard = PatternGuard <$> (pattern' <* is "<-") <*> fnBody <|>
SimpleGuard <$> fnBody
adaptFnBody :: Parser String
adaptFnBody = do start <- otherText
next <- ((is "where" ->>- string) |?)
other <- ((is ";" ->>- string) |?)
let x = maybe start ((wrapCurly start) ++) next ++ fold other
pure x
where
otherText = (spacing |?) ->>- (textElem |*)
textElem = check "" (`notElem` ["where", ";"]) lexeme ->>- (spacing |?)
statements :: Parser a -> Parser [a]
statements parser = fold <$> someSepBy (is ";") (maybeToList <$> (parser |?))
withinContext :: Parser a -> Parser [a]
withinContext = withinCurlyBrackets . statements
withinContextTupled :: Parser a1 -> Parser a2 -> Parser ([a1], [a2])
withinContextTupled p1 p2 = withinCurlyBrackets $
(,) <$> statements p1 <*> statements p2
sepByOps :: Parser a -> Parser b -> Parser ([a], [b])
sepByOps sep p = do x <- p
y <- (((,) <$> sep <*> p) |+)
pure $ (fst <$> y, x : (snd <$> y))