{-# LANGUAGE PatternSynonyms #-}
{-|
Module : Toml
Description : TOML parsing, printing, and codecs
Copyright : (c) Eric Mertens, 2023
License : ISC
Maintainer : emertens@gmail.com
This is the high-level interface to the toml-parser library.
It enables parsing, printing, and conversion into and out of
application-specific representations.
This parser implements TOML 1.1.0 <https://toml.io/en/v1.1.0>
as carefully as possible.
Use "Toml.Schema" to implement functions mapping between TOML
values and your application types.
Use "Toml.Syntax" and "Toml.Semantics" for low-level TOML syntax
processing and semantic validation. Most applications will not
need to use these modules directly unless the application is
about TOML itself.
The types and functions of this package are parameterized over
an annotation type in order to allow applications to provide
detailed feedback messages tracked back to specific source
locations in an original TOML file. While the default annotation
is a simple file position, some applications might upgrade this
annotation to track multiple file names or synthetically generated
sources. Other applications won't need source location and can
replace annotations with a simple unit type.
-}
module Toml (
-- * Types
Table,
Value,
-- * Located types
Located(..),
Position(..),
Table'(..),
Value'(..),
valueAnn,
valueType,
forgetTableAnns,
forgetValueAnns,
-- * Parsing
decode',
decode,
parse,
DecodeError,
Result(..),
-- * Printing
encode,
prettyToml,
DocClass(..),
-- * Error rendering
prettyDecodeError,
prettyLocated,
prettyMatchMessage,
prettySemanticError,
) where
import Data.Text (Text)
import Text.Printf (printf)
import Toml.Pretty
import Toml.Schema
import Toml.Semantics
import Toml.Syntax
-- | Parse a TOML formatted 'String' or report a structured error message.
parse' :: Text -> Either DecodeError (Table' Position)
parse' str =
case parseRawToml str of
Left e -> Left (ErrSyntax e)
Right exprs ->
case semantics exprs of
Left e -> Left (ErrSemantics e)
Right tab -> Right tab
-- | Parse a TOML formatted 'String' or report a human-readable error message.
parse :: Text -> Either String (Table' Position)
parse str =
case parse' str of
Left e -> Left (prettyDecodeError e)
Right x -> Right x
-- | Sum of errors that can occur during TOML decoding
data DecodeError
= ErrSyntax (Located String) -- ^ Error during the lexer/parser phase
| ErrSemantics (SemanticError Position) -- ^ Error during TOML validation
| ErrSchema (MatchMessage Position) -- ^ Error during schema matching
-- | Decode TOML syntax into an application value.
decode' :: FromValue a => Text -> Result DecodeError a
decode' str =
case parse' str of
Left e -> Failure [e]
Right tab ->
case runMatcher (fromValue (Table' startPos tab)) of
Failure es -> Failure (ErrSchema <$> es)
Success ws x -> Success (ErrSchema <$> ws) x
-- | Wrapper rending error and warning messages into human-readable strings.
decode :: FromValue a => Text -> Result String a
decode str =
case decode' str of
Failure e -> Failure (map prettyDecodeError e)
Success w x -> Success (map prettyDecodeError w) x
-- | Use the 'ToTable' instance to encode a value to a TOML string.
encode :: ToTable a => a -> TomlDoc
encode = prettyToml . toTable
-- | Human-readable representation of a 'DecodeError'
prettyDecodeError :: DecodeError -> String
prettyDecodeError = \case
ErrSyntax e -> prettyLocated e
ErrSemantics e -> prettySemanticError e
ErrSchema e -> prettyMatchMessage e
-- | Render a TOML decoding error as a human-readable string.
prettyMatchMessage :: MatchMessage Position -> String
prettyMatchMessage (MatchMessage loc scope msg) = prefix ++ msg ++ " in " ++ path
where
prefix =
case loc of
Nothing -> ""
Just l -> prettyPosition l ++ ": "
path =
case scope of
[] -> "<top-level>"
ScopeKey key : scope' -> shows (prettySimpleKey key) (foldr f "" scope')
ScopeIndex i : scope' -> foldr f "" (ScopeIndex i : scope') -- should be impossible
f (ScopeIndex i) = showChar '[' . shows i . showChar ']'
f (ScopeKey key) = showChar '.' . shows (prettySimpleKey key)
-- | Render a semantic TOML error in a human-readable string.
prettySemanticError :: SemanticError Position -> String
prettySemanticError (SemanticError a key kind) =
printf "%s: key error: %s %s" (prettyPosition a) (show (prettySimpleKey key))
case kind of
AlreadyAssigned -> "is already assigned" :: String
ClosedTable -> "is a closed table"
ImplicitlyTable -> "is already implicitly defined to be a table"