toml-parser-1.1.0.0: src/Toml/Lexer/Utils.hs
{-|
Module : Toml.Lexer.Utils
Description : Wrapper and actions for generated lexer
Copyright : (c) Eric Mertens, 2023
License : ISC
Maintainer : emertens@gmail.com
This module provides a custom engine for the Alex generated
lexer. This lexer drive provides nested states, unicode support,
and file location tracking.
The various states of this module are needed to deal with the varying
lexing rules while lexing values, keys, and string-literals.
-}
module Toml.Lexer.Utils (
-- * Types
Action,
Context(..),
-- * Input processing
locatedUncons,
-- * Actions
value,
value_,
token,
token_,
squareO,
squareC,
curlyO,
curlyC,
equals,
timeValue,
eofToken,
-- * String literals
strFrag,
startMlStr,
startStr,
endStr,
unicodeEscape,
) where
import Control.Monad.Trans.State.Strict (State, state)
import Data.Char (ord, chr, isAscii)
import Data.Foldable (asum)
import Data.Time.Format (parseTimeM, defaultTimeLocale, ParseTime)
import Numeric (readHex)
import Toml.Located (Located(..))
import Toml.Position (move, Position)
import Toml.Lexer.Token (Token(..))
-- | Type of actions associated with lexer patterns
type Action = Located String -> State [Context] [Located Token]
-- | Representation of the current lexer state.
data Context
= ListContext Position -- ^ processing an inline list, lex values
| TableContext Position -- ^ processing an inline table, don't lex values
| ValueContext -- ^ processing after an equals, lex one value
| MlStrContext Position [String] -- ^ position of opening delimiter and list of fragments
| StrContext Position [String] -- ^ position of opening delimiter and list of fragments
deriving Show
-- | Add a literal fragment of a string to the current string state.
strFrag :: Action
strFrag s = state \case
StrContext p acc : st -> ([], StrContext p (locThing s : acc) : st)
MlStrContext p acc : st -> ([], MlStrContext p (locThing s : acc) : st)
_ -> error "strFrag: panic"
-- | End the current string state and emit the string literal token.
endStr :: Action
endStr x = state \case
StrContext p acc : st -> ([Located p (TokString (concat (reverse (locThing x : acc))))], st)
MlStrContext p acc : st -> ([Located p (TokMlString (concat (reverse (locThing x : acc))))], st)
_ -> error "endStr: panic"
-- | Start a basic string literal
startStr :: Action
startStr t = state \case
ValueContext : st -> ([], StrContext (locPosition t) [] : st)
st -> ([], StrContext (locPosition t) [] : st)
-- | Start a multi-line basic string literal
startMlStr :: Action
startMlStr t = state \case
ValueContext : st -> ([], MlStrContext (locPosition t) [] : st)
st -> ([], MlStrContext (locPosition t) [] : st)
-- | Resolve a unicode escape sequence and add it to the current string literal
unicodeEscape :: Action
unicodeEscape (Located p lexeme) =
case readHex (drop 2 lexeme) of
[(n,_)] | 0xd800 <= n, n < 0xe000 -> pure [Located p (TokError "non-scalar unicode escape")]
| n >= 0x110000 -> pure [Located p (TokError "unicode escape too large")]
| otherwise -> strFrag (Located p [chr n])
_ -> error "unicodeEscape: panic"
-- | Record an @=@ token and update the state
equals :: Action
equals t = state \case
st -> ([TokEquals <$ t], ValueContext : st)
-- | Record an opening square bracket and update the state
squareO :: Action
squareO t = state \case
ValueContext : st -> ([TokSquareO <$ t], ListContext (locPosition t) : st)
ListContext p : st -> ([TokSquareO <$ t], ListContext (locPosition t): ListContext p : st)
st -> ([TokSquareO <$ t], st)
-- | Record a closing square bracket and update the state
squareC :: Action
squareC t = state \case
ListContext _ : st -> ([TokSquareC <$ t], st)
st -> ([TokSquareC <$ t], st)
-- | Record an opening curly bracket and update the state
curlyO :: Action
curlyO t = state \case
ValueContext : st -> ([TokCurlyO <$ t], TableContext (locPosition t) : st)
ListContext p : st -> ([TokCurlyO <$ t], TableContext (locPosition t) : ListContext p : st)
st -> ([TokCurlyO <$ t], st)
-- | Record a closing curly bracket and update the state
curlyC :: Action
curlyC t = state \case
TableContext _ : st -> ([TokCurlyC <$ t], st)
st -> ([TokCurlyC <$ t], st)
-- | Emit a token ignoring the current lexeme
token_ :: Token -> Action
token_ t x = pure [t <$ x]
-- | Emit a token using the current lexeme
token :: (String -> Token) -> Action
token f x = pure [f <$> x]
-- | Emit a value token and update the current state
value_ :: Token -> Action
value_ t = value (const t)
-- | Emit a value token using the current lexeme and update the current state
value :: (String -> Token) -> Action
value f x = state \st ->
case st of
ValueContext : st' -> ([f <$> x], st')
_ -> ([f <$> x], st )
-- | Attempt to parse the current lexeme as a date-time token.
timeValue ::
ParseTime a =>
String {- ^ description for error messages -} ->
[String] {- ^ possible valid patterns -} ->
(a -> Token) {- ^ token constructor -} ->
Action
timeValue description patterns constructor = value \str ->
case asum [parseTimeM False defaultTimeLocale pat str | pat <- patterns] of
Nothing -> TokError ("malformed " ++ description)
Just t -> constructor t
-- | Pop the first character off a located string if it's not empty.
-- The resulting 'Int' will either be the ASCII value of the character
-- or @1@ for non-ASCII Unicode values. To avoid a clash, @\x1@ is
-- remapped to @0@.
locatedUncons :: Located String -> Maybe (Int, Located String)
locatedUncons Located { locPosition = p, locThing = str } =
case str of
"" -> Nothing
x:xs
| x == '\1' -> Just (0, rest)
| isAscii x -> Just (ord x, rest)
| otherwise -> Just (1, rest)
where
rest = Located { locPosition = move x p, locThing = xs }
-- | Generate the correct terminating token given the current lexer state.
eofToken :: [Context] -> Located String -> Located Token
eofToken (MlStrContext p _ : _) _ = Located p (TokError "unterminated multi-line string literal")
eofToken (StrContext p _ : _) _ = Located p (TokError "unterminated string literal")
eofToken (ListContext p : _) _ = Located p (TokError "unterminated '['")
eofToken (TableContext p : _) _ = Located p (TokError "unterminated '{'")
eofToken (ValueContext : s) t = eofToken s t
eofToken _ t = TokEOF <$ t