{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Language.JVM.Type
(
ClassName (..)
, strCls
, dotCls
, JType (..)
, JBaseType (..)
, jBaseTypeToChar
, JRefType (..)
, refTypeDepth
, MethodDescriptor (..)
, FieldDescriptor (..)
, NameAndType (..)
, (<:>)
, TypeParse (..)
, typeFromText
, typeToText
, parseOnly
, parseFlatJRefType
, jRefTypeToFlatText
) where
import Data.String
import Control.Applicative
import Data.Semigroup
import GHC.Generics (Generic)
import Prelude hiding (takeWhile)
import Control.DeepSeq (NFData)
import Data.Attoparsec.Text
import Control.Monad.Writer hiding ((<>))
import qualified Data.Text as Text
import qualified Data.Text.Lazy as Lazy
import qualified Data.Text.Lazy.Builder as Builder
newtype ClassName = ClassName
{ classNameAsText :: Text.Text
} deriving (Eq, Ord, Generic, NFData)
instance Show ClassName where
show = show . classNameAsText
strCls :: String -> ClassName
strCls = dotCls . Text.pack
dotCls :: Text.Text -> ClassName
dotCls = ClassName . Text.intercalate "/" . Text.splitOn "."
data JBaseType
= JTByte
| JTChar
| JTDouble
| JTFloat
| JTInt
| JTLong
| JTShort
| JTBoolean
deriving (Show, Eq, Ord, Generic, NFData)
data JRefType
= JTClass !ClassName
| JTArray !JType
deriving (Show, Eq, Ord, Generic, NFData)
refTypeDepth :: JRefType -> Int
refTypeDepth = \case
JTArray (JTRef a) -> 1 + (refTypeDepth a)
JTArray _ -> 1
JTClass _ -> 0
data JType
= JTBase JBaseType
| JTRef JRefType
deriving (Show, Eq, Ord, Generic, NFData)
jBaseTypeToChar :: JBaseType -> Char
jBaseTypeToChar = \case
JTByte -> 'B'
JTChar -> 'C'
JTDouble -> 'D'
JTFloat -> 'F'
JTInt -> 'I'
JTLong -> 'J'
JTShort -> 'S'
JTBoolean -> 'Z'
data MethodDescriptor = MethodDescriptor
{ methodDescriptorArguments :: [JType]
, methodDescriptorReturnType :: Maybe JType
} deriving (Show, Ord, Eq, Generic, NFData)
newtype FieldDescriptor = FieldDescriptor
{ fieldDescriptorType :: JType
} deriving (Show, Ord, Eq, Generic, NFData)
data NameAndType a = NameAndType
{ ntName :: Text.Text
, ntDescriptor :: a
} deriving (Show, Eq, Ord, Generic, NFData)
(<:>) :: Text.Text -> a -> NameAndType a
(<:>) = NameAndType
class TypeParse a where
parseType :: Parser a
typeToBuilder :: a -> Builder.Builder
typeFromText :: TypeParse a => Text.Text -> Either String a
typeFromText = parseOnly (parseType <* endOfInput)
typeToText :: TypeParse a => a -> Text.Text
typeToText = Lazy.toStrict . Builder.toLazyText . typeToBuilder
instance TypeParse ClassName where
parseType = ClassName <$> takeWhile1 (notInClass ".;[<>:") <?> "ClassName"
typeToBuilder = Builder.fromText . classNameAsText
instance TypeParse JBaseType where
parseType = try . (<?> "BaseType") $ do
anyChar >>= \case
'B' -> return $ JTByte
'C' -> return $ JTChar
'D' -> return $ JTDouble
'F' -> return $ JTFloat
'I' -> return $ JTInt
'J' -> return $ JTLong
'S' -> return $ JTShort
'Z' -> return $ JTBoolean
s -> fail $ "Unknown char " ++ show s
typeToBuilder = Builder.singleton . jBaseTypeToChar
instance TypeParse JRefType where
parseType = try . (<?> "RefType") $ do
anyChar >>= \case
'L' -> do
txt <- takeWhile (/= ';')
_ <- char ';'
return $ JTClass (ClassName txt)
'[' -> JTArray <$> parseType
s -> fail $ "Unknown char " ++ show s
typeToBuilder = \case
JTClass cn ->
Builder.singleton 'L' <> typeToBuilder cn <> Builder.singleton ';'
JTArray t -> do
Builder.singleton '[' <> typeToBuilder t
parseFlatJRefType :: Parser (JRefType)
parseFlatJRefType =
JTArray <$> (char '[' *> parseType)
<|> JTClass <$> parseType
jRefTypeToFlatText :: JRefType -> Text.Text
jRefTypeToFlatText = \case
JTClass t' -> classNameAsText t'
JTArray t' -> Lazy.toStrict . Builder.toLazyText $ Builder.singleton '[' <> typeToBuilder t'
instance TypeParse JType where
parseType =
(JTRef <$> parseType <|> JTBase <$> parseType)
<?> "JType"
typeToBuilder = \case
JTRef r -> typeToBuilder r
JTBase r -> typeToBuilder r
instance TypeParse MethodDescriptor where
typeToBuilder md =
execWriter $ do
tell $ Builder.singleton '('
mapM_ (tell . typeToBuilder) (methodDescriptorArguments md)
tell $ Builder.singleton ')'
tell . maybe (Builder.singleton 'V') typeToBuilder $ methodDescriptorReturnType md
parseType = do
_ <- char '('
args <- many' parseType <?> "method arguments"
_ <- char ')'
returnType <- choice
[ char 'V' >> return Nothing
, Just <$> parseType
] <?> "return type"
return $ MethodDescriptor args returnType
instance TypeParse FieldDescriptor where
parseType = FieldDescriptor <$> parseType
typeToBuilder (FieldDescriptor t) = typeToBuilder t
instance TypeParse t => TypeParse (NameAndType t) where
parseType = do
name <- many1 $ notChar ':'
_ <- char ':'
_type <- parseType
return $ NameAndType (Text.pack name) _type
typeToBuilder (NameAndType name _type) =
Builder.fromText name
<> Builder.singleton ':'
<> typeToBuilder _type
fromString' ::
TypeParse t
=> String
-> t
fromString' =
either (error . ("Failed " ++)) id . typeFromText . Text.pack
instance IsString ClassName where
fromString = strCls
instance IsString JType where
fromString = fromString'
instance IsString FieldDescriptor where
fromString = fromString'
instance IsString MethodDescriptor where
fromString = fromString'
instance TypeParse t => IsString (NameAndType t) where
fromString = fromString'