Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2141bd44cb | |||
| 3ac33af1b5 | |||
| 9046e3621b | |||
| 7c0f49ccca | |||
| 6e16a502aa | |||
| b45502a550 | |||
| 83ff583c1c | |||
| f19d6f6d39 | |||
| cbd7df780a | |||
| 9d405fac61 | |||
| dde92cc64d | |||
| c8261b361a | |||
| d0d37dd05e | |||
| e8ab0ed082 | |||
| 544774d8b9 | |||
| f2cc603690 | |||
| a0f5d736f9 | |||
| 8416f1b9c8 | |||
| 245e70169e | |||
| 8497b949a4 | |||
| 251d8daa0f | |||
| 1ad70c0b00 | |||
| 7f6e8f5cb5 | |||
| 88e9b545eb | |||
| 11fda5eaa7 | |||
| 1a153200fa | |||
| 815388b842 | |||
| 22e238317c | |||
| 5d48938221 | |||
| 44c1032e15 | |||
| 842149be5e | |||
| 48b6270fd0 | |||
| b668fc94ff | |||
| a829814cd6 | |||
| 66e0efba81 | |||
| 31248ecc97 | |||
| ac60aeba3e | |||
| b833d6ebe0 | |||
| 3f0e00ac63 | |||
| 4370ec3c1f | |||
| 162bed1b12 | |||
| e91fac7060 | |||
| 0eb8c64fc6 | |||
| f96627fcc9 | |||
| b5ddd0b5c9 | |||
| 531f5d7f7f | |||
| 5a16f08ccc | |||
| e5eb3a9792 | |||
| 8e3b978ba4 | |||
| 1b2ae2ae22 | |||
| d9b7ba40bc | |||
| 03e0571f4e | |||
| 4978de5220 | |||
| b80b71936a |
@@ -32,3 +32,8 @@ hie.yaml
|
||||
src/Grammar/Scanner.hs
|
||||
src/Grammar/Parser.hs
|
||||
src/Grammar/Parser.info
|
||||
src/Main
|
||||
*.class
|
||||
out/
|
||||
mine.txt
|
||||
ref.txt
|
||||
@@ -0,0 +1 @@
|
||||
public class Runner { public static void main(String[] args) { FieldsTest f = new FieldsTest(); System.out.println(f.b); } }
|
||||
+135
-37
@@ -1,6 +1,21 @@
|
||||
module Codegen.ClassFile where
|
||||
|
||||
import Codegen.ConstPool
|
||||
( generateConstPool,
|
||||
lookupClassIndex,
|
||||
lookupFieldRefIndex,
|
||||
lookupUtf8Index,
|
||||
)
|
||||
import Codegen.Types
|
||||
import Data.Bits (shiftR, (.&.))
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Grammar.AST (Type)
|
||||
import Grammar.TAST
|
||||
( TypedClass (..),
|
||||
TypedExpr (..),
|
||||
TypedFieldDecl (..),
|
||||
TypedMethodDecl (..),
|
||||
)
|
||||
|
||||
data ClassFile = ClassFile
|
||||
{ magic :: Magic,
|
||||
@@ -19,7 +34,7 @@ data ClassFile = ClassFile
|
||||
|
||||
-- Counts are derivable from list lengths, so no need to store them separately
|
||||
|
||||
type CP_Infos = [CP_Info]
|
||||
type Index = Int
|
||||
|
||||
type Interfaces = [Word16] -- constant pool indices
|
||||
|
||||
@@ -29,8 +44,6 @@ type Method_Infos = [Method_Info]
|
||||
|
||||
type Attribute_Infos = [Attribute_Info]
|
||||
|
||||
type Index = Int -- constant pool index (1-based)
|
||||
|
||||
newtype Magic = Magic Word32 deriving (Show, Eq) -- always 0xCAFEBABE
|
||||
|
||||
newtype MinorVersion = MinorVersion Int deriving (Show, Eq)
|
||||
@@ -56,40 +69,6 @@ data AccessFlag
|
||||
| ACC_ENUM
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- Duplicate field names fixed by prefixing per constructor
|
||||
data CP_Info
|
||||
= Class_Info
|
||||
{ci_nameIndex :: Index}
|
||||
| FieldRef_Info
|
||||
{ fr_classIndex :: Index,
|
||||
fr_nameAndTypeIndex :: Index
|
||||
}
|
||||
| MethodRef_Info
|
||||
{ mr_classIndex :: Index,
|
||||
mr_nameAndTypeIndex :: Index
|
||||
}
|
||||
| InterfaceMethodRef_Info
|
||||
{ imr_classIndex :: Index,
|
||||
imr_nameAndTypeIndex :: Index
|
||||
}
|
||||
| String_Info
|
||||
{si_stringIndex :: Index}
|
||||
| Integer_Info
|
||||
{ii_value :: Int}
|
||||
| Float_Info
|
||||
{fi_value :: Float}
|
||||
| Long_Info
|
||||
{li_value :: Integer} -- Long fits in Haskell Integer
|
||||
| Double_Info
|
||||
{di_value :: Double}
|
||||
| NameAndType_Info
|
||||
{ nt_nameIndex :: Index,
|
||||
nt_descriptorIndex :: Index
|
||||
}
|
||||
| Utf8_Info
|
||||
{u8_value :: String}
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Field_Info = Field_Info
|
||||
{ fieldAccessFlags :: AccessFlags,
|
||||
fieldNameIndex :: Index,
|
||||
@@ -128,3 +107,122 @@ data ExceptionEntry = ExceptionEntry
|
||||
catchType :: Index -- 0 means catches all (finally)
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
generateClassFile :: TypedClass -> ClassFile
|
||||
generateClassFile cls =
|
||||
let constPool = makeConstpool cls
|
||||
in ClassFile
|
||||
{ magic = Magic 0xCAFEBABE,
|
||||
minorVersion = MinorVersion 0,
|
||||
majorVersion = MajorVersion 48,
|
||||
constantPool = constPool,
|
||||
accessFlags = makeAccessFlags,
|
||||
thisClass = makeThisClass constPool cls,
|
||||
superClass = makeSuperClass constPool,
|
||||
interfaces = makeInterface,
|
||||
fields = makeFields constPool cls,
|
||||
methods = makeMethods constPool cls,
|
||||
attributes = makeAttributes constPool
|
||||
}
|
||||
|
||||
makeConstpool :: TypedClass -> CP_Infos
|
||||
makeConstpool = generateConstPool
|
||||
|
||||
makeAccessFlags :: AccessFlags
|
||||
makeAccessFlags = AccessFlags [ACC_PUBLIC, ACC_SUPER]
|
||||
|
||||
makeThisClass :: CP_Infos -> TypedClass -> ThisClass
|
||||
makeThisClass constPool (Class name _ _) =
|
||||
ThisClass (lookupClassIndex constPool name)
|
||||
|
||||
makeSuperClass :: CP_Infos -> SuperClass
|
||||
makeSuperClass constPool =
|
||||
SuperClass (lookupClassIndex constPool "java/lang/Object")
|
||||
|
||||
makeInterface :: Interfaces
|
||||
makeInterface = []
|
||||
|
||||
makeFields :: CP_Infos -> TypedClass -> Field_Infos
|
||||
makeFields constPool (Class _ fields _) = map (makeField constPool) fields
|
||||
|
||||
makeField :: CP_Infos -> TypedFieldDecl -> Field_Info
|
||||
makeField constPool (Field fieldType name _) =
|
||||
Field_Info
|
||||
{ fieldAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
fieldNameIndex = lookupUtf8Index constPool name,
|
||||
fieldDescIndex = lookupUtf8Index constPool (typeDescriptor fieldType),
|
||||
fieldAttributes = []
|
||||
}
|
||||
|
||||
makeAttributes :: CP_Infos -> Attribute_Infos
|
||||
makeAttributes _ = []
|
||||
|
||||
makeMethods :: CP_Infos -> TypedClass -> Method_Infos
|
||||
makeMethods constPool (Class _ fields methods) =
|
||||
makeInitMethod constPool fields : map (makeMethod constPool) methods
|
||||
|
||||
makeInitMethod :: CP_Infos -> [TypedFieldDecl] -> Method_Info
|
||||
makeInitMethod constPool fields =
|
||||
Method_Info
|
||||
{ methodAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
methodNameIndex = lookupUtf8Index constPool "<init>",
|
||||
methodDescIndex = lookupUtf8Index constPool "()V",
|
||||
methodAttributes = [makeInitCode constPool fields]
|
||||
}
|
||||
|
||||
makeInitCode :: CP_Infos -> [TypedFieldDecl] -> Attribute_Info
|
||||
makeInitCode constPool fields =
|
||||
Code_Attribute
|
||||
{ codeNameIndex = lookupUtf8Index constPool "Code",
|
||||
maxStack = if null initFields then 1 else 2,
|
||||
maxLocals = 1,
|
||||
codeBody = [0x2A, 0xB7, 0x00, 0x08] ++ fieldInits ++ [0xB1],
|
||||
exceptionTable = [],
|
||||
codeAttributes = []
|
||||
}
|
||||
where
|
||||
initFields = [f | f@(Field _ _ (Just _)) <- fields]
|
||||
fieldInits = concatMap (fieldInitBytes constPool) initFields
|
||||
|
||||
fieldInitBytes :: CP_Infos -> TypedFieldDecl -> [Word8]
|
||||
fieldInitBytes constPool (Field _ name (Just expr)) =
|
||||
let idx = lookupFieldRefIndex constPool ("field ref: " ++ name)
|
||||
in [0x2A]
|
||||
++ pushExpr expr
|
||||
++ [0xB5, fromIntegral (idx `shiftR` 8 .&. 0xFF), fromIntegral (idx .&. 0xFF)]
|
||||
fieldInitBytes _ _ = []
|
||||
|
||||
pushExpr :: TypedExpr -> [Word8]
|
||||
pushExpr (Integer n _) = pushInt (fromIntegral n)
|
||||
pushExpr (Bool True _) = [0x04]
|
||||
pushExpr (Bool False _) = [0x03]
|
||||
pushExpr (Char c _) = pushInt (fromEnum c)
|
||||
pushExpr _ = []
|
||||
|
||||
pushInt :: Int -> [Word8]
|
||||
pushInt n
|
||||
| n >= -1 && n <= 5 = [fromIntegral (0x03 + n)]
|
||||
| n >= -128 && n <= 127 = [0x10, fromIntegral n]
|
||||
| n >= -32768 && n <= 32767 = [0x11, fromIntegral (n `shiftR` 8 .&. 0xFF), fromIntegral (n .&. 0xFF)]
|
||||
| otherwise = [0x10, 0]
|
||||
|
||||
makeMethod :: CP_Infos -> TypedMethodDecl -> Method_Info
|
||||
makeMethod constPool (Method returnType name params _body) =
|
||||
Method_Info
|
||||
{ methodAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
methodNameIndex = lookupUtf8Index constPool name,
|
||||
methodDescIndex = lookupUtf8Index constPool (methodDescriptor params returnType),
|
||||
methodAttributes = []
|
||||
}
|
||||
|
||||
methodDescriptor :: [(Type, String)] -> Type -> String
|
||||
methodDescriptor params returnType =
|
||||
"(" ++ concatMap (typeDescriptor . fst) params ++ ")" ++ typeDescriptor returnType
|
||||
|
||||
typeDescriptor :: Type -> String
|
||||
typeDescriptor t = case t of
|
||||
"int" -> "I"
|
||||
"boolean" -> "Z"
|
||||
"char" -> "C"
|
||||
"void" -> "V"
|
||||
_ -> "L" ++ t ++ ";"
|
||||
|
||||
+265
-52
@@ -1,54 +1,267 @@
|
||||
module ConstantPool where
|
||||
data Index_Constant_Pool
|
||||
module Codegen.ConstPool where
|
||||
|
||||
data CPInfo =
|
||||
ClassInfo
|
||||
{ tag_cp :: Tag
|
||||
, index_cp :: Index_Constant_Pool
|
||||
, desc :: String
|
||||
}
|
||||
| FieldRefInfo
|
||||
{ tag_cp :: Tag
|
||||
, index_name_cp :: Index_Constant_Pool
|
||||
, index_nameandtype_cp :: Index_Constant_Pool
|
||||
, desc :: String
|
||||
}
|
||||
| MethodRefInfo
|
||||
{ tag_cp :: Tag
|
||||
, index_name_cp :: Index_Constant_Pool
|
||||
, index_nameandtype_cp :: Index_Constant_Pool
|
||||
, desc :: String
|
||||
}
|
||||
| StringInfo
|
||||
{ tag_cp :: Tag
|
||||
, index_cp :: Index_Constant_Pool
|
||||
, desc :: String
|
||||
}
|
||||
| IntegerInfo
|
||||
{ tag_cp :: Tag
|
||||
, numi_cp :: Int
|
||||
, desc :: String
|
||||
}
|
||||
| NameAndTypeInfo
|
||||
{ tag_cp :: Tag
|
||||
, index_name_cp :: Index_Constant_Pool
|
||||
, index_descr_cp :: Index_Constant_Pool
|
||||
, desc :: String
|
||||
}
|
||||
| Utf8Info
|
||||
{ tag_cp :: Tag
|
||||
, tam_cp :: Int
|
||||
, cad_cp :: String
|
||||
, desc :: String
|
||||
}
|
||||
deriving ()
|
||||
import Codegen.Types
|
||||
import Data.List (isSuffixOf)
|
||||
import qualified Grammar.TAST as TAST
|
||||
|
||||
data Tag =
|
||||
TagClass
|
||||
| TagFieldRef
|
||||
| TagMethodRef
|
||||
| TagString
|
||||
| TagInteger
|
||||
| TagNameAndType
|
||||
| TagUtf8
|
||||
deriving (Eq, Ord, Show)
|
||||
------------------------------------------------------------------------
|
||||
-- Recursive extraction of Tags from an already-built pool
|
||||
------------------------------------------------------------------------
|
||||
|
||||
extractTags :: CP_Infos -> [Tag]
|
||||
extractTags [] = []
|
||||
extractTags (x : xs) = tag_cp x : extractTags xs
|
||||
|
||||
countEntries :: CP_Infos -> Int
|
||||
countEntries [] = 0
|
||||
countEntries (_ : xs) = 1 + countEntries xs
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Helper constructors
|
||||
------------------------------------------------------------------------
|
||||
|
||||
mkClassInfo :: IndexConstantPool -> String -> CP_Info
|
||||
mkClassInfo = ClassInfo TagClass
|
||||
|
||||
mkFieldRefInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkFieldRefInfo = FieldRefInfo TagFieldRef
|
||||
|
||||
mkMethodRefInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkMethodRefInfo = MethodRefInfo TagMethodRef
|
||||
|
||||
mkStringInfo :: IndexConstantPool -> String -> CP_Info
|
||||
mkStringInfo = StringInfo TagString
|
||||
|
||||
mkIntegerInfo :: Int -> String -> CP_Info
|
||||
mkIntegerInfo = IntegerInfo TagInteger
|
||||
|
||||
mkNameAndTypeInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkNameAndTypeInfo = NameAndTypeInfo TagNameAndType
|
||||
|
||||
mkUtf8Info :: String -> String -> CP_Info
|
||||
mkUtf8Info str = Utf8Info TagUtf8 (length str) str
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Tag utilities
|
||||
------------------------------------------------------------------------
|
||||
|
||||
tagToByte :: Tag -> Int
|
||||
tagToByte TagClass = 7
|
||||
tagToByte TagFieldRef = 9
|
||||
tagToByte TagMethodRef = 10
|
||||
tagToByte TagString = 8
|
||||
tagToByte TagInteger = 3
|
||||
tagToByte TagNameAndType = 12
|
||||
tagToByte TagUtf8 = 1
|
||||
|
||||
tagToString :: Tag -> String
|
||||
tagToString TagClass = "Class"
|
||||
tagToString TagFieldRef = "FieldRef"
|
||||
tagToString TagMethodRef = "MethodRef"
|
||||
tagToString TagString = "String"
|
||||
tagToString TagInteger = "Integer"
|
||||
tagToString TagNameAndType = "NameAndType"
|
||||
tagToString TagUtf8 = "Utf8"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Descriptor helpers
|
||||
------------------------------------------------------------------------
|
||||
|
||||
typeToDescriptor :: String -> String
|
||||
typeToDescriptor "int" = "I"
|
||||
typeToDescriptor "boolean" = "Z"
|
||||
typeToDescriptor "char" = "C"
|
||||
typeToDescriptor "void" = "V"
|
||||
typeToDescriptor t = "L" ++ t ++ ";"
|
||||
|
||||
methodDescriptor :: [(String, String)] -> String -> String
|
||||
methodDescriptor params ret =
|
||||
"(" ++ paramsDesc params ++ ")" ++ typeToDescriptor ret
|
||||
where
|
||||
paramsDesc [] = ""
|
||||
paramsDesc ((pty, _) : ps) = typeToDescriptor pty ++ paramsDesc ps
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Recursive traversal of TAST to collect CP_Info entries
|
||||
------------------------------------------------------------------------
|
||||
|
||||
collectFromExprs :: [TAST.TypedExpr] -> CP_Infos
|
||||
collectFromExprs [] = []
|
||||
collectFromExprs (e : es) = collectFromTypedExpr e ++ collectFromExprs es
|
||||
|
||||
collectFromTypedExpr :: TAST.TypedExpr -> CP_Infos
|
||||
collectFromTypedExpr (TAST.Integer n _) =
|
||||
[mkIntegerInfo (fromIntegral n) ("int " ++ show n)]
|
||||
collectFromTypedExpr (TAST.Bool _ _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.Char c _) =
|
||||
[mkIntegerInfo (fromEnum c) ("char " ++ show c)]
|
||||
collectFromTypedExpr (TAST.Null _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.This _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.LocalOrFieldVar _ _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.InstVar obj name typ) =
|
||||
collectFromTypedExpr obj
|
||||
++ [ mkUtf8Info name ("field name: " ++ name),
|
||||
mkUtf8Info (typeToDescriptor typ) ("field descriptor: " ++ typeToDescriptor typ),
|
||||
mkNameAndTypeInfo 0 0 ("field NameAndType: " ++ name),
|
||||
mkFieldRefInfo 0 0 ("field ref: " ++ name)
|
||||
]
|
||||
collectFromTypedExpr (TAST.Unary _ e _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedExpr (TAST.Binary _ e1 e2 _) =
|
||||
collectFromTypedExpr e1 ++ collectFromTypedExpr e2
|
||||
collectFromTypedExpr (TAST.StmtExprExpr se _) =
|
||||
collectFromTypedStmtExpr se
|
||||
|
||||
collectFromTypedStmtExpr :: TAST.TypedStmtExpr -> CP_Infos
|
||||
collectFromTypedStmtExpr (TAST.Assign lhs rhs _) =
|
||||
collectFromTypedExpr lhs ++ collectFromTypedExpr rhs
|
||||
collectFromTypedStmtExpr (TAST.New t args _) =
|
||||
collectFromExprs args
|
||||
++ [ mkUtf8Info t ("class name: " ++ t),
|
||||
mkClassInfo 0 ("class: " ++ t)
|
||||
]
|
||||
collectFromTypedStmtExpr (TAST.MethodCall obj name args retTyp) =
|
||||
collectFromTypedExpr obj
|
||||
++ collectFromExprs args
|
||||
++ [ mkUtf8Info name ("method name: " ++ name),
|
||||
mkUtf8Info (typeToDescriptor retTyp) ("method ret desc: " ++ typeToDescriptor retTyp),
|
||||
mkNameAndTypeInfo 0 0 ("method NameAndType: " ++ name),
|
||||
mkMethodRefInfo 0 0 ("method ref: " ++ name)
|
||||
]
|
||||
|
||||
collectFromStmts :: [TAST.TypedStmt] -> CP_Infos
|
||||
collectFromStmts [] = []
|
||||
collectFromStmts (s : ss) = collectFromTypedStmt s ++ collectFromStmts ss
|
||||
|
||||
collectFromTypedStmt :: TAST.TypedStmt -> CP_Infos
|
||||
collectFromTypedStmt (TAST.Block stmts _) =
|
||||
collectFromStmts stmts
|
||||
collectFromTypedStmt (TAST.Return (Just e) _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedStmt (TAST.Return Nothing _) =
|
||||
[]
|
||||
collectFromTypedStmt (TAST.While cond body _) =
|
||||
collectFromTypedExpr cond ++ collectFromTypedStmt body
|
||||
collectFromTypedStmt (TAST.LocalVarDecl _ _ _) =
|
||||
[]
|
||||
collectFromTypedStmt (TAST.If cond then_ mElse _) =
|
||||
collectFromTypedExpr cond
|
||||
++ collectFromTypedStmt then_
|
||||
++ collectFromMaybeStmt mElse
|
||||
collectFromTypedStmt (TAST.StmtExprStmt se _) =
|
||||
collectFromTypedStmtExpr se
|
||||
|
||||
collectFromMaybeStmt :: Maybe TAST.TypedStmt -> CP_Infos
|
||||
collectFromMaybeStmt Nothing = []
|
||||
collectFromMaybeStmt (Just s) = collectFromTypedStmt s
|
||||
|
||||
collectFromFields :: [TAST.TypedFieldDecl] -> CP_Infos
|
||||
collectFromFields [] = []
|
||||
collectFromFields (TAST.Field fType fName _: fs ) =
|
||||
[ mkUtf8Info fName ("field: " ++ fName),
|
||||
|
||||
mkUtf8Info (typeToDescriptor fType) ("field desc: " ++ fType)
|
||||
]
|
||||
++ collectFromFields fs
|
||||
|
||||
collectFromMethods :: [TAST.TypedMethodDecl] -> CP_Infos
|
||||
collectFromMethods [] = []
|
||||
collectFromMethods (TAST.Method mType mName params body : ms) =
|
||||
[ mkUtf8Info mName ("method: " ++ mName),
|
||||
mkUtf8Info (methodDescriptor params mType) ("method desc: " ++ mName)
|
||||
]
|
||||
++ collectFromTypedStmt body
|
||||
++ collectFromMethods ms
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Build the complete constant pool for a class
|
||||
------------------------------------------------------------------------
|
||||
|
||||
generateConstPool :: TAST.TypedClass -> CP_Infos
|
||||
generateConstPool (TAST.Class cName fields methods) =
|
||||
[ mkUtf8Info cName ("this class: " ++ cName), -- #1
|
||||
mkUtf8Info "java/lang/Object" "super class", -- #2
|
||||
mkClassInfo 1 ("this class: " ++ cName), -- #3 → #1
|
||||
mkClassInfo 2 "super class", -- #4 → #2
|
||||
mkUtf8Info "<init>" "<init>", -- #5
|
||||
mkUtf8Info "()V" "init descriptor", -- #6
|
||||
mkNameAndTypeInfo 5 6 "<init>:()V", -- #7
|
||||
mkMethodRefInfo 4 7 "java/lang/Object.<init>:()V", -- #8
|
||||
mkUtf8Info "Code" "Code" -- #9
|
||||
]
|
||||
++ collectFromFields fields
|
||||
++ buildInitFieldPool fields (length fields)
|
||||
++ collectFromMethods methods
|
||||
|
||||
-- Build NameAndType + Fieldref entries for each field with an initializer.
|
||||
-- The base pool has 9 fixed entries; collectFromFields adds 2 per field.
|
||||
-- So for field at 0-based index i: name Utf8 = #(9 + 2i + 1), desc = #(9 + 2i + 2).
|
||||
-- NameAndType entries follow at #(9 + 2*numFields + 2j + 1) for j-th init field.
|
||||
buildInitFieldPool :: [TAST.TypedFieldDecl] -> Int -> CP_Infos
|
||||
buildInitFieldPool fields numFields = go 0 0 fields
|
||||
where
|
||||
go _ _ [] = []
|
||||
go fieldIdx initIdx (TAST.Field _ _ Nothing : rest) =
|
||||
go (fieldIdx + 1) initIdx rest
|
||||
go fieldIdx initIdx (TAST.Field _ name _ : rest) =
|
||||
let nameUtf8Idx = 9 + 2 * fieldIdx + 1
|
||||
descUtf8Idx = 9 + 2 * fieldIdx + 2
|
||||
natCpIdx = 9 + 2 * numFields + 2 * initIdx + 1
|
||||
in [ mkNameAndTypeInfo nameUtf8Idx descUtf8Idx ("field NameAndType: " ++ name),
|
||||
mkFieldRefInfo 3 natCpIdx ("field ref: " ++ name)
|
||||
]
|
||||
++ go (fieldIdx + 1) (initIdx + 1) rest
|
||||
|
||||
lookupClassIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupClassIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
ClassInfo {desc = descVal}
|
||||
| name == "java/lang/Object" -> descVal == "super class" || isSuffixOf name descVal
|
||||
| otherwise -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
lookupUtf8Index :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupUtf8Index pool value =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
Utf8Info {cad_cp = cadVal, desc = descVal} -> cadVal == value || isSuffixOf value descVal
|
||||
_ -> False
|
||||
|
||||
lookupNameAndTypeIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupNameAndTypeIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> lookupUtf8Index pool name
|
||||
where
|
||||
matches entry = case entry of
|
||||
NameAndTypeInfo {desc = descVal} -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
lookupFieldRefIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupFieldRefIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
FieldRefInfo {desc = descVal} -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
findIndex :: (CP_Info -> Bool) -> CP_Infos -> Maybe IndexConstantPool
|
||||
findIndex predicate pool = go 1 pool
|
||||
where
|
||||
go _ [] = Nothing
|
||||
go i (x : xs)
|
||||
| predicate x = Just i
|
||||
| otherwise = go (i + 1) xs
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module Codegen.Lowerer where
|
||||
|
||||
import Codegen.ClassFile
|
||||
import Codegen.Types (CP_Info (..), CP_Infos)
|
||||
import Data.Word (Word8)
|
||||
import Numeric (showHex)
|
||||
|
||||
@@ -50,7 +51,7 @@ lowerClassFile cf =
|
||||
methodName :: ClassFile -> Method_Info -> String
|
||||
methodName cf m =
|
||||
case lookupPool (constantPool cf) (methodNameIndex m) of
|
||||
Just (Utf8_Info name) -> name
|
||||
Just (Utf8Info {cad_cp = name}) -> name
|
||||
_ -> "<unknown>"
|
||||
|
||||
-- Lower a single method's Code attribute into a BtcProgram
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
module Codegen.Serializer where
|
||||
|
||||
import Data.Bits (shiftR, (.&.))
|
||||
import Data.Word (Word8)
|
||||
import Codegen.ClassFile
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
import Codegen.Types
|
||||
import Data.Bits (shiftR, (.&.), (.|.))
|
||||
import Data.Char (ord)
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
|
||||
-- Split a 16-bit Int into two bytes (big-endian), as JVM expects
|
||||
indexBytes :: Int -> [Word8]
|
||||
@@ -77,3 +81,51 @@ serializeInstruction instr = case instr of
|
||||
serializeProgram :: BtcProgram -> [Word8]
|
||||
serializeProgram (BtcProgram lines) =
|
||||
concatMap (serializeInstruction . instruction) lines
|
||||
|
||||
serializeClassFile :: ClassFile -> [Word8]
|
||||
serializeClassFile cf =
|
||||
u4 (let Magic m = magic cf in m)
|
||||
++ u2 (let MinorVersion v = minorVersion cf in v)
|
||||
++ u2 (let MajorVersion v = majorVersion cf in v)
|
||||
++ u2 (length (constantPool cf) + 1)
|
||||
++ concatMap cpEntry (constantPool cf)
|
||||
++ aflags (accessFlags cf)
|
||||
++ u2 (let ThisClass i = thisClass cf in i)
|
||||
++ u2 (let SuperClass i = superClass cf in i)
|
||||
++ u2 (length (interfaces cf))
|
||||
++ concatMap (\i -> u2 (fromIntegral (i :: Word16) :: Int)) (interfaces cf)
|
||||
++ u2 (length (fields cf)) ++ concatMap fieldEntry (fields cf)
|
||||
++ u2 (length (methods cf)) ++ concatMap methodEntry (methods cf)
|
||||
++ u2 (length (attributes cf)) ++ concatMap attrEntry (attributes cf)
|
||||
where
|
||||
u2 :: Int -> [Word8]
|
||||
u2 n = [fromIntegral ((n `shiftR` 8) .&. 0xFF), fromIntegral (n .&. 0xFF)]
|
||||
u4 :: (Integral a) => a -> [Word8]
|
||||
u4 n = let w = fromIntegral n :: Word32
|
||||
in map (\s -> fromIntegral ((w `shiftR` s) .&. 0xFF)) [24,16,8,0]
|
||||
aflags (AccessFlags fs) = u2 (foldr (\f a -> a .|. fval f) (0 :: Int) fs)
|
||||
fval :: AccessFlag -> Int
|
||||
fval ACC_PUBLIC = 0x0001; fval ACC_PRIVATE = 0x0002
|
||||
fval ACC_PROTECTED = 0x0004; fval ACC_STATIC = 0x0008
|
||||
fval ACC_FINAL = 0x0010; fval ACC_SUPER = 0x0020
|
||||
fval ACC_INTERFACE = 0x0200; fval ACC_ABSTRACT = 0x0400
|
||||
fval ACC_SYNTHETIC = 0x1000; fval ACC_ENUM = 0x4000
|
||||
cpEntry (Utf8Info _ _ s _) = [1] ++ u2 (length s) ++ map (fromIntegral . ord) s
|
||||
cpEntry (ClassInfo _ i _) = [7] ++ u2 i
|
||||
cpEntry (FieldRefInfo _ c n _) = [9] ++ u2 c ++ u2 n
|
||||
cpEntry (MethodRefInfo _ c n _) = [10] ++ u2 c ++ u2 n
|
||||
cpEntry (StringInfo _ i _) = [8] ++ u2 i
|
||||
cpEntry (IntegerInfo _ v _) = [3] ++ u4 v
|
||||
cpEntry (NameAndTypeInfo _ n d _) = [12] ++ u2 n ++ u2 d
|
||||
fieldEntry fi = aflags (fieldAccessFlags fi) ++ u2 (fieldNameIndex fi)
|
||||
++ u2 (fieldDescIndex fi) ++ u2 (0 :: Int)
|
||||
methodEntry mi = aflags (methodAccessFlags mi) ++ u2 (methodNameIndex mi)
|
||||
++ u2 (methodDescIndex mi) ++ u2 (length (methodAttributes mi))
|
||||
++ concatMap attrEntry (methodAttributes mi)
|
||||
attrEntry (Code_Attribute ni ms ml code exc ca) =
|
||||
let body = u2 ms ++ u2 ml ++ u4 (length code) ++ code
|
||||
++ u2 (length exc) ++ concatMap excEntry exc
|
||||
++ u2 (length ca) ++ concatMap attrEntry ca
|
||||
in u2 ni ++ u4 (length body) ++ body
|
||||
attrEntry (Generic_Attribute ni dat) = u2 ni ++ u4 (length dat) ++ dat
|
||||
excEntry e = u2 (startPc e) ++ u2 (endPc e) ++ u2 (handlerPc e) ++ u2 (catchType e)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
module Codegen.Types where
|
||||
|
||||
type IndexConstantPool = Int
|
||||
|
||||
type CP_Infos = [CP_Info]
|
||||
|
||||
data CP_Info
|
||||
= ClassInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| FieldRefInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_nameandtype_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| MethodRefInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_nameandtype_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| StringInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| IntegerInfo
|
||||
{ tag_cp :: Tag,
|
||||
numi_cp :: Int,
|
||||
desc :: String
|
||||
}
|
||||
| NameAndTypeInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_descr_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| Utf8Info
|
||||
{ tag_cp :: Tag,
|
||||
tam_cp :: Int,
|
||||
cad_cp :: String,
|
||||
desc :: String
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data Tag
|
||||
= TagClass
|
||||
| TagFieldRef
|
||||
| TagMethodRef
|
||||
| TagString
|
||||
| TagInteger
|
||||
| TagNameAndType
|
||||
| TagUtf8
|
||||
deriving (Eq, Ord, Show)
|
||||
+1
-1
@@ -6,7 +6,7 @@ type Program = [Class]
|
||||
|
||||
data Class = Class Type [FieldDecl] [MethodDecl] deriving (Show, Eq)
|
||||
|
||||
data FieldDecl = Field Type String deriving (Show, Eq)
|
||||
data FieldDecl = Field Type String (Maybe Expr) deriving (Show, Eq)
|
||||
|
||||
data MethodDecl = Method Type String [(Type, String)] Stmt deriving (Show, Eq)
|
||||
|
||||
|
||||
+19
-5
@@ -10,6 +10,8 @@ import Grammar.AST
|
||||
%error { parseError }
|
||||
|
||||
%token
|
||||
public { TokenVisibility Public }
|
||||
private { TokenVisibility Private }
|
||||
class { TokenClass }
|
||||
void { TokenVoid }
|
||||
return { TokenReturn }
|
||||
@@ -68,15 +70,26 @@ Classes : Classes ClassDecl { $2 : $1 }
|
||||
| ClassDecl { [$1] }
|
||||
|
||||
ClassDecl : class id '{' Decls '}' { Class $2 (fst $4) (snd $4) }
|
||||
| public class id '{' Decls '}' { Class $3 (fst $5) (snd $5) }
|
||||
| private class id '{' Decls '}' { Class $3 (fst $5) (snd $5) }
|
||||
|
||||
Decls : FieldDecl Decls { ($1 : fst $2, snd $2) }
|
||||
| MethodDecl Decls { (fst $2, $1 : snd $2) }
|
||||
| {- leer -} { ([], []) }
|
||||
|
||||
FieldDecl : Type id ';' { Field $1 $2 }
|
||||
FieldDecl : Type id ';' { Field $1 $2 Nothing }
|
||||
| public Type id ';' { Field $2 $3 Nothing }
|
||||
| private Type id ';' { Field $2 $3 Nothing }
|
||||
| Type id '=' Expr ';' { Field $1 $2 (Just $4) }
|
||||
| public Type id '=' Expr ';' { Field $2 $3 (Just $5) }
|
||||
| private Type id '=' Expr ';' { Field $2 $3 (Just $5) }
|
||||
|
||||
MethodDecl : Type id '(' Params ')' Block { Method $1 $2 $4 $6 }
|
||||
| void id '(' Params ')' Block { Method "void" $2 $4 $6 }
|
||||
MethodDecl : Type id '(' Params ')' Block { Method $1 $2 $4 $6 }
|
||||
| public Type id '(' Params ')' Block { Method $2 $3 $5 $7 }
|
||||
| private Type id '(' Params ')' Block { Method $2 $3 $5 $7 }
|
||||
| void id '(' Params ')' Block { Method "void" $2 $4 $6 }
|
||||
| public void id '(' Params ')' Block { Method "void" $3 $5 $7 }
|
||||
| private void id '(' Params ')' Block { Method "void" $3 $5 $7 }
|
||||
|
||||
Params : ParamList { $1 }
|
||||
| {- leer -} { [] }
|
||||
@@ -97,7 +110,8 @@ Stmts : Stmts Stmt { $2 : $1 }
|
||||
| {- leer -} { [] }
|
||||
|
||||
Stmt : Block { $1 }
|
||||
| return Expr ';' { Return $2 }
|
||||
| return Expr ';' { Return (Just $2) }
|
||||
| return ';' { Return Nothing }
|
||||
| while '(' Expr ')' Stmt { While $3 $5 }
|
||||
| if '(' Expr ')' Stmt else Stmt { If $3 $5 (Just $7) }
|
||||
| if '(' Expr ')' Stmt { If $3 $5 Nothing }
|
||||
@@ -141,5 +155,5 @@ ExprList : Expr { [$1] }
|
||||
|
||||
{
|
||||
parseError :: [Token] -> a
|
||||
parseError tokens = error $ "Lexical error or syntax error at: " ++ show tokens
|
||||
parseError tokens = error $ "abc Lexical error or syntax error at: " ++ show tokens
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ tokens :-
|
||||
"/*"([^\*]|(\*+([^\*\/])))*\*+\/ ; -- multi-line comments
|
||||
|
||||
-- key words
|
||||
public { \_ -> TokenVisibility Public }
|
||||
private { \_ -> TokenVisibility Private }
|
||||
class { \_ -> TokenClass }
|
||||
void { \_ -> TokenVoid }
|
||||
return { \_ -> TokenReturn }
|
||||
@@ -49,8 +51,14 @@ tokens :-
|
||||
$alpha [$alpha $digit]* { \s -> TokenIdent s }
|
||||
|
||||
{
|
||||
data Visibility
|
||||
= Public
|
||||
| Private
|
||||
deriving (Eq, Show)
|
||||
|
||||
data Token
|
||||
= TokenClass
|
||||
= TokenVisibility Visibility
|
||||
| TokenClass
|
||||
| TokenStatic
|
||||
| TokenVoid
|
||||
| TokenReturn
|
||||
|
||||
@@ -2,6 +2,12 @@ module Grammar.TAST where
|
||||
|
||||
import Grammar.AST (BinaryOperator, Type, UnaryOperator)
|
||||
|
||||
data TypedClass = Class Type [TypedFieldDecl] [TypedMethodDecl] deriving (Show, Eq)
|
||||
|
||||
data TypedFieldDecl = Field Type String (Maybe TypedExpr) deriving (Show, Eq)
|
||||
|
||||
data TypedMethodDecl = Method Type String [(Type, String)] TypedStmt deriving (Show, Eq)
|
||||
|
||||
data TypedExpr
|
||||
= This Type
|
||||
| LocalOrFieldVar String Type
|
||||
|
||||
+49
-5
@@ -1,10 +1,54 @@
|
||||
module Main where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.TAST
|
||||
import Typecheck.SemanticChecker
|
||||
import Codegen.ClassFile (ClassFile, generateClassFile)
|
||||
import Codegen.Serializer (serializeClassFile)
|
||||
import qualified Data.ByteString as BS
|
||||
import Grammar.AST (Program)
|
||||
import Grammar.Parser (parse)
|
||||
import Grammar.Scanner (Token, alexScanTokens)
|
||||
import Grammar.TAST (TypedClass (Class))
|
||||
import System.Environment (getArgs)
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let t = getTypeFromExpr $ TypedExpr This "myType"
|
||||
putStrLn t
|
||||
args <- getArgs
|
||||
case args of
|
||||
[path] -> runPipeline path
|
||||
_ -> putStrLn "Usage: compoiler <input.java>"
|
||||
|
||||
runPipeline :: FilePath -> IO ()
|
||||
runPipeline path = do
|
||||
source <- readFile path
|
||||
let tokens = scan source
|
||||
let ast = parseProgram tokens
|
||||
let typedClasses = typeCheckProgram ast
|
||||
let classFiles = map generateClassFile typedClasses
|
||||
mapM_ writeClassFile (zip typedClasses classFiles)
|
||||
reportSuccess ast typedClasses classFiles
|
||||
|
||||
writeClassFile :: (TypedClass, ClassFile) -> IO ()
|
||||
writeClassFile (tc, cf) = do
|
||||
let outPath = "out/" ++ className tc ++ ".class"
|
||||
BS.writeFile outPath (BS.pack (serializeClassFile cf))
|
||||
putStrLn ("Written: " ++ outPath)
|
||||
|
||||
scan :: String -> [Token]
|
||||
scan = alexScanTokens
|
||||
|
||||
parseProgram :: [Token] -> Program
|
||||
parseProgram = parse
|
||||
|
||||
typeCheckProgram :: Program -> [TypedClass]
|
||||
typeCheckProgram classes =
|
||||
map (\cls -> typeCheckClass cls [] classes) classes
|
||||
|
||||
reportSuccess :: Program -> [TypedClass] -> [ClassFile] -> IO ()
|
||||
reportSuccess ast typed classFiles = do
|
||||
putStrLn ("Parsed classes: " ++ show (length ast))
|
||||
putStrLn ("Typed classes: " ++ show (length typed))
|
||||
putStrLn ("Generated class files: " ++ show (length classFiles))
|
||||
putStrLn ("Class names: " ++ unwords (map className typed))
|
||||
|
||||
className :: TypedClass -> String
|
||||
className (Class name _ _) = name
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
module Testsuite.AbcFiles.ArithmeticTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "basic",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 1, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 2, instruction = IAdd},
|
||||
BtcLine {lineNumber = 3, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 4, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 5, instruction = IMul},
|
||||
BtcLine {lineNumber = 6, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 7, instruction = ISub},
|
||||
BtcLine {lineNumber = 8, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 9, instruction = ISub},
|
||||
BtcLine {lineNumber = 10, instruction = IReturn}
|
||||
]
|
||||
),
|
||||
( "logic",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 1, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 2, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 3, instruction = IAdd},
|
||||
BtcLine {lineNumber = 4, instruction = IAdd},
|
||||
BtcLine {lineNumber = 5, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
module Testsuite.AbcFiles.EmptyTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 1},
|
||||
BtcLine {lineNumber = 4, instruction = Return}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,48 @@
|
||||
module Testsuite.AstFiles.ArithmeticTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"ArithmeticTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"basic"
|
||||
[("int", "a"), ("int", "b"), ("int", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Subtract
|
||||
(Binary Add (LocalOrFieldVar "a") (LocalOrFieldVar "b"))
|
||||
( Binary
|
||||
Modulo
|
||||
( Binary
|
||||
Divide
|
||||
(Binary Multiply (LocalOrFieldVar "c") (LocalOrFieldVar "a"))
|
||||
(LocalOrFieldVar "b")
|
||||
)
|
||||
(LocalOrFieldVar "c")
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
Method
|
||||
"boolean"
|
||||
"logic"
|
||||
[("boolean", "a"), ("boolean", "b"), ("boolean", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
And
|
||||
(Unary Not (LocalOrFieldVar "a"))
|
||||
(Binary Or (LocalOrFieldVar "c") (LocalOrFieldVar "b"))
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
module Testsuite.AstFiles.EmptyTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"EmptyTest"
|
||||
[]
|
||||
[]
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
import Control.Exception (throwIO)
|
||||
import Control.Monad (unless)
|
||||
import Data.List (sort, isSuffixOf)
|
||||
import System.Directory (doesDirectoryExist, listDirectory, doesFileExist)
|
||||
import System.Exit (ExitCode (..), exitFailure)
|
||||
import System.FilePath (takeExtension, takeFileName, (</>), isExtensionOf, takeDirectory)
|
||||
import System.Process (readProcessWithExitCode)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..), exitOnFailures, printTestResults, runNamedTests)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let executableTestsDir = "src/Testsuite/ExecutableTests"
|
||||
directoryExists <- doesDirectoryExist executableTestsDir
|
||||
unless directoryExists $ do
|
||||
putStrLn ("ExecutableTests directory not found: " ++ executableTestsDir)
|
||||
exitFailure
|
||||
|
||||
harnessFiles <- discoverHarnessFiles executableTestsDir
|
||||
if null harnessFiles
|
||||
then do
|
||||
putStrLn "No harness files found to execute."
|
||||
exitFailure
|
||||
else do
|
||||
let namedHarnesses =
|
||||
[ (takeFileName harnessFile, runHarness harnessFile)
|
||||
| harnessFile <- harnessFiles
|
||||
]
|
||||
results <- runNamedTests namedHarnesses
|
||||
printTestResults "Full Testsuite Harness" results
|
||||
exitOnFailures results
|
||||
|
||||
discoverHarnessFiles :: FilePath -> IO [FilePath]
|
||||
discoverHarnessFiles baseDir = do
|
||||
entries <- listDirectory baseDir
|
||||
harnessFiles <- concat <$> mapM (findHarnessesInDir baseDir) entries
|
||||
pure (sort harnessFiles)
|
||||
|
||||
findHarnessesInDir :: FilePath -> String -> IO [FilePath]
|
||||
findHarnessesInDir baseDir entry = do
|
||||
let fullPath = baseDir </> entry
|
||||
isDir <- doesDirectoryExist fullPath
|
||||
if isDir && entry /= "." && entry /= ".."
|
||||
then do
|
||||
subEntries <- listDirectory fullPath
|
||||
let harnesses =
|
||||
[ fullPath </> file
|
||||
| file <- subEntries,
|
||||
takeExtension file == ".hs",
|
||||
"Harness.hs" `isSuffixOf` file,
|
||||
file /= "AllHarness.hs"
|
||||
]
|
||||
pure harnesses
|
||||
else pure []
|
||||
|
||||
runHarness :: FilePath -> IO ()
|
||||
runHarness harnessPath = do
|
||||
(exitCode, stdOut, stdErr) <- readProcessWithExitCode "runghc" ["-isrc", harnessPath] ""
|
||||
putStrLn ("\n--- Output from " ++ takeFileName harnessPath ++ " ---")
|
||||
unless (null stdOut) (putStrLn stdOut)
|
||||
unless (null stdErr) (putStrLn stdErr)
|
||||
case exitCode of
|
||||
ExitSuccess -> pure ()
|
||||
ExitFailure _ -> throwIO (TestFailure "")
|
||||
@@ -0,0 +1,30 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticABCTest where
|
||||
|
||||
import Codegen.ClassFile (generateClassFile)
|
||||
import Codegen.Lowerer (BtcProgram, lowerClassFile)
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AbcFiles.ArithmeticTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Testsuite.TastFiles.ArithmeticTestTAST (expectedTAST)
|
||||
|
||||
mainArithmeticABCTest :: IO ()
|
||||
mainArithmeticABCTest = do
|
||||
let actualABC = generateABCProgram expectedTAST
|
||||
if actualABC == expectedABC
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual ABC:",
|
||||
show actualABC,
|
||||
"Expected ABC:",
|
||||
show expectedABC
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
generateABCProgram :: [TypedClass] -> [(String, BtcProgram)]
|
||||
generateABCProgram [typedClass] = lowerClassFile (generateClassFile typedClass)
|
||||
generateABCProgram _ = error "Expected exactly one typed class for the ABC test"
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticASTTest where
|
||||
|
||||
import Grammar.Parser
|
||||
import Grammar.Scanner
|
||||
import Control.Exception (throwIO)
|
||||
import Testsuite.AstFiles.ArithmeticTestAST (expectedAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainArithmeticASTTest :: IO ()
|
||||
mainArithmeticASTTest = do
|
||||
let javaFilePath = "src/Testsuite/javaFiles/ArithmeticTest.java"
|
||||
java <- readFile javaFilePath
|
||||
let actualAST = parse . alexScanTokens $ java
|
||||
if actualAST == expectedAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Arithmetic AST test failed.",
|
||||
"Actual AST:",
|
||||
show actualAST,
|
||||
"Expected AST:",
|
||||
show expectedAST
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,227 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticBytecodeBytesTest where
|
||||
|
||||
import Codegen.Lowerer (BtcProgram)
|
||||
import Codegen.Serializer (serializeProgram)
|
||||
import Control.Exception (throwIO)
|
||||
import Data.Bits ((.|.), shiftL)
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Testsuite.AbcFiles.ArithmeticTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainArithmeticBytecodeBytesTest :: IO ()
|
||||
mainArithmeticBytecodeBytesTest = do
|
||||
let classFilePath = "src/Testsuite/classFiles/ArithmeticTest.class"
|
||||
methodCodes <- parseMethodCodeBytesFromFile classFilePath
|
||||
failures <- fmap concat (mapM (checkMethod methodCodes) expectedABC)
|
||||
if null failures
|
||||
then pure ()
|
||||
else throwIO (TestFailure (unlines failures))
|
||||
|
||||
checkMethod :: [(String, [Word8])] -> (String, BtcProgram) -> IO [String]
|
||||
checkMethod methodCodes (methodName, expectedProgram) = do
|
||||
let expectedBytes = serializeProgram expectedProgram
|
||||
maybeActualBytes = lookup methodName methodCodes
|
||||
case maybeActualBytes of
|
||||
Nothing ->
|
||||
pure
|
||||
[ "Method '" ++ methodName ++ "' not found in reference class file"
|
||||
]
|
||||
Just actualBytes ->
|
||||
if expectedBytes == actualBytes
|
||||
then pure []
|
||||
else
|
||||
pure ["Method '" ++ methodName ++ "' bytecode mismatch"]
|
||||
|
||||
newtype Parser a = Parser
|
||||
{ runParser :: [Word8] -> Either String (a, [Word8])
|
||||
}
|
||||
|
||||
instance Functor Parser where
|
||||
fmap f p = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
pure (f value, rest)
|
||||
|
||||
instance Applicative Parser where
|
||||
pure value = Parser $ \input -> Right (value, input)
|
||||
pf <*> px = Parser $ \input -> do
|
||||
(f, rest1) <- runParser pf input
|
||||
(x, rest2) <- runParser px rest1
|
||||
pure (f x, rest2)
|
||||
|
||||
instance Monad Parser where
|
||||
p >>= f = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
runParser (f value) rest
|
||||
|
||||
parseFail :: String -> Parser a
|
||||
parseFail msg = Parser $ \_ -> Left msg
|
||||
|
||||
u1 :: Parser Word8
|
||||
u1 = Parser $ \input ->
|
||||
case input of
|
||||
[] -> Left "Unexpected end of input while reading u1"
|
||||
(b : rest) -> Right (b, rest)
|
||||
|
||||
u2 :: Parser Word16
|
||||
u2 = do
|
||||
hi <- u1
|
||||
lo <- u1
|
||||
pure ((fromIntegral hi `shiftL` 8) .|. fromIntegral lo)
|
||||
|
||||
u4 :: Parser Word32
|
||||
u4 = do
|
||||
b1 <- u1
|
||||
b2 <- u1
|
||||
b3 <- u1
|
||||
b4 <- u1
|
||||
pure
|
||||
( (fromIntegral b1 `shiftL` 24)
|
||||
.|. (fromIntegral b2 `shiftL` 16)
|
||||
.|. (fromIntegral b3 `shiftL` 8)
|
||||
.|. fromIntegral b4
|
||||
)
|
||||
|
||||
takeN :: Int -> Parser [Word8]
|
||||
takeN n
|
||||
| n < 0 = parseFail "Negative takeN requested"
|
||||
| otherwise = Parser $ \input ->
|
||||
if length input < n
|
||||
then Left ("Unexpected end of input while reading " ++ show n ++ " bytes")
|
||||
else Right (splitAt n input)
|
||||
|
||||
skipN :: Int -> Parser ()
|
||||
skipN n = do
|
||||
_ <- takeN n
|
||||
pure ()
|
||||
|
||||
parseMethodCodeBytesFromFile :: FilePath -> IO [(String, [Word8])]
|
||||
parseMethodCodeBytesFromFile path = do
|
||||
bytes <- BS.readFile path
|
||||
case runParser parseClassFile (BS.unpack bytes) of
|
||||
Left err -> error ("Failed parsing class file '" ++ path ++ "': " ++ err)
|
||||
Right (methods, _) -> pure methods
|
||||
|
||||
parseClassFile :: Parser [(String, [Word8])]
|
||||
parseClassFile = do
|
||||
magic <- u4
|
||||
if magic /= 0xCAFEBABE
|
||||
then parseFail "Invalid class file magic"
|
||||
else pure ()
|
||||
|
||||
_minorVersion <- u2
|
||||
_majorVersion <- u2
|
||||
|
||||
cpCount <- fromIntegral <$> u2
|
||||
utf8Map <- parseConstantPool cpCount 1 Map.empty
|
||||
|
||||
_accessFlags <- u2
|
||||
_thisClass <- u2
|
||||
_superClass <- u2
|
||||
|
||||
interfacesCount <- fromIntegral <$> u2
|
||||
skipN (interfacesCount * 2)
|
||||
|
||||
fieldsCount <- fromIntegral <$> u2
|
||||
skipMembers fieldsCount
|
||||
|
||||
methodsCount <- fromIntegral <$> u2
|
||||
parseMethods utf8Map methodsCount
|
||||
|
||||
parseConstantPool :: Int -> Int -> Map.Map Int String -> Parser (Map.Map Int String)
|
||||
parseConstantPool cpCount idx utf8Map
|
||||
| idx >= cpCount = pure utf8Map
|
||||
| otherwise = do
|
||||
tag <- u1
|
||||
case tag of
|
||||
1 -> do
|
||||
len <- fromIntegral <$> u2
|
||||
bytes <- takeN len
|
||||
let value = map (toEnum . fromIntegral) bytes
|
||||
parseConstantPool cpCount (idx + 1) (Map.insert idx value utf8Map)
|
||||
3 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
4 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
5 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
6 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
7 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
8 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
9 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
10 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
11 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
12 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
15 -> skipN 3 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
16 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
17 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
18 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
19 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
20 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
_ -> parseFail ("Unknown constant pool tag " ++ show tag)
|
||||
|
||||
skipMembers :: Int -> Parser ()
|
||||
skipMembers 0 = pure ()
|
||||
skipMembers n = do
|
||||
_accessFlags <- u2
|
||||
_nameIndex <- u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
skipAttributes attrCount
|
||||
skipMembers (n - 1)
|
||||
|
||||
skipAttributes :: Int -> Parser ()
|
||||
skipAttributes 0 = pure ()
|
||||
skipAttributes n = do
|
||||
_attrNameIndex <- u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
skipN attrLen
|
||||
skipAttributes (n - 1)
|
||||
|
||||
parseMethods :: Map.Map Int String -> Int -> Parser [(String, [Word8])]
|
||||
parseMethods _ 0 = pure []
|
||||
parseMethods utf8Map n = do
|
||||
_accessFlags <- u2
|
||||
nameIndex <- fromIntegral <$> u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
|
||||
let methodName = Map.findWithDefault ("<unknown-" ++ show nameIndex ++ ">") nameIndex utf8Map
|
||||
|
||||
maybeCode <- parseMethodAttributes utf8Map attrCount
|
||||
rest <- parseMethods utf8Map (n - 1)
|
||||
case maybeCode of
|
||||
Nothing -> pure rest
|
||||
Just code -> pure ((methodName, code) : rest)
|
||||
|
||||
parseMethodAttributes :: Map.Map Int String -> Int -> Parser (Maybe [Word8])
|
||||
parseMethodAttributes _ 0 = pure Nothing
|
||||
parseMethodAttributes utf8Map n = do
|
||||
attrNameIndex <- fromIntegral <$> u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
let attrName = Map.findWithDefault "" attrNameIndex utf8Map
|
||||
|
||||
current <-
|
||||
if attrName == "Code"
|
||||
then parseCodeAttribute attrLen
|
||||
else skipN attrLen >> pure Nothing
|
||||
|
||||
next <- parseMethodAttributes utf8Map (n - 1)
|
||||
pure (pickFirst current next)
|
||||
|
||||
pickFirst :: Maybe a -> Maybe a -> Maybe a
|
||||
pickFirst (Just x) _ = Just x
|
||||
pickFirst Nothing y = y
|
||||
|
||||
parseCodeAttribute :: Int -> Parser (Maybe [Word8])
|
||||
parseCodeAttribute _declaredLength = do
|
||||
_maxStack <- u2
|
||||
_maxLocals <- u2
|
||||
codeLen <- fromIntegral <$> u4
|
||||
codeBytes <- takeN codeLen
|
||||
|
||||
exceptionTableLength <- fromIntegral <$> u2
|
||||
skipN (exceptionTableLength * 8)
|
||||
|
||||
nestedAttrCount <- fromIntegral <$> u2
|
||||
skipAttributes nestedAttrCount
|
||||
|
||||
pure (Just codeBytes)
|
||||
@@ -0,0 +1,19 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticHarness where
|
||||
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticABCTest (mainArithmeticABCTest)
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticASTTest (mainArithmeticASTTest)
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticBytecodeBytesTest (mainArithmeticBytecodeBytesTest)
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticTASTTest (mainArithmeticTASTTest)
|
||||
import Testsuite.ExecutableTests.HarnessSupport
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let tests =
|
||||
[ ("Arithmetic AST", mainArithmeticASTTest),
|
||||
("Arithmetic TAST", mainArithmeticTASTTest),
|
||||
("Arithmetic ABC", mainArithmeticABCTest),
|
||||
("Arithmetic Bytecode Bytes", mainArithmeticBytecodeBytesTest)
|
||||
]
|
||||
results <- runNamedTests tests
|
||||
printTestResults "Arithmetic Harness" results
|
||||
exitOnFailures results
|
||||
@@ -0,0 +1,29 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticTASTTest where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.AST (Class)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AstFiles.ArithmeticTestAST (expectedAST)
|
||||
import Testsuite.TastFiles.ArithmeticTestTAST (expectedTAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
mainArithmeticTASTTest :: IO ()
|
||||
mainArithmeticTASTTest = do
|
||||
let actualTAST = typeCheckProgram expectedAST
|
||||
if actualTAST == expectedTAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual TAST:",
|
||||
show actualTAST,
|
||||
"Expected TAST:",
|
||||
show expectedTAST
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
typeCheckProgram :: [Class] -> [TypedClass]
|
||||
typeCheckProgram classes = map (\cls -> typeCheckClass cls [] classes) classes
|
||||
@@ -0,0 +1,30 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyABCTest where
|
||||
|
||||
import Codegen.ClassFile (generateClassFile)
|
||||
import Codegen.Lowerer (BtcProgram, lowerClassFile)
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AbcFiles.EmptyTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Testsuite.TastFiles.EmptyTestTAST (expectedTAST)
|
||||
|
||||
mainEmptyABCTest :: IO ()
|
||||
mainEmptyABCTest = do
|
||||
let actualABC = generateABCProgram expectedTAST
|
||||
if actualABC == expectedABC
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual ABC:",
|
||||
show actualABC,
|
||||
"Expected ABC:",
|
||||
show expectedABC
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
generateABCProgram :: [TypedClass] -> [(String, BtcProgram)]
|
||||
generateABCProgram [typedClass] = lowerClassFile (generateClassFile typedClass)
|
||||
generateABCProgram _ = error "Expected exactly one typed class for the ABC test"
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyASTTest where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.Parser
|
||||
import Grammar.Scanner
|
||||
import Testsuite.AstFiles.EmptyTestAST (expectedAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainEmptyASTTest :: IO ()
|
||||
mainEmptyASTTest = do
|
||||
let javaFilePath = "src/Testsuite/javaFiles/EmptyTest.java"
|
||||
java <- readFile javaFilePath
|
||||
let actualAST = parse . alexScanTokens $ java
|
||||
if actualAST == expectedAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Empty AST test failed.",
|
||||
"Actual AST:",
|
||||
show actualAST,
|
||||
"Expected AST:",
|
||||
show expectedAST
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,224 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyBytecodeBytesTest where
|
||||
|
||||
import Codegen.Lowerer (BtcProgram)
|
||||
import Codegen.Serializer (serializeProgram)
|
||||
import Control.Exception (throwIO)
|
||||
import Data.Bits ((.|.), shiftL)
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Testsuite.AbcFiles.EmptyTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainEmptyBytecodeBytesTest :: IO ()
|
||||
mainEmptyBytecodeBytesTest = do
|
||||
let classFilePath = "src/Testsuite/classFiles/EmptyTest.class"
|
||||
methodCodes <- parseMethodCodeBytesFromFile classFilePath
|
||||
failures <- fmap concat (mapM (checkMethod methodCodes) expectedABC)
|
||||
if null failures
|
||||
then pure ()
|
||||
else throwIO (TestFailure (unlines failures))
|
||||
|
||||
checkMethod :: [(String, [Word8])] -> (String, BtcProgram) -> IO [String]
|
||||
checkMethod methodCodes (methodName, expectedProgram) = do
|
||||
let expectedBytes = serializeProgram expectedProgram
|
||||
maybeActualBytes = lookup methodName methodCodes
|
||||
case maybeActualBytes of
|
||||
Nothing ->
|
||||
pure
|
||||
[ "Method '" ++ methodName ++ "' not found in reference class file"
|
||||
]
|
||||
Just actualBytes ->
|
||||
if expectedBytes == actualBytes
|
||||
then pure []
|
||||
else
|
||||
pure ["Method '" ++ methodName ++ "' bytecode mismatch"]
|
||||
|
||||
newtype Parser a = Parser
|
||||
{ runParser :: [Word8] -> Either String (a, [Word8])
|
||||
}
|
||||
|
||||
instance Functor Parser where
|
||||
fmap f p = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
pure (f value, rest)
|
||||
|
||||
instance Applicative Parser where
|
||||
pure value = Parser $ \input -> Right (value, input)
|
||||
pf <*> px = Parser $ \input -> do
|
||||
(f, rest1) <- runParser pf input
|
||||
(x, rest2) <- runParser px rest1
|
||||
pure (f x, rest2)
|
||||
|
||||
instance Monad Parser where
|
||||
p >>= f = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
runParser (f value) rest
|
||||
|
||||
parseFail :: String -> Parser a
|
||||
parseFail msg = Parser $ \_ -> Left msg
|
||||
|
||||
u1 :: Parser Word8
|
||||
u1 = Parser $ \input ->
|
||||
case input of
|
||||
[] -> Left "Unexpected end of input while reading u1"
|
||||
(b : rest) -> Right (b, rest)
|
||||
|
||||
u2 :: Parser Word16
|
||||
u2 = do
|
||||
hi <- u1
|
||||
lo <- u1
|
||||
pure ((fromIntegral hi `shiftL` 8) .|. fromIntegral lo)
|
||||
|
||||
u4 :: Parser Word32
|
||||
u4 = do
|
||||
b1 <- u1
|
||||
b2 <- u1
|
||||
b3 <- u1
|
||||
b4 <- u1
|
||||
pure
|
||||
( (fromIntegral b1 `shiftL` 24)
|
||||
.|. (fromIntegral b2 `shiftL` 16)
|
||||
.|. (fromIntegral b3 `shiftL` 8)
|
||||
.|. fromIntegral b4
|
||||
)
|
||||
|
||||
takeN :: Int -> Parser [Word8]
|
||||
takeN n
|
||||
| n < 0 = parseFail "Negative takeN requested"
|
||||
| otherwise = Parser $ \input ->
|
||||
if length input < n
|
||||
then Left ("Unexpected end of input while reading " ++ show n ++ " bytes")
|
||||
else Right (splitAt n input)
|
||||
|
||||
skipN :: Int -> Parser ()
|
||||
skipN n = do
|
||||
_ <- takeN n
|
||||
pure ()
|
||||
|
||||
parseMethodCodeBytesFromFile :: FilePath -> IO [(String, [Word8])]
|
||||
parseMethodCodeBytesFromFile path = do
|
||||
bytes <- BS.readFile path
|
||||
case runParser parseClassFile (BS.unpack bytes) of
|
||||
Left err -> error ("Failed parsing class file '" ++ path ++ "': " ++ err)
|
||||
Right (methods, _) -> pure methods
|
||||
|
||||
parseClassFile :: Parser [(String, [Word8])]
|
||||
parseClassFile = do
|
||||
magic <- u4
|
||||
if magic /= 0xCAFEBABE
|
||||
then parseFail "Invalid class file magic"
|
||||
else pure ()
|
||||
|
||||
_minorVersion <- u2
|
||||
_majorVersion <- u2
|
||||
|
||||
cpCount <- fromIntegral <$> u2
|
||||
utf8Map <- parseConstantPool cpCount 1 Map.empty
|
||||
|
||||
_accessFlags <- u2
|
||||
_thisClass <- u2
|
||||
_superClass <- u2
|
||||
|
||||
interfacesCount <- fromIntegral <$> u2
|
||||
skipN (interfacesCount * 2)
|
||||
|
||||
fieldsCount <- fromIntegral <$> u2
|
||||
skipMembers fieldsCount
|
||||
|
||||
methodsCount <- fromIntegral <$> u2
|
||||
parseMethods utf8Map methodsCount
|
||||
|
||||
parseConstantPool :: Int -> Int -> Map.Map Int String -> Parser (Map.Map Int String)
|
||||
parseConstantPool cpCount idx utf8Map
|
||||
| idx >= cpCount = pure utf8Map
|
||||
| otherwise = do
|
||||
tag <- u1
|
||||
case tag of
|
||||
1 -> do
|
||||
len <- fromIntegral <$> u2
|
||||
bytes <- takeN len
|
||||
let value = map (toEnum . fromIntegral) bytes
|
||||
parseConstantPool cpCount (idx + 1) (Map.insert idx value utf8Map)
|
||||
3 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
4 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
5 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
6 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
7 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
8 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
9 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
10 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
11 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
12 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
15 -> skipN 3 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
16 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
17 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
18 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
19 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
20 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
_ -> parseFail ("Unknown constant pool tag " ++ show tag)
|
||||
|
||||
skipMembers :: Int -> Parser ()
|
||||
skipMembers 0 = pure ()
|
||||
skipMembers n = do
|
||||
_accessFlags <- u2
|
||||
_nameIndex <- u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
skipAttributes attrCount
|
||||
skipMembers (n - 1)
|
||||
|
||||
skipAttributes :: Int -> Parser ()
|
||||
skipAttributes 0 = pure ()
|
||||
skipAttributes n = do
|
||||
_attrNameIndex <- u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
skipN attrLen
|
||||
skipAttributes (n - 1)
|
||||
|
||||
parseMethods :: Map.Map Int String -> Int -> Parser [(String, [Word8])]
|
||||
parseMethods _ 0 = pure []
|
||||
parseMethods utf8Map n = do
|
||||
_accessFlags <- u2
|
||||
nameIndex <- fromIntegral <$> u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
|
||||
let methodName = Map.findWithDefault ("<unknown-" ++ show nameIndex ++ ">") nameIndex utf8Map
|
||||
|
||||
maybeCode <- parseMethodAttributes utf8Map attrCount
|
||||
rest <- parseMethods utf8Map (n - 1)
|
||||
case maybeCode of
|
||||
Nothing -> pure rest
|
||||
Just code -> pure ((methodName, code) : rest)
|
||||
|
||||
parseMethodAttributes :: Map.Map Int String -> Int -> Parser (Maybe [Word8])
|
||||
parseMethodAttributes _ 0 = pure Nothing
|
||||
parseMethodAttributes utf8Map n = do
|
||||
attrNameIndex <- fromIntegral <$> u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
let attrName = Map.findWithDefault "" attrNameIndex utf8Map
|
||||
|
||||
current <-
|
||||
if attrName == "Code"
|
||||
then parseCodeAttribute attrLen
|
||||
else skipN attrLen >> pure Nothing
|
||||
|
||||
next <- parseMethodAttributes utf8Map (n - 1)
|
||||
pure (pickFirst current next)
|
||||
|
||||
pickFirst :: Maybe a -> Maybe a -> Maybe a
|
||||
pickFirst (Just x) _ = Just x
|
||||
pickFirst Nothing y = y
|
||||
|
||||
parseCodeAttribute :: Int -> Parser (Maybe [Word8])
|
||||
parseCodeAttribute _declaredLength = do
|
||||
_maxStack <- u2
|
||||
_maxLocals <- u2
|
||||
codeLen <- fromIntegral <$> u4
|
||||
codeBytes <- takeN codeLen
|
||||
exceptionTableLen <- fromIntegral <$> u2
|
||||
skipN (exceptionTableLen * 8)
|
||||
attrCount <- fromIntegral <$> u2
|
||||
skipAttributes attrCount
|
||||
pure (Just codeBytes)
|
||||
@@ -0,0 +1,19 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyHarness where
|
||||
|
||||
import Testsuite.ExecutableTests.Empty.EmptyABCTest (mainEmptyABCTest)
|
||||
import Testsuite.ExecutableTests.Empty.EmptyASTTest (mainEmptyASTTest)
|
||||
import Testsuite.ExecutableTests.Empty.EmptyBytecodeBytesTest (mainEmptyBytecodeBytesTest)
|
||||
import Testsuite.ExecutableTests.Empty.EmptyTASTTest (mainEmptyTASTTest)
|
||||
import Testsuite.ExecutableTests.HarnessSupport
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let tests =
|
||||
[ ("Empty AST", mainEmptyASTTest),
|
||||
("Empty TAST", mainEmptyTASTTest),
|
||||
("Empty ABC", mainEmptyABCTest),
|
||||
("Empty Bytecode Bytes", mainEmptyBytecodeBytesTest)
|
||||
]
|
||||
results <- runNamedTests tests
|
||||
printTestResults "Empty Harness" results
|
||||
exitOnFailures results
|
||||
@@ -0,0 +1,29 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyTASTTest where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.AST (Class)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AstFiles.EmptyTestAST (expectedAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Testsuite.TastFiles.EmptyTestTAST (expectedTAST)
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
mainEmptyTASTTest :: IO ()
|
||||
mainEmptyTASTTest = do
|
||||
let actualTAST = typeCheckProgram expectedAST
|
||||
if actualTAST == expectedTAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual TAST:",
|
||||
show actualTAST,
|
||||
"Expected TAST:",
|
||||
show expectedTAST
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
typeCheckProgram :: [Class] -> [TypedClass]
|
||||
typeCheckProgram classes = map (\cls -> typeCheckClass cls [] classes) classes
|
||||
@@ -0,0 +1,110 @@
|
||||
module Testsuite.ExecutableTests.HarnessSupport
|
||||
( TestResult (..),
|
||||
TestFailure (..),
|
||||
runNamedTests,
|
||||
printTestResults,
|
||||
exitOnFailures
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Exception (Exception, SomeException, fromException, try)
|
||||
import Data.List (dropWhileEnd, intercalate)
|
||||
import System.Exit (ExitCode (..), exitFailure)
|
||||
import System.IO (BufferMode (LineBuffering), hFlush, hSetBuffering, stderr, stdout)
|
||||
|
||||
newtype TestFailure = TestFailure {failureDetails :: String}
|
||||
deriving (Show)
|
||||
|
||||
instance Exception TestFailure
|
||||
|
||||
data TestResult = TestResult
|
||||
{ testName :: String,
|
||||
passed :: Bool,
|
||||
details :: String
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
runNamedTests :: [(String, IO ())] -> IO [TestResult]
|
||||
runNamedTests tests = do
|
||||
hSetBuffering stdout LineBuffering
|
||||
hSetBuffering stderr LineBuffering
|
||||
runNamedTests' tests
|
||||
|
||||
runNamedTests' :: [(String, IO ())] -> IO [TestResult]
|
||||
runNamedTests' [] = pure []
|
||||
runNamedTests' (test : rest) = do
|
||||
result <- runNamedTest test
|
||||
results <- runNamedTests' rest
|
||||
pure (result : results)
|
||||
|
||||
runNamedTest :: (String, IO ()) -> IO TestResult
|
||||
runNamedTest (name, action) = do
|
||||
putStrLn ("[RUN ] " ++ name)
|
||||
hFlush stdout
|
||||
result <- try action :: IO (Either SomeException ())
|
||||
case result of
|
||||
Right () -> do
|
||||
putStrLn ("[DONE] " ++ name)
|
||||
hFlush stdout
|
||||
pure (TestResult name True "")
|
||||
Left ex -> do
|
||||
let details = failureDetailsForSummary ex
|
||||
let runtimeDetails = failureDetailsForRuntime ex
|
||||
if null runtimeDetails
|
||||
then pure ()
|
||||
else putStrLn runtimeDetails
|
||||
putStrLn ("[DONE] " ++ name)
|
||||
hFlush stdout
|
||||
pure (TestResult name False details)
|
||||
|
||||
printTestResults :: String -> [TestResult] -> IO ()
|
||||
printTestResults title results = do
|
||||
putStrLn ("\n== " ++ title ++ " ==")
|
||||
mapM_ printResult results
|
||||
let total = length results
|
||||
failures = length (filter (not . passed) results)
|
||||
successes = total - failures
|
||||
putStrLn ("Summary: " ++ show successes ++ "/" ++ show total ++ " passed")
|
||||
|
||||
printResult :: TestResult -> IO ()
|
||||
printResult (TestResult name True _) =
|
||||
putStrLn ("[PASS] " ++ name)
|
||||
printResult (TestResult name False info) = do
|
||||
putStrLn ("[FAIL] " ++ name)
|
||||
if null info
|
||||
then pure ()
|
||||
else mapM_ (putStrLn . (" " ++)) (lines info)
|
||||
|
||||
exitOnFailures :: [TestResult] -> IO ()
|
||||
exitOnFailures results =
|
||||
if any (not . passed) results
|
||||
then exitFailure
|
||||
else pure ()
|
||||
|
||||
renderException :: SomeException -> String
|
||||
renderException ex =
|
||||
case fromException ex :: Maybe ExitCode of
|
||||
Just ExitSuccess -> "unexpected ExitSuccess exception"
|
||||
Just (ExitFailure code) -> "exit failure code " ++ show code
|
||||
Nothing -> sanitize (show ex)
|
||||
|
||||
failureDetailsForRuntime :: SomeException -> String
|
||||
failureDetailsForRuntime ex =
|
||||
case fromException ex :: Maybe TestFailure of
|
||||
Just (TestFailure details) -> stripTrailingNewlines details
|
||||
Nothing -> indentOneLine (renderException ex)
|
||||
|
||||
failureDetailsForSummary :: SomeException -> String
|
||||
failureDetailsForSummary ex =
|
||||
case fromException ex :: Maybe TestFailure of
|
||||
Just (TestFailure details) -> stripTrailingNewlines details
|
||||
Nothing -> renderException ex
|
||||
|
||||
indentOneLine :: String -> String
|
||||
indentOneLine text = " " ++ text
|
||||
|
||||
stripTrailingNewlines :: String -> String
|
||||
stripTrailingNewlines = dropWhileEnd (== '\n') . dropWhileEnd (== '\r')
|
||||
|
||||
sanitize :: String -> String
|
||||
sanitize = intercalate " " . words
|
||||
@@ -0,0 +1,57 @@
|
||||
module Testsuite.TastFiles.ArithmeticTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"ArithmeticTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"basic"
|
||||
[("int", "a"), ("int", "b"), ("int", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Subtract
|
||||
(Binary Add (LocalOrFieldVar "a" "int") (LocalOrFieldVar "b" "int") "int")
|
||||
( Binary
|
||||
Modulo
|
||||
( Binary
|
||||
Divide
|
||||
(Binary Multiply (LocalOrFieldVar "c" "int") (LocalOrFieldVar "a" "int") "int")
|
||||
(LocalOrFieldVar "b" "int")
|
||||
"int"
|
||||
)
|
||||
(LocalOrFieldVar "c" "int")
|
||||
"int"
|
||||
)
|
||||
"int"
|
||||
)
|
||||
)
|
||||
"int"
|
||||
]
|
||||
"int"
|
||||
)
|
||||
, Method
|
||||
"boolean"
|
||||
"logic"
|
||||
[("boolean", "a"), ("boolean", "b"), ("boolean", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
And
|
||||
(Unary Not (LocalOrFieldVar "a" "boolean") "boolean")
|
||||
(Binary Or (LocalOrFieldVar "c" "boolean") (LocalOrFieldVar "b" "boolean") "boolean")
|
||||
"boolean"
|
||||
)
|
||||
)
|
||||
"boolean"
|
||||
]
|
||||
"boolean"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
module Testsuite.TastFiles.EmptyTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"EmptyTest"
|
||||
[]
|
||||
[]
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
public class ArithmeticTest {
|
||||
|
||||
public int basic(int a, int b, int c) {
|
||||
return a + b - (((c * a) / b) % c);
|
||||
}
|
||||
|
||||
public boolean logic(boolean a, boolean b, boolean c) {
|
||||
return !a && (c || b);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
public class FieldsTest
|
||||
{
|
||||
public class FieldsTest {
|
||||
|
||||
public int a;
|
||||
public int b = 42;
|
||||
}
|
||||
@@ -18,6 +18,13 @@ public class Main {
|
||||
LoopTest loop = new LoopTest();
|
||||
MethodOverloadTest overload = new MethodOverloadTest();
|
||||
ShenaniganceTest shenanigance = new ShenaniganceTest();
|
||||
AllSyntaxTest allSyntax = new AllSyntaxTest(0);
|
||||
CombinedControlTest combined = new CombinedControlTest(6);
|
||||
ExpressionTest expression = new ExpressionTest();
|
||||
IfTest ifTest = new IfTest();
|
||||
MultiClassTest multiClass = new MultiClassTest();
|
||||
SingletonTest singleton = new SingletonTest();
|
||||
WhileTest whileTest = new WhileTest();
|
||||
|
||||
// constructing a basic class works
|
||||
System.out.println("test empty non-null. Expected: non-null, Real: " + (empty != null));
|
||||
@@ -53,6 +60,28 @@ public class Main {
|
||||
// other syntactic sugar
|
||||
System.out.println("test shenanigance.testAssignment(). Expected: 5, Real: " + shenanigance.testAssignment());
|
||||
System.out.println("test shenanigance.divEqual(). Expected: " + (234_343_000 / 4) + ", Real: " + shenanigance.divEqual());
|
||||
System.out.println("test shenanigance.testIf(5). Expected: true, Real: " + shenanigance.testIf(5));
|
||||
// AllSyntaxTest tests
|
||||
System.out.println("test AllSyntaxTest.inc(). Expected: 1, Real: " + allSyntax.inc());
|
||||
System.out.println("test AllSyntaxTest.inc(4). Expected: 5, Real: " + allSyntax.inc(4));
|
||||
System.out.println("test AllSyntaxTest.sumUpTo(5). Expected: 15, Real: " + AllSyntaxTest.sumUpTo(5));
|
||||
System.out.println("test AllSyntaxTest.predicate('a'). Expected: true, Real: " + allSyntax.predicate('a'));
|
||||
// CombinedControlTest tests
|
||||
System.out.println("test CombinedControlTest.compute(). Expected: -3, Real: " + combined.compute());
|
||||
// ExpressionTest tests
|
||||
System.out.println("test ExpressionTest instance created. Expected: non-null, Real: " + (expression != null));
|
||||
System.out.println("test ExpressionTest.shortCircuit(2, 1). Expected: true, Real: " + expression.shortCircuit(2, 1));
|
||||
System.out.println("test ExpressionTest.shortCircuit(0, 1). Expected: false, Real: " + expression.shortCircuit(0, 1));
|
||||
System.out.println("test ExpressionTest.charArithmetic('A', 2). Expected: C, Real: " + expression.charArithmetic('A', 2));
|
||||
// IfTest tests
|
||||
System.out.println("test IfTest instance created. Expected: non-null, Real: " + (ifTest != null));
|
||||
System.out.println("test IfTest.ifElseTest(-1). Expected: false, Real: " + ifTest.ifElseTest(-1));
|
||||
System.out.println("test IfTest.ifElseTest(0). Expected: true, Real: " + ifTest.ifElseTest(0));
|
||||
System.out.println("test IfTest.ifElseTest(11). Expected: true, Real: " + ifTest.ifElseTest(11));
|
||||
// MultiClassTest tests
|
||||
System.out.println("test MultiClassTest instance created. Expected: non-null, Real: " + (multiClass != null));
|
||||
// SingletonTest tests
|
||||
System.out.println("test SingletonTest.getInstance(). Expected: non-null, Real: " + (singleton.getInstance() != null));
|
||||
// WhileTest tests
|
||||
System.out.println("test WhileTest.whileLoopTest(5). Expected: 15, Real: " + WhileTest.whileLoopTest(5));
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,4 @@ class ShenaniganceTest {
|
||||
x /= 4;
|
||||
return x;
|
||||
}
|
||||
|
||||
boolean testIf(int x) {
|
||||
if (true && x < 8) {
|
||||
char f = 'c';
|
||||
return f > x ;
|
||||
}
|
||||
ifn't {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
module Typecheck.SemanticChecker where
|
||||
|
||||
import Data.List (find)
|
||||
import Grammar.AST as AST
|
||||
import Grammar.TAST as TAST
|
||||
|
||||
--------------------------------------------------
|
||||
-- Helper functions
|
||||
-- Get type functions
|
||||
--------------------------------------------------
|
||||
getTypeFromTypedExpr :: TypedExpr -> Type
|
||||
getTypeFromTypedExpr (TAST.This t) = t
|
||||
getTypeFromTypedExpr (TAST.This t) = t
|
||||
getTypeFromTypedExpr (TAST.LocalOrFieldVar _ t) = t
|
||||
getTypeFromTypedExpr (TAST.InstVar _ _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Unary _ _ t) = t
|
||||
@@ -26,46 +27,263 @@ getTypeFromTypedStmtExpr (TAST.MethodCall _ _ _ t) = t
|
||||
|
||||
getTypeFromTypedStmt :: TypedStmt -> Type
|
||||
getTypeFromTypedStmt (TAST.Block _ t) = t
|
||||
getTypeFromTypedStmt (TAST.Return _ t) = t
|
||||
getTypeFromTypedStmt (TAST.Return _ t) = t
|
||||
getTypeFromTypedStmt (TAST.While _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.LocalVarDecl _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.If _ _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.StmtExprStmt _ t) = t
|
||||
|
||||
|
||||
upperBound :: Type -> Type -> Type
|
||||
upperBound t1 t2
|
||||
| t1 == t2 = t1
|
||||
| otherwise = "Object" -- // Or: Throw an error because types don't match
|
||||
--------------------------------------------------
|
||||
-- Typechecking Class
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckClass :: AST.Class -> [(String, Type)] -> [AST.Class] -> TAST.TypedClass
|
||||
typeCheckClass (AST.Class typ fields methods) symtab cls =
|
||||
let checkedFields = map (\f -> typeCheckField f symtab cls) fields
|
||||
checkedMethods = map (\m -> typeCheckMethod m symtab cls) methods
|
||||
in TAST.Class typ checkedFields checkedMethods
|
||||
|
||||
typeCheckField :: AST.FieldDecl -> [(String, Type)] -> [AST.Class] -> TAST.TypedFieldDecl
|
||||
typeCheckField (AST.Field typ name Nothing) symtab cls =
|
||||
TAST.Field typ name Nothing
|
||||
typeCheckField (AST.Field expectedTyp name (Just expr)) symtab cls =
|
||||
let checkedExpr = typeCheckExpr expr symtab cls
|
||||
actualTyp = getTypeFromTypedExpr checkedExpr
|
||||
in if expectedTyp == actualTyp
|
||||
then TAST.Field expectedTyp name (Just checkedExpr)
|
||||
else error $ "Type mismatch: Expected " ++ expectedTyp ++ " found " ++ actualTyp
|
||||
|
||||
|
||||
|
||||
typeCheckMethod :: AST.MethodDecl -> [(String, Type)] -> [AST.Class] -> TAST.TypedMethodDecl
|
||||
typeCheckMethod (AST.Method typ name params stmt) symtab cls =
|
||||
let checkedStmt = typeCheckStmt stmt symtab cls
|
||||
stmtTyp = getTypeFromTypedStmt checkedStmt
|
||||
in if typ == stmtTyp || stmtTyp == "void"
|
||||
then TAST.Method typ name params checkedStmt
|
||||
else error $ "Return type mismatch: expected " ++ typ ++ ", got " ++ stmtTyp
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmt :: Stmt -> [(String, Type)] -> [Class] -> TypedStmt
|
||||
-- If-Else Statement
|
||||
typeCheckStmt (AST.If cond body elseBranch) symtab cls =
|
||||
typeCheckStmt :: AST.Stmt -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmt
|
||||
|
||||
-- If-Statement
|
||||
typeCheckStmt (AST.If cond body Nothing) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
checkedElse = fmap (\e -> typeCheckStmt e symtab cls) elseBranch
|
||||
typ = case checkedElse of
|
||||
Nothing -> getTypeFromTypedStmt checkedBody
|
||||
Just ce -> upperBound (getTypeFromTypedStmt checkedBody) (getTypeFromTypedStmt ce)
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody checkedElse typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
typ = getTypeFromTypedStmt checkedBody
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody Nothing typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- If-Else-Statement
|
||||
typeCheckStmt (AST.If cond body (Just elseBody)) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
checkedElse = Just (typeCheckStmt elseBody symtab cls)
|
||||
typ = upperBound (getTypeFromTypedStmt checkedBody) (getTypeFromTypedStmt (maybe checkedBody id checkedElse))
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody checkedElse typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- While-Statement
|
||||
typeCheckStmt (AST.While cond body) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
typ = getTypeFromTypedStmt checkedBody
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.While checkedCond checkedBody typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- Return Statement
|
||||
typeCheckStmt (AST.Return maybeExpr) symtab cls =
|
||||
let checkedExpr = fmap (\expr -> typeCheckExpr expr symtab cls) maybeExpr
|
||||
typ = maybe "void" getTypeFromTypedExpr checkedExpr
|
||||
in TAST.Return checkedExpr typ
|
||||
|
||||
|
||||
typeCheckStmt (AST.LocalVarDecl typ str) _ _ =
|
||||
TAST.LocalVarDecl typ str typ
|
||||
|
||||
-- Block statement
|
||||
typeCheckStmt (AST.Block stmts) symtab cls =
|
||||
let checkedStmts = map (\s -> typeCheckStmt s symtab cls) stmts
|
||||
typ = case reverse checkedStmts of
|
||||
[] -> "void"
|
||||
(lastStmt : _) -> getTypeFromTypedStmt lastStmt
|
||||
in TAST.Block checkedStmts typ
|
||||
|
||||
-- Expression statement
|
||||
typeCheckStmt (AST.StmtExprStmt stmtExpr) symtab cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtab cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprStmt checkedStmtExpr typ
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmtExpr :: AST.StmtExpr -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmtExpr
|
||||
|
||||
-- Assign
|
||||
typeCheckStmtExpr (AST.Assign lhs rhs) symtab cls =
|
||||
let checkedLhs = typeCheckExpr lhs symtab cls
|
||||
checkedRhs = typeCheckExpr rhs symtab cls
|
||||
lhsType = getTypeFromTypedExpr checkedLhs
|
||||
rhsType = getTypeFromTypedExpr checkedRhs
|
||||
in if lhsType == rhsType || rhsType == "null"
|
||||
then TAST.Assign checkedLhs checkedRhs lhsType
|
||||
else error "Type mismatch in assignment"
|
||||
|
||||
-- New
|
||||
typeCheckStmtExpr (AST.New typ args) symtab cls =
|
||||
let checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
in TAST.New typ checkedArgs typ
|
||||
|
||||
-- Method call
|
||||
typeCheckStmtExpr (AST.MethodCall target methodName args) symtab cls =
|
||||
let checkedTarget = typeCheckExpr target symtab cls
|
||||
checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
targetType = getTypeFromTypedExpr checkedTarget
|
||||
returnType = lookupMethodReturnType targetType methodName cls
|
||||
in TAST.MethodCall checkedTarget methodName checkedArgs returnType
|
||||
|
||||
-- Helper methods
|
||||
lookupMethodReturnType :: Type -> String -> [AST.Class] -> Type
|
||||
lookupMethodReturnType classType methodName classes =
|
||||
case findMethod classType methodName classes of
|
||||
Just t -> t
|
||||
Nothing -> error $ "Method not found: " ++ methodName ++ " in class " ++ classType
|
||||
|
||||
findMethod :: Type -> String -> [AST.Class] -> Maybe Type
|
||||
findMethod classType methodName classes =
|
||||
case find (\(AST.Class clsName _ _) -> clsName == classType) classes of
|
||||
Just (AST.Class _ _ methods) ->
|
||||
case find (\(AST.Method ret name _ _) -> name == methodName) methods of
|
||||
Just (AST.Method ret _ _ _) -> Just ret
|
||||
Nothing -> Nothing
|
||||
Nothing -> Nothing
|
||||
|
||||
lookupFieldType :: Type -> String -> [AST.Class] -> Type
|
||||
lookupFieldType classType fieldName classes =
|
||||
case find (\(AST.Class clsName _ _) -> clsName == classType) classes of
|
||||
Just (AST.Class _ fields _) ->
|
||||
case find (\(AST.Field _ name _) -> name == fieldName) fields of
|
||||
Just (AST.Field t _ _) -> t
|
||||
Nothing -> error $ "Field not found: " ++ fieldName ++ " in class " ++ classType
|
||||
Nothing -> error $ "Class not found: " ++ classType
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckExpr :: Expr -> [(String, Type)] -> [Class] -> TypedExpr
|
||||
typeCheckExpr :: AST.Expr -> [(String, Type)] -> [AST.Class] -> TAST.TypedExpr
|
||||
|
||||
-- boolean literals
|
||||
typeCheckExpr (AST.Bool value) symtbl cls =
|
||||
TAST.Bool value "boolean"
|
||||
|
||||
-- integer literals
|
||||
typeCheckExpr (AST.Integer value) symtbl cls =
|
||||
TAST.Integer value "int"
|
||||
|
||||
-- variable references
|
||||
typeCheckExpr (AST.LocalOrFieldVar varName) symtbl cls =
|
||||
case lookup varName symtbl of
|
||||
Just t -> TAST.LocalOrFieldVar varName t
|
||||
Nothing -> error $ "Undefined variable: " ++ varName
|
||||
|
||||
-- this reference
|
||||
typeCheckExpr AST.This _ cls =
|
||||
case cls of
|
||||
(AST.Class className _ _ : _) -> TAST.This className
|
||||
[] -> error "No class context for this expression"
|
||||
|
||||
-- field access on an object
|
||||
typeCheckExpr (AST.InstVar obj fieldName) symtbl cls =
|
||||
let checkedObj = typeCheckExpr obj symtbl cls
|
||||
objType = getTypeFromTypedExpr checkedObj
|
||||
fieldType = lookupFieldType objType fieldName cls
|
||||
in TAST.InstVar checkedObj fieldName fieldType
|
||||
|
||||
-- unary operators
|
||||
typeCheckExpr (AST.Unary op expr) symtbl cls =
|
||||
let checkedExpr = typeCheckExpr expr symtbl cls
|
||||
exprType = getTypeFromTypedExpr checkedExpr
|
||||
in case op of
|
||||
AST.Not -> if exprType == "boolean"
|
||||
then TAST.Unary AST.Not checkedExpr "boolean"
|
||||
else error "Operator ! requires boolean operand"
|
||||
AST.Negate -> if exprType == "int"
|
||||
then TAST.Unary AST.Negate checkedExpr "int"
|
||||
else error "Unary - requires integer operand"
|
||||
|
||||
-- binary operators
|
||||
typeCheckExpr (AST.Binary op left right) symtbl cls =
|
||||
let checkedLeft = typeCheckExpr left symtbl cls
|
||||
checkedRight = typeCheckExpr right symtbl cls
|
||||
leftType = getTypeFromTypedExpr checkedLeft
|
||||
rightType = getTypeFromTypedExpr checkedRight
|
||||
in case op of
|
||||
AST.Add -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Subtract -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Multiply -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Divide -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Modulo -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.CompLessThan -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompLessOrEqual -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompGreaterThan -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompGreaterOrEqual -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompEqual -> checkEquality leftType rightType checkedLeft checkedRight op
|
||||
AST.CompNotEqual -> checkEquality leftType rightType checkedLeft checkedRight op
|
||||
AST.BitAnd -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.BitOr -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.BitXor -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.And -> checkBoolOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Or -> checkBoolOp leftType rightType checkedLeft checkedRight op
|
||||
|
||||
-- character literals
|
||||
typeCheckExpr (AST.Char value) _ _ =
|
||||
TAST.Char value "char"
|
||||
|
||||
-- null literal
|
||||
typeCheckExpr AST.Null _ _ =
|
||||
TAST.Null "null"
|
||||
|
||||
-- statement expression
|
||||
typeCheckExpr (AST.StmtExprExpr stmtExpr) symtbl cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtbl cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprExpr checkedStmtExpr typ
|
||||
|
||||
-- Helper methods
|
||||
checkIntOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkIntOp leftType rightType left right op =
|
||||
if leftType == "int" && rightType == "int"
|
||||
then TAST.Binary op left right "int"
|
||||
else error "Integer binary operator requires int operands"
|
||||
|
||||
checkBoolOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkBoolOp leftType rightType left right op =
|
||||
if leftType == "boolean" && rightType == "boolean"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error "Boolean binary operator requires boolean operands"
|
||||
|
||||
checkIntComparison :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkIntComparison leftType rightType left right op =
|
||||
if leftType == "int" && rightType == "int"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error "Comparison operator requires int operands"
|
||||
|
||||
checkEquality :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkEquality leftType rightType left right op =
|
||||
if leftType == rightType || leftType == "null" || rightType == "null"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error $ "Equality operator: cannot compare " ++ leftType ++ " with " ++ rightType
|
||||
|
||||
upperBound :: Type -> Type -> Type
|
||||
upperBound t1 t2
|
||||
| t1 == t2 = t1
|
||||
| otherwise = "Object"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
public class ArithmeticTest {
|
||||
public int basic(int a, int b, int c)
|
||||
{
|
||||
return a + b - c * a / b % c;
|
||||
}
|
||||
|
||||
public boolean logic(boolean a, boolean b, boolean c)
|
||||
{
|
||||
return !a && (c || b);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
module Main where
|
||||
|
||||
import Codegen.Assemble (assembleProgram)
|
||||
import Codegen.ClassFile (ClassFile(..), ClassFileMethod(..))
|
||||
import Codegen.Errors (CodegenError(..))
|
||||
import Codegen.IR
|
||||
( ClassDef(..)
|
||||
, Expr(..)
|
||||
, Literal(..)
|
||||
, MethodDef(..)
|
||||
, Program(..)
|
||||
, Stmt(..)
|
||||
, Type(..)
|
||||
)
|
||||
import Data.List (isInfixOf)
|
||||
import System.Exit (exitFailure)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
testVerticalSliceReturnInt
|
||||
testUnsupportedStatementFails
|
||||
putStrLn "Codegen smoke tests passed."
|
||||
|
||||
testVerticalSliceReturnInt :: IO ()
|
||||
testVerticalSliceReturnInt =
|
||||
case assembleProgram program of
|
||||
Left err -> failTest ("Expected successful assemble, got error: " ++ show err)
|
||||
Right [classFile] -> do
|
||||
assertEqual "class name" "Main" (cfClassName classFile)
|
||||
case cfMethods classFile of
|
||||
[method] -> do
|
||||
assertEqual "method name" "constSeven" (cfmName method)
|
||||
assertEqual "method descriptor" "()I" (cfmDescriptor method)
|
||||
assertEqual "method instructions" ["iconst 7", "ireturn"] (cfmCode method)
|
||||
_ -> failTest "Expected exactly one method in generated class"
|
||||
Right _ -> failTest "Expected exactly one generated class"
|
||||
where
|
||||
program =
|
||||
Program
|
||||
[ ClassDef
|
||||
{ className = "Main"
|
||||
, classFields = []
|
||||
, classMethods =
|
||||
[ MethodDef
|
||||
{ methodName = "constSeven"
|
||||
, methodParams = []
|
||||
, methodReturnType = IntType
|
||||
, methodBody = Return (Literal (IntLit 7))
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
testUnsupportedStatementFails :: IO ()
|
||||
testUnsupportedStatementFails =
|
||||
case assembleProgram program of
|
||||
Left (LoweringError msg)
|
||||
| "Unsupported statement" `isInfixOf` msg -> pure ()
|
||||
| otherwise -> failTest ("Lowering failed with unexpected message: " ++ msg)
|
||||
Left err -> failTest ("Expected LoweringError, got: " ++ show err)
|
||||
Right _ -> failTest "Expected lowering to fail for unsupported statement"
|
||||
where
|
||||
program =
|
||||
Program
|
||||
[ ClassDef
|
||||
{ className = "Main"
|
||||
, classFields = []
|
||||
, classMethods =
|
||||
[ MethodDef
|
||||
{ methodName = "badMethod"
|
||||
, methodParams = []
|
||||
, methodReturnType = IntType
|
||||
, methodBody = While (Literal (BoolLit True)) (Return (Literal (IntLit 0)))
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
assertEqual :: (Eq a, Show a) => String -> a -> a -> IO ()
|
||||
assertEqual label expected actual
|
||||
| expected == actual = pure ()
|
||||
| otherwise =
|
||||
failTest
|
||||
( "Assertion failed for "
|
||||
++ label
|
||||
++ ": expected "
|
||||
++ show expected
|
||||
++ ", got "
|
||||
++ show actual
|
||||
)
|
||||
|
||||
failTest :: String -> IO ()
|
||||
failTest message = do
|
||||
putStrLn ("[FAIL] " ++ message)
|
||||
exitFailure
|
||||
@@ -1,4 +0,0 @@
|
||||
func Main.main : int
|
||||
entry:
|
||||
t0 = 0
|
||||
return t0
|
||||
@@ -1,11 +0,0 @@
|
||||
[
|
||||
Class "Main" []
|
||||
[
|
||||
Method "int" "main" []
|
||||
(Block
|
||||
[
|
||||
Return (Integer 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
[
|
||||
Class "Main" []
|
||||
[
|
||||
Method "int" "main" []
|
||||
(TypedStmt
|
||||
(Block
|
||||
[
|
||||
TypedStmt
|
||||
(Return (Integer 0))
|
||||
"int"
|
||||
]
|
||||
)
|
||||
"int"
|
||||
)
|
||||
]
|
||||
]
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
class ClassWithInt {
|
||||
Integer i;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# run all tests: ./test.sh 2>&1 | grep -E "Testing|Results"
|
||||
# inspect a diff: diff <(javap -c /tmp/compoiler-ref/FieldsTest.class) <(javap -c /tmp/compoiler-out/FieldsTest.class)
|
||||
|
||||
JAVA_DIR="src/Testsuite/javaFiles"
|
||||
REF_DIR="/tmp/compoiler-ref"
|
||||
OUT_DIR="/tmp/compoiler-out"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
ERROR=0
|
||||
|
||||
mkdir -p "$REF_DIR" "$OUT_DIR"
|
||||
|
||||
for javafile in "$JAVA_DIR"/*.java; do
|
||||
classname=$(basename "$javafile" .java)
|
||||
echo -n "Testing $classname ... "
|
||||
|
||||
# Run our compiler
|
||||
compiler_out=$(src/Main "$javafile" 2>&1)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR (compiler crashed)"
|
||||
echo " $compiler_out"
|
||||
((ERROR++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Move generated .class to OUT_DIR
|
||||
mv "out/${classname}.class" "$OUT_DIR/" 2>/dev/null
|
||||
if [ ! -f "$OUT_DIR/${classname}.class" ]; then
|
||||
echo "ERROR (no .class generated)"
|
||||
((ERROR++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Compile reference with javac
|
||||
javac "$javafile" -d "$REF_DIR" 2>/dev/null
|
||||
if [ ! -f "$REF_DIR/${classname}.class" ]; then
|
||||
echo "ERROR (javac failed)"
|
||||
((ERROR++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Compare structure and bytecode only (strip CP indices from instructions,
|
||||
# skip debug info like LineNumberTable and SourceFile)
|
||||
normalize() {
|
||||
javap -c "$1" 2>&1 \
|
||||
| grep -v "^Classfile\|Last modified\|SHA-256\|Compiled from\|LineNumberTable\|line [0-9]\|SourceFile" \
|
||||
| sed 's/#[0-9]*/\#N/g' \
|
||||
| sed 's/[[:space:]]*\/\/.*$//'
|
||||
}
|
||||
ref=$(normalize "$REF_DIR/${classname}.class")
|
||||
mine=$(normalize "$OUT_DIR/${classname}.class")
|
||||
|
||||
if diff <(echo "$ref") <(echo "$mine") > /dev/null 2>&1; then
|
||||
echo "PASS"
|
||||
((PASS++))
|
||||
else
|
||||
echo "FAIL"
|
||||
diff <(echo "$ref") <(echo "$mine")
|
||||
((FAIL++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed, $ERROR errors"
|
||||
Reference in New Issue
Block a user