346 lines
9.7 KiB
Haskell
346 lines
9.7 KiB
Haskell
{-# LANGUAGE RecursiveDo #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
{-# LANGUAGE ImplicitParams #-}
|
|
{-# LANGUAGE LambdaCase, BlockArguments #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
module Rlp.Parse.Decls
|
|
(
|
|
)
|
|
where
|
|
----------------------------------------------------------------------------------
|
|
import Control.Monad
|
|
import Control.Monad.State
|
|
import Text.Megaparsec hiding (State)
|
|
import Text.Megaparsec.Char
|
|
import Text.Megaparsec.Char.Lexer qualified as L
|
|
import Data.Functor.Classes
|
|
import Data.Functor.Foldable
|
|
import Data.Text (Text)
|
|
import Data.Text qualified as T
|
|
import Data.HashMap.Strict qualified as H
|
|
import Data.Maybe (maybeToList)
|
|
import Data.List (foldl1')
|
|
import Data.Char
|
|
import Data.Function (fix)
|
|
import Data.Functor
|
|
import Data.Functor.Const
|
|
import Data.Fix hiding (cata)
|
|
import GHC.Exts (IsString)
|
|
import Lens.Micro
|
|
import Lens.Micro.Platform
|
|
import Rlp.Parse.Types
|
|
import Rlp.Parse.Utils
|
|
import Rlp.Syntax
|
|
----------------------------------------------------------------------------------
|
|
|
|
parseTest' :: (Show a) => Parser a -> Text -> IO ()
|
|
parseTest' p s = case runState (runParserT p "test" s) init of
|
|
(Left e, _) -> putStr (errorBundlePretty e)
|
|
(Right x, st) -> print st *> print x
|
|
where
|
|
init = ParserState mempty
|
|
|
|
lexeme :: Parser a -> Parser a
|
|
lexeme = L.lexeme sc
|
|
|
|
symbol :: Text -> Parser Text
|
|
symbol = L.symbol sc
|
|
|
|
sc :: Parser ()
|
|
sc = L.space hspace1 (void lineComment) (void blockComment)
|
|
|
|
scn :: Parser ()
|
|
scn = L.space space1 (void lineComment) (void blockComment)
|
|
|
|
type OnFold = (?foldGuard :: Parser ())
|
|
|
|
-- TODO: return comment text
|
|
-- TODO: '---' should not start a comment
|
|
lineComment :: Parser Text
|
|
lineComment = L.skipLineComment "--" $> "<unimpl>"
|
|
|
|
-- TODO: return comment text
|
|
blockComment :: Parser Text
|
|
blockComment = L.skipBlockCommentNested "{-" "-}" $> "<unimpl>"
|
|
|
|
decl :: (OnFold) => Parser PartialDecl'
|
|
decl = choice
|
|
-- declarations that begin with a keyword before those beginning with an
|
|
-- arbitrary name
|
|
[ infixD
|
|
, dataD
|
|
, funD
|
|
, tySigD
|
|
]
|
|
|
|
funD :: (OnFold) => Parser PartialDecl'
|
|
funD = FunD <$> lexeme varid <*> params <*> (fsymbol "=" *> body) <*> whereClause
|
|
where
|
|
params = many pat1
|
|
body = fmap Const partialExpr
|
|
|
|
fsymbol :: (OnFold) => Text -> Parser Text
|
|
fsymbol p = scn *> ?foldGuard *> symbol p
|
|
|
|
flexeme :: (OnFold) => Parser a -> Parser a
|
|
flexeme p = scn *> ?foldGuard *> lexeme p
|
|
|
|
whereClause :: Parser Where'
|
|
whereClause = optionalList $
|
|
lexeme "where" *> pure
|
|
[ FunB "fixme" [] (VarE "fixme")
|
|
]
|
|
|
|
standalonePartialExpr :: Parser PartialExpr'
|
|
standalonePartialExpr = standaloneOf partialExpr
|
|
|
|
standaloneOf :: Parser a -> Parser a
|
|
standaloneOf = (<* eof)
|
|
|
|
partialExpr :: Parser PartialExpr'
|
|
partialExpr = choice
|
|
[ try application
|
|
, Fix <$> infixExpr
|
|
]
|
|
<?> "expression"
|
|
where
|
|
application = foldl1' mkApp <$> some partialExpr1
|
|
infixExpr = mkB <$> partialExpr1' <*> infixOp' <*> partialExpr'
|
|
|
|
mkB a f b = B f a b
|
|
partialExpr1' = unFix <$> partialExpr1
|
|
partialExpr' = unFix <$> partialExpr
|
|
infixOp' = lexeme infixOp
|
|
|
|
mkApp :: PartialExpr' -> PartialExpr' -> PartialExpr'
|
|
mkApp f x = Fix . E $ f `AppEF` x
|
|
|
|
partialExpr1 :: Parser PartialExpr'
|
|
partialExpr1 = choice
|
|
[ try $ lexeme "(" *> partialExpr' <* lexeme ")"
|
|
, Fix <$> varid'
|
|
, Fix <$> lit'
|
|
]
|
|
<?> "expression"
|
|
where
|
|
partialExpr' = wrapFix . P . unwrapFix <$> partialExpr
|
|
varid' = E . VarEF <$> varid
|
|
lit' = E . LitEF <$> lit
|
|
|
|
infixOp :: Parser Name
|
|
infixOp = symvar <|> symcon <?> "operator"
|
|
|
|
symvar :: Parser Name
|
|
symvar = T.pack <$>
|
|
liftA2 (:) (satisfy isVarSym) (many $ satisfy isSym)
|
|
|
|
symcon :: Parser Name
|
|
symcon = T.pack <$>
|
|
liftA2 (:) (char ':') (many $ satisfy isSym)
|
|
|
|
pat1 :: (OnFold) => Parser Pat'
|
|
pat1 = VarP <$> flexeme varid
|
|
<?> "pattern"
|
|
|
|
conid :: Parser ConId
|
|
conid = NameCon <$> lexeme namecon
|
|
<|> SymCon <$> lexeme (char '(' *> symcon <* char ')')
|
|
<?> "constructor identifier"
|
|
|
|
namecon :: Parser Name
|
|
namecon = T.pack <$>
|
|
liftA2 (:) (satisfy isUpper)
|
|
(many $ satisfy isNameTail)
|
|
|
|
varid :: Parser VarId
|
|
varid = NameVar <$> try (lexeme namevar)
|
|
<|> SymVar <$> lexeme (char '(' *> symvar <* char ')')
|
|
<?> "variable identifier"
|
|
|
|
decls :: Parser [PartialDecl']
|
|
decls = do
|
|
space
|
|
i <- L.indentLevel
|
|
let indentGuard = L.indentGuard scn EQ i
|
|
let ?foldGuard = void $ L.indentGuard scn GT i
|
|
fix \ds -> (:) <$> (indentGuard *> decl)
|
|
<*> (try ds <|> eof *> pure [])
|
|
|
|
namevar :: Parser Name
|
|
namevar = word
|
|
& withPredicate (`notElem` keywords) empty
|
|
where word = T.pack <$>
|
|
liftA2 (:) (satisfy isLower) (many $ satisfy isNameTail)
|
|
|
|
keywords :: (IsString a) => [a]
|
|
keywords =
|
|
[ "where"
|
|
, "infix"
|
|
, "infixr"
|
|
, "infixl"
|
|
]
|
|
|
|
isNameTail :: Char -> Bool
|
|
isNameTail c = isAlphaNum c
|
|
|| c == '\''
|
|
|| c == '_'
|
|
|
|
isVarSym :: Char -> Bool
|
|
isVarSym = (`T.elem` "\\!#$%&*+./<=>?@^|-~")
|
|
|
|
isSym :: Char -> Bool
|
|
isSym c = c == ':' || isVarSym c
|
|
|
|
infixD :: Parser (Decl' e)
|
|
infixD = do
|
|
o <- getOffset
|
|
a <- infixWord
|
|
p <- prec
|
|
op <- infixOp
|
|
region (setErrorOffset o) $ updateOpTable a p op
|
|
pure $ InfixD a p op
|
|
where
|
|
infixWord :: Parser Assoc
|
|
infixWord = choice $ lexeme <$>
|
|
[ "infixr" $> InfixR
|
|
, "infixl" $> InfixL
|
|
, "infix" $> Infix
|
|
]
|
|
|
|
prec :: Parser Int
|
|
prec = do
|
|
o <- getOffset
|
|
n <- lexeme L.decimal <?> "precedence level (an integer)"
|
|
if 0 <= n && n <= 9 then
|
|
pure n
|
|
else
|
|
region (setErrorOffset o) $
|
|
registerCustomFailure (RlpParErrOutOfBoundsPrecedence n)
|
|
$> 9
|
|
|
|
updateOpTable :: Assoc -> Int -> Name -> Parser ()
|
|
updateOpTable a p op = do
|
|
t <- use psOpTable
|
|
psOpTable <~ H.alterF f op t
|
|
where
|
|
f Nothing = pure (Just (a,p))
|
|
f (Just x) = registerCustomFailure RlpParErrDuplicateInfixD
|
|
$> Just x
|
|
|
|
tySigD :: Parser (Decl' e)
|
|
tySigD = undefined -- TySigD <$> (flexeme)
|
|
|
|
dataD :: Parser (Decl' e)
|
|
dataD = DataD <$> (lexeme "data" *> conid) <*> many typaram
|
|
<*> optionalList (symbol "=" *> conalts)
|
|
where
|
|
typaram :: Parser Name
|
|
typaram = lexeme namevar
|
|
|
|
conalts :: Parser [ConAlt]
|
|
conalts = (:) <$> conalt <*> optionalList (symbol "|" *> conalts)
|
|
|
|
conalt :: Parser ConAlt
|
|
conalt = ConAlt <$> conid <*> many type1
|
|
|
|
type1 :: Parser Type
|
|
type1 = choice
|
|
[ lexeme "(" *> type_ <* lexeme ")"
|
|
, TyVar <$> namevar
|
|
, TyCon <$> namecon
|
|
]
|
|
|
|
type_ :: Parser Type
|
|
type_ = choice
|
|
[ try $ (:->) <$> type1 <*> (lexeme "->" *> type_)
|
|
, type1
|
|
]
|
|
|
|
lit :: Parser Lit'
|
|
lit = int
|
|
<?> "literal"
|
|
where
|
|
int = IntL <$> L.decimal
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- completing partial expressions
|
|
|
|
complete :: (?pt :: OpTable) => PartialExpr' -> RlpExpr'
|
|
complete = cata completePartial
|
|
|
|
completePartial :: (?pt :: OpTable) => PartialE -> RlpExpr'
|
|
completePartial (E e) = completeRlpExpr e
|
|
completePartial p@(B o l r) = completeB (build p)
|
|
completePartial (P e) = completePartial e
|
|
|
|
completeRlpExpr :: (?pt :: OpTable) => RlpExprF' RlpExpr' -> RlpExpr'
|
|
completeRlpExpr = embed
|
|
|
|
completeB :: (?pt :: OpTable) => PartialE -> RlpExpr'
|
|
completeB p = case build p of
|
|
B o l r -> (o' `AppE` l') `AppE` r'
|
|
where
|
|
-- TODO: how do we know it's symbolic?
|
|
o' = VarE (SymVar o)
|
|
l' = completeB l
|
|
r' = completeB r
|
|
P e -> completeB e
|
|
E e -> completeRlpExpr e
|
|
|
|
build :: (?pt :: OpTable) => PartialE -> PartialE
|
|
build e = go id e (rightmost e) where
|
|
rightmost :: PartialE -> PartialE
|
|
rightmost (B _ _ r) = rightmost r
|
|
rightmost p@(E _) = p
|
|
rightmost p@(P _) = p
|
|
|
|
go :: (?pt :: OpTable)
|
|
=> (PartialE -> PartialE)
|
|
-> PartialE -> PartialE -> PartialE
|
|
go f p@(WithInfo o _ r) = case r of
|
|
E _ -> mkHole o (f . f')
|
|
P _ -> mkHole o (f . f')
|
|
B _ _ _ -> go (mkHole o (f . f')) r
|
|
where f' r' = p & pR .~ r'
|
|
go f _ = id
|
|
|
|
mkHole :: (?pt :: OpTable)
|
|
=> OpInfo
|
|
-> (PartialE -> PartialE)
|
|
-> PartialE
|
|
-> PartialE
|
|
mkHole _ hole p@(P _) = hole p
|
|
mkHole _ hole p@(E _) = hole p
|
|
mkHole (a,d) hole p@(WithInfo (a',d') _ _)
|
|
| d' < d = above
|
|
| d' > d = below
|
|
| d == d' = case (a,a') of
|
|
-- left-associative operators of equal precedence are
|
|
-- associated left
|
|
(InfixL,InfixL) -> above
|
|
-- right-associative operators are handled similarly
|
|
(InfixR,InfixR) -> below
|
|
-- non-associative operators of equal precedence, or equal
|
|
-- precedence operators of different associativities are
|
|
-- invalid
|
|
(_, _) -> error "invalid expression"
|
|
where
|
|
above = p & pL %~ hole
|
|
below = hole p
|
|
|
|
examplePrecTable :: OpTable
|
|
examplePrecTable = H.fromList
|
|
[ ("+", (InfixL,6))
|
|
, ("*", (InfixL,7))
|
|
, ("^", (InfixR,8))
|
|
, (".", (InfixR,7))
|
|
, ("~", (Infix, 9))
|
|
, ("=", (Infix, 4))
|
|
, ("&&", (Infix, 3))
|
|
, ("||", (Infix, 2))
|
|
, ("$", (InfixR,0))
|
|
, ("&", (InfixL,0))
|
|
]
|
|
|