Reputation: 63
Hello I was given code to run in Haskell and I am a bit new to this language. I get it to compile but I don't know how to quite use it. By looking through it I believe it is supposed to parse a java class file and I have tried parse file.class but its giving me a "failed to load interface file name". If someone could point out what I'm doing wrong (I'm sure its something small) I would appreciate it.
module Parse where
import Data.Binary
import Data.Binary.Get
import Data.Word
import Control.Monad
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B
import qualified Data.ByteString.Lazy as L
{-
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
-}
getWord16 :: Get Word16
getWord16 = getWord16be
getWord32 :: Get Word32
getWord32 = getWord32be
data JavaClass = JavaClass {
classMinorVersion :: Word16
, classMajorVersion :: MajorVersion
, classConstantPoolCount :: Word16
} deriving Show
getJavaClass :: Get JavaClass
getJavaClass = do
checkClassMagic
minorVersion <- getMinorVersion
majorVersion <- getMajorVersion
constPoolCount <- getConstantsPoolCount
return $ JavaClass minorVersion majorVersion constPoolCount
checkClassMagic :: Get ()
checkClassMagic = do
magic <- getWord32
if magic /= 0xCAFEBABE then
fail "Invalid magic number for Java class"
else
return ()
getMinorVersion :: Get Word16
getMinorVersion = getWord16 >>= return
{-
J2SE 7.0 = 51 (0x33 hex),
J2SE 6.0 = 50 (0x32 hex),
J2SE 5.0 = 49 (0x31 hex),
JDK 1.4 = 48 (0x30 hex),
JDK 1.3 = 47 (0x2F hex),
JDK 1.2 = 46 (0x2E hex),
JDK 1.1 = 45 (0x2D hex).
-}
data MajorVersion
= J2SE_7_0
| J2SE_6_0
| J2SE_5_0
| JDK_1_4
| JDK_1_3
| JDK_1_2
| JDK_1_1
deriving (Eq, Show)
getMajorVersion :: Get MajorVersion
getMajorVersion = do
version <- getWord16be
return $ convert version
where convert 51 = J2SE_7_0
convert 50 = J2SE_6_0
convert 49 = J2SE_5_0
convert 48 = JDK_1_4
convert 47 = JDK_1_3
convert 46 = JDK_1_2
convert 45 = JDK_1_1
getConstantsPoolCount :: Get Word16
getConstantsPoolCount = getWord16 >>= return
getConstantsPool = undefined
data Tag
= TAG_STRING
| TAG_INTEGER
| TAG_FLOAT
| TAG_LONG
| TAG_DOUBLE
| TAG_CLASS_REF
| TAG_STRING_REF
| TAG_FIELD_REF
| TAG_METHOD_REF
| TAG_INTERFACE_REF
| TAG_NAME_AND_TYPE
getTag :: Get Tag
getTag = do
tag <- getWord8
return $ convert tag
where convert 1 = TAG_STRING
convert 3 = TAG_INTEGER
convert 4 = TAG_FLOAT
convert 5 = TAG_LONG
convert 6 = TAG_DOUBLE
convert 7 = TAG_CLASS_REF
convert 8 = TAG_STRING_REF
convert 9 = TAG_FIELD_REF
convert 10 = TAG_METHOD_REF
convert 11 = TAG_INTERFACE_REF
convert 12 = TAG_NAME_AND_TYPE
parse :: B.ByteString -> JavaClass
parse b = runGet getJavaClass $ L.fromChunks [b]
Upvotes: 0
Views: 1042
Reputation: 8126
You can invoke it from GHCi as hairyhum says.
But if what you're really trying to do is to call it from a program ( perhaps you're in the same class as the person who asked this: Dissecting java Class file in haskell), then put it in the same directory as your main program, and at the top of the program, put:
import Parse
Then in your program, read the Java class file. You've probably seen something like this:
s <- readFile "MyJava.class"
That would work if we wanted to read the contents of the file into a String
. But the parse
command expects a ByteString
, so we need to use a different implementation of readFile
. So at the top of your program, put:
import qualified Data.ByteString as BS
Now you can read the file like so:
s <- BS.readFile "MyJava.class"
Now you have the class data, and can invoke the parse
command. That should be enough information to help you finish that part of the assignment.
UPDATE
Let's look at the type signature for the function parse
in the code you were given.
parse :: B.ByteString -> JavaClass
So parse
takes a ByteString
and returns a JavaClass
. Now let's look at the definition of JavaClass
.
data JavaClass = JavaClass {
classMinorVersion :: Word16
, classMajorVersion :: MajorVersion
, classConstantPoolCount :: Word16
} deriving Show
So as written, all parse
is going to give you is the minor version, major version, and the constant pool count. But if you analyse the code, you should be able to see how to modify it to get the additional information you want. I suggest you analyse that code in detail to understand how it works.
UPDATE 2
If you just want to try it out in GHCI, you could do something like this:
:load Parse
:m + Data.ByteString
classData <- Data.ByteString.readFile "file.class"
Parse.parse classData
Here's a GHCi session where I did exactly that.
ghci> :load Parse
[1 of 1] Compiling Parse ( Parse.hs, interpreted )
Ok, modules loaded: Parse.
ghci> :m + Data.ByteString
ghci> classData <- Data.ByteString.readFile "file.class"
Loading package array-0.4.0.1 ... linking ... done.
Loading package deepseq-1.3.0.1 ... linking ... done.
Loading package bytestring-0.10.0.2 ... linking ... done.
ghci> Parse.parse classData
Loading package containers-0.5.0.0 ... linking ... done.
Loading package binary-0.7.0.1 ... linking ... done.
JavaClass {classMinorVersion = 3, classMajorVersion = JDK_1_1, classConstantPoolCount = 85}
But to take it to the next step, you would write a program to invoke parse
as I've described above. Suppose your program is in a file called "MyProgram.hs". Then you can run it from the command line by typing runghc MyProgram.hs
.
I recommend reading Chapter 1 of Real World Haskell.
EDIT: Changed "class" to "classData" because "class" is a Haskell keyword.
Upvotes: 1