{- | Parser for 'TOML' data type: keys, tables, array of tables. Uses value parsers from "Toml.Parser.Value". -} module Toml.Parser.TOML ( keyP , tomlP ) where import Control.Applicative (Alternative (..)) import Control.Monad.Combinators (between, sepEndBy) import Data.List.NonEmpty (NonEmpty (..)) import Data.Semigroup ((<>)) import Data.Text (Text) import Toml.Parser.Core (Parser, alphaNumChar, char, eof, lexeme, sc, text, try) import Toml.Parser.String (basicStringP, literalStringP) import Toml.Parser.Value (anyValueP) import Toml.PrefixTree (Key (..), KeysDiff (..), Piece (..), keysDiff, single) 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 <$> keyComponentP `NC.sepBy1` 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 -- Helper functions for building TOML tomlKV :: Key -> AnyValue -> TOML tomlKV k v = mempty { tomlPairs = HashMap.singleton k v } tomlT :: Key -> TOML -> TOML tomlT k t = mempty { tomlTables = single k t } tomlA :: Key -> NonEmpty TOML -> TOML tomlA k a = mempty { tomlTableArrays = HashMap.singleton k a } -- | Parser for lines starting with 'key =', either values, inline tables or -- inline arrays of tables. hasKeyP :: Maybe Key -> Parser TOML hasKeyP key = do k <- keyP <* text "=" table k <|> try (tableArray k) <|> (tomlKV k <$> anyValueP) where table :: Key -> Parser TOML table k = do (kDiff, _) <- childKeyP key (pure k) tomlT kDiff <$> inlineTableP tableArray :: Key -> Parser TOML tableArray k = do (kDiff, _) <- childKeyP key (pure k) tomlA kDiff <$> inlineTableArrayP -- | Parser for inline tables. inlineTableP :: Parser TOML inlineTableP = between (text "{") (text "}") $ mconcat <$> (tomlKV <$> (keyP <* text "=") <*> anyValueP ) `sepEndBy` text "," -- | Parser for inline arrays of tables. inlineTableArrayP :: Parser (NonEmpty TOML) inlineTableArrayP = between (text "[") (text "]") $ inlineTableP `NC.sepEndBy1` text "," -- | Parser for an array of tables under a certain key. tableArrayP :: Key -> Parser (NonEmpty TOML) tableArrayP key = localTomlP (Just key) `NC.sepBy1` sameKeyP key tableArrayNameP -- | Parser for a '.toml' file tomlP :: Parser TOML tomlP = sc *> localTomlP Nothing <* eof -- | Parser for a toml under a certain key localTomlP :: Maybe Key -> Parser TOML localTomlP key = mconcat <$> many (subArray <|> subTable <|> hasKeyP key) where subTable :: Parser TOML subTable = do (kDiff, k) <- try $ childKeyP key tableNameP tomlT kDiff <$> localTomlP (Just k) subArray :: Parser TOML subArray = do (kDiff, k) <- try $ childKeyP key tableArrayNameP tomlA kDiff <$> tableArrayP k -- | @childKeyP (Just key) p@ checks if the result of @p@ if a child key of -- @key@ and returns the difference of the keys and the child key. -- @childKeyP Nothing p@ is only called from @tomlP@ (no parent key). childKeyP :: Maybe Key -> Parser Key -> Parser (Key, Key) childKeyP Nothing parser = (\k -> (k, k)) <$> parser childKeyP (Just key) parser = do k <- parser case keysDiff key k of FstIsPref d -> pure (d, k) _ -> 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 -> Parser Key sameKeyP key parser = try $ do k <- parser case keysDiff key k of Equal -> pure k _ -> fail $ show k ++ " is not the same as " ++ show key