module Toml.Parser
( ParseException (..)
, parse
) where
import Control.Applicative (Alternative (..))
import Control.Applicative.Combinators (between, manyTill, sepBy)
import Control.Monad (void)
import Data.Semigroup ((<>))
import Data.Text (Text)
import Data.Void (Void)
import Text.Megaparsec (Parsec, parseErrorPretty', try)
import Text.Megaparsec.Char (alphaNumChar, anyChar, char, space, space1)
import Toml.PrefixTree (Key (..), Piece (..), fromList)
import Toml.Type (AnyValue, TOML (..), UValue (..), typeCheck)
import qualified Control.Applicative.Combinators.NonEmpty as NC
import qualified Data.HashMap.Lazy as HashMap
import qualified Data.Text as Text
import qualified Text.Megaparsec as Mega (parse)
import qualified Text.Megaparsec.Char.Lexer as L
type Parser = Parsec Void Text
sc :: Parser ()
sc = L.space space1 lineComment blockComment
where
lineComment = L.skipLineComment "#"
blockComment = empty
lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc
text :: Text -> Parser Text
text = L.symbol sc
text_ :: Text -> Parser ()
text_ = void . text
integer :: Parser Integer
integer = L.signed sc (lexeme L.decimal)
float :: Parser Double
float = L.signed sc $ lexeme L.float
bareKeyP :: Parser Text
bareKeyP = lexeme $ Text.pack <$> bareStrP
where
bareStrP :: Parser String
bareStrP = some $ alphaNumChar <|> char '_' <|> char '-'
literalStringP :: Parser Text
literalStringP = lexeme $ Text.pack <$> (char '\'' *> anyChar `manyTill` char '\'')
stringP :: Parser Text
stringP = lexeme $ Text.pack <$> (char '"' *> anyChar `manyTill` char '"')
quote :: Text -> Text
quote t = "\"" <> t <> "\""
keyComponentP :: Parser Piece
keyComponentP = Piece <$> (bareKeyP <|> (quote <$> stringP))
keyP :: Parser Key
keyP = Key <$> NC.sepBy1 keyComponentP (char '.')
tableNameP :: Parser Key
tableNameP = lexeme $ between (char '[') (char ']') keyP
boolP :: Parser Bool
boolP = False <$ text "false"
<|> True <$ text "true"
arrayP :: Parser [UValue]
arrayP = lexeme $ between (char '[' *> space) (char ']') (valueP `sepBy` spComma)
where
spComma :: Parser ()
spComma = char ',' *> space
valueP :: Parser UValue
valueP = UBool <$> boolP
<|> UFloat <$> try float
<|> UInt <$> integer
<|> UString <$> (literalStringP <|> stringP)
<|> UArray <$> arrayP
keyValP :: Parser (Key, AnyValue)
keyValP = do
k <- keyP
text_ "="
uval <- valueP
case typeCheck uval of
Left err -> fail $ show err
Right v -> pure (k, v)
tableHeaderP :: Parser (Key, TOML)
tableHeaderP = do
k <- tableNameP
toml <- makeToml <$> many keyValP
pure (k, toml)
where
makeToml :: [(Key, AnyValue)] -> TOML
makeToml kv = TOML (HashMap.fromList kv) mempty
tomlP :: Parser TOML
tomlP = do
sc
kvs <- many keyValP
tables <- many tableHeaderP
pure TOML { tomlPairs = HashMap.fromList kvs
, tomlTables = fromList tables
}
newtype ParseException = ParseException Text
deriving (Show, Eq)
parse :: Text -> Either ParseException TOML
parse t = case Mega.parse tomlP "" t of
Left err -> Left $ ParseException $ Text.pack $ parseErrorPretty' t err
Right toml -> Right toml