{-|
Module      : Text.SExpression.Internal
Description : Internal parser functions
Copyright   : (C) Richard Cook, 2019
Licence     : MIT
Maintainer  : rcook@rcook.org
Stability   : stable
Portability : portable

This module provides internal parser functions.
-}

{-# OPTIONS_GHC -Wall -Werror #-}

{-# LANGUAGE CPP #-}

#undef MEGAPARSEC_7_OR_LATER
#ifdef MIN_VERSION_GLASGOW_HASKELL
-- GHC >= 7.10.1.0
#if MIN_VERSION_GLASGOW_HASKELL(8,0,0,0)
-- GHC >= 8.0.0.0
#if MIN_VERSION_megaparsec(7,0,0)
#define MEGAPARSEC_7_OR_LATER
#endif
#endif
#endif

module Text.SExpression.Internal
    ( -- * S-expression parser
      parseSExpr
    , -- * S-expression value parsers
      parseAtom
    , parseConsList
    , parseList
    , parseNumber
    , parseQuoted
    , parseString
    ) where

import Control.Applicative (empty)
import Control.Monad (void)
import Text.Megaparsec
    ( (<|>)
    , endBy
    , many
#ifdef MEGAPARSEC_7_OR_LATER
    , noneOf
    , oneOf
#endif
    , sepBy
    , some
    , try
    )
import Text.Megaparsec.Char
    ( char
    , digitChar
    , letterChar
#ifndef MEGAPARSEC_7_OR_LATER
    , noneOf
    , oneOf
#endif
    , space1
    )
import Text.Megaparsec.Char.Lexer
    ( space
    , skipLineComment
    )
import Text.SExpression.Types (Parser, SExpr(..))

sc :: Parser ()
sc = space space1 lineComment empty
    where
        lineComment = skipLineComment ";"

symbol :: Parser Char
symbol = oneOf "!$%&|*+-/:<=>?@^_~#"

-- | S-expression parser
parseSExpr ::
    Parser SExpr    -- ^ parser
parseSExpr =
    parseAtom
    <|> parseString
    <|> parseNumber
    <|> parseQuoted
    <|> do
            void $ char '('
            lst <- (try parseList) <|> parseConsList
            void $ char ')' >> sc
            pure lst

-- | Parse s-expression atom
parseAtom ::
    Parser SExpr    -- ^ parser
parseAtom = do
    h <- letterChar <|> symbol
    t <- many (letterChar <|> digitChar <|> symbol)
    let s = h : t
    pure $ case s of
                "#t" -> Bool True
                "#f" -> Bool False
                _ -> Atom s

-- | Parse s-expression list
parseList ::
    Parser SExpr    -- ^ parser
parseList = List <$> parseSExpr `sepBy` sc

-- | Parse s-expression cons list
parseConsList ::
    Parser SExpr    -- ^ parser
parseConsList = do
    h <- parseSExpr `endBy` sc
    t <- char '.' >> sc >> parseSExpr
    pure $ ConsList h t

-- | Parse s-expression number literal
parseNumber ::
    Parser SExpr    -- ^ parser
parseNumber = (Number . read) <$> some digitChar

-- | Parse s-expression string literal
parseString ::
    Parser SExpr    -- ^ parser
parseString = do
    void $ char '"'
    s <- many (noneOf "\"")
    void $ char '"'
    pure $ String s

-- | Parse s-expression quoted expression
parseQuoted ::
    Parser SExpr    -- ^ parser
parseQuoted = do
    void $ char '\''
    e <- parseSExpr
    pure $ List [Atom "quote", e]