-- |
-- Abstract Syntax Tree for the Common Intermediate Language.
-- Note; currently this is just a subset of CIL.
--

module Language.Cil.Syntax (
    keywords
  , DottedName
  , AssemblyName
  , TypeName
  , GenParamName
  , MethodName
  , FieldName
  , ParamName
  , LocalName
  , Version
  , PublicKeyToken
  , Offset
  , Assembly      (..)
  , AssemblyRef   (..)
  , TypeDef       (..)
  , GenParam      (..)
  , ClassAttr     (..)
  , ClassDecl     (..)
  , TypeSpec      (..)
  , FieldDef      (..)
  , FieldAttr     (..)
  , MethodDef     (..)
  , MethAttr      (..)
  , Parameter     (..)
  , ParamAttr     (..)
  , MethodDecl    (..)
  , Directive     (..)
  , Local         (..)
  , Label
  , OpCode        (..)
  , PrimitiveType (..)
  , Alignment     (..)
  , CallConv      (..)
  ) where

keywords :: [String]
keywords = ["add", "mul", "or", "pop", "sub", "value"]

-- | A name in the CIL world.
-- These need to confirm to certain restrictions, altough these aren't
-- currently checked.
type DottedName = String

type AssemblyName  = DottedName
type TypeName      = DottedName
type GenParamName  = DottedName
type MethodName    = DottedName
type FieldName     = DottedName
type ParamName     = DottedName
type LocalName     = DottedName

-- | An offset, e.g. for local variables or arguments
type Offset = Int

-- | A Label in CIL.
type Label = String

-- | A Version number in CIL
type Version = (Int, Int, Int, Int)

-- | A public key token
type PublicKeyToken = String

-- | The top level Assembly.
-- This is the root of a CIL program.
data Assembly
  = Assembly [AssemblyRef] AssemblyName [TypeDef]
  deriving Show

-- | Assembly reference.
data AssemblyRef
  = AssemblyRef AssemblyName Version PublicKeyToken
  deriving Show

-- | A Type definition in CIL, either a class or a value type.
data TypeDef
  = Class [ClassAttr] TypeName (Maybe TypeSpec) [TypeSpec] [ClassDecl]
  | GenericClass [ClassAttr] TypeName [GenParam] [ClassDecl]
  deriving Show

-- | A parameter to a generic class.
-- Not fully implemented yet, constraints aren't supported.
data GenParam
  = GenParam -- constraintFlags :: [ConstraintFlag]
             -- constraints     :: [DottedName]
             {- paramName       -} GenParamName
  deriving Show

-- | Attribures to class definitions.
data ClassAttr
  = CaPrivate
  | CaPublic
  | CaNestedPublic
  | CaNestedPrivate
  deriving Show

-- | Class declarations, i.e. the body of a class.
data ClassDecl
  = FieldDef  FieldDef
  | MethodDef MethodDef
  | TypeDef   TypeDef
  deriving Show

-- | Type specification.
data TypeSpec
  = TypeSpec TypeName
  deriving Show

-- | Field definition.
data FieldDef
  = Field [FieldAttr] PrimitiveType FieldName
  deriving Show

-- | Attributes to field definitions.
data FieldAttr
  = FaStatic
  | FaPublic
  | FaPrivate
  | FaAssembly
  | FaInitOnly
  deriving Show

-- | Primitive types in CIL.
data PrimitiveType
  = Void
  | Bool
  | Char
  | Byte
  | Int32
  | Int64
  | Float32
  | Double64
  | IntPtr
  | String
  | Object
  | ValueType AssemblyName TypeName
  | ReferenceType AssemblyName TypeName
  | GenericReferenceType AssemblyName TypeName [GenParamName]
  | GenericReferenceTypeInstance AssemblyName TypeName [PrimitiveType]
  | ByRef PrimitiveType
  | GenericType Offset
  | Array PrimitiveType
  deriving (Eq, Ord, Show)

-- | A specification of pointer alignment.
data Alignment
  = ByteAligned
  | DoubleByteAligned
  | QuadByteAligned
  deriving Show

-- | A Method definition in CIL.
data MethodDef
  = Constructor [MethAttr] PrimitiveType [Parameter] [MethodDecl]
  | Method [MethAttr] PrimitiveType MethodName [Parameter] [MethodDecl]
  deriving Show

-- | Attributes to method definitions.
data MethAttr
  = MaStatic
  | MaPublic
  | MaPrivate
  | MaAssembly
  | MaVirtual
  | MaHidebysig
  deriving (Show, Eq)

-- | A formal parameter to a method.
data Parameter
  = Param (Maybe ParamAttr) PrimitiveType ParamName
  deriving Show

-- | Attributes to parameter definitions.
data ParamAttr
  = PaIn
  | PaOut
  | PaOpt
  deriving Show

-- | Method declarations, i.e. the body of a method.
data MethodDecl
  = Directive Directive
  | Label     Label
  | OpCode    OpCode
  | Comment   String
  deriving Show

-- | Directive meta data for method definitions.
data Directive
  = EntryPoint
  | LocalsInit [Local]
  | MaxStack Int
  deriving Show

-- | Local variables used inside a method definition.
data Local
  = Local PrimitiveType LocalName
  deriving Show

-- | CIL OpCodes inside a method definition.
-- See <http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes_fields.aspx>
-- for a more complete list with documentation.
data OpCode
  = Add                -- ^ Pops 2 values, adds the values, pushes result.
  | Add_ovf            -- ^ Pops 2 values, adds the values with a signed overflow check, pushes result.
  | Add_ovf_un         -- ^ Pops 2 values, adds the values with an unsigned overflow check, pushes result.
  | And                -- ^ Pops 2 values, do bitwise AND between the values, pushes result.
  | Beq Label          -- ^ Pops 2 values, if first value is equal to second value, jump to specified label.
  | Bge Label          -- ^ Pops 2 values, if first value is greater or equal to second value, jump to specified label.
  | Bgt Label          -- ^ Pops 2 values, if first value is greater than second value, jump to specified label.
  | Ble Label          -- ^ Pops 2 values, if first value is less or equal to second value, jump to specified label.
  | Blt Label          -- ^ Pops 2 values, if first value is less than second value, jump to specified label.
  | Box PrimitiveType  -- ^ Pops 1 value, boxes value type, pushes object reference.
  | Br Label           -- ^ Unconditionally jump to specified label.
  | Break              -- ^ Inform a debugger that a breakpoint has been reached.
  | Brfalse Label      -- ^ Pops 1 value, if value is false, null reference or zero, jump to specified label.
  | Brtrue Label       -- ^ Pops 1 value, if value is true, not null or non-zero, jump to specified label.
  | Call
      { callConv     :: [CallConv]      -- ^ Method is associated with class or instance.
      , returnType   :: PrimitiveType   -- ^ Return type of the method.
      , assemblyName :: AssemblyName    -- ^ Name of the assembly where the method resides.
      , typeName     :: TypeName        -- ^ Name of the type of which the method is a member.
      , methodName   :: MethodName      -- ^ Name of the method.
      , paramTypes   :: [PrimitiveType] -- ^ Types of the formal parameters of the method.
      } -- ^ Pops /n/ values, calls specified method, pushes return value. (where /n/ is the number of formal parameters of the method).
  | CallVirt
      { returnType   :: PrimitiveType   -- ^ Return type of the method.
      , assemblyName :: AssemblyName    -- ^ Name of the assembly where the method resides.
      , typeName     :: TypeName        -- ^ Name of the type of which the method is a member.
      , methodName   :: MethodName      -- ^ Name of the method.
      , paramTypes   :: [PrimitiveType] -- ^ Types of the formal parameters of the method.
      } -- ^ Pops /n/ values, calls specified virtual method, pushes return value. (where /n/ is the number of formal parameters of the method).
  | Castclass PrimitiveType -- ^ Pops 1 value, attempts to cast it to the specified type. If this succeeds, pushes the cast value. Otherwise throws an InvalidCastException.
  | Ceq                -- ^ Pops 2 values, if they are equal, pushes 1 to stack; otherwise, pushes 0.
  | Cgt                -- ^ Pops 2 values and compares them.
  | Ckfinite           -- ^ Pops a float or double. Throws an ArithmeticException if the popped value is NaN or +/- infinity. Pushes the popped value.
  | Clt                -- ^ Pops 2 values and compares them.
  | Dup                -- ^ Pops 1 value, copies it, pushes the same value twise.
  | Div                -- ^ Pops 2 values, divides the first by the second, pushes the result.
  | Div_un             -- ^ Pops 2 integers, divides the first by the second when consider as unsigned integers, pushes the result.
  | Isinst TypeName    -- ^ Tests if an object reference is an instance of class, returning either a null reference or an instance of that class or interface.
  | Ldarg Offset       -- ^ Loads /n/-th argument to current method onto stack.
  | Ldarg_0            -- ^ Loads 0th argument to current method onto stack.
  | Ldarg_1            -- ^ Loads 1th argument to current method onto stack.
  | Ldarg_2            -- ^ Loads 2th argument to current method onto stack.
  | Ldarg_3            -- ^ Loads 3th argument to current method onto stack.
  | LdargN DottedName  -- ^ Loads named argument to current method onto stack.
  | Ldarga Offset      -- ^ Loads address of the /n/-th argument to current method onto stack.
  | LdargaN DottedName -- ^ Loads address of the named argument to current method onto stack.
  | Ldc_i4 Integer     -- ^ Loads the supplied 32-bit integer onto the stack.
  | Ldc_i4_0           -- ^ Loads the value 0 onto the stack.
  | Ldc_i4_1           -- ^ Loads the value 1 onto the stack.
  | Ldc_i4_2           -- ^ Loads the value 2 onto the stack.
  | Ldc_i4_3           -- ^ Loads the value 3 onto the stack.
  | Ldc_i4_4           -- ^ Loads the value 4 onto the stack.
  | Ldc_i4_5           -- ^ Loads the value 5 onto the stack.
  | Ldc_i4_6           -- ^ Loads the value 6 onto the stack.
  | Ldc_i4_7           -- ^ Loads the value 7 onto the stack.
  | Ldc_i4_8           -- ^ Loads the value 8 onto the stack.
  | Ldc_i4_m1          -- ^ Loads the value -1 onto the stack.
  | Ldc_i4_s Int       -- ^ Loads the supplied 8-bit integer onto the stack as 32-bit integer (short form).
  | Ldc_i8 Integer     -- ^ Loads the supplied 64-bit integer onto the stack.
  | Ldc_r4 Float       -- ^ Loads the supplied 32-bit float onto the stack.
  | Ldc_r8 Double      -- ^ Loads the supplied 64-bit double onto the stack.
  | Ldelem_i           -- ^ Pops an array reference and an index. Pushes the native integer in the specified slot of the array.
  | Ldelem_i1          -- ^ Pops an array reference and an index. Pushes the 8-bit integer in the specified slot of the array.
  | Ldelem_i2          -- ^ Pops an array reference and an index. Pushes the 16-bit integer in the specified slot of the array.
  | Ldelem_i4          -- ^ Pops an array reference and an index. Pushes the 32-bit integer in the specified slot of the array.
  | Ldelem_i8          -- ^ Pops an array reference and an index. Pushes the 64-bit integer in the specified slot of the array.
  | Ldelem_u1          -- ^ Pops an array reference and an index. Pushes the unsigned 8-bit integer in the specified slot of the array.
  | Ldelem_u2          -- ^ Pops an array reference and an index. Pushes the unsigned 16-bit integer in the specified slot of the array.
  | Ldelem_u4          -- ^ Pops an array reference and an index. Pushes the unsigned 32-bit integer in the specified slot of the array.
  | Ldelem_u8          -- ^ Pops an array reference and an index. Pushes the unsigned 64-bit integer in the specified slot of the array.
  | Ldelem_r4          -- ^ Pops an array reference and an index. Pushes the float in the specified slot of the array.
  | Ldelem_r8          -- ^ Pops an array reference and an index. Pushes the double in the specified slot of the array.
  | Ldelem_ref         -- ^ Pops an array reference and an index. Pushes the object reference in the specified slot of the array.
  | Ldelema            -- ^ Pops an array reference and an index. Pushes the address of the specified slot of the array.
  | Ldfld
      { fieldType    :: PrimitiveType  -- ^ Type of the field.
      , assemblyName :: AssemblyName   -- ^ Name of the assembly where the field resides.
      , typeName     :: TypeName       -- ^ Name of the type of which the field is a member.
      , fieldName    :: FieldName      -- ^ Name of the field.
      } -- ^ Pops object reference, find value of specified field on object, pushes value to the stack.
  | Ldflda
      { fieldType    :: PrimitiveType  -- ^ Type of the field.
      , assemblyName :: AssemblyName   -- ^ Name of the assembly where the field resides.
      , typeName     :: TypeName       -- ^ Name of the type of which the field is a member.
      , fieldName    :: FieldName      -- ^ Name of the field.
      } -- ^ Pops object reference, find address of specified field on the object, pushes address to the stack.
  | Ldftn 
      { callConv     :: [CallConv]       -- ^ Method is associated with class or instance.
      , returnType   :: PrimitiveType    -- ^ Return type of the method.
      , assemblyName :: AssemblyName     -- ^ Name of the assembly where the method resides.
      , typeName     :: TypeName         -- ^ Name of the type of which the method is a member.
      , methodName   :: MethodName       -- ^ Name of the method.
      , paramTypes   :: [PrimitiveType]  -- ^ Types of the formal parameters of the method.
      } -- ^ Pops object reference, finds address of specified method, pushes address as native int to the stack.
  | Ldind_i            -- ^ Pops an address, pushes the native integer stored at the address.
  | Ldind_i1           -- ^ Pops an address, pushes the 8-bit integer stored at the address as a 32-bit integer.
  | Ldind_i2           -- ^ Pops an address, pushes the 16-bit integer stored at the address as a 32-bit integer.
  | Ldind_i4           -- ^ Pops an address, pushes the 32-bit integer stored at the address.
  | Ldind_i8           -- ^ Pops an address, pushes the 64-bit integer stored at the address as a 64-bit integer.
  | Ldind_r4           -- ^ Pops an address, pushes the 32-bit float stored at the address.
  | Ldind_r8           -- ^ Pops an address, pushes the 64-bit double stored at the address.
  | Ldind_ref          -- ^ Pops an address, pushes the object reference specified at the address.
  | Ldind_u1           -- ^ Pops an address, pushes the 8-bit unsigned integer stored at the address as a 32-bit integer.
  | Ldind_u2           -- ^ Pops an address, pushes the 16-bit unsigned integer stored at the address as a 32-bit integer.
  | Ldind_u4           -- ^ Pops an address, pushes the 32-bit unsigned integer stored at the address as a 32-bit integer.
  | Ldlen              -- ^ Pops an array reference, pushes the native unsigned integer length of the array.
  | Ldloc Offset       -- ^ Pushes value of local variable, specified by index, to the stack.
  | Ldloc_0            -- ^ Pushes 0th local variable to the stack.
  | Ldloc_1            -- ^ Pushes 1th local variable to the stack.
  | Ldloc_2            -- ^ Pushes 2th local variable to the stack.
  | Ldloc_3            -- ^ Pushes 3th local variable to the stack.
  | LdlocN DottedName  -- ^ Pushes value of local variable, specified by name, to the stack.
  | Ldloca Offset      -- ^ Pushes address of local variable, specified by index, to the stack.
  | LdlocaN DottedName -- ^ Pushes address of local variable, specified by name, to the stack.
  | Ldnull             -- ^ Pushes a size-agnostic null reference on the stack.
  | Ldsfld
      { fieldType    :: PrimitiveType  -- ^ Type of the field.
      , assemblyName :: AssemblyName   -- ^ Name of the assembly where the field resides.
      , typeName     :: TypeName       -- ^ Name of the type of which the field is a member.
      , fieldName    :: FieldName      -- ^ Name of the field.
      } -- ^ Pops type reference, find value of specified field on the type, pushes value to the stack.
  | Ldsflda
      { fieldType    :: PrimitiveType  -- ^ Type of the field.
      , assemblyName :: AssemblyName   -- ^ Name of the assembly where the field resides.
      , typeName     :: TypeName       -- ^ Name of the type of which the field is a member.
      , fieldName    :: FieldName      -- ^ Name of the field.
      } -- ^ Pops type reference, find address of specified field on the type, pushes address to the stack.
  | Ldstr String       -- ^ Pushes an object reference to the specified string constant.
  | Ldtoken PrimitiveType -- ^ Pushes the RuntimeTypeHandle of the specified type.
  | Mul                -- ^ Pops 2 values, multiplies the values, pushes result.
  | Mul_ovf            -- ^ Pops 2 values, multiplies the values with a signed overflow check, pushes result.
  | Mul_ovf_un         -- ^ Pops 2 values, multiplies the values with an unsigned overflow check, pushes result.
  | Neg                -- ^ Pops 1 value, negates the value, pushes the value.
  | Newobj
      { returnType   :: PrimitiveType    -- ^ Return type of the constructor (almost alway Void).
      , assemblyName :: AssemblyName     -- ^ Name of the assembly where the constructor resides.
      , typeName     :: TypeName         -- ^ Name of the type of which the constructor is a member.
      , paramTypes   :: [PrimitiveType]  -- ^ Types of the formal paramters of the constructor.
      } -- ^ Creates a new object or instance of a value type. Pops /n/ values, calls the specified constructor, pushes a new object reference onto the stack (where /n/ is the number of formal parameters of the constructor).
  | Newarr PrimitiveType -- ^ Creates a new one-dimensional array. Pops a native int or int32, pushes a new array with that length.
  | Nop                -- ^ No operation is performed.
  | Not                -- ^ Pops 1 value, does a bitwise complement, pushes result.
  | Or                 -- ^ Pops 2 values, do bitwise OR between the values, pushes result.
  | Pop                -- ^ Pops the top of the stack.
  | Rem                -- ^ Pops 2 values, divides the first value by the second value, pushes the remainder.
  | Rem_un             -- ^ Pops 2 integers, divides the first by the second when considered as unsigned integers, pushes the remainder.
  | Ret                -- ^ Returns from the current method. Pushes top of the stack to the top of the callers stack (if stack is not empty).
  | Shl                -- ^ Pops 2 values, shifts the first to the left by the number of bits specified by the second, pushes the result.
  | Shr                -- ^ Pops 2 values, shifts the first to the right by the number of bits specified by the second (with sign extension), pushes the result.
  | Shr_un             -- ^ Pops 2 values, shifts the first to the right by the number of bits specified by the second (without sign extension), pushes the result.
  | Stelem_i           -- ^ Pops an array reference, an index, and a native integer. Stores the integer in the array.
  | Stelem_i1          -- ^ Pops an array reference, an index, and an 8-bit integer. Stores the integer in the array.
  | Stelem_i2          -- ^ Pops an array reference, an index, and a 16-bit integer. Stores the integer in the array.
  | Stelem_i4          -- ^ Pops an array reference, an index, and a 32-bit integer. Stores the integer in the array.
  | Stelem_i8          -- ^ Pops an array reference, an index, and a 64-bit integer. Stores the integer in the array.
  | Stelem_r4          -- ^ Pops an array reference, an index, and a float. Stores the float in the array.
  | Stelem_r8          -- ^ Pops an array reference, an index, and a double integer. Stores the double in the array.
  | Stelem_ref          -- ^ Pops an array reference, an index, and an object reference. Stores the object reference in the array.
  | Stfld
      { fieldType    :: PrimitiveType  -- ^ Type of the field.
      , assemblyName :: AssemblyName   -- ^ Name of the assembly where the field resides.
      , typeName     :: TypeName       -- ^ Name of the type of which the field is a member.
      , fieldName    :: FieldName      -- ^ Name of the field.
      } -- ^ Replaces the value stored in the field of an object reference or pointer with a new value.
  | Stind_i            -- ^ Pops an address and a native integer, stores the integer at the address.
  | Stind_i1           -- ^ Pops an address and a 8-bit integer, stores the integer at the address.
  | Stind_i2           -- ^ Pops an address and a 16-bit integer, stores the integer at the address.
  | Stind_i4           -- ^ Pops an address and a 32-bit integer, stores the integer at the address.
  | Stind_i8           -- ^ Pops an address and a 64-bit integer, stores the integer at the address.
  | Stind_r4           -- ^ Pops an address and a 32-bit float, stores the float at the address.
  | Stind_r8           -- ^ Pops an address and a 64-bit double, stores the double at the address.
  | Stind_ref          -- ^ Pops an address and an object reference, stores the object reference at the address.
  | Stloc Offset       -- ^ Pops 1 value, stores it in the local variable specified by index.
  | Stloc_0            -- ^ Pops 1 value, stores it in the 0th local variable.
  | Stloc_1            -- ^ Pops 1 value, stores it in the 1th local variable.
  | Stloc_2            -- ^ Pops 1 value, stores it in the 2th local variable.
  | Stloc_3            -- ^ Pops 1 value, stores it in the 3th local variable.
  | StlocN DottedName  -- ^ Pops 1 value, stores it in the local variable specified by name.
  | Stsfld
      { fieldType    :: PrimitiveType  -- ^ Type of the field.
      , assemblyName :: AssemblyName   -- ^ Name of the assembly where the field resides.
      , typeName     :: TypeName       -- ^ Name of the type of which the field is a member.
      , fieldName    :: FieldName      -- ^ Name of the field.
      } -- ^ Replaces the value stored in the static field of a type with a new value.
  | Sub                -- ^ Pops 2 values, subtracts second value from the first value, pushes result.
  | Sub_ovf            -- ^ Pops 2 values, subtracts second value from the first value with a signed overflow check, pushes result.
  | Sub_ovf_un         -- ^ Pops 2 values, subtracts second value from the first value with an unsigned overflow check, pushes result.
  | Switch [Label]     -- ^ Pops an unsigned integer value and transfers control to label with matching index.
  | Tail               -- ^ Performs subsequent call as a tail call, by replacing current stack frame with callee stack frame.
  | Tailcall OpCode    -- ^ Performs provided call as a tail call, by replacing current stack frame with callee stack frame.
  | Throw              -- ^ Pops an object reference from the stack and throws it as an exception.
  | Unaligned Alignment -- ^ Performs subsequent load or store operation under a weaker-than-usual alignment precondition.
  | UnalignedPtr  Alignment OpCode -- ^ Performs provided load or store operation under a weaker-than-usual alignment precondition.
  | Unbox PrimitiveType  -- ^ Pops 1 value, unboxes object reference, pushes value type.
  | Unbox_any PrimitiveType  -- ^ Pops 1 value, unboxes and loads value, equivalent to unbox followed by ldobj
  | Volatile           -- ^ Marks subsequent pointer reference as volatile, i.e. the value it points at can be modified from another thread.
  | VolatilePtr OpCode -- ^ Marks provided pointer reference as volatile, i.e. the value it points at can be modified from another thread.
  | Xor                -- ^ Pops 2 values, do bitwise XOR between the values, pushes result.
  deriving Show

-- | Calling convention for method calls.
data CallConv
  = CcInstance
  deriving Show