{-# LANGUAGE CPP #-}
module Distribution.SPDX.Parser (parseExpression, unsafeParseExpr) where

#if !MIN_VERSION_base(4, 8, 0)
import Control.Applicative
#endif

import Data.Char
import Text.ParserCombinators.ReadP

import Distribution.SPDX.Types
import Distribution.SPDX.Licenses (licenseIdentifiers, licenseExceptions)

license :: ReadP LicenseId
license = choice (map string licenseIdentifiers)

licenseException :: ReadP LicenseExceptionId
licenseException = choice (map string licenseExceptions)

elicense :: ReadP LicenseExpression
elicense = (\l -> ELicense False (Right l) Nothing) <$> license

elicenseWith :: ReadP LicenseExpression
elicenseWith = (\l e -> ELicense False (Right l) (Just e)) <$> license <* skipSpaces1 <* string "WITH" <* skipSpaces1 <*> licenseException

elicenseAndNewer :: ReadP LicenseExpression
elicenseAndNewer = (\l -> ELicense True (Right l) Nothing) <$> license <* char '+'

elicenseWithAndNewer :: ReadP LicenseExpression
elicenseWithAndNewer = (\l e -> ELicense True (Right l) (Just e)) <$> license <* char '+' <* string " WITH " <*> licenseException

elicenseRef :: ReadP LicenseExpression
elicenseRef = (\licId -> ELicense False (Left $ LicenseRef Nothing licId) Nothing) <$ string "LicenseRef-" <*> idString

elicenseDocRef :: ReadP LicenseExpression
elicenseDocRef = f <$ string "DocumentRef-" <*> idString <* char ':' <* string "LicenseRef-" <*> idString
  where f docId licId = ELicense False (Left $ LicenseRef (Just docId) licId) Nothing

idString :: ReadP String
idString = munch1 p
  where p '.' = True
        p '-' = True
        p c   = isAlphaNum c

skipSpaces1 :: ReadP ()
skipSpaces1 = () <$ char ' ' <* skipSpaces

parens :: ReadP a -> ReadP a
parens = between (char '(') (skipSpaces <* char ')')

terminal :: ReadP LicenseExpression
terminal = choice [ elicense
                  , elicenseWith
                  , elicenseAndNewer
                  , elicenseWithAndNewer
                  , elicenseRef
                  , elicenseDocRef
                  , parens expression
                  ]

conjunction :: ReadP LicenseExpression
conjunction = chainr1 terminal (EConjunction <$ skipSpaces1 <* string "AND" <* skipSpaces1)

disjunction :: ReadP LicenseExpression
disjunction = chainr1 conjunction (EDisjunction <$ skipSpaces1 <* string "OR" <* skipSpaces1)

expression :: ReadP LicenseExpression
expression = skipSpaces *> disjunction

-- | Parse SPDX License Expression
--
-- >>> parseExpression "LGPL-2.1 OR MIT"
-- [EDisjunction (ELicense False "LGPL-2.1" Nothing) (ELicense False "MIT" Nothing)]
parseExpression :: String -> [LicenseExpression]
parseExpression = map fst . readP_to_S (expression <* skipSpaces <* eof)

unsafeParseExpr :: String -> LicenseExpression
unsafeParseExpr s = f . parseExpression $ s
  where f []      = error $ "Failed parse of license expression: " ++ s
        f [l]     = l
        f (_:_:_) = error $ "Ambigious parse of license expression: " ++ s