ligo/tools/lsp/squirrel/src/Parser.hs

268 lines
5.9 KiB
Haskell
Raw Normal View History

2020-04-30 14:39:51 +04:00
module Parser where
import Control.Monad.State
import Control.Monad.Writer
import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.Identity
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 PrettyPrint
2020-04-30 14:39:51 +04:00
2020-04-30 17:46:39 +04:00
import Debug.Trace
2020-04-30 14:39:51 +04:00
data Error
2020-04-30 21:06:01 +04:00
= Expected
{ eMsg :: Text
, eWhole :: Text
, eRange :: Range
}
2020-04-30 14:39:51 +04:00
deriving stock (Show)
instance Pretty Error where
pp (Expected msg found r) = "<" <> pp msg <> pp r <> ": " <> pp found <> ">"
2020-04-30 14:39:51 +04:00
newtype Parser a = Parser
{ unParser
:: WriterT [Error]
( ReaderT ParserEnv
( StateT ParseForest
( ExceptT Error
( Identity ))))
a
}
deriving newtype
( Functor
, Applicative
, Monad
, MonadState ParseForest
, MonadWriter [Error]
, MonadReader ParserEnv
, MonadError Error
)
2020-04-30 21:06:01 +04:00
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
}
2020-04-30 14:39:51 +04:00
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
2020-04-30 17:46:39 +04:00
field :: Text -> Parser a -> Parser a
2020-04-30 14:39:51 +04:00
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
}
2020-04-30 17:46:39 +04:00
parser <* put st
2020-04-30 14:39:51 +04:00
{ pfGrove = grove'
, pfRange = if firstOne then diffRange rng ptRange else rng
}
2020-04-30 21:06:01 +04:00
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
2020-04-30 14:39:51 +04:00
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 []
2020-04-30 17:46:39 +04:00
some' = do
(x, consumed) <- productive p
if consumed then do
xs <- many'
return (x : xs)
else do
return [x]
2020-04-30 14:39:51 +04:00
some :: Text -> Parser a -> Parser [a]
some msg p = some'
where
many' = some' <|> pure []
2020-04-30 17:46:39 +04:00
some' = do
(x, consumed) <- productive p
if consumed then do
xs <- many'
return (x : xs)
else do
return [x]
2020-04-30 14:39:51 +04:00
getTreeID :: Parser (Maybe Int)
getTreeID = Parser do
pfGrove <$> get >>= return . \case
[] -> Nothing
(_, tree) : _ -> Just (ptID tree)
2020-04-30 17:46:39 +04:00
productive :: Parser a -> Parser (a, Bool)
productive p = do
2020-04-30 14:39:51 +04:00
was <- getTreeID
res <- p
now <- getTreeID
2020-04-30 17:46:39 +04:00
return (res, was /= now)
2020-04-30 14:39:51 +04:00
data ParserEnv = ParserEnv
{ peRange :: Range
, peSource :: ByteString
}
runParser :: Parser a -> FilePath -> IO (a, [Error])
runParser (Parser parser) fin = do
pforest <- toParseTree fin
text <- ByteString.readFile fin
let
res
= runIdentity
$ runExceptT
$ flip runStateT pforest
$ flip runReaderT (ParserEnv (pfRange pforest) text)
$ runWriterT
$ parser
either (error . show) (return . fst) res
token :: Text -> Parser Text
token node = do
tree@ParseTree {ptName, ptRange} <- takeNext node
if ptName == node
then do
2020-04-30 21:06:01 +04:00
cutOut ptRange
2020-04-30 14:39:51 +04:00
else do
2020-04-30 21:06:01 +04:00
die' node ptRange
2020-04-30 14:39:51 +04:00
anything :: Parser Text
anything = do
tree <- takeNext "anything"
2020-04-30 21:06:01 +04:00
cutOut $ ptRange tree
2020-04-30 14:39:51 +04:00
consume :: Text -> Parser ()
consume node = do
ParseTree {ptName, ptRange} <- takeNext node
when (ptName /= node) do
2020-04-30 21:06:01 +04:00
complain node ptRange
2020-04-30 14:39:51 +04:00
2020-04-30 17:46:39 +04:00
consumeOrDie :: Text -> Parser ()
consumeOrDie node = do
ParseTree {ptName, ptRange} <- takeNext node
when (ptName /= node) do
2020-04-30 21:06:01 +04:00
die' node ptRange
2020-04-30 17:46:39 +04:00
2020-04-30 21:06:01 +04:00
cutOut :: Range -> Parser Text
cutOut (Range (_, _, s) (_, _, f)) = do
bs <- asks peSource
return $ decodeUtf8 $ ByteString.take (f - s) (ByteString.drop s bs)
2020-04-30 14:39:51 +04:00
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
2020-04-30 17:46:39 +04:00
notFollowedBy :: Parser a -> Parser ()
notFollowedBy parser = do
good <- do
parser
return False
<|> do
return True
unless good do
die "notFollowedBy"
2020-04-30 14:39:51 +04:00
class Stubbed a where
stub :: Error -> a
instance Stubbed Text where
2020-04-30 17:46:39 +04:00
stub = pack . show