{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Language.JVM.Common
Copyright   : Galois, Inc. 2012-2014
License     : BSD3
Maintainer  : atomb@galois.com
Stability   : stable
Portability : non-portable

Basic datatypes and utilities for the JVM parser.
-}

module Language.JVM.Common where

import Data.Array
import Data.Int
import Data.Word
import Text.PrettyPrint

-- | Replace '/' characters with '.' characters
slashesToDots :: String -> String
slashesToDots = map (\c -> if c == '/' then '.' else c)

-- | Replace '.' characters with '/' characters
dotsToSlashes :: String -> String
dotsToSlashes = map (\c -> if c == '.' then '/' else c)

-- | JVM Type
data Type
  = ArrayType Type
  | BooleanType
  | ByteType
  | CharType
  | ClassType String -- ^ ClassType with name of packages separated by slash '/'
  | DoubleType
  | FloatType
  | IntType
  | LongType
  | ShortType
  deriving (Eq, Ord)

stringTy :: Type
stringTy = ClassType "java/lang/String"

intArrayTy :: Type
intArrayTy = ArrayType IntType

byteArrayTy :: Type
byteArrayTy = ArrayType ByteType

charArrayTy :: Type
charArrayTy = ArrayType CharType

-- | Returns true if type is an integer value.
isIValue :: Type -> Bool
isIValue BooleanType = True
isIValue ByteType    = True
isIValue CharType    = True
isIValue IntType     = True
isIValue ShortType   = True
isIValue _           = False

-- | Returns true if type is a reference value.
isRValue :: Type -> Bool
isRValue (ArrayType _) = True
isRValue (ClassType _) = True
isRValue _             = False

-- | Returns true if Java type is a primitive type.  Primitive types are
-- the Boolean type or numeric types.
isPrimitiveType :: Type -> Bool
isPrimitiveType (ArrayType _) = False
isPrimitiveType BooleanType   = True
isPrimitiveType ByteType      = True
isPrimitiveType CharType      = True
isPrimitiveType (ClassType _) = False
isPrimitiveType DoubleType    = True
isPrimitiveType FloatType     = True
isPrimitiveType IntType       = True
isPrimitiveType LongType      = True
isPrimitiveType ShortType     = True

-- | Returns number of bits that a Java type is expected to take on the stack.
-- Type should be a primitive type.
stackWidth :: Type -> Int
stackWidth BooleanType = 32
stackWidth ByteType    = 32
stackWidth CharType    = 32
stackWidth DoubleType  = 64
stackWidth FloatType   = 32
stackWidth IntType     = 32
stackWidth LongType    = 64
stackWidth ShortType   = 32
stackWidth _ = error "internal: illegal type"

-- | Returns true if Java type denotes a floating point.
isFloatType :: Type -> Bool
isFloatType FloatType = True
isFloatType DoubleType = True
isFloatType _ = False

-- | Returns true if Java type denotes a reference.
isRefType :: Type -> Bool
isRefType (ArrayType _) = True
isRefType (ClassType _) = True
isRefType _ = False

-- | Unique identifier of field
data FieldId = FieldId {
    fieldIdClass :: !String -- ^ Class name
  , fieldIdName  :: !String -- ^ Field name
  , fieldIdType  :: !Type   -- ^ Field type
  } deriving (Eq, Ord, Show)

ppFldId :: FieldId -> String
ppFldId fldId = slashesToDots (fieldIdClass fldId) ++ "." ++ fieldIdName fldId

-- MethodKey {{{1
-- | A unique identifier for looking up a method in a class.
data MethodKey = MethodKey {
    methodKeyName :: String
  , methodKeyParameterTypes :: [Type]
  , methodKeyReturnType :: Maybe Type
  } deriving (Eq, Ord, Show)

ppMethodKey :: MethodKey -> Doc
ppMethodKey (MethodKey name params ret) =
       text name
    <> (parens . commas . map ppType) params
    <> maybe "void" ppType ret
  where commas = sep . punctuate comma

-- | A value stored in the constant pool.
data ConstantPoolValue
  = Long Int64
  | Float Float
  | Double Double
  | Integer Int32
  | String String
  | ClassRef String
  deriving (Eq,Show)

-- | A local variable index.
type LocalVariableIndex = Word16

-- | A program counter value.
type PC = Word16

-- | A JVM Instruction
data Instruction
  = Aaload
  | Aastore
  | Aconst_null
  | Aload LocalVariableIndex
  -- Anewarray replaced by generalized Newarray
  | Areturn
  | Arraylength
  | Astore LocalVariableIndex
  | Athrow
  | Baload
  | Bastore
  -- Bipush replaced with Ldc
  | Caload
  | Castore
  | Checkcast Type
  | D2f
  | D2i
  | D2l
  | Dadd
  | Daload
  | Dastore
  | Dcmpg
  | Dcmpl
  -- Dconst_x has been replaced by Ldc
  | Ddiv
  | Dload LocalVariableIndex
  | Dmul
  | Dneg
  | Drem
  | Dreturn
  | Dstore LocalVariableIndex
  | Dsub
  | Dup
  | Dup_x1
  | Dup_x2
  | Dup2
  | Dup2_x1
  | Dup2_x2
  | F2d
  | F2i
  | F2l
  | Fadd
  | Faload
  | Fastore
  | Fcmpg
  | Fcmpl
  -- Fconst_x has been replaced by Ldc
  | Fdiv
  | Fload LocalVariableIndex
  | Fmul
  | Fneg
  | Frem
  | Freturn
  | Fstore LocalVariableIndex
  | Fsub
  -- | getfield instruction
  | Getfield FieldId
  | Getstatic FieldId
  | Goto PC
  -- Goto_w has been replaced with Goto
  | I2b
  | I2c
  | I2d
  | I2f
  | I2l
  | I2s
  | Iadd
  | Iaload
  | Iand
  | Iastore
  -- Iconst_x replaced with sipush
  | Idiv
  | If_acmpeq PC
  | If_acmpne PC
  | If_icmpeq PC
  | If_icmpne PC
  | If_icmplt PC
  | If_icmpge PC
  | If_icmpgt PC
  | If_icmple PC
  | Ifeq PC
  | Ifne PC
  | Iflt PC
  | Ifge PC
  | Ifgt PC
  | Ifle PC
  | Ifnonnull PC
  | Ifnull PC
  | Iinc LocalVariableIndex Int16
  | Iload LocalVariableIndex
  | Imul
  | Ineg
  | Instanceof Type
  | Invokeinterface String MethodKey
  | Invokespecial   Type   MethodKey
  | Invokestatic    String MethodKey
  | Invokevirtual   Type   MethodKey
  | Ior
  | Irem
  | Ireturn
  | Ishl
  | Ishr
  | Istore LocalVariableIndex
  | Isub
  | Iushr
  | Ixor
  | Jsr PC
  | L2d
  | L2f
  | L2i
  | Ladd
  | Laload
  | Land
  | Lastore
  | Lcmp
  -- Lconst_x has been replaced by generalized Ldc
  -- Ldc, Ldc_w and Ldc2_w have been merged into single generalized Ldc
  | Ldc ConstantPoolValue
  | Ldiv
  | Lload LocalVariableIndex
  | Lmul
  | Lneg
  | Lookupswitch PC {-default -} [(Int32,PC)] {- (key, target) -}
  | Lor
  | Lrem
  | Lreturn
  | Lshl
  | Lshr
  | Lstore LocalVariableIndex
  | Lsub
  | Lushr
  | Lxor
  | Monitorenter
  | Monitorexit
  | Multianewarray Type Word8
  | New String
  -- The type is the type of the array.
  | Newarray Type
  | Nop
  | Pop
  | Pop2
  | Putfield  FieldId
  | Putstatic FieldId
  | Ret LocalVariableIndex
  | Return
  | Saload
  | Sastore
  -- Sipush has been replced by ldc
  | Swap
  | Tableswitch PC Int32 Int32 [PC]
  deriving (Eq,Show)

-- | TODO: improve this
ppInstruction :: Instruction -> Doc
ppInstruction = text . show

-- | An entry in the exception table for a method
data ExceptionTableEntry = ExceptionTableEntry {
  -- | The starting program counter value where the exception handler applies
    startPc :: PC
  -- | The ending program counter value where the exception handler applies.
  , endPc :: PC
  -- | The program counter value to jump to when an exception is caught.
  , handlerPc :: PC
  -- | The type of exception that should be caught or Nothing if all types of
  -- exceptions should be caught.
  , catchType :: Maybe Type
  } deriving (Eq,Show)

type ExceptionTable = [ExceptionTableEntry]

type InstructionStream = Array PC (Maybe Instruction)

--------------------------------------------------------------------------------
-- Utility functions

canThrowException :: Instruction -> Bool
canThrowException Arraylength{}     = True
canThrowException Checkcast{}       = True
canThrowException Getfield{}        = True
canThrowException Getstatic{}       = True
canThrowException Idiv{}            = True
canThrowException Invokeinterface{} = True
canThrowException Invokespecial{}   = True
canThrowException Invokestatic{}    = True
canThrowException Invokevirtual{}   = True
canThrowException Irem{}            = True
canThrowException Ldiv{}            = True
canThrowException Lrem{}            = True
canThrowException Monitorenter{}    = True
canThrowException Monitorexit{}     = True
canThrowException Multianewarray{}  = True
canThrowException Newarray{}        = True
canThrowException New{}             = True
canThrowException Putfield{}        = True
canThrowException Putstatic{}       = True
canThrowException Athrow{}          = True
canThrowException inst              = isArrayLoad inst || isReturn inst

isArrayLoad :: Instruction -> Bool
isArrayLoad Aaload{}  = True
isArrayLoad Aastore{} = True
isArrayLoad Baload{}  = True
isArrayLoad Bastore{} = True
isArrayLoad Caload{}  = True
isArrayLoad Castore{} = True
isArrayLoad Daload{}  = True
isArrayLoad Dastore{} = True
isArrayLoad Faload{}  = True
isArrayLoad Fastore{} = True
isArrayLoad Iaload{}  = True
isArrayLoad Iastore{} = True
isArrayLoad Laload{}  = True
isArrayLoad Lastore{} = True
isArrayLoad Saload{}  = True
isArrayLoad Sastore{} = True
isArrayLoad _         = False

isReturn :: Instruction -> Bool
isReturn Areturn{} = True
isReturn Dreturn{} = True
isReturn Freturn{} = True
isReturn Ireturn{} = True
isReturn Lreturn{} = True
isReturn Return{}  = True
isReturn _         = False

breaksControlFlow :: Instruction -> Bool
breaksControlFlow Jsr{}    = True
breaksControlFlow Ret{}    = True
breaksControlFlow Goto{}   = True
breaksControlFlow Athrow{} = True
breaksControlFlow inst     = isReturn inst

nextPcPrim :: InstructionStream -> PC -> PC
nextPcPrim istrm pc = findNext istrm (pc + 1)
  where findNext is i =
          case (is ! i) of
            Just _  -> i
            Nothing -> findNext is (i+1)

safeNextPcPrim :: InstructionStream -> PC -> Maybe PC
safeNextPcPrim istrm pc | pc <= snd (bounds istrm) = Just $ nextPcPrim istrm pc
                        | otherwise                = Nothing

--------------------------------------------------------------------------------
-- Instances

instance Show Type where
  show ByteType       = "byte"
  show CharType       = "char"
  show DoubleType     = "double"
  show FloatType      = "float"
  show IntType        = "int"
  show LongType       = "long"
  show (ClassType st) = slashesToDots st
  show ShortType      = "short"
  show BooleanType    = "boolean"
  show (ArrayType tp) = (show tp) ++ "[]"

ppType :: Type -> Doc
ppType = text . show