packages feed

ruin-0.1.0.0: Data/Ruin/QQ/Parser.hs

module Data.Ruin.QQ.Parser (
  QQ(..),
  pQQ,
  ) where

import           Data.Char (isSpace)
import           Data.Functor (void)
import           Data.Maybe (fromMaybe)

import           Text.Parsec
import           Text.Parsec.String (Parser)

data QQ = MkQQ
  (Maybe String)
  [(Bool,String,String)]
  deriving Show

pQQ :: Parser QQ
pQQ = do
  optional gap
  typename <- optionMaybe (pTypename <* gap)
  affixes <- optionMaybe (pAffixes <* gap)
  case affixes of
    Just (Nothing,Nothing) -> fail "Refusing a degenerate affix specification: remove (...)."
    _ -> return ()
  binders <- go
  eof
  pure (MkQQ typename (map (interpretAffixes affixes) binders))

  where

  go = ((:) <$> pBinder <*> go1) <|> pure []
  go1 = (gap *> go) <|> pure []

interpretAffixes ::
     Maybe (Maybe String,Maybe String)
  -> (b,Maybe String,String) -> (b,String,String)
interpretAffixes x (b,mvar,field) = (b,var,field)
  where
  var = case fromMaybe field mvar of
    "_" -> "_"   -- do not apply affixes to _
    o -> maybe id affix x o

  affix (pre,suf) = maybe id (++) pre . maybe id (flip (++)) suf

gap :: Parser ()
gap = void $ many1 $ satisfy isSpace

rest :: Parser Char
rest = char '_' <|> char '\'' <|> alphaNum

pTypename :: Parser String
pTypename = (:) <$> upper <*> many rest <?> "type name"

pAffixes :: Parser (Maybe String,Maybe String)
pAffixes =
    (<?> "affix spec")
  $ between (char '(') (char ')')
  $ (,) <$> optionMaybe pPrefix <* ellipsis <*> optionMaybe pSuffix

ellipsis :: Parser ()
ellipsis = () <$ string "..."

pPrefix :: Parser String
pPrefix = pVar

pSuffix :: Parser String
pSuffix = many1 $ char '_' <|> char '\'' <|> alphaNum

pBinder :: Parser (Bool,Maybe String,String)
pBinder = try (char '!' *> pAt True) <|> pAt False <?> "binder"

pAt :: Bool -> Parser (Bool,Maybe String,String)
pAt b = (,,) b <$> optionMaybe (try (pVar <* char '@')) <*> pField

pVar :: Parser String
pVar = (:) <$> (char '_' <|> lower) <*> many rest <?> "Haskell variable name"

pField :: Parser String
pField = (many1 $ satisfy $ not . isSpace) <?> "field name"