swarm-0.7.0.0: src/swarm-lang/Swarm/Language/Parser/Core.hs
{-# LANGUAGE TemplateHaskell #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Core data type definitions and utilities for the Swarm language
-- parser.
module Swarm.Language.Parser.Core (
-- * Parser configuration
Antiquoting (..),
LanguageVersion (..),
ParserConfig,
defaultParserConfig,
antiquoting,
languageVersion,
-- * Comment parsing state
WSState (..),
freshLine,
preWSLoc,
comments,
-- * Parser type
Parser,
ParserError,
-- ** Running
runParser,
runParser',
runParserTH,
) where
import Control.Lens (makeLenses, (^.))
import Control.Monad.Reader (ReaderT, runReaderT)
import Control.Monad.State (StateT, runStateT)
import Data.Bifunctor (second)
import Data.Sequence (Seq)
import Data.Sequence qualified as Seq
import Data.Text (Text)
import Data.Void (Void)
import Language.Haskell.TH qualified as TH
import Swarm.Language.Syntax (Comment)
import Text.Megaparsec hiding (runParser, runParser')
import Text.Megaparsec qualified as MP
import Text.Megaparsec.State (initialPosState, initialState)
import Witch (from)
------------------------------------------------------------
-- Custom parser state
-- | When parsing a term using a quasiquoter (i.e. something in the
-- Swarm source code that will be parsed at compile time), we want
-- to allow antiquoting, i.e. writing something like $x to refer to
-- an existing Haskell variable. But when parsing a term entered by
-- the user at the REPL, we do not want to allow this syntax.
data Antiquoting = AllowAntiquoting | DisallowAntiquoting
deriving (Eq, Ord, Show)
-- | Which version of the Swarm language are we parsing? As a general
-- rule, we want to support one older version in addition to the
-- current version, to allow for upgrading code via @swarm format@.
data LanguageVersion = SwarmLang0_6 | SwarmLangLatest
deriving (Eq, Ord, Show, Enum, Bounded)
-- | Read-only parser configuration.
data ParserConfig = ParserConfig
{ _antiquoting :: Antiquoting
, _languageVersion :: LanguageVersion
}
makeLenses ''ParserConfig
defaultParserConfig :: ParserConfig
defaultParserConfig =
ParserConfig
{ _antiquoting = DisallowAntiquoting
, _languageVersion = SwarmLangLatest
}
-- | Miscellaneous state relating to parsing whitespace + comments
data WSState = WS
{ _freshLine :: Bool
-- ^ Are we currently on a (so far) blank line, i.e. have there been
-- no nontrivial tokens since the most recent newline? This field
-- is updated every time we parse a lexeme or symbol (set to
-- false), or a newline (set to true).
, _preWSLoc :: Int
-- ^ The last source location before we started consuming
-- whitespace. We use this to assign more accurate source spans
-- to AST nodes, which do *not* include any trailing whitespace.
, _comments :: Seq Comment
-- ^ The actual sequence of comments, in the order they were encountered
}
makeLenses ''WSState
initWSState :: WSState
initWSState = WS {_freshLine = True, _preWSLoc = 0, _comments = Seq.empty}
------------------------------------------------------------
-- Parser types
type Parser = ReaderT ParserConfig (StateT WSState (Parsec Void Text))
type ParserError = ParseErrorBundle Text Void
------------------------------------------------------------
-- Running parsers
-- | Run a parser on some input text, returning either the result +
-- all collected comments, or a parse error message.
runParser :: Parser a -> Text -> Either ParserError (a, Seq Comment)
runParser = runParser' defaultParserConfig
-- | Like 'runParser', but allow configuring with an arbitrary
-- 'ParserConfig'.
runParser' :: ParserConfig -> Parser a -> Text -> Either ParserError (a, Seq Comment)
runParser' cfg p t =
(\pt -> parse pt "" t)
. fmap (second (^. comments))
. flip runStateT initWSState
. flip runReaderT cfg
$ p
-- | A utility for running a parser in an arbitrary 'MonadFail' (which
-- is going to be the TemplateHaskell 'Language.Haskell.TH.Q' monad --- see
-- "Swarm.Language.Parser.QQ"), with a specified source position.
runParserTH :: (Monad m, MonadFail m) => TH.Loc -> Parser a -> String -> m a
runParserTH loc p s =
either (fail . errorBundlePretty) (return . fst)
. snd
. flip MP.runParser' initState
. flip runStateT initWSState
. flip runReaderT defaultParserConfig {_antiquoting = AllowAntiquoting}
$ p
where
file = TH.loc_filename loc
(line, col) = TH.loc_start loc
initState :: State Text Void
initState =
(initialState file (from s))
{ statePosState =
(initialPosState file (from s))
{ pstateSourcePos = SourcePos file (mkPos line) (mkPos col)
}
}