{- | Parser for 'TOML' data type: keys, tables, array of tables. Uses value parsers from "Toml.Parser.Value". -} module Toml.Parser.TOML ( keyP , hasKeyP , tableP , tableArrayP , inlineTableP , tomlP ) where import Control.Applicative (Alternative (..)) import Control.Monad.Combinators (between, eitherP, optional, sepEndBy) import Data.List.NonEmpty (NonEmpty (..), (<|)) import Data.Semigroup ((<>)) import Data.Text (Text) import Toml.Parser.Core (Parser, alphaNumChar, char, lexeme, sc, text, try) import Toml.Parser.String (basicStringP, literalStringP) import Toml.Parser.Value (anyValueP) import Toml.PrefixTree (Key (..), KeysDiff (..), Piece (..), fromList, keysDiff) import Toml.Type (AnyValue, TOML (..)) import qualified Control.Applicative.Combinators.NonEmpty as NC import qualified Data.HashMap.Lazy as HashMap import qualified Data.Text as Text -- | Parser for bare key piece, like @foo@. bareKeyPieceP :: Parser Text bareKeyPieceP = lexeme $ Text.pack <$> bareStrP where bareStrP :: Parser String bareStrP = some $ alphaNumChar <|> char '_' <|> char '-' -- | Parser for 'Piece'. keyComponentP :: Parser Piece keyComponentP = Piece <$> (bareKeyPieceP <|> (quote "\"" <$> basicStringP) <|> (quote "'" <$> literalStringP)) where -- adds " or ' to both sides quote :: Text -> Text -> Text quote q t = q <> t <> q -- | Parser for 'Key': dot-separated list of 'Piece'. keyP :: Parser Key keyP = Key <$> NC.sepBy1 keyComponentP (char '.') -- | Parser for table name: 'Key' inside @[]@. tableNameP :: Parser Key tableNameP = between (text "[") (text "]") keyP -- | Parser for array of tables name: 'Key' inside @[[]]@. tableArrayNameP :: Parser Key tableArrayNameP = between (text "[[") (text "]]") keyP -- Tables -- | Parser for lines starting with 'key =', either values or inline tables. hasKeyP :: Parser (Key, Either AnyValue TOML) hasKeyP = (,) <$> keyP <* text "=" <*> eitherP anyValueP inlineTableP -- | Parser for inline tables. inlineTableP :: Parser TOML inlineTableP = between (text "{") (text "}") (tomlFromInline <$> hasKeyP `sepEndBy` text ",") -- | Parser for a table. tableP :: Parser (Key, TOML) tableP = do key <- tableNameP toml <- subTableContent key pure (key, toml) -- | Parser for an array of tables. tableArrayP :: Parser (Key, NonEmpty TOML) tableArrayP = do key <- tableArrayNameP localToml <- subTableContent key more <- optional $ sameKeyP key tableArrayP case more of Nothing -> pure (key, localToml :| []) Just (_, tomls) -> pure (key, localToml <| tomls) -- | Parser for a '.toml' file tomlP :: Parser TOML tomlP = do sc (val, inline) <- distributeEithers <$> many hasKeyP (table, array) <- fmap distributeEithers $ many $ eitherPairP (try tableP) tableArrayP pure TOML { tomlPairs = HashMap.fromList val , tomlTables = fromList $ inline ++ table , tomlTableArrays = HashMap.fromList array } -- | Parser for full 'TOML' under a certain key subTableContent :: Key -> Parser TOML subTableContent key = do (val, inline) <- distributeEithers <$> many hasKeyP (table, array) <- fmap distributeEithers $ many $ childKeyP key $ eitherPairP (try tableP) tableArrayP pure TOML { tomlPairs = HashMap.fromList val , tomlTables = fromList $ inline ++ table , tomlTableArrays = HashMap.fromList array } -- | @childKeyP key p@ returns the result of @p@ if the key returned by @p@ is -- a child key of the @key@, and fails otherwise. childKeyP :: Key -> Parser (Key, a) -> Parser (Key, a) childKeyP key p = try $ do (k, x) <- p case keysDiff key k of FstIsPref k' -> pure (k', x) _ -> fail $ show k ++ " is not a child key of " ++ show key -- | @sameKeyP key p@ returns the result of @p@ if the key returned by @p@ is -- the same as @key@, and fails otherwise. sameKeyP :: Key -> Parser (Key, a) -> Parser (Key, a) sameKeyP key parser = try $ do (k, x) <- parser case keysDiff key k of Equal -> pure (k, x) _ -> fail $ show k ++ " is not the same as " ++ show key -- Helper functions -- | Helper function to create a 'TOML' from a list of key/values or inline tables. tomlFromInline :: [(Key, Either AnyValue TOML)] -> TOML tomlFromInline kvs = let (lefts, rights) = distributeEithers kvs in TOML (HashMap.fromList lefts) (fromList rights) mempty -- | Helper function to seperate the 'Either'. distributeEithers :: [(c, Either a b)] -> ([(c, a)], [(c, b)]) distributeEithers = foldr distribute ([], []) where distribute :: (c, Either a b) -> ([(c, a)], [(c, b)]) -> ([(c, a)], [(c, b)]) distribute (k, Left a) (ls, rs) = ((k, a) : ls, rs) distribute (k, Right b) (ls, rs) = (ls, (k, b) : rs) -- | Helper function to make an 'Either' parser. eitherPairP :: Alternative m => m (c, a) -> m (c, b) -> m (c, Either a b) eitherPairP a b = (fmap Left <$> a) <|> (fmap Right <$> b)