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

399 lines
9.6 KiB
Haskell
Raw Normal View History

2020-04-30 14:39:51 +04:00
module Parser (module Parser, gets, pfGrove) where
2020-04-30 14:39:51 +04:00
import Control.Monad.State
import Control.Monad.Writer
import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.Identity
import Data.Foldable
2020-04-30 14:39:51 +04:00
import Data.Text.Encoding
import Data.Text (Text, pack, unpack)
import qualified Data.Text as Text
2020-04-30 14:39:51 +04:00
import qualified Data.ByteString as ByteString
import Data.ByteString (ByteString)
import ParseTree
import Range
import Pretty
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
2020-05-08 00:52:33 +04:00
-- | Parse error.
2020-04-30 14:39:51 +04:00
data Error
2020-04-30 21:06:01 +04:00
= Expected
2020-05-08 00:52:33 +04:00
{ eMsg :: Text -- ^ Description of what was expected.
, eWhole :: Text -- ^ Offending text.
, eRange :: Range -- ^ Location of the error.
2020-04-30 21:06:01 +04:00
}
2020-05-08 00:52:33 +04:00
deriving (Show) via PP Error
2020-04-30 14:39:51 +04:00
instance Pretty Error where
pp (Expected msg found r) = "" <> pp msg <> pp r <> "" <> pp found <> ""
2020-05-08 00:52:33 +04:00
-- | Parser of tree-sitter-made tree.
2020-04-30 14:39:51 +04:00
newtype Parser a = Parser
{ unParser
2020-05-08 00:52:33 +04:00
:: WriterT [Error] -- Early I though to report errors that way. Obs.
( ReaderT ParserEnv -- Range/Something.
( StateT ParseForest -- Current forest to recognise.
( ExceptT Error -- Backtracking. Change `Error` to `()`?
( IO )))) -- I forgot why. `#include`? Debug via `print`?
2020-04-30 14:39:51 +04:00
a
}
deriving newtype
( Functor
, Applicative
, Monad
, MonadState ParseForest
, MonadWriter [Error]
, MonadReader ParserEnv
, MonadError Error
, MonadIO
2020-04-30 14:39:51 +04:00
)
2020-05-08 00:52:33 +04:00
-- | Generate error originating at current location.
2020-04-30 21:06:01 +04:00
makeError :: Text -> Parser Error
makeError msg = do
rng <- getRange
makeError' msg rng
2020-05-08 00:52:33 +04:00
-- | Generate error originating at given location.
2020-04-30 21:06:01 +04:00
makeError' :: Text -> Range -> Parser Error
makeError' msg rng = do
rng <- getRange
src <- cutOut rng
return Expected
{ eMsg = msg
, eWhole = src
, eRange = rng
}
2020-05-08 00:52:33 +04:00
-- | Pick next tree in a forest or die with msg.
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-05-08 00:52:33 +04:00
-- | Pick a tree with that /field name/ or die with name as msg.
--
-- Will erase all subtrees with different names on the path!
--
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-05-08 00:52:33 +04:00
-- | Variuos error reports.
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
2020-05-08 00:52:33 +04:00
-- | If a parser fails, return stub with error originating here.
2020-04-30 14:39:51 +04:00
stubbed :: Stubbed a => Text -> Parser a -> Parser a
stubbed msg parser = do
parser <|> fallback msg
2020-05-08 00:52:33 +04:00
-- | The forest must start with tree of that name. Its subtrees become new
-- forest. Otherwise, it dies with name as msg.
2020-04-30 14:39:51 +04:00
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
2020-05-08 00:52:33 +04:00
-- | Because `ExceptT` requires error to be `Monoid` for `Alternative`.
2020-04-30 14:39:51 +04:00
(<|>) :: 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
2020-05-08 00:52:33 +04:00
-- | Custom `Alternative.many`.
--
-- TODO: remove msg.
--
2020-04-30 14:39:51 +04:00
many :: Text -> Parser a -> Parser [a]
many msg p = many'
where
many' = some' <|> pure []
2020-04-30 17:46:39 +04:00
some' = do
hasPossibleInput
2020-04-30 17:46:39 +04:00
(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
2020-05-08 00:52:33 +04:00
-- | Custom `Alternative.some`.
--
-- TODO: remove msg.
--
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
hasPossibleInput
2020-04-30 17:46:39 +04:00
(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
2020-05-08 00:52:33 +04:00
-- | Get UID of current tree. Obsolete.
--
-- TODO: remove.
--
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-05-08 00:52:33 +04:00
-- | Assert the parser consumes input. Obsolete.
--
-- TODO: remove.
--
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
2020-05-08 00:52:33 +04:00
-- | The `not <$> eos`. Obsolete.
--
-- TODO: remove.
--
hasPossibleInput :: Parser ()
hasPossibleInput = do
yes <- gets (not . null . pfGrove)
unless yes do
die "something"
2020-05-08 00:52:33 +04:00
-- | The source of file being parsed. BS, because tree-sitter has offsets
-- in /bytes/.
2020-04-30 14:39:51 +04:00
data ParserEnv = ParserEnv
{ peSource :: ByteString
2020-04-30 14:39:51 +04:00
}
2020-05-08 00:52:33 +04:00
-- | Debug print via IO. Obsolete.
--
-- TODO: remove. Also, remove IO from Parser tf stack.
--
puts :: MonadIO m => Show a => a -> m ()
puts = liftIO . print
2020-05-08 00:52:33 +04:00
-- | Run parser on given file.
--
-- TODO: invent /proper/ 'ERROR'-node collector.
--
2020-04-30 14:39:51 +04:00
runParser :: Parser a -> FilePath -> IO (a, [Error])
runParser (Parser parser) fin = do
pforest <- toParseTree fin
text <- ByteString.readFile fin
res <-
runExceptT
2020-04-30 14:39:51 +04:00
$ flip runStateT pforest
$ flip runReaderT (ParserEnv text)
2020-04-30 14:39:51 +04:00
$ runWriterT
$ parser
either (error . show) (return . fst) res
2020-05-08 00:52:33 +04:00
-- | Run parser on given file and pretty-print stuff.
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
2020-05-08 00:52:33 +04:00
-- | Consume next tree if it has give name. Or die.
2020-04-30 14:39:51 +04:00
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
2020-05-08 00:52:33 +04:00
-- | Consume next tree, return its textual representation.
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
2020-05-08 00:52:33 +04:00
-- | TODO: remove, b/c obsolete.
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-05-08 00:52:33 +04:00
-- | TODO: remove, its literally is `void . token`.
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-05-08 00:52:33 +04:00
-- | Extract textual representation of given range.
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
2020-05-08 00:52:33 +04:00
-- | Get range of current tree or forest before the parser was run.
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)
2020-05-08 00:52:33 +04:00
-- | Get current range.
2020-04-30 14:39:51 +04:00
getRange :: Parser Range
getRange = snd <$> range (return ())
2020-05-08 00:52:33 +04:00
-- | Remove all keys until given key is found; remove the latter as well.
--
-- Notice: this works differently from `Prelude.remove`!
--
2020-04-30 14:39:51 +04:00
delete :: Eq k => k -> [(k, v)] -> [(k, v)]
delete _ [] = []
delete k ((k', v) : rest) =
if k == k'
then rest
else delete k rest
2020-04-30 14:39:51 +04:00
2020-05-08 00:52:33 +04:00
-- | Parser negation.
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-05-08 00:52:33 +04:00
-- | For types that have a default replacer with an `Error`.
2020-04-30 14:39:51 +04:00
class Stubbed a where
stub :: Error -> a
instance Stubbed Text where
stub = pack . show
2020-05-08 00:52:33 +04:00
-- | This is bad, but I had to.
--
-- TODO: find a way to remove this instance.
--
instance Stubbed [a] where
stub _ = []
2020-05-08 00:52:33 +04:00
-- | `Nothing` would be bad default replacer.
instance Stubbed a => Stubbed (Maybe a) where
stub = Just . stub
2020-05-08 00:52:33 +04:00
-- | Universal accessor.
--
-- Usage:
--
-- > inside "$field:$treename"
-- > inside "$field"
-- > inside ":$treename" -- don't, use "subtree"
--
inside :: Stubbed a => Text -> Parser a -> Parser a
inside sig parser = do
let (f, st') = Text.breakOn ":" sig
let st = Text.drop 1 st'
if Text.null f
then do
2020-05-08 00:52:33 +04:00
-- The order is important.
subtree st do
stubbed f do
parser
else do
field f do
stubbed f do
if Text.null st
then do
parser
else do
subtree st do
parser
2020-05-08 00:52:33 +04:00
-- Auto-accumulated information to be fed into AST being build.
data ASTInfo = ASTInfo
{ aiRange :: Range
, aiComments :: [Text]
}
2020-05-08 00:52:33 +04:00
-- | Equip given constructor with info.
ctor :: (ASTInfo -> a) -> Parser a
ctor = (<$> (ASTInfo <$> getRange <*> pure []))
2020-05-08 00:52:33 +04:00
-- | /Actual/ debug pring.
dump :: Parser ()
dump = gets pfGrove >>= traceShowM