miso-1.12.0.0: src/Miso/Util/Parser.hs
-----------------------------------------------------------------------------
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module : Miso.Util.Parser
-- Copyright : (C) 2016-2026 David M. Johnson
-- License : BSD3-style (see the file LICENSE)
-- Maintainer : David M. Johnson <code@dmj.io>
-- Stability : experimental
-- Portability : non-portable
--
-- = Overview
--
-- "Miso.Util.Parser" is an internal parser combinator library that
-- operates over a token stream produced by "Miso.Util.Lexer". It is used
-- by miso's JSON pipeline ("Miso.JSON.Parser") and the client-side router
-- ("Miso.Router"). It is __not__ designed for general application use,
-- but is exposed for downstream code that needs to build custom parsers.
--
-- = Core types
--
-- @
-- newtype 'ParserT' r token m a = Parser
-- { 'runParserT' :: r -> token -> m (a, token) }
--
-- type 'Parser' token a = 'ParserT' () [token] [] a
-- @
--
-- 'ParserT' is a monad transformer parameterised by:
--
-- * @r@ — a read-only environment (accessible via 'askParser')
-- * @token@ — the input stream type (typically @[t]@)
-- * @m@ — the result monad; using @[]@ gives non-deterministic\/backtracking parsing
-- * @a@ — the parsed result
--
-- The @'Parser' token a@ convenience alias fixes @r = ()@ and @m = []@,
-- which gives a standard backtracking parser over a @[token]@ stream.
--
-- = Primitive combinators
--
-- @
-- 'anyToken' :: 'ParserT' r [a] [] a -- consume any single token
-- 'satisfy' :: (a -> Bool) -> 'ParserT' r [a] [] a -- consume if predicate holds
-- 'token_' :: Eq t => t -> 'Parser' t t -- match a specific token
-- 'peek' :: 'Parser' a a -- look ahead without consuming
-- 'endOfInput' :: 'Parser' a () -- succeed only at end of stream
-- 'allTokens' :: 'ParserT' r a [] a -- return the entire remaining stream
-- 'modifyTokens' :: (t -> t) -> 'ParserT' r t [] () -- transform the token stream
-- 'askParser' :: 'ParserT' r token [] r -- read the environment
-- 'errorOut' :: e -> 'ParserT' r e [] () -- inject a custom error token
-- @
--
-- = Error type
--
-- @
-- data 'ParseError' a token
-- = UnexpectedParse [token] -- input remained after a successful parse
-- | LexicalError 'Miso.Util.Lexer.LexerError' -- upstream lex failure
-- | Ambiguous [(a, [token])] -- multiple distinct parses
-- | NoParses token -- no parse succeeded
-- | EmptyStream -- input was empty
-- @
--
-- = Running a parser
--
-- @
-- 'parse' :: 'Parser' token a -> [token] -> Either ('ParseError' a token) a
-- @
--
-- 'parse' returns 'Right' only when exactly one parse consumes all input.
-- Ambiguous or partial parses produce a 'Left' error.
--
-- = See also
--
-- * "Miso.Util.Lexer" — produces the token stream consumed here
-- * "Miso.JSON.Parser" — JSON parser built on this module
-- * "Miso.Router" — URI\/route parser built on this module
-- * "Miso.Util" — 'Miso.Util.sepBy', 'Miso.Util.oneOf' used alongside parsers
----------------------------------------------------------------------------
module Miso.Util.Parser
( -- ** Types
Parser
, ParserT (..)
, ParseError (..)
-- ** Combinators
, parse
, anyToken
, satisfy
, peek
, token_
, errorOut
, allTokens
, modifyTokens
, askParser
, endOfInput
) where
----------------------------------------------------------------------------
#if __GLASGOW_HASKELL__ <= 881
import Control.Monad.Fail (MonadFail (..))
#endif
import Control.Applicative
import Control.Monad
import Data.Maybe (isNothing)
----------------------------------------------------------------------------
import Miso.Util.Lexer (LexerError)
----------------------------------------------------------------------------
-- | A type for expressing failure during parsing.
data ParseError a token
= UnexpectedParse [token]
| LexicalError LexerError
| Ambiguous [(a, [token])]
| NoParses token
| EmptyStream
deriving (Show, Eq)
----------------------------------------------------------------------------
-- | Executes a parser against a series of tokens.
parse
:: Parser token a
-- ^ Parser to run
-> [token]
-- ^ Input token stream
-> Either (ParseError a token) a
parse _ [] = Left EmptyStream
parse parser tokens =
case runParserT parser () tokens of
[] -> Left (NoParses (last tokens))
[(x, [])] -> Right x
[(_, xs)] -> Left (UnexpectedParse xs)
xs -> Left (Ambiguous xs)
----------------------------------------------------------------------------
-- | Convenience synonym when defining parser combinators
type Parser token a = ParserT () [token] [] a
----------------------------------------------------------------------------
-- | Core type for parsing
newtype ParserT r token m a
= Parser
{ runParserT :: r -> token -> m (a, token)
-- ^ Run the parser given a read-only environment @r@ and input @token@;
-- returns zero or more @(result, remaining-input)@ pairs in @m@
}
----------------------------------------------------------------------------
instance Functor (ParserT r token []) where
fmap f (Parser run) = Parser $ \r input ->
case run r input of
tokens -> [ (f x, toks) | (x, toks) <- tokens ]
----------------------------------------------------------------------------
instance Applicative (ParserT r token []) where
pure x = Parser $ \_ s -> pure (x,s)
Parser f <*> Parser g = Parser $ \r input -> do
(k, s) <- f r input
(x, t) <- g r s
pure (k x, t)
----------------------------------------------------------------------------
instance Alternative (ParserT r token []) where
empty = Parser $ \_ _ -> []
Parser f <|> Parser g =
Parser $ \r tokens ->
case f r tokens of
[] -> g r tokens
x -> x
----------------------------------------------------------------------------
instance Monad (ParserT r token []) where
return = pure
Parser f >>= k = Parser $ \r tokens -> do
(x, tokens') <- f r tokens
runParserT (k x) r tokens'
----------------------------------------------------------------------------
instance MonadFail (ParserT r token []) where
fail _ = empty
----------------------------------------------------------------------------
instance MonadPlus (ParserT r token [])
----------------------------------------------------------------------------
-- | Match any token.
anyToken :: ParserT r [a] [] a
anyToken = Parser $ \_ input ->
case input of
t : ts -> [(t, ts)]
_ -> []
----------------------------------------------------------------------------
-- | Succeeds for any token for which the predicate @f@ returns 'True'.
-- Returns the parsed token.
satisfy
:: (a -> Bool)
-- ^ Predicate; the next token is consumed only if this returns 'True'
-> ParserT r [a] [] a
satisfy f = do
t <- anyToken
guard (f t)
pure t
----------------------------------------------------------------------------
-- | Succeeds if the next token in the stream matches the given one.
-- Returns the parsed token.
token_
:: Eq token
=> token
-- ^ Expected token value
-> Parser token token
token_ t = satisfy (==t)
----------------------------------------------------------------------------
-- | Returns all input from a parser
allTokens :: ParserT r a [] a
allTokens = Parser $ \_ input -> [(input, input)]
----------------------------------------------------------------------------
-- | Modifies tokens
modifyTokens
:: (t -> t)
-- ^ Transform to apply to the current token stream
-> ParserT r t [] ()
modifyTokens f = Parser $ \_ input -> [((), f input)]
----------------------------------------------------------------------------
-- | Retrieves read-only state from a Parser
askParser :: ParserT r token [] r
askParser = Parser $ \r input -> [(r, input)]
----------------------------------------------------------------------------
-- | Views the next token without consuming input
peek :: Parser a a
peek = Parser $ \_ tokens ->
case tokens of
[] -> []
(x:xs) -> [(x, x:xs)]
----------------------------------------------------------------------------
-- | Parser combinator that always fails
errorOut
:: errorToken
-- ^ Error token injected as the current stream (useful for error propagation)
-> ParserT r errorToken [] ()
errorOut x = Parser $ \_ _ -> [((),x)]
----------------------------------------------------------------------------
-- | Parser combinator that only succeeds if there are no more tokens.
endOfInput :: Parser a ()
endOfInput = guard . isNothing =<< optional anyToken
----------------------------------------------------------------------------