module Shower.Parser (pShower) where

import Data.Void
import Data.Char
import Text.Megaparsec
import Text.Megaparsec.Char

import Shower.Class

type Parser = Parsec Void String

pLexeme :: Parser a -> Parser a
pLexeme p = p <* space

pShower :: Shower a => Parser a
pShower = space *> pExpr

pExpr :: Shower a => Parser a
pExpr = showerSpace <$> some pPart

pPart :: Shower a => Parser a
pPart =
  pRecord <|>
  pList <|>
  pTuple <|>
  pStringLit <|>
  pCharLit <|>
  pAtom

pRecord :: Shower a => Parser a
pRecord = do
  _ <- pLexeme (char '{')
  fields <- pField `sepBy` pLexeme (char ',')
  _ <- pLexeme (char '}')
  return (showerRecord fields)

pField :: Shower a => Parser (a, a)
pField = do
  name <- pExpr
  _ <- pLexeme (char '=')
  value <- pExpr
  return (name, value)

pList :: Shower a => Parser a
pList = do
  _ <- pLexeme (char '[')
  elements <- pExpr `sepBy` pLexeme (char ',')
  _ <- pLexeme (char ']')
  return (showerList elements)

pTuple :: Shower a => Parser a
pTuple = do
  _ <- pLexeme (char '(')
  elements <- pExpr `sepBy` pLexeme (char ',')
  _ <- pLexeme (char ')')
  return (showerTuple elements)

pStringLit :: Shower a => Parser a
pStringLit =
  pLexeme $ do
    _ <- char '"'
    s <- manyTill pStringPart (char '"')
    return (showerStringLit (concat s))
  where
    pStringPart = string "\\\"" <|> ((:[]) <$> anySingle)

pCharLit :: Shower a => Parser a
pCharLit =
  pLexeme $ try $ do
    _ <- char '\''
    c <- string "\\'" <|> ((:[]) <$> anySingle)
    _ <- char '\''
    return (showerCharLit c)

pAtom :: Shower a => Parser a
pAtom =
  pLexeme $ do
    s <- some (satisfy atomChar)
    return (showerAtom s)
  where
    atomChar c =
      not (c `elem` "()[]{},=") &&
      not (isSpace c)