{-# LANGUAGE Safe #-}
-- | This module parses files using the syntax demonstrated below.
-- The full grammar is available in the Happy source file.
--
-- @
-- -- Line comments until newline
-- layout:
--   based:
--     configuration:
--       {} -- empty section
--
--     sections:
--      "glguy"
--
--     {- Block comments
--        {- nested comments -}
--        "O'caml style {- strings in comments"
--        so you can comment out otherwise valid
--        portions of your config
--     -}
--     atoms      : yes
--
--     decimal    : -1234
--     hexadecimal: 0x1234
--     octal      : 0o1234
--     binary     : 0b1010
--
-- lists:
--    * sections: in-lists
--      next-section: still-in-list
--    * [ "inline", "lists" ]
--    * * "nestable"
--      * "layout"
--      * "lists"
--    * 3
--
-- unicode : "standard Haskell format strings (1 ≤ 2)\\x2228(2 ≤ 3)"
-- @
module Config
  ( Section(..)
  , Value(..)
  , Atom(..)
  , parse
  , pretty
  ) where

import Config.Value  (Atom(..), Value(..), Section(..))
import Config.Parser (parseValue)
import Config.Pretty (pretty)
import Config.Lexer  (scanTokens)
import Config.Tokens (Error(..), Position(..), Located(..), layoutPass, Token)
import qualified Config.Tokens as T

import Numeric (showIntAtBase)
import Data.Char (intToDigit)
import Data.Text (Text)
import qualified Data.Text as Text

-- | Parse a configuration file and return the result on the
-- right, or the position of an error on the left.
-- Note: Text file lines are terminated by new-lines.
parse ::
  Text                {- ^ Source                      -} ->
  Either String Value {- ^ Either ErrorMessage Result -}
parse txt =
  case parseValue (layoutPass (scanTokens txt)) of
    Right x -> Right x
    Left (Located posn token) -> Left (explain posn token)

explain :: Position -> Token -> String
explain posn token
   = show (posLine   posn) ++ ":"
  ++ show (posColumn posn) ++ ": "
  ++ case token of
       T.Error e     -> explainError e
       T.Atom atom   -> "parse error: unexpected atom: " ++ Text.unpack atom
       T.String str  -> "parse error: unexpected string: " ++ show (Text.unpack str)
       T.Bullet      -> "parse error: unexpected bullet '*'"
       T.Comma       -> "parse error: unexpected comma ','"
       T.Section s   -> "parse error: unexpected section: " ++ Text.unpack s
       T.Number 2  n -> "parse error: unexpected number: 0b" ++ showIntAtBase 2  intToDigit n ""
       T.Number 8  n -> "parse error: unexpected number: 0o" ++ showIntAtBase 8  intToDigit n ""
       T.Number 16 n -> "parse error: unexpected number: 0x" ++ showIntAtBase 16 intToDigit n ""
       T.Number _  n -> "parse error: unexpected number: "   ++ showIntAtBase 10 intToDigit n ""
       T.OpenList    -> "parse error: unexpected start of list '['"
       T.CloseList   -> "parse error: unexpected end of list ']'"
       T.OpenMap     -> "parse error: unexpected start of section '{'"
       T.CloseMap    -> "parse error: unexpected end of section '}'"
       T.LayoutSep   -> "parse error: unexpected end of block"
       T.LayoutEnd   -> "parse error: unexpected end of block"
       T.EOF         -> "parse error: unexpected end of file"

explainError :: Error -> String
explainError e =
  case e of
    T.UntermComment       -> "lexical error: unterminated comment"
    T.UntermCommentString -> "lexical error: unterminated string literal in comment"
    T.UntermString        -> "lexical error: unterminated string literal"
    T.UntermFile          -> "lexical error: unterminated line"
    T.BadEscape c         -> "lexical error: bad escape sequence: " ++ Text.unpack c
    T.NoMatch c           -> "lexical error at character " ++ show c