packages feed

scfg-1.0.0: src/Data/Scfg/Parser.hs

{-# LANGUAGE OverloadedStrings #-}

module Data.Scfg.Parser (parseConfig) where

import Control.Monad (void)
import Data.Char (isControl)
import Data.Functor (($>))
import Data.List.NonEmpty (head)
import Data.Maybe (catMaybes, fromMaybe)
import Data.Scfg.Types
import Data.Text (Text)
import qualified Data.Text as T
import Data.Void (Void)
import Text.Megaparsec hiding (ParseError)
import Text.Megaparsec.Char
import Prelude hiding (head)

type Parser = Parsec Void Text

parseConfig :: Text -> Either ParseError Config
parseConfig input = case parse config "" input of
  Left bundle ->
    let firstError = head (bundleErrors bundle)
        offset = errorOffset firstError
        (_, state) = reachOffset offset (bundlePosState bundle)
        SourcePos _ l c = pstateSourcePos state
     in Left
          ParseError
            { errorLine = unPos l
            , errorColumn = unPos c
            , errorMessage = T.pack (errorBundlePretty bundle)
            }
  Right c -> Right c

config :: Parser Config
config = catMaybes <$> many line <* eof

line :: Parser (Maybe Directive)
line = hspace *> ((newline $> Nothing) <|> (comment $> Nothing) <|> (Just <$> directive))

comment :: Parser ()
comment = (char '#' *> takeWhileP Nothing isVChar *> newline) $> ()

directive :: Parser Directive
directive = do
  n <- word
  params <- many (try (hspace1 *> word))
  children <- optional (try (hspace1 *> block))
  _ <- hspace *> (void newline <|> eof)
  pure (Directive n params (fromMaybe [] children))

block :: Parser [Directive]
block = do
  _ <- char '{'
  _ <- hspace *> newline
  ds <- catMaybes <$> many (try line)
  _ <- hspace *> char '}'
  pure ds

word :: Parser Text
word = dquoteWord <|> squoteWord <|> atom

atom :: Parser Text
atom = T.concat <$> some (escPair <|> takeWhile1P (Just "character") isAtomChar)

dquoteWord :: Parser Text
dquoteWord = char '"' *> (T.concat <$> many (escPair <|> takeWhile1P (Just "character") isDqChar)) <* char '"'

squoteWord :: Parser Text
squoteWord = char '\'' *> (takeWhile1P (Just "character") isSqChar <|> pure "") <* char '\''

escPair :: Parser Text
escPair = T.singleton <$> (char '\\' *> satisfy isVChar)

isVChar :: Char -> Bool
isVChar c = c == '\t' || (not (isControl c) && c /= '\n')

isAtomChar :: Char -> Bool
isAtomChar c = not (isControl c) && c /= ' ' && c /= '\n' && c /= '{' && c /= '}' && c /= '"' && c /= '\\' && c /= '\''

isDqChar :: Char -> Bool
isDqChar c = (c == '\t' || not (isControl c)) && c /= '"' && c /= '\\' && c /= '\n'

isSqChar :: Char -> Bool
isSqChar c = (c == '\t' || not (isControl c)) && c /= '\'' && c /= '\n'