-- Copyright (c) Microsoft. All rights reserved.

-- Licensed under the MIT license. See LICENSE file in the project root for full license information.



{-# LANGUAGE OverloadedStrings #-}



module Language.Bond.Codegen.CustomMapping

    ( AliasMapping(..)

    , Fragment(..)

    , NamespaceMapping(..)

    , parseAliasMapping

    , parseNamespaceMapping

    ) where



import Control.Applicative hiding (some)

import Data.Void (Void)

import Language.Bond.Syntax.Types

import Prelude

import Text.Megaparsec hiding (many, optional, (<|>))

import Text.Megaparsec.Char

import qualified Text.Megaparsec.Char.Lexer as L



-- | Specification of a fragment of type alias mappings.

data Fragment =

    Fragment String |                   -- ^ hardcoded string fragment

    Placeholder Int                     -- ^ placeholder for the n-th type argument of the type alias, applicable only to generic aliases



-- | Specification of a type alias mapping.

data AliasMapping = AliasMapping

    { aliasName :: QualifiedName        -- ^ qualified name of a type alias

    , aliasTemplate :: [Fragment]       -- ^ list of fragments comprising the custom mapping for the alias

    }



-- | Specification of namespace mapping.

data NamespaceMapping = NamespaceMapping

    { fromNamespace :: QualifiedName    -- ^ schema namespace

    , toNamespace :: QualifiedName      -- ^ namespace in the generated code

    }



type Parser = Parsec Void String



identifier :: Parser String

identifier = some (alphaNumChar <|> char '_') <?> "identifier"



qualifiedName :: Parser [String]

qualifiedName = sepBy1 identifier (char '.') <?> "qualified name"



sc :: Parser ()

sc = L.space space1 empty empty



symbol :: String -> Parser String

symbol = L.symbol sc



equal :: Parser String

equal = symbol "="



-- consume whitespace after every lexeme

lexeme :: Parser a -> Parser a

lexeme = L.lexeme sc



decimal :: Parser Integer

decimal = lexeme L.decimal



hexadecimal :: Parser Integer

hexadecimal = lexeme . try $ char '0' >> char' 'x' >> L.hexadecimal



octal :: Parser Integer

octal = lexeme . try $ char '0' >> char' 'o' >> L.octal



natural :: Parser Integer

natural = hexadecimal <|> octal <|> decimal



integer :: Parser Integer

integer = L.signed sc natural



-- | Parse a type alias mapping specification used in command-line arguments of

-- <https://microsoft.github.io/bond/manual/compiler.html#command-line-options gbc>.

--

-- ==== __Examples__

--

-- > > parseAliasMapping "Example.OrderedSet=SortedSet<{0}>"

-- > Right (AliasMapping {aliasName = ["Example","OrderedSet"], aliasTemplate = [Fragment "SortedSet<",Placeholder 0,Fragment ">"]})

parseAliasMapping :: String -> Either (ParseError Char Void) AliasMapping

parseAliasMapping s = parse aliasMapping "" s

  where

    aliasMapping = AliasMapping <$> qualifiedName <* equal <*> some (placeholder <|> fragment) <* eof

    placeholder = Placeholder <$> fromIntegral <$> between (char '{') (char '}') integer

    fragment = Fragment <$> some (notChar '{')



-- | Parse a namespace mapping specification used in command-line arguments of

-- <https://microsoft.github.io/bond/manual/compiler.html#command-line-options gbc>.

--

-- ==== __Examples__

--

-- > > parseNamespaceMapping "bond=Microsoft.Bond"

-- > Right (NamespaceMapping {fromNamespace = ["bond"], toNamespace = ["Microsoft","Bond"]})

parseNamespaceMapping :: String -> Either (ParseError Char Void) NamespaceMapping

parseNamespaceMapping s = parse namespaceMapping "" s

  where

    namespaceMapping = NamespaceMapping <$> qualifiedName <* equal <*> qualifiedName