ligo/tools/lsp/squirrel/src/Parser.hs
2020-08-11 12:31:51 +04:00

286 lines
6.3 KiB
Haskell

module Parser (module Parser, gets, pfGrove) where
import Control.Monad.State
import Control.Monad.Writer
import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.Identity
import Data.Foldable
import Data.Text.Encoding
import Data.Text (Text, pack, unpack)
import qualified Data.ByteString as ByteString
import Data.ByteString (ByteString)
import ParseTree
import Range
import Pretty
import Debug.Trace
data Error
= Expected
{ eMsg :: Text
, eWhole :: Text
, eRange :: Range
}
deriving stock (Show)
instance Pretty Error where
pp (Expected msg found r) = "<<<" <> pp msg <> pp r <> ": " <> pp found <> ">>>"
newtype Parser a = Parser
{ unParser
:: WriterT [Error]
( ReaderT ParserEnv
( StateT ParseForest
( ExceptT Error
( IO ))))
a
}
deriving newtype
( Functor
, Applicative
, Monad
, MonadState ParseForest
, MonadWriter [Error]
, MonadReader ParserEnv
, MonadError Error
, MonadIO
)
makeError :: Text -> Parser Error
makeError msg = do
rng <- getRange
makeError' msg rng
makeError' :: Text -> Range -> Parser Error
makeError' msg rng = do
rng <- getRange
src <- cutOut rng
return Expected
{ eMsg = msg
, eWhole = src
, eRange = rng
}
takeNext :: Text -> Parser ParseTree
takeNext msg = do
st@Forest {pfGrove, pfRange} <- get
case pfGrove of
[] -> die msg
(_, t) : f -> do
put st
{ pfRange = diffRange pfRange (ptRange t)
, pfGrove = f
}
return t
field :: Text -> Parser a -> Parser a
field name parser = do
grove <- gets pfGrove
case grove of
(name', t) : _
| name == name' -> do
sandbox True t
_ -> do
case lookup name grove of
Just tree -> sandbox False tree
Nothing -> die name
where
sandbox firstOne tree@ParseTree {ptID, ptRange} = do
st@Forest {pfGrove = grove, pfRange = rng} <- get
let grove' = delete name grove
put Forest
{ pfID = ptID
, pfGrove = [(name, tree)]
, pfRange = ptRange
}
parser <* put st
{ pfGrove = grove'
, pfRange = if firstOne then diffRange rng ptRange else rng
}
fallback :: Stubbed a => Text -> Parser a
fallback' :: Stubbed a => Text -> Range -> Parser a
die :: Text -> Parser a
die' :: Text -> Range -> Parser a
complain :: Text -> Range -> Parser ()
fallback msg = pure . stub =<< makeError msg
fallback' msg rng = pure . stub =<< makeError' msg rng
die msg = throwError =<< makeError msg
die' msg rng = throwError =<< makeError' msg rng
complain msg rng = tell . pure =<< makeError' msg rng
stubbed :: Stubbed a => Text -> Parser a -> Parser a
stubbed msg parser = do
parser <|> fallback msg
subtree :: Text -> Parser a -> Parser a
subtree msg parser = do
ParseTree {ptChildren, ptName} <- takeNext msg
if ptName == msg
then do
save <- get
put ptChildren
parser <* put save
else do
die msg
(<|>) :: Parser a -> Parser a -> Parser a
Parser l <|> Parser r = Parser (l `catchError` const r)
select :: [Parser a] -> Parser a
select = foldl1 (<|>)
optional :: Parser a -> Parser (Maybe a)
optional p = fmap Just p <|> return Nothing
many :: Text -> Parser a -> Parser [a]
many msg p = many'
where
many' = some' <|> pure []
some' = do
hasPossibleInput
(x, consumed) <- productive p
if consumed then do
xs <- many'
return (x : xs)
else do
return [x]
some :: Text -> Parser a -> Parser [a]
some msg p = some'
where
many' = some' <|> pure []
some' = do
hasPossibleInput
(x, consumed) <- productive p
if consumed then do
xs <- many'
return (x : xs)
else do
return [x]
getTreeID :: Parser (Maybe Int)
getTreeID = Parser do
pfGrove <$> get >>= return . \case
[] -> Nothing
(_, tree) : _ -> Just (ptID tree)
productive :: Parser a -> Parser (a, Bool)
productive p = do
was <- getTreeID
res <- p
now <- getTreeID
return (res, was /= now)
hasPossibleInput :: Parser ()
hasPossibleInput = do
yes <- gets (not . null . pfGrove)
unless yes do
die "something"
data ParserEnv = ParserEnv
{ peSource :: ByteString
}
puts :: MonadIO m => Show a => a -> m ()
puts = liftIO . print
runParser :: Parser a -> FilePath -> IO (a, [Error])
runParser (Parser parser) fin = do
pforest <- toParseTree fin
text <- ByteString.readFile fin
res <-
runExceptT
$ flip runStateT pforest
$ flip runReaderT (ParserEnv text)
$ runWriterT
$ parser
either (error . show) (return . fst) res
debugParser :: Parser a -> FilePath -> IO a
debugParser parser fin = do
(res, errs) <- runParser parser fin
putStrLn "Errors:"
for_ errs (print . nest 2 . pp)
putStrLn ""
putStrLn "Result:"
return res
token :: Text -> Parser Text
token node = do
tree@ParseTree {ptName, ptRange} <- takeNext node
if ptName == node
then do
cutOut ptRange
else do
die' node ptRange
anything :: Parser Text
anything = do
tree <- takeNext "anything"
cutOut $ ptRange tree
consume :: Text -> Parser ()
consume node = do
ParseTree {ptName, ptRange} <- takeNext node
when (ptName /= node) do
complain node ptRange
consumeOrDie :: Text -> Parser ()
consumeOrDie node = do
ParseTree {ptName, ptRange} <- takeNext node
when (ptName /= node) do
die' node ptRange
cutOut :: Range -> Parser Text
cutOut (Range (_, _, s) (_, _, f)) = do
bs <- asks peSource
return $ decodeUtf8 $ ByteString.take (f - s) (ByteString.drop s bs)
range :: Parser a -> Parser (a, Range)
range parser =
get >>= \case
Forest {pfGrove = (,) _ ParseTree {ptRange} : _} -> do
a <- parser
return (a, ptRange)
Forest {pfRange} -> do
a <- parser
return (a, pfRange)
getRange :: Parser Range
getRange = snd <$> range (return ())
delete :: Eq k => k -> [(k, v)] -> [(k, v)]
delete _ [] = []
delete k ((k', v) : rest) =
if k == k'
then rest
else (k', v) : delete k rest
notFollowedBy :: Parser a -> Parser ()
notFollowedBy parser = do
good <- do
parser
return False
<|> do
return True
unless good do
die "notFollowedBy"
class Stubbed a where
stub :: Error -> a
instance Stubbed Text where
stub = pack . show