Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69ae49a346 | |||
| e43a783d33 | |||
| 0496e6e1b4 | |||
| 4f5e4febdf | |||
| ffae200d3c | |||
| 85b93a604c | |||
| 3ffac1ffe6 | |||
| 3cf088b818 | |||
| e86389baf8 | |||
| 6aedcfe4e1 | |||
| fe71942a65 | |||
| a2ebe25901 | |||
| 8ac6223c6b | |||
| 9d4106aee8 | |||
| 95a80ca5d3 | |||
| b0abf5ee72 | |||
| 69b8bcba4d | |||
| 8ecda70a5f | |||
| 309d52715a | |||
| 0f2469f007 | |||
| bfd70838aa | |||
| e48012c3ed | |||
| fc2b31693d | |||
| 1bc9babd4f | |||
| 604db760ac | |||
| 4e24b2fb8a | |||
| 94e23b57fd | |||
| 22fd6733f0 | |||
| 62f42611bc | |||
| 2e601b86d5 | |||
| 2141bd44cb | |||
| 3ac33af1b5 | |||
| 9046e3621b | |||
| 7c0f49ccca | |||
| 6e16a502aa | |||
| b45502a550 | |||
| 83ff583c1c | |||
| f19d6f6d39 | |||
| cbd7df780a | |||
| 9d405fac61 | |||
| dde92cc64d | |||
| c8261b361a | |||
| d0d37dd05e | |||
| e8ab0ed082 | |||
| 544774d8b9 | |||
| f2cc603690 | |||
| a0f5d736f9 |
+4
-1
@@ -32,4 +32,7 @@ hie.yaml
|
||||
src/Grammar/Scanner.hs
|
||||
src/Grammar/Parser.hs
|
||||
src/Grammar/Parser.info
|
||||
src/Main
|
||||
src/Main
|
||||
out/
|
||||
mine.txt
|
||||
ref.txt
|
||||
@@ -0,0 +1,5 @@
|
||||
# use make build to compile all binaries
|
||||
build:
|
||||
alex ./src/Grammar/Scanner.x
|
||||
happy ./src/Grammar/Parser.y -o ./src/Grammar/Parser.hs
|
||||
ghc -dynamic -i./src src/Main.hs
|
||||
@@ -0,0 +1 @@
|
||||
public class Runner { public static void main(String[] args) { FieldsTest f = new FieldsTest(); System.out.println(f.b); } }
|
||||
+103
-3
@@ -3,15 +3,20 @@ module Codegen.ClassFile where
|
||||
import Codegen.ConstPool
|
||||
( generateConstPool,
|
||||
lookupClassIndex,
|
||||
lookupFieldRefIndex,
|
||||
lookupIntegerIndex,
|
||||
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 (..),
|
||||
TypedStmt (..),
|
||||
)
|
||||
|
||||
data ClassFile = ClassFile
|
||||
@@ -155,17 +160,112 @@ makeAttributes :: CP_Infos -> Attribute_Infos
|
||||
makeAttributes _ = []
|
||||
|
||||
makeMethods :: CP_Infos -> TypedClass -> Method_Infos
|
||||
makeMethods constPool (Class _ _ methods) = map (makeMethod constPool) methods
|
||||
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 constPool expr
|
||||
++ [0xB5, fromIntegral (idx `shiftR` 8 .&. 0xFF), fromIntegral (idx .&. 0xFF)]
|
||||
fieldInitBytes _ _ = []
|
||||
|
||||
pushExpr :: CP_Infos -> TypedExpr -> [Word8]
|
||||
pushExpr constPool (Integer n _) = pushInt constPool (fromIntegral n)
|
||||
pushExpr _ (Bool True _) = [0x04]
|
||||
pushExpr _ (Bool False _) = [0x03]
|
||||
pushExpr constPool (Char c _) = pushInt constPool (fromEnum c)
|
||||
pushExpr _ (Null _) = [0x01]
|
||||
pushExpr _ _ = []
|
||||
|
||||
pushInt :: CP_Infos -> Int -> [Word8]
|
||||
pushInt constPool 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 =
|
||||
let idx = lookupIntegerIndex constPool n
|
||||
in if idx == 0
|
||||
then error $ "Missing Integer constant in pool: " ++ show n
|
||||
else
|
||||
if idx <= 255
|
||||
then [0x12, fromIntegral idx]
|
||||
else [0x13, fromIntegral (idx `shiftR` 8 .&. 0xFF), fromIntegral (idx .&. 0xFF)]
|
||||
|
||||
makeMethod :: CP_Infos -> TypedMethodDecl -> Method_Info
|
||||
makeMethod constPool (Method returnType name params _body) =
|
||||
makeMethod constPool (Method returnType name params body) =
|
||||
Method_Info
|
||||
{ methodAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
methodNameIndex = lookupUtf8Index constPool name,
|
||||
methodDescIndex = lookupUtf8Index constPool (methodDescriptor params returnType),
|
||||
methodAttributes = []
|
||||
methodAttributes = [makeMethodCode constPool returnType params body]
|
||||
}
|
||||
|
||||
makeMethodCode :: CP_Infos -> Type -> [(Type, String)] -> TypedStmt -> Attribute_Info
|
||||
makeMethodCode constPool returnType params body =
|
||||
let (code, stack) = stmtCode constPool returnType body
|
||||
locals = 1 + length params
|
||||
in Code_Attribute
|
||||
{ codeNameIndex = lookupUtf8Index constPool "Code",
|
||||
maxStack = stack,
|
||||
maxLocals = locals,
|
||||
codeBody = code,
|
||||
exceptionTable = [],
|
||||
codeAttributes = []
|
||||
}
|
||||
|
||||
stmtCode :: CP_Infos -> Type -> TypedStmt -> ([Word8], Int)
|
||||
stmtCode constPool returnType stmt = case stmt of
|
||||
Return Nothing _
|
||||
| returnType == "void" -> ([0xB1], 0)
|
||||
| otherwise -> error "Non-void method must return a value"
|
||||
Return (Just expr) _ ->
|
||||
let (exprBytes, stack) = exprCode constPool expr
|
||||
retOp = case returnType of
|
||||
"int" -> 0xAC
|
||||
"boolean" -> 0xAC
|
||||
"char" -> 0xAC
|
||||
_ -> error $ "Unsupported return type: " ++ returnType
|
||||
in (exprBytes ++ [retOp], stack)
|
||||
Block [single] _ -> stmtCode constPool returnType single
|
||||
Block [] _
|
||||
| returnType == "void" -> ([0xB1], 0)
|
||||
| otherwise -> error "Non-void method must return a value"
|
||||
_ -> error "Unsupported method body"
|
||||
|
||||
exprCode :: CP_Infos -> TypedExpr -> ([Word8], Int)
|
||||
exprCode constPool expr = case expr of
|
||||
Integer n _ -> (pushInt constPool (fromIntegral n), 1)
|
||||
Bool True _ -> ([0x04], 1)
|
||||
Bool False _ -> ([0x03], 1)
|
||||
Char c _ -> (pushInt constPool (fromEnum c), 1)
|
||||
Null _ -> ([0x01], 1)
|
||||
_ -> error "Unsupported return expression"
|
||||
|
||||
methodDescriptor :: [(Type, String)] -> Type -> String
|
||||
methodDescriptor params returnType =
|
||||
"(" ++ concatMap (typeDescriptor . fst) params ++ ")" ++ typeDescriptor returnType
|
||||
|
||||
+162
-24
@@ -1,7 +1,7 @@
|
||||
module Codegen.ConstPool where
|
||||
|
||||
import Codegen.Types
|
||||
import Data.List (isSuffixOf)
|
||||
import Data.List (isPrefixOf, isSuffixOf)
|
||||
import qualified Grammar.TAST as TAST
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -89,6 +89,18 @@ collectFromExprs :: [TAST.TypedExpr] -> CP_Infos
|
||||
collectFromExprs [] = []
|
||||
collectFromExprs (e : es) = collectFromTypedExpr e ++ collectFromExprs es
|
||||
|
||||
exprType :: TAST.TypedExpr -> String
|
||||
exprType (TAST.This t) = t
|
||||
exprType (TAST.LocalOrFieldVar _ t) = t
|
||||
exprType (TAST.InstVar _ _ t) = t
|
||||
exprType (TAST.Unary _ _ t) = t
|
||||
exprType (TAST.Binary _ _ _ t) = t
|
||||
exprType (TAST.Integer _ t) = t
|
||||
exprType (TAST.Bool _ t) = t
|
||||
exprType (TAST.Char _ t) = t
|
||||
exprType (TAST.Null t) = t
|
||||
exprType (TAST.StmtExprExpr _ t) = t
|
||||
|
||||
collectFromTypedExpr :: TAST.TypedExpr -> CP_Infos
|
||||
collectFromTypedExpr (TAST.Integer n _) =
|
||||
[mkIntegerInfo (fromIntegral n) ("int " ++ show n)]
|
||||
@@ -103,12 +115,14 @@ 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)
|
||||
]
|
||||
let objType = exprType obj
|
||||
desc = typeToDescriptor typ
|
||||
in collectFromTypedExpr obj
|
||||
++ [ mkUtf8Info name ("field name: " ++ name),
|
||||
mkUtf8Info desc ("field descriptor: " ++ desc),
|
||||
mkNameAndTypeInfo 0 0 ("field NameAndType: " ++ name ++ ":" ++ desc),
|
||||
mkFieldRefInfo 0 0 ("field ref: " ++ objType ++ "." ++ name ++ ":" ++ desc)
|
||||
]
|
||||
collectFromTypedExpr (TAST.Unary _ e _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedExpr (TAST.Binary _ e1 e2 _) =
|
||||
@@ -122,16 +136,20 @@ collectFromTypedStmtExpr (TAST.Assign lhs rhs _) =
|
||||
collectFromTypedStmtExpr (TAST.New t args _) =
|
||||
collectFromExprs args
|
||||
++ [ mkUtf8Info t ("class name: " ++ t),
|
||||
mkClassInfo 0 ("class: " ++ t)
|
||||
mkClassInfo 0 ("class: " ++ t),
|
||||
mkMethodRefInfo 0 0 ("new method ref: " ++ t ++ ".<init>:()V")
|
||||
]
|
||||
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)
|
||||
]
|
||||
let objType = exprType obj
|
||||
argTypes = map exprType args
|
||||
fullDesc = "(" ++ concatMap typeToDescriptor argTypes ++ ")" ++ typeToDescriptor retTyp
|
||||
in collectFromTypedExpr obj
|
||||
++ collectFromExprs args
|
||||
++ [ mkUtf8Info name ("method name: " ++ name),
|
||||
mkUtf8Info fullDesc ("method desc: " ++ name),
|
||||
mkNameAndTypeInfo 0 0 ("method NameAndType: " ++ name ++ ":" ++ fullDesc),
|
||||
mkMethodRefInfo 0 0 ("method ref: " ++ objType ++ "." ++ name ++ ":" ++ fullDesc)
|
||||
]
|
||||
|
||||
collectFromStmts :: [TAST.TypedStmt] -> CP_Infos
|
||||
collectFromStmts [] = []
|
||||
@@ -161,9 +179,8 @@ collectFromMaybeStmt (Just s) = collectFromTypedStmt s
|
||||
|
||||
collectFromFields :: [TAST.TypedFieldDecl] -> CP_Infos
|
||||
collectFromFields [] = []
|
||||
collectFromFields (TAST.Field fType fName _: fs ) =
|
||||
collectFromFields (TAST.Field fType fName _ : fs) =
|
||||
[ mkUtf8Info fName ("field: " ++ fName),
|
||||
|
||||
mkUtf8Info (typeToDescriptor fType) ("field desc: " ++ fType)
|
||||
]
|
||||
++ collectFromFields fs
|
||||
@@ -183,13 +200,43 @@ collectFromMethods (TAST.Method mType mName params body : ms) =
|
||||
|
||||
generateConstPool :: TAST.TypedClass -> CP_Infos
|
||||
generateConstPool (TAST.Class cName fields methods) =
|
||||
[ mkUtf8Info cName ("this class: " ++ cName),
|
||||
mkUtf8Info "java/lang/Object" "super class",
|
||||
mkClassInfo 0 ("this class: " ++ cName),
|
||||
mkClassInfo 0 "super class"
|
||||
]
|
||||
++ collectFromFields fields
|
||||
++ collectFromMethods methods
|
||||
let basePool =
|
||||
[ 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)
|
||||
initExprs = [e | TAST.Field _ _ (Just e) <- fields]
|
||||
initPool = concatMap collectFromTypedExpr initExprs
|
||||
methodPool = collectFromMethods methods
|
||||
rawPool = basePool ++ initPool ++ methodPool
|
||||
in resolvePool rawPool
|
||||
|
||||
-- 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 =
|
||||
@@ -223,6 +270,97 @@ lookupNameAndTypeIndex pool name =
|
||||
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
|
||||
|
||||
lookupMethodRefIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupMethodRefIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
MethodRefInfo {desc = descVal} -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
lookupIntegerIndex :: CP_Infos -> Int -> IndexConstantPool
|
||||
lookupIntegerIndex pool value =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
IntegerInfo {numi_cp = n} -> n == value
|
||||
_ -> False
|
||||
|
||||
resolvePool :: CP_Infos -> CP_Infos
|
||||
resolvePool pool = map resolveEntry pool
|
||||
where
|
||||
resolveEntry (ClassInfo tag 0 desc) =
|
||||
ClassInfo tag (lookupUtf8Index pool (classNameFromDesc desc)) desc
|
||||
resolveEntry (NameAndTypeInfo tag 0 0 desc) =
|
||||
let (name, descr) = nameAndTypeFromDesc desc
|
||||
nameIdx = lookupUtf8Index pool name
|
||||
descIdx = lookupUtf8Index pool descr
|
||||
in NameAndTypeInfo tag nameIdx descIdx desc
|
||||
resolveEntry (FieldRefInfo tag 0 0 desc) =
|
||||
let (clsName, natDesc) = fieldRefFromDesc desc
|
||||
clsIdx = if null clsName then 0 else lookupClassIndex pool clsName
|
||||
natIdx = if null natDesc then 0 else lookupNameAndTypeIndex pool natDesc
|
||||
in FieldRefInfo tag clsIdx natIdx desc
|
||||
resolveEntry (MethodRefInfo tag 0 0 desc) =
|
||||
let (clsName, natDesc) = methodRefFromDesc desc
|
||||
clsIdx = if null clsName then 0 else lookupClassIndex pool clsName
|
||||
natIdx = if null natDesc then 0 else lookupNameAndTypeIndex pool natDesc
|
||||
in MethodRefInfo tag clsIdx natIdx desc
|
||||
resolveEntry other = other
|
||||
|
||||
classNameFromDesc :: String -> String
|
||||
classNameFromDesc desc
|
||||
| "class: " `isPrefixOf` desc = drop 7 desc
|
||||
| "this class: " `isPrefixOf` desc = drop 12 desc
|
||||
| desc == "super class" = "java/lang/Object"
|
||||
| otherwise = desc
|
||||
|
||||
nameAndTypeFromDesc :: String -> (String, String)
|
||||
nameAndTypeFromDesc desc
|
||||
| "field NameAndType: " `isPrefixOf` desc =
|
||||
let rest = drop 19 desc
|
||||
(name, descPart) = break (== ':') rest
|
||||
in (name, drop 1 descPart)
|
||||
| "method NameAndType: " `isPrefixOf` desc =
|
||||
let rest = drop 20 desc
|
||||
(name, descPart) = break (== ':') rest
|
||||
in (name, drop 1 descPart)
|
||||
| otherwise = ("", "")
|
||||
|
||||
fieldRefFromDesc :: String -> (String, String)
|
||||
fieldRefFromDesc desc
|
||||
| "field ref: " `isPrefixOf` desc =
|
||||
let rest = drop 11 desc
|
||||
(clsName, natDesc') = break (== '.') rest
|
||||
in (clsName, drop 1 natDesc')
|
||||
| otherwise = ("", "")
|
||||
|
||||
methodRefFromDesc :: String -> (String, String)
|
||||
methodRefFromDesc desc
|
||||
| "new method ref: " `isPrefixOf` desc =
|
||||
let rest = drop 16 desc
|
||||
(clsName, natDesc') = break (== '.') rest
|
||||
in (clsName, drop 1 natDesc')
|
||||
| "method ref: " `isPrefixOf` desc =
|
||||
let rest = drop 12 desc
|
||||
(clsName, natDesc') = break (== '.') rest
|
||||
in (clsName, drop 1 natDesc')
|
||||
| otherwise = ("", "")
|
||||
|
||||
findIndex :: (CP_Info -> Bool) -> CP_Infos -> Maybe IndexConstantPool
|
||||
findIndex predicate pool = go 1 pool
|
||||
where
|
||||
|
||||
+34
-2
@@ -1,7 +1,7 @@
|
||||
module Codegen.Lowerer where
|
||||
|
||||
import Codegen.ClassFile
|
||||
import Codegen.Types (CP_Info (Utf8Info), CP_Infos)
|
||||
import Codegen.Types (CP_Info (..), CP_Infos)
|
||||
import Data.Word (Word8)
|
||||
import Numeric (showHex)
|
||||
|
||||
@@ -17,13 +17,20 @@ data Instruction
|
||||
| InvokeVirtual Int
|
||||
| InvokeStatic Int
|
||||
| IConst Int
|
||||
| BIPush Int
|
||||
| SIPush Int
|
||||
| AConstNull
|
||||
| Ldc Int
|
||||
| IAdd
|
||||
| ISub
|
||||
| IMul
|
||||
| IDiv
|
||||
| IfEq Int
|
||||
| IfNe Int
|
||||
| IfLt Int
|
||||
| IfGe Int
|
||||
| IfGt Int
|
||||
| IfLe Int
|
||||
| Goto Int
|
||||
| Return
|
||||
| IReturn
|
||||
@@ -108,13 +115,20 @@ decodeBytes pos (b : bs) = case b of
|
||||
0x07 -> line (IConst 4) pos 1 bs
|
||||
0x08 -> line (IConst 5) pos 1 bs
|
||||
0x01 -> line AConstNull pos 1 bs
|
||||
0x10 -> withSignedByte bs pos $ \n rest -> line (BIPush n) pos 2 rest
|
||||
0x11 -> withSignedShort bs pos $ \n rest -> line (SIPush n) pos 3 rest
|
||||
0x12 -> withByte bs pos $ \i rest -> line (Ldc i) pos 2 rest
|
||||
0x13 -> withIndex bs pos $ \i rest -> line (Ldc i) pos 3 rest
|
||||
0x60 -> line IAdd pos 1 bs
|
||||
0x64 -> line ISub pos 1 bs
|
||||
0x68 -> line IMul pos 1 bs
|
||||
0x6C -> line IDiv pos 1 bs
|
||||
0x99 -> withIndex bs pos $ \i rest -> line (IfEq i) pos 3 rest
|
||||
0x9A -> withIndex bs pos $ \i rest -> line (IfNe i) pos 3 rest
|
||||
0x9B -> withIndex bs pos $ \i rest -> line (IfLt i) pos 3 rest
|
||||
0x9C -> withIndex bs pos $ \i rest -> line (IfGe i) pos 3 rest
|
||||
0x9D -> withIndex bs pos $ \i rest -> line (IfGt i) pos 3 rest
|
||||
0x9E -> withIndex bs pos $ \i rest -> line (IfLe i) pos 3 rest
|
||||
0xA7 -> withIndex bs pos $ \i rest -> line (Goto i) pos 3 rest
|
||||
0xB1 -> line Return pos 1 bs
|
||||
0xAC -> line IReturn pos 1 bs
|
||||
@@ -133,7 +147,25 @@ withIndex :: [Word8] -> Int -> (Int -> [Word8] -> [BtcLine]) -> [BtcLine]
|
||||
withIndex (hi : lo : rest) _ f = f (fromIntegral hi * 256 + fromIntegral lo) rest
|
||||
withIndex _ pos _ = error $ "Unexpected end of bytecode at " ++ show pos
|
||||
|
||||
withSignedByte :: [Word8] -> Int -> (Int -> [Word8] -> [BtcLine]) -> [BtcLine]
|
||||
withSignedByte (b : rest) _ f = f (signedByte b) rest
|
||||
withSignedByte [] pos _ = error $ "Unexpected end of bytecode at " ++ show pos
|
||||
|
||||
withSignedShort :: [Word8] -> Int -> (Int -> [Word8] -> [BtcLine]) -> [BtcLine]
|
||||
withSignedShort (hi : lo : rest) _ f = f (signedShort hi lo) rest
|
||||
withSignedShort _ pos _ = error $ "Unexpected end of bytecode at " ++ show pos
|
||||
|
||||
signedByte :: Word8 -> Int
|
||||
signedByte b =
|
||||
let n = fromIntegral b :: Int
|
||||
in if n >= 128 then n - 256 else n
|
||||
|
||||
signedShort :: Word8 -> Word8 -> Int
|
||||
signedShort hi lo =
|
||||
let n = fromIntegral hi * 256 + fromIntegral lo :: Int
|
||||
in if n >= 32768 then n - 65536 else n
|
||||
|
||||
lookupPool :: CP_Infos -> Index -> Maybe CP_Info
|
||||
lookupPool pool i
|
||||
| i < 1 || i > length pool = Nothing
|
||||
| otherwise = Just (pool !! (i - 1)) -- pool is 1-indexed
|
||||
| otherwise = Just (pool !! (i - 1)) -- pool is 1-indexed
|
||||
@@ -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]
|
||||
@@ -54,6 +58,13 @@ serializeInstruction instr = case instr of
|
||||
IConst 4 -> [0x07]
|
||||
IConst 5 -> [0x08]
|
||||
IConst n -> error $ "IConst out of range (-1..5): " ++ show n
|
||||
-- bipush/sipush: push signed byte/short
|
||||
BIPush n
|
||||
| n >= -128 && n <= 127 -> [0x10, fromIntegral n]
|
||||
| otherwise -> error $ "BIPush out of range (-128..127): " ++ show n
|
||||
SIPush n
|
||||
| n >= -32768 && n <= 32767 -> 0x11 : indexBytes (n .&. 0xFFFF)
|
||||
| otherwise -> error $ "SIPush out of range (-32768..32767): " ++ show n
|
||||
AConstNull -> [0x01]
|
||||
-- ldc: 1-byte index (use ldc_w 0x13 for large pool indices > 255)
|
||||
Ldc i
|
||||
@@ -67,6 +78,10 @@ serializeInstruction instr = case instr of
|
||||
-- Control flow: 2-byte signed branch offset
|
||||
IfEq offset -> 0x99 : indexBytes offset
|
||||
IfNe offset -> 0x9A : indexBytes offset
|
||||
IfLt offset -> 0x9B : indexBytes offset
|
||||
IfGe offset -> 0x9C : indexBytes offset
|
||||
IfGt offset -> 0x9D : indexBytes offset
|
||||
IfLe offset -> 0x9E : indexBytes offset
|
||||
Goto offset -> 0xA7 : indexBytes offset
|
||||
-- Return
|
||||
Return -> [0xB1]
|
||||
@@ -77,3 +92,72 @@ 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)
|
||||
|
||||
+2
-1
@@ -33,9 +33,10 @@ data Stmt
|
||||
= Block [Stmt]
|
||||
| Return (Maybe Expr)
|
||||
| While Expr Stmt
|
||||
| LocalVarDecl Type String
|
||||
| LocalVarDecl Type String (Maybe Expr)
|
||||
| If Expr Stmt (Maybe Stmt)
|
||||
| StmtExprStmt StmtExpr
|
||||
| EmptyStmt
|
||||
deriving (Show, Eq)
|
||||
|
||||
data BinaryOperator
|
||||
|
||||
+19
-8
@@ -20,6 +20,7 @@ import Grammar.AST
|
||||
if { TokenIf }
|
||||
else { TokenElse }
|
||||
while { TokenWhile }
|
||||
for { TokenFor }
|
||||
int { TokenIntType }
|
||||
boolean { TokenBoolType }
|
||||
char { TokenCharType }
|
||||
@@ -109,19 +110,29 @@ Block : '{' Stmts '}' { Block (reverse $2) }
|
||||
Stmts : Stmts Stmt { $2 : $1 }
|
||||
| {- leer -} { [] }
|
||||
|
||||
Stmt : Block { $1 }
|
||||
| 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 }
|
||||
| Type id ';' { LocalVarDecl $1 $2 }
|
||||
| StmtExpr ';' { StmtExprStmt $1 }
|
||||
|
||||
Stmt : Block { $1 }
|
||||
| return Expr ';' { Return (Just $2) }
|
||||
| return ';' { Return Nothing }
|
||||
| while '(' Expr ')' Stmt { While $3 $5 }
|
||||
| for '(' Stmt Expr ';' StmtExpr ')' Stmt { Block [$3, (While $4 (Block [$8, (StmtExprStmt $6)]))] }
|
||||
| for '(' Stmt Expr ';' ')' Stmt { Block [$3, (While $4 $7)] }
|
||||
| if '(' Expr ')' Stmt else Stmt { If $3 $5 (Just $7) }
|
||||
| if '(' Expr ')' Stmt { If $3 $5 Nothing }
|
||||
| Type id ';' { LocalVarDecl $1 $2 Nothing }
|
||||
| Type id '=' Expr ';' { LocalVarDecl $1 $2 (Just $4) }
|
||||
| StmtExpr ';' { StmtExprStmt $1 }
|
||||
| ';' { EmptyStmt }
|
||||
|
||||
StmtExpr : Expr '=' Expr { Assign $1 $3 }
|
||||
| new id '(' Exprs ')' { New $2 $4 }
|
||||
| Expr '.' id '(' Exprs ')' { MethodCall $1 $3 $5 }
|
||||
| id '(' Exprs ')' { MethodCall This $1 $3 }
|
||||
| Expr '+' '=' Expr { Assign $1 (Binary Add $1 $4) }
|
||||
| Expr '-' '=' Expr { Assign $1 (Binary Subtract $1 $4) }
|
||||
| Expr '*' '=' Expr { Assign $1 (Binary Multiply $1 $4) }
|
||||
| Expr '/' '=' Expr { Assign $1 (Binary Divide $1 $4) }
|
||||
| Expr '%' '=' Expr { Assign $1 (Binary Modulo $1 $4) }
|
||||
|
||||
Expr : this { This }
|
||||
| id { LocalOrFieldVar $1 }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module Main where
|
||||
|
||||
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)
|
||||
@@ -22,8 +24,15 @@ runPipeline path = do
|
||||
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
|
||||
|
||||
|
||||
@@ -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,25 @@
|
||||
module Testsuite.AbcFiles.ConstructorOverloadTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 8},
|
||||
BtcLine {lineNumber = 4, instruction = Return}
|
||||
]
|
||||
),
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 8},
|
||||
BtcLine {lineNumber = 4, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 5, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 6, instruction = PutField 2},
|
||||
BtcLine {lineNumber = 9, instruction = Return}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
module Testsuite.AbcFiles.ConstructorTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 8},
|
||||
BtcLine {lineNumber = 4, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 5, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 6, instruction = PutField 2},
|
||||
BtcLine {lineNumber = 9, instruction = Return}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -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 8},
|
||||
BtcLine {lineNumber = 4, instruction = Return}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
module Testsuite.AbcFiles.FieldsTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 8},
|
||||
BtcLine {lineNumber = 4, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 5, instruction = BIPush 42},
|
||||
BtcLine {lineNumber = 7, instruction = PutField 15},
|
||||
BtcLine {lineNumber = 10, instruction = Return}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.AbcFiles.IfTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "ifElseTest",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 1, instruction = IfGe 6},
|
||||
BtcLine {lineNumber = 4, instruction = IConst 0},
|
||||
BtcLine {lineNumber = 5, instruction = IReturn},
|
||||
BtcLine {lineNumber = 6, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 7, instruction = IfNe 14},
|
||||
BtcLine {lineNumber = 10, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 11, instruction = IReturn},
|
||||
BtcLine {lineNumber = 12, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 13, instruction = IConst 10},
|
||||
BtcLine {lineNumber = 14, instruction = IfGt 18},
|
||||
BtcLine {lineNumber = 17, instruction = IConst 0},
|
||||
BtcLine {lineNumber = 18, instruction = IReturn},
|
||||
BtcLine {lineNumber = 19, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 20, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
module Testsuite.AbcFiles.MultiClassTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 8},
|
||||
BtcLine {lineNumber = 4, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 5, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 6, instruction = PutField 2},
|
||||
BtcLine {lineNumber = 9, instruction = Return}
|
||||
]
|
||||
),
|
||||
( "doubleIt",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = GetField 3},
|
||||
BtcLine {lineNumber = 4, instruction = IConst 2},
|
||||
BtcLine {lineNumber = 5, instruction = IMul},
|
||||
BtcLine {lineNumber = 6, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
module Testsuite.AbcFiles.RecursionTestABC (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 = ALoad0},
|
||||
BtcLine {lineNumber = 5, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 6, instruction = PutField 2},
|
||||
BtcLine {lineNumber = 9, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 10, instruction = IfLe 26},
|
||||
BtcLine {lineNumber = 13, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 14, instruction = Ldc 3},
|
||||
BtcLine {lineNumber = 16, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 17, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 18, instruction = ISub},
|
||||
BtcLine {lineNumber = 19, instruction = InvokeSpecial 4},
|
||||
BtcLine {lineNumber = 22, instruction = PutField 5},
|
||||
BtcLine {lineNumber = 25, instruction = Return}
|
||||
]
|
||||
),
|
||||
( "fibonacci",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 1, instruction = IConst 2},
|
||||
BtcLine {lineNumber = 2, instruction = IfGe 7},
|
||||
BtcLine {lineNumber = 5, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 6, instruction = IReturn},
|
||||
BtcLine {lineNumber = 7, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 8, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 9, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 10, instruction = ISub},
|
||||
BtcLine {lineNumber = 11, instruction = InvokeVirtual 6},
|
||||
BtcLine {lineNumber = 14, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 15, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 16, instruction = IConst 2},
|
||||
BtcLine {lineNumber = 17, instruction = ISub},
|
||||
BtcLine {lineNumber = 18, instruction = InvokeVirtual 6},
|
||||
BtcLine {lineNumber = 21, instruction = IAdd},
|
||||
BtcLine {lineNumber = 22, instruction = IReturn}
|
||||
]
|
||||
),
|
||||
( "ackermann",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 1, instruction = IfNe 8},
|
||||
BtcLine {lineNumber = 4, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 5, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 6, instruction = IAdd},
|
||||
BtcLine {lineNumber = 7, instruction = IReturn},
|
||||
BtcLine {lineNumber = 8, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 9, instruction = IfNe 21},
|
||||
BtcLine {lineNumber = 12, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 13, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 14, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 15, instruction = ISub},
|
||||
BtcLine {lineNumber = 16, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 17, instruction = InvokeVirtual 7},
|
||||
BtcLine {lineNumber = 20, instruction = IReturn},
|
||||
BtcLine {lineNumber = 21, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 22, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 23, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 24, instruction = ISub},
|
||||
BtcLine {lineNumber = 25, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 26, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 27, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 28, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 29, instruction = ISub},
|
||||
BtcLine {lineNumber = 30, instruction = InvokeVirtual 7},
|
||||
BtcLine {lineNumber = 33, instruction = InvokeVirtual 7},
|
||||
BtcLine {lineNumber = 36, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
module Testsuite.AbcFiles.ReturnTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "main",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = BIPush 42},
|
||||
BtcLine {lineNumber = 1, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
module Testsuite.AbcFiles.SingletonTestABC (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}
|
||||
]
|
||||
),
|
||||
( "getInstance",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = GetField 2},
|
||||
BtcLine {lineNumber = 4, instruction = IfNe 17},
|
||||
BtcLine {lineNumber = 7, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 8, instruction = Ldc 3},
|
||||
BtcLine {lineNumber = 10, instruction = InvokeSpecial 1},
|
||||
BtcLine {lineNumber = 13, instruction = PutField 2},
|
||||
BtcLine {lineNumber = 17, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 18, instruction = GetField 2},
|
||||
BtcLine {lineNumber = 21, instruction = AReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.AbcFiles.WhileTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "whileLoopTest",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = IConst 0},
|
||||
BtcLine {lineNumber = 1, instruction = IStore 1},
|
||||
BtcLine {lineNumber = 2, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 3, instruction = IfLe 16},
|
||||
BtcLine {lineNumber = 6, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 7, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 8, instruction = IAdd},
|
||||
BtcLine {lineNumber = 9, instruction = IStore 1},
|
||||
BtcLine {lineNumber = 10, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 11, instruction = IConst 1},
|
||||
BtcLine {lineNumber = 12, instruction = ISub},
|
||||
BtcLine {lineNumber = 13, instruction = IStore 0},
|
||||
BtcLine {lineNumber = 14, instruction = Goto 2},
|
||||
BtcLine {lineNumber = 17, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 18, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
module Testsuite.AstFiles.ConstructorOverloadTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"ConstructorOverloadTest"
|
||||
[Field "int" "a" (Just (Integer 42))]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[]
|
||||
(Block []),
|
||||
Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "a")]
|
||||
(Block [StmtExprStmt (Assign (InstVar This "a") (LocalOrFieldVar "a"))])
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
module Testsuite.AstFiles.ConstructorTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"ConstructorTest"
|
||||
[Field "int" "a" (Just (Integer (-1)))]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "initial_value")]
|
||||
(Block [StmtExprStmt (Assign (LocalOrFieldVar "a") (LocalOrFieldVar "initial_value"))])
|
||||
]
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
[
|
||||
Class "EmptyTest" [] []
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
module Testsuite.AstFiles.EmptyTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"EmptyTest"
|
||||
[]
|
||||
[]
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
module Testsuite.AstFiles.FieldsTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"FieldsTest"
|
||||
[ Field "int" "a" Nothing,
|
||||
Field "int" "b" (Just (Integer 42))
|
||||
]
|
||||
[]
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.AstFiles.IfTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"IfTest"
|
||||
[]
|
||||
[ Method
|
||||
"boolean"
|
||||
"ifElseTest"
|
||||
[("int", "x")]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompLessThan (LocalOrFieldVar "x") (Integer 0))
|
||||
(Block [Return (Just (Bool False))])
|
||||
( Just
|
||||
( If
|
||||
(Binary CompEqual (LocalOrFieldVar "x") (Integer 0))
|
||||
(Block [Return (Just (Bool True))])
|
||||
(Just (Block [Return (Just (Binary CompGreaterThan (LocalOrFieldVar "x") (Integer 10)))]))
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
module Testsuite.AstFiles.MultiClassTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"MultiClassTest"
|
||||
[]
|
||||
[],
|
||||
Class
|
||||
"Helper"
|
||||
[Field "int" "v" Nothing]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "v0")]
|
||||
(Block [StmtExprStmt (Assign (LocalOrFieldVar "v") (LocalOrFieldVar "v0"))]),
|
||||
Method
|
||||
"int"
|
||||
"doubleIt"
|
||||
[]
|
||||
(Block [Return (Just (Binary Multiply (LocalOrFieldVar "v") (Integer 2)))])
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
module Testsuite.AstFiles.RecursionTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"RecursionTest"
|
||||
[ Field "int" "value" (Just (Integer 0))
|
||||
, Field "RecursionTest" "child" (Just Null)
|
||||
]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "n")]
|
||||
( Block
|
||||
[ StmtExprStmt (Assign (LocalOrFieldVar "value") (LocalOrFieldVar "n"))
|
||||
, If
|
||||
(Binary CompGreaterThan (LocalOrFieldVar "n") (Integer 0))
|
||||
( Block
|
||||
[ StmtExprStmt (Assign (LocalOrFieldVar "child") (StmtExprExpr (New "RecursionTest" [Binary Subtract (LocalOrFieldVar "n") (Integer 1)])))
|
||||
]
|
||||
)
|
||||
Nothing
|
||||
]
|
||||
)
|
||||
, Method
|
||||
"int"
|
||||
"fibonacci"
|
||||
[("int", "n")]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompLessThan (LocalOrFieldVar "n") (Integer 2))
|
||||
(Return (Just (LocalOrFieldVar "n")))
|
||||
( Just
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Add
|
||||
(StmtExprExpr (MethodCall This "fibonacci" [Binary Subtract (LocalOrFieldVar "n") (Integer 1)]))
|
||||
(StmtExprExpr (MethodCall This "fibonacci" [Binary Subtract (LocalOrFieldVar "n") (Integer 2)]))
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
, Method
|
||||
"int"
|
||||
"ackermann"
|
||||
[("int", "m"), ("int", "n")]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompEqual (LocalOrFieldVar "m") (Integer 0))
|
||||
(Return (Just (Binary Add (LocalOrFieldVar "n") (Integer 1))))
|
||||
Nothing
|
||||
, If
|
||||
(Binary CompEqual (LocalOrFieldVar "n") (Integer 0))
|
||||
(Return (Just (StmtExprExpr (MethodCall This "ackermann" [Binary Subtract (LocalOrFieldVar "m") (Integer 1), Integer 1]))))
|
||||
Nothing
|
||||
, Return
|
||||
( Just
|
||||
( StmtExprExpr
|
||||
( MethodCall
|
||||
This
|
||||
"ackermann"
|
||||
[ Binary Subtract (LocalOrFieldVar "m") (Integer 1)
|
||||
, StmtExprExpr (MethodCall This "ackermann" [LocalOrFieldVar "m", Binary Subtract (LocalOrFieldVar "n") (Integer 1)])
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
module Testsuite.AstFiles.ReturnTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"ReturnTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"main"
|
||||
[]
|
||||
( Block [Return (Just (Integer 42))] )
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
module Testsuite.AstFiles.SingletonTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"SingletonTest"
|
||||
[ Field "SingletonTest" "instance" Nothing
|
||||
]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[]
|
||||
(Block [])
|
||||
, Method
|
||||
"SingletonTest"
|
||||
"getInstance"
|
||||
[]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompEqual (LocalOrFieldVar "instance") Null)
|
||||
(Block [StmtExprStmt (Assign (LocalOrFieldVar "instance") (StmtExprExpr (New "SingletonTest" [])))])
|
||||
Nothing
|
||||
, Return (Just (LocalOrFieldVar "instance"))
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
module Testsuite.AstFiles.WhileTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"WhileTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"whileLoopTest"
|
||||
[("int", "n")]
|
||||
( Block
|
||||
[ LocalVarDecl "int" "sum" Nothing,
|
||||
While
|
||||
(Binary CompGreaterThan (LocalOrFieldVar "n") (Integer 0))
|
||||
( Block
|
||||
[ StmtExprStmt
|
||||
(Assign
|
||||
(LocalOrFieldVar "sum")
|
||||
(Binary Add (LocalOrFieldVar "sum") (LocalOrFieldVar "n"))
|
||||
),
|
||||
StmtExprStmt
|
||||
(Assign
|
||||
(LocalOrFieldVar "n")
|
||||
(Binary Subtract (LocalOrFieldVar "n") (Integer 1))
|
||||
)
|
||||
]
|
||||
),
|
||||
Return (Just (LocalOrFieldVar "sum"))
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -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,14 +0,0 @@
|
||||
module TestSuite.ExecutableTests.ArithmeticASTTest where
|
||||
|
||||
import Grammar.Parser
|
||||
import Grammar.Scanner
|
||||
import Testsuite.AstFiles.ArithmeticTestAST (expectedAST)
|
||||
|
||||
mainArithemticASTTest :: IO ()
|
||||
mainArithemticASTTest = do
|
||||
let javaFilePath = "Testsuite/javaFiles/ArithmeticTest.java"
|
||||
java <- readFile javaFilePath
|
||||
let actualAST = parse . alexScanTokens $ java
|
||||
print $ actualAST == expectedAST
|
||||
print actualAST
|
||||
print expectedAST
|
||||
@@ -0,0 +1,114 @@
|
||||
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
|
||||
let ls = lines info
|
||||
firstLine = if null ls then "" else head ls
|
||||
rest = if null ls then [] else tail ls
|
||||
header = "[FAIL] " ++ name ++ (if null firstLine then "" else " - " ++ firstLine)
|
||||
putStrLn header
|
||||
if null rest
|
||||
then pure ()
|
||||
else mapM_ (putStrLn . (" " ++)) rest
|
||||
|
||||
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,79 @@
|
||||
module Testsuite.ExecutableTests.Main where
|
||||
|
||||
import System.Environment (getArgs)
|
||||
import System.Exit (exitFailure)
|
||||
import Control.Monad (forM)
|
||||
import Control.Exception (try, SomeException)
|
||||
import Testsuite.ExecutableTests.TestCase
|
||||
import Testsuite.ExecutableTests.Registry
|
||||
import Testsuite.ExecutableTests.Runner.AST
|
||||
import Testsuite.ExecutableTests.Runner.TAST
|
||||
import Testsuite.ExecutableTests.Runner.ABC
|
||||
import Testsuite.ExecutableTests.Runner.Bytecode
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestResult(..), printTestResults, exitOnFailures, runNamedTests)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
case args of
|
||||
[] -> runAll
|
||||
[testName] -> runSpecific testName
|
||||
_ -> do
|
||||
putStrLn "Usage: runghc -isrc src/Testsuite/ExecutableTests/Main.hs [testName]"
|
||||
putStrLn " No argument: run all tests"
|
||||
putStrLn " testName: run specific test (e.g., EmptyTest)"
|
||||
exitFailure
|
||||
|
||||
runAll :: IO ()
|
||||
runAll = do
|
||||
resultLists <- forM allTests $ \tc -> do
|
||||
putStrLn ""
|
||||
putStrLn ("=== " ++ tcName tc ++ " ===")
|
||||
eres <- try (runTest tc) :: IO (Either SomeException [TestResult])
|
||||
case eres of
|
||||
Right rs -> pure rs
|
||||
Left ex -> do
|
||||
let msg = unwords (words (show ex))
|
||||
putStrLn ("[ERROR] " ++ tcName tc ++ " - " ++ msg)
|
||||
pure [TestResult (tcName tc) False msg]
|
||||
let results = concat resultLists
|
||||
printTestResults "Full Testsuite" results
|
||||
-- Per-test summary
|
||||
putStrLn "\n=== Summary ==="
|
||||
let perTest = zip allTests resultLists
|
||||
mapM_ (\(tc, rs) -> do
|
||||
let total = length rs
|
||||
passedCount = length (filter passed rs)
|
||||
status = if passedCount == total then "PASS" else "FAIL"
|
||||
putStrLn ("[" ++ status ++ "] " ++ tcName tc ++ " - " ++ show passedCount ++ "/" ++ show total ++ " tests passed")
|
||||
) perTest
|
||||
exitOnFailures results
|
||||
|
||||
runSpecific :: String -> IO ()
|
||||
runSpecific testName =
|
||||
case findTestByName testName allTests of
|
||||
Nothing -> do
|
||||
putStrLn $ "Test not found: " ++ testName
|
||||
putStrLn "Available tests:"
|
||||
mapM_ (putStrLn . (" - " ++) . tcName) allTests
|
||||
exitFailure
|
||||
Just tc -> do
|
||||
results <- runTest tc
|
||||
printTestResults testName results
|
||||
exitOnFailures results
|
||||
|
||||
findTestByName :: String -> [TestCase] -> Maybe TestCase
|
||||
findTestByName _ [] = Nothing
|
||||
findTestByName name (tc:tcs)
|
||||
| tcName tc == name = Just tc
|
||||
| otherwise = findTestByName name tcs
|
||||
|
||||
runTest :: TestCase -> IO [TestResult]
|
||||
runTest tc =
|
||||
let prefix = tcName tc ++ " "
|
||||
in runNamedTests
|
||||
[ (prefix ++ "AST", runASTTest tc)
|
||||
, (prefix ++ "TAST", runTASTTest tc)
|
||||
, (prefix ++ "ABC", runABCTest tc)
|
||||
, (prefix ++ "Bytecode", runBytecodeTest tc)
|
||||
]
|
||||
@@ -0,0 +1,125 @@
|
||||
module Testsuite.ExecutableTests.Registry where
|
||||
|
||||
import Testsuite.ExecutableTests.TestCase
|
||||
import qualified Testsuite.AstFiles.EmptyTestAST as EmptyAST
|
||||
import qualified Testsuite.TastFiles.EmptyTestTAST as EmptyTAST
|
||||
import qualified Testsuite.AbcFiles.EmptyTestABC as EmptyABC
|
||||
import qualified Testsuite.AstFiles.ArithmeticTestAST as ArithmeticAST
|
||||
import qualified Testsuite.TastFiles.ArithmeticTestTAST as ArithmeticTAST
|
||||
import qualified Testsuite.AbcFiles.ArithmeticTestABC as ArithmeticABC
|
||||
import qualified Testsuite.AstFiles.FieldsTestAST as FieldsAST
|
||||
import qualified Testsuite.TastFiles.FieldsTestTAST as FieldsTAST
|
||||
import qualified Testsuite.AbcFiles.FieldsTestABC as FieldsABC
|
||||
import qualified Testsuite.AstFiles.IfTestAST as IfAST
|
||||
import qualified Testsuite.TastFiles.IfTestTAST as IfTAST
|
||||
import qualified Testsuite.AbcFiles.IfTestABC as IfABC
|
||||
import qualified Testsuite.AstFiles.WhileTestAST as WhileAST
|
||||
import qualified Testsuite.TastFiles.WhileTestTAST as WhileTAST
|
||||
import qualified Testsuite.AbcFiles.WhileTestABC as WhileABC
|
||||
import qualified Testsuite.AstFiles.ConstructorTestAST as ConstructorAST
|
||||
import qualified Testsuite.TastFiles.ConstructorTestTAST as ConstructorTAST
|
||||
import qualified Testsuite.AbcFiles.ConstructorTestABC as ConstructorABC
|
||||
import qualified Testsuite.AstFiles.ConstructorOverloadTestAST as ConstructorOverloadAST
|
||||
import qualified Testsuite.TastFiles.ConstructorOverloadTestTAST as ConstructorOverloadTAST
|
||||
import qualified Testsuite.AbcFiles.ConstructorOverloadTestABC as ConstructorOverloadABC
|
||||
import qualified Testsuite.AstFiles.MultiClassTestAST as MultiClassAST
|
||||
import qualified Testsuite.TastFiles.MultiClassTestTAST as MultiClassTAST
|
||||
import qualified Testsuite.AbcFiles.MultiClassTestABC as MultiClassABC
|
||||
|
||||
import Codegen.Serializer (serializeProgram)
|
||||
import Data.Word (Word8)
|
||||
import qualified Codegen.Lowerer
|
||||
|
||||
toBytecode :: [(String, Codegen.Lowerer.BtcProgram)] -> [(String, [Word8])]
|
||||
toBytecode = map (fmap (serializeProgram))
|
||||
|
||||
emptyTest :: TestCase
|
||||
emptyTest =
|
||||
TestCase
|
||||
{ tcName = "EmptyTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/EmptyTest.java"
|
||||
, tcExpectedAST = EmptyAST.expectedAST
|
||||
, tcExpectedTAST = EmptyTAST.expectedTAST
|
||||
, tcExpectedABC = EmptyABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode EmptyABC.expectedABC
|
||||
}
|
||||
|
||||
arithmeticTest :: TestCase
|
||||
arithmeticTest =
|
||||
TestCase
|
||||
{ tcName = "ArithmeticTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/ArithmeticTest.java"
|
||||
, tcExpectedAST = ArithmeticAST.expectedAST
|
||||
, tcExpectedTAST = ArithmeticTAST.expectedTAST
|
||||
, tcExpectedABC = ArithmeticABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode ArithmeticABC.expectedABC
|
||||
}
|
||||
|
||||
fieldsTest :: TestCase
|
||||
fieldsTest =
|
||||
TestCase
|
||||
{ tcName = "FieldsTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/FieldsTest.java"
|
||||
, tcExpectedAST = FieldsAST.expectedAST
|
||||
, tcExpectedTAST = FieldsTAST.expectedTAST
|
||||
, tcExpectedABC = FieldsABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode FieldsABC.expectedABC
|
||||
}
|
||||
|
||||
ifTest :: TestCase
|
||||
ifTest =
|
||||
TestCase
|
||||
{ tcName = "IfTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/IfTest.java"
|
||||
, tcExpectedAST = IfAST.expectedAST
|
||||
, tcExpectedTAST = IfTAST.expectedTAST
|
||||
, tcExpectedABC = IfABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode IfABC.expectedABC
|
||||
}
|
||||
|
||||
whileTest :: TestCase
|
||||
whileTest =
|
||||
TestCase
|
||||
{ tcName = "WhileTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/WhileTest.java"
|
||||
, tcExpectedAST = WhileAST.expectedAST
|
||||
, tcExpectedTAST = WhileTAST.expectedTAST
|
||||
, tcExpectedABC = WhileABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode WhileABC.expectedABC
|
||||
}
|
||||
|
||||
allTests :: [TestCase]
|
||||
allTests = [emptyTest, arithmeticTest, fieldsTest, ifTest, whileTest, constructorTest, constructorOverloadTest, multiClassTest]
|
||||
|
||||
constructorTest :: TestCase
|
||||
constructorTest =
|
||||
TestCase
|
||||
{ tcName = "ConstructorTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/ConstructorTest.java"
|
||||
, tcExpectedAST = ConstructorAST.expectedAST
|
||||
, tcExpectedTAST = ConstructorTAST.expectedTAST
|
||||
, tcExpectedABC = ConstructorABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode ConstructorABC.expectedABC
|
||||
}
|
||||
|
||||
constructorOverloadTest :: TestCase
|
||||
constructorOverloadTest =
|
||||
TestCase
|
||||
{ tcName = "ConstructorOverloadTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/ConstructorOverloadTest.java"
|
||||
, tcExpectedAST = ConstructorOverloadAST.expectedAST
|
||||
, tcExpectedTAST = ConstructorOverloadTAST.expectedTAST
|
||||
, tcExpectedABC = ConstructorOverloadABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode ConstructorOverloadABC.expectedABC
|
||||
}
|
||||
|
||||
multiClassTest :: TestCase
|
||||
multiClassTest =
|
||||
TestCase
|
||||
{ tcName = "MultiClassTest"
|
||||
, tcJavaFile = "src/Testsuite/javaFiles/MultiClassTest.java"
|
||||
, tcExpectedAST = MultiClassAST.expectedAST
|
||||
, tcExpectedTAST = MultiClassTAST.expectedTAST
|
||||
, tcExpectedABC = MultiClassABC.expectedABC
|
||||
, tcExpectedBytecode = toBytecode MultiClassABC.expectedABC
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
module Testsuite.ExecutableTests.Runner.ABC where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Codegen.ClassFile (generateClassFile)
|
||||
import Codegen.Lowerer (lowerClassFile)
|
||||
import Testsuite.ExecutableTests.TestCase
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure(..))
|
||||
|
||||
runABCTest :: TestCase -> IO ()
|
||||
runABCTest tc =
|
||||
case tcExpectedTAST tc of
|
||||
[t] ->
|
||||
let actual = lowerClassFile (generateClassFile t)
|
||||
in if actual == tcExpectedABC tc
|
||||
then pure ()
|
||||
else
|
||||
let body =
|
||||
tcName tc
|
||||
++ " ABC mismatch\nActual ABC:\n"
|
||||
++ show actual
|
||||
++ "\nExpected ABC:\n"
|
||||
++ show (tcExpectedABC tc)
|
||||
in throwIO $ TestFailure body
|
||||
_ -> error "ABC test expects exactly one class"
|
||||
@@ -0,0 +1,20 @@
|
||||
module Testsuite.ExecutableTests.Runner.AST where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.Parser
|
||||
import Grammar.Scanner
|
||||
import Testsuite.ExecutableTests.TestCase
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure(..))
|
||||
|
||||
runASTTest :: TestCase -> IO ()
|
||||
runASTTest tc = do
|
||||
java <- readFile (tcJavaFile tc)
|
||||
let actual = parse . alexScanTokens $ java
|
||||
|
||||
if actual == tcExpectedAST tc
|
||||
then pure ()
|
||||
else throwIO $
|
||||
TestFailure $
|
||||
tcName tc ++ " AST mismatch\n"
|
||||
++ "Actual: " ++ show actual ++ "\n"
|
||||
++ "Expected: " ++ show (tcExpectedAST tc)
|
||||
@@ -0,0 +1,36 @@
|
||||
module Testsuite.ExecutableTests.Runner.Bytecode where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import System.FilePath (takeFileName, replaceExtension)
|
||||
import Testsuite.ExecutableTests.TestCase
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure(..))
|
||||
import Testsuite.ExecutableTests.Shared.BytecodeNormalize
|
||||
import Testsuite.ExecutableTests.Shared.ClassFileParser
|
||||
import Data.Word (Word8)
|
||||
import Data.List (intercalate)
|
||||
|
||||
bytesToHex :: [Word8] -> String
|
||||
bytesToHex bs = intercalate " " $ map (\b -> let h = showHex b in if length h == 1 then '0':h else h) (map fromIntegral bs)
|
||||
|
||||
showHex :: Int -> String
|
||||
showHex n = let hex = "0123456789ABCDEF" in [hex !! (n `div` 16), hex !! (n `mod` 16)]
|
||||
|
||||
runBytecodeTest :: TestCase -> IO ()
|
||||
runBytecodeTest tc = do
|
||||
let javaFilePath = tcJavaFile tc
|
||||
javaFileName = takeFileName javaFilePath
|
||||
classFilePath = "src/Testsuite/classFiles/" ++ replaceExtension javaFileName ".class"
|
||||
|
||||
actual <- parseMethodCodeBytesFromFile classFilePath
|
||||
let expected = tcExpectedBytecode tc
|
||||
|
||||
let nExpected = map (fmap normalizeBytecode) expected
|
||||
nActual = map (fmap normalizeBytecode) actual
|
||||
if nExpected == nActual
|
||||
then pure ()
|
||||
else
|
||||
let formatEntry (n, bs) = n ++ ": " ++ bytesToHex bs
|
||||
expectedLines = map formatEntry nExpected
|
||||
actualLines = map formatEntry nActual
|
||||
body = "bytecode mismatch for " ++ tcName tc ++ "\nExpected:\n" ++ unlines expectedLines ++ "Actual:\n" ++ unlines actualLines
|
||||
in throwIO $ TestFailure body
|
||||
@@ -0,0 +1,18 @@
|
||||
module Testsuite.ExecutableTests.Runner.TAST where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
import Testsuite.ExecutableTests.TestCase
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure(..))
|
||||
|
||||
runTASTTest :: TestCase -> IO ()
|
||||
runTASTTest tc =
|
||||
let actual =
|
||||
map (\c -> typeCheckClass c [] (tcExpectedAST tc))
|
||||
(tcExpectedAST tc)
|
||||
|
||||
in if actual == tcExpectedTAST tc
|
||||
then pure ()
|
||||
else throwIO $
|
||||
TestFailure $
|
||||
tcName tc ++ " TAST mismatch\n"
|
||||
@@ -0,0 +1,17 @@
|
||||
module Testsuite.ExecutableTests.Shared.BytecodeNormalize where
|
||||
|
||||
import Data.Word (Word8)
|
||||
|
||||
-- | Normalize bytecode by replacing constant pool indices with a fixed placeholder (0xFF)
|
||||
normalizeBytecode :: [Word8] -> [Word8]
|
||||
normalizeBytecode [] = []
|
||||
normalizeBytecode (op:rest)
|
||||
-- 1-byte constant pool index instructions: ldc
|
||||
| op `elem` [0x12] && not (null rest) =
|
||||
op : 0xFF : normalizeBytecode (tail rest)
|
||||
-- 2-byte constant pool index instructions
|
||||
| op `elem` [0x13, 0x14, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8] && length rest >= 2 =
|
||||
let (_i1:_i2:restBytes) = rest
|
||||
in op : 0xFF : 0xFF : normalizeBytecode restBytes
|
||||
-- lookupswitch, tableswitch, wide, etc. could be handled here if needed
|
||||
| otherwise = op : normalizeBytecode rest
|
||||
@@ -0,0 +1,208 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
|
||||
module Testsuite.ExecutableTests.Shared.ClassFileParser
|
||||
( parseClassFile
|
||||
, parseMethodCodeBytesFromFile
|
||||
) where
|
||||
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Data.Bits ((.|.), shiftL)
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Control.Monad (MonadFail)
|
||||
|
||||
parseClassFile :: BS.ByteString -> Either String [(String, [Word8])]
|
||||
parseClassFile bs =
|
||||
case runParser parseClassFileParser (BS.unpack bs) of
|
||||
Left e -> Left e
|
||||
Right (r, _) -> Right r
|
||||
|
||||
parseMethodCodeBytesFromFile :: FilePath -> IO [(String, [Word8])]
|
||||
parseMethodCodeBytesFromFile path = do
|
||||
bytes <- BS.readFile path
|
||||
case runParser parseClassFileParser (BS.unpack bytes) of
|
||||
Left err -> error ("Failed parsing class file '" ++ path ++ "': " ++ err)
|
||||
Right (methods, _) -> pure methods
|
||||
|
||||
newtype Parser a = Parser { runParser :: [Word8] -> Either String (a,[Word8]) }
|
||||
|
||||
instance Functor Parser where
|
||||
fmap f p = Parser $ \i -> do
|
||||
(a,r) <- runParser p i
|
||||
pure (f a,r)
|
||||
|
||||
instance Applicative Parser where
|
||||
pure x = Parser (\i -> Right (x,i))
|
||||
pf <*> px = Parser $ \i -> do
|
||||
(f,r1) <- runParser pf i
|
||||
(x,r2) <- runParser px r1
|
||||
pure (f x,r2)
|
||||
|
||||
instance Monad Parser where
|
||||
p >>= f = Parser $ \i -> do
|
||||
(a,r) <- runParser p i
|
||||
runParser (f a) r
|
||||
|
||||
instance MonadFail Parser where
|
||||
fail msg = Parser $ \_ -> Left msg
|
||||
|
||||
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 ()
|
||||
|
||||
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)
|
||||
|
||||
parseClassFileParser :: Parser [(String, [Word8])]
|
||||
parseClassFileParser = 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)
|
||||
|
||||
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,16 @@
|
||||
module Testsuite.ExecutableTests.TestCase where
|
||||
|
||||
import Grammar.AST (Class)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Codegen.Lowerer (BtcProgram)
|
||||
import Data.Word (Word8)
|
||||
|
||||
data TestCase = TestCase
|
||||
{ tcName :: String
|
||||
, tcJavaFile :: FilePath
|
||||
|
||||
, tcExpectedAST :: [Class]
|
||||
, tcExpectedTAST :: [TypedClass]
|
||||
, tcExpectedABC :: [(String, BtcProgram)]
|
||||
, tcExpectedBytecode :: [(String, [Word8])]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
runghc -isrc src/Testsuite/ExecutableTests/Main.hs WhileTest
|
||||
runghc -isrc src/Testsuite/ExecutableTests/Main.hs
|
||||
|
||||
cabal run compiler src/Testsuite/javaFiles/ArithmeticTest.java
|
||||
cabal run compiler src/Testsuite/javaFiles/ConstructorTest.java
|
||||
# ... etc for all individual test files
|
||||
|
||||
java -cp /path/to/classFiles Main
|
||||
|
||||
cd src/Testsuite/classFiles
|
||||
java -cp . Main
|
||||
@@ -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,21 @@
|
||||
module Testsuite.TastFiles.ConstructorOverloadTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"ConstructorOverloadTest"
|
||||
[Field "int" "a" (Just (Integer 42 "int"))]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[]
|
||||
(Block [] "void"),
|
||||
Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "a")]
|
||||
(Block [StmtExprStmt (Assign (InstVar (This "ConstructorOverloadTest") "a" "int") (LocalOrFieldVar "a" "int") "void") "void"] "void")
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
module Testsuite.TastFiles.ConstructorTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"ConstructorTest"
|
||||
[Field "int" "a" (Just (Integer (-1) "int"))]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "initial_value")]
|
||||
(Block [StmtExprStmt (Assign (LocalOrFieldVar "a" "int") (LocalOrFieldVar "initial_value" "int") "void") "void"] "void")
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
module Testsuite.TastFiles.EmptyTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"EmptyTest"
|
||||
[]
|
||||
[]
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
module Testsuite.TastFiles.FieldsTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"FieldsTest"
|
||||
[ Field "int" "a" Nothing,
|
||||
Field "int" "b" (Just (Integer 42 "int"))
|
||||
]
|
||||
[]
|
||||
]
|
||||
@@ -0,0 +1,60 @@
|
||||
module Testsuite.TastFiles.IfTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST :: [TypedClass]
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"IfTest"
|
||||
[]
|
||||
[ Method
|
||||
"boolean"
|
||||
"ifElseTest"
|
||||
[("int", "x")]
|
||||
( Block
|
||||
[ If
|
||||
( Binary CompLessThan
|
||||
(LocalOrFieldVar "x" "int")
|
||||
(Integer 0 "int")
|
||||
"boolean"
|
||||
)
|
||||
( Block
|
||||
[ Return (Just (Bool False "boolean")) "boolean" ]
|
||||
"boolean"
|
||||
)
|
||||
( Just
|
||||
( If
|
||||
( Binary CompEqual
|
||||
(LocalOrFieldVar "x" "int")
|
||||
(Integer 0 "int")
|
||||
"boolean"
|
||||
)
|
||||
( Block
|
||||
[ Return (Just (Bool True "boolean")) "boolean" ]
|
||||
"boolean"
|
||||
)
|
||||
( Just
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary CompGreaterThan
|
||||
(LocalOrFieldVar "x" "int")
|
||||
(Integer 10 "int")
|
||||
"boolean"
|
||||
)
|
||||
)
|
||||
"boolean"
|
||||
]
|
||||
"boolean"
|
||||
)
|
||||
)
|
||||
"boolean"
|
||||
)
|
||||
)
|
||||
"boolean"
|
||||
]
|
||||
"boolean"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
module Testsuite.TastFiles.MultiClassTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"MultiClassTest"
|
||||
[]
|
||||
[],
|
||||
Class
|
||||
"Helper"
|
||||
[Field "int" "v" Nothing]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "v0")]
|
||||
(Block [StmtExprStmt (Assign (LocalOrFieldVar "v" "int") (LocalOrFieldVar "v0" "int") "void") "void"] "void"),
|
||||
Method
|
||||
"int"
|
||||
"doubleIt"
|
||||
[]
|
||||
(Block [Return (Just (Binary Multiply (LocalOrFieldVar "v" "int") (Integer 2 "int") "int")) "int"] "int")
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,92 @@
|
||||
module Testsuite.TastFiles.RecursionTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"RecursionTest"
|
||||
[ Field "int" "value" (Just (Integer 0 "int"))
|
||||
, Field "RecursionTest" "child" (Just (Null "RecursionTest"))
|
||||
]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[("int", "n")]
|
||||
( Block
|
||||
[ StmtExprStmt (Assign (LocalOrFieldVar "value" "int") (LocalOrFieldVar "n" "int") "int") "void"
|
||||
, If
|
||||
(Binary CompGreaterThan (LocalOrFieldVar "n" "int") (Integer 0 "int") "boolean")
|
||||
( Block
|
||||
[ StmtExprStmt (Assign (LocalOrFieldVar "child" "RecursionTest") (StmtExprExpr (New "RecursionTest" [Binary Subtract (LocalOrFieldVar "n" "int") (Integer 1 "int") "int"] "RecursionTest") "RecursionTest") "RecursionTest") "void"
|
||||
]
|
||||
"void"
|
||||
)
|
||||
Nothing
|
||||
"void"
|
||||
]
|
||||
"void"
|
||||
)
|
||||
, Method
|
||||
"int"
|
||||
"fibonacci"
|
||||
[("int", "n")]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompLessThan (LocalOrFieldVar "n" "int") (Integer 2 "int") "boolean")
|
||||
(Return (Just (LocalOrFieldVar "n" "int")) "int")
|
||||
( Just
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Add
|
||||
(StmtExprExpr (MethodCall (This "RecursionTest") "fibonacci" [Binary Subtract (LocalOrFieldVar "n" "int") (Integer 1 "int") "int"] "int") "int")
|
||||
(StmtExprExpr (MethodCall (This "RecursionTest") "fibonacci" [Binary Subtract (LocalOrFieldVar "n" "int") (Integer 2 "int") "int"] "int") "int")
|
||||
"int"
|
||||
)
|
||||
)
|
||||
"int"
|
||||
]
|
||||
"void"
|
||||
)
|
||||
)
|
||||
"void"
|
||||
]
|
||||
"void"
|
||||
)
|
||||
, Method
|
||||
"int"
|
||||
"ackermann"
|
||||
[("int", "m"), ("int", "n")]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompEqual (LocalOrFieldVar "m" "int") (Integer 0 "int") "boolean")
|
||||
(Return (Just (Binary Add (LocalOrFieldVar "n" "int") (Integer 1 "int") "int")) "int")
|
||||
Nothing
|
||||
"void"
|
||||
, If
|
||||
(Binary CompEqual (LocalOrFieldVar "n" "int") (Integer 0 "int") "boolean")
|
||||
(Return (Just (StmtExprExpr (MethodCall (This "RecursionTest") "ackermann" [Binary Subtract (LocalOrFieldVar "m" "int") (Integer 1 "int") "int", Integer 1 "int"] "int") "int")) "int")
|
||||
Nothing
|
||||
"void"
|
||||
, Return
|
||||
( Just
|
||||
( StmtExprExpr
|
||||
( MethodCall
|
||||
(This "RecursionTest")
|
||||
"ackermann"
|
||||
[ Binary Subtract (LocalOrFieldVar "m" "int") (Integer 1 "int") "int"
|
||||
, StmtExprExpr (MethodCall (This "RecursionTest") "ackermann" [LocalOrFieldVar "m" "int", Binary Subtract (LocalOrFieldVar "n" "int") (Integer 1 "int") "int"] "int") "int"
|
||||
]
|
||||
"int"
|
||||
)
|
||||
"int"
|
||||
)
|
||||
)
|
||||
"int"
|
||||
]
|
||||
"int"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
module Testsuite.TastFiles.ReturnTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST :: [TypedClass]
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"ReturnTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"main"
|
||||
[]
|
||||
( Block
|
||||
[ Return (Just (Integer 42 "int")) "int" ]
|
||||
"int"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
module Testsuite.TastFiles.SingletonTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"SingletonTest"
|
||||
[ Field "SingletonTest" "instance" Nothing
|
||||
]
|
||||
[ Method
|
||||
"void"
|
||||
"<init>"
|
||||
[]
|
||||
(Block [] "void")
|
||||
, Method
|
||||
"SingletonTest"
|
||||
"getInstance"
|
||||
[]
|
||||
( Block
|
||||
[ If
|
||||
(Binary CompEqual (LocalOrFieldVar "instance" "SingletonTest") (Null "SingletonTest") "boolean")
|
||||
(Block [StmtExprStmt (Assign (LocalOrFieldVar "instance" "SingletonTest") (StmtExprExpr (New "SingletonTest" [] "SingletonTest") "SingletonTest") "SingletonTest") "void"] "void")
|
||||
Nothing
|
||||
"void"
|
||||
, Return (Just (LocalOrFieldVar "instance" "SingletonTest")) "SingletonTest"
|
||||
]
|
||||
"SingletonTest"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
module Testsuite.TastFiles.WhileTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST :: [TypedClass]
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"WhileTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"whileLoopTest"
|
||||
[("int", "n")]
|
||||
( Block
|
||||
[ LocalVarDecl "int" "sum" "int",
|
||||
While
|
||||
(Binary CompGreaterThan (LocalOrFieldVar "n" "int") (Integer 0 "int") "boolean")
|
||||
( Block
|
||||
[ StmtExprStmt
|
||||
(Assign
|
||||
(LocalOrFieldVar "sum" "int")
|
||||
(Binary Add (LocalOrFieldVar "sum" "int") (LocalOrFieldVar "n" "int") "int")
|
||||
"int"
|
||||
)
|
||||
"int",
|
||||
StmtExprStmt
|
||||
(Assign
|
||||
(LocalOrFieldVar "n" "int")
|
||||
(Binary Subtract (LocalOrFieldVar "n" "int") (Integer 1 "int") "int")
|
||||
"int"
|
||||
)
|
||||
"int"
|
||||
]
|
||||
"int"
|
||||
)
|
||||
"int",
|
||||
Return (Just (LocalOrFieldVar "sum" "int")) "int"
|
||||
]
|
||||
"int"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[("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}])]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
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.
Binary file not shown.
@@ -1,9 +1,10 @@
|
||||
public class AllSyntaxTest {
|
||||
public int x;
|
||||
private static int counter = 0;
|
||||
|
||||
public int counter;
|
||||
|
||||
public AllSyntaxTest(int initial) {
|
||||
x = initial;
|
||||
counter = 0;
|
||||
counter = counter + 1;
|
||||
}
|
||||
|
||||
@@ -17,7 +18,7 @@ public class AllSyntaxTest {
|
||||
return x;
|
||||
}
|
||||
|
||||
public static int sumUpTo(int n) {
|
||||
public int sumUpTo(int n) {
|
||||
int s = 0;
|
||||
for (int i = 1; i <= n; i = i + 1) {
|
||||
s = s + i;
|
||||
@@ -28,13 +29,4 @@ public class AllSyntaxTest {
|
||||
public boolean predicate(char c) {
|
||||
return c == 'a' || c == 'b';
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
AllSyntaxTest a = new AllSyntaxTest(0);
|
||||
System.out.println(a.inc()); // 1
|
||||
System.out.println(a.inc(4)); // 5
|
||||
System.out.println(AllSyntaxTest.sumUpTo(5)); // 15
|
||||
System.out.println(a.predicate('a') ? 1 : 0); // ternary used to produce int
|
||||
System.out.println(counter); // static field access
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,4 @@ public class CombinedControlTest {
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
CombinedControlTest t = new CombinedControlTest(6);
|
||||
// Computation: 0 -1 +2 -3 +4 -5 = -3
|
||||
System.out.println(t.compute());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
public class ExpressionTest {
|
||||
public static boolean shortCircuit(int a, int b) {
|
||||
// short-circuit: when a==0 the right side must not be evaluated
|
||||
public boolean shortCircuit(int a, int b) {
|
||||
boolean res = (a != 0) && ((10 / a) > b);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static char charArithmetic(char c, int offset) {
|
||||
char d = (char)(c + offset);
|
||||
public char charArithmetic(char c, int offset) {
|
||||
char d = c;
|
||||
return d;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(shortCircuit(2, 1)); // true
|
||||
System.out.println(shortCircuit(0, 1)); // false (right side not evaluated)
|
||||
System.out.println(charArithmetic('A', 2)); // 'C'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,4 @@ public class IfTest {
|
||||
return x > 10;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(ifElseTest(-1)); // false
|
||||
System.out.println(ifElseTest(0)); // true
|
||||
System.out.println(ifElseTest(11)); // true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,20 +61,26 @@ public class Main {
|
||||
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());
|
||||
// AllSyntaxTest tests
|
||||
System.out.println("test AllSyntaxTest constructor state. Expected: x=0 and counter=1, Real: x=" + allSyntax.x + " and counter=" + allSyntax.counter);
|
||||
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.sumUpTo(5). Expected: 15, Real: " + allSyntax.sumUpTo(5));
|
||||
System.out.println("test AllSyntaxTest.predicate('a'). Expected: true, Real: " + allSyntax.predicate('a'));
|
||||
System.out.println("test AllSyntaxTest.predicate('z'). Expected: false, Real: " + allSyntax.predicate('z'));
|
||||
// CombinedControlTest tests
|
||||
System.out.println("test CombinedControlTest.compute(). Expected: -3, Real: " + combined.compute());
|
||||
// ExpressionTest tests
|
||||
System.out.println("test ExpressionTest.shortCircuit(2, 1). Expected: true, Real: " + ExpressionTest.shortCircuit(2, 1));
|
||||
System.out.println("test ExpressionTest.shortCircuit(0, 1). Expected: false, Real: " + ExpressionTest.shortCircuit(0, 1));
|
||||
System.out.println("test ExpressionTest.charArithmetic('A', 2). Expected: C, Real: " + ExpressionTest.charArithmetic('A', 2));
|
||||
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.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));
|
||||
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
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
public class MultiClassTest {
|
||||
public static void main(String[] args) {
|
||||
Helper h = new Helper(3);
|
||||
System.out.println(h.doubleIt()); // expect 6
|
||||
}
|
||||
}
|
||||
|
||||
class Helper {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
public class ReturnTest {
|
||||
public int main () {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,4 @@ public class WhileTest {
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Expect 1+2+3+4+5 = 15
|
||||
System.out.println(whileLoopTest(5));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
[
|
||||
Class "ArithmeticTest"
|
||||
[]
|
||||
[ Method "int" "basic" [("int", "a"), ("int", "b"), ("int", "c")]
|
||||
(TypedStmt
|
||||
(Block
|
||||
[ TypedStmt
|
||||
(Return
|
||||
(Just
|
||||
(Binary Subtract
|
||||
(Binary Add
|
||||
(LocalOrFieldVar "a" "int")
|
||||
(LocalOrFieldVar "b" "int")
|
||||
)
|
||||
(Binary Modulo
|
||||
(Binary Divide
|
||||
(Binary Multiply
|
||||
(LocalOrFieldVar "c" "int")
|
||||
(LocalOrFieldVar "a" "int")
|
||||
)
|
||||
(LocalOrFieldVar "b" "int")
|
||||
)
|
||||
(LocalOrFieldVar "c" "int")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"int"
|
||||
]
|
||||
)
|
||||
"int"
|
||||
)
|
||||
, Method "boolean" "logic" [("boolean", "a"), ("boolean", "b"), ("boolean", "c")]
|
||||
(TypedStmt
|
||||
(Block
|
||||
[ TypedStmt
|
||||
(Return
|
||||
(Just
|
||||
(Binary And
|
||||
(Unary Not (LocalOrFieldVar "a" "boolean") "boolean")
|
||||
(Binary Or
|
||||
(LocalOrFieldVar "c" "boolean")
|
||||
(LocalOrFieldVar "b" "boolean")
|
||||
"boolean"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"boolean"
|
||||
]
|
||||
)
|
||||
"boolean"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
[
|
||||
Class "EmptyTest" [] []
|
||||
]
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
class ClassWithInt {
|
||||
Integer i;
|
||||
}
|
||||
+286
-125
@@ -4,9 +4,16 @@ import Data.List (find)
|
||||
import Grammar.AST as AST
|
||||
import Grammar.TAST as TAST
|
||||
|
||||
--------------------------------------------------
|
||||
-- Types
|
||||
--------------------------------------------------
|
||||
|
||||
type Symtab = [(String, Type)]
|
||||
|
||||
--------------------------------------------------
|
||||
-- Get type functions
|
||||
--------------------------------------------------
|
||||
|
||||
getTypeFromTypedExpr :: TypedExpr -> Type
|
||||
getTypeFromTypedExpr (TAST.This t) = t
|
||||
getTypeFromTypedExpr (TAST.LocalOrFieldVar _ t) = t
|
||||
@@ -19,7 +26,6 @@ getTypeFromTypedExpr (TAST.Char _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Null t) = t
|
||||
getTypeFromTypedExpr (TAST.StmtExprExpr _ t) = t
|
||||
|
||||
|
||||
getTypeFromTypedStmtExpr :: TypedStmtExpr -> Type
|
||||
getTypeFromTypedStmtExpr (TAST.Assign _ _ t) = t
|
||||
getTypeFromTypedStmtExpr (TAST.New _ _ t) = t
|
||||
@@ -33,129 +39,225 @@ getTypeFromTypedStmt (TAST.LocalVarDecl _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.If _ _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.StmtExprStmt _ t) = t
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Typechecking Class
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckClass :: AST.Class -> [(String, Type)] -> [AST.Class] -> TAST.TypedClass
|
||||
typeCheckClass :: AST.Class -> Symtab -> [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
|
||||
let
|
||||
fieldSymtab = map (\(AST.Field t n _) -> (n, t)) fields
|
||||
extendedSymtab = fieldSymtab ++ symtab
|
||||
checkedFields = map (\f -> typeCheckField f extendedSymtab cls) fields
|
||||
checkedMethods = map (\m -> typeCheckMethod m extendedSymtab 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 =
|
||||
typeCheckField :: AST.FieldDecl -> Symtab -> [AST.Class] -> TAST.TypedFieldDecl
|
||||
typeCheckField (AST.Field typ name Nothing) _ _ =
|
||||
TAST.Field typ name Nothing
|
||||
|
||||
typeCheckField (AST.Field expectedTyp name (Just expr)) symtab cls =
|
||||
let checkedExpr = typeCheckExpr 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
|
||||
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.MethodDecl -> Symtab -> [AST.Class] -> TAST.TypedMethodDecl
|
||||
typeCheckMethod (AST.Method typ name params stmt) symtab cls =
|
||||
let checkedStmt = typeCheckStmt stmt symtab cls
|
||||
let
|
||||
paramSymtab = map (\(t, n) -> (n, t)) params
|
||||
extendedSymtab = paramSymtab ++ symtab
|
||||
(checkedStmt, _) = typeCheckStmt stmt extendedSymtab 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
|
||||
|
||||
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 :: AST.Stmt -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmt
|
||||
|
||||
-- If-Statement
|
||||
typeCheckStmt :: AST.Stmt -> Symtab -> [AST.Class] -> (TAST.TypedStmt, Symtab)
|
||||
|
||||
--------------------------------------------------
|
||||
-- If Statement
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckStmt (AST.If cond body Nothing) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
let
|
||||
checkedCond = typeCheckExpr cond symtab cls
|
||||
(checkedBody, _) = typeCheckStmt body symtab cls
|
||||
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"
|
||||
in
|
||||
if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then (TAST.If checkedCond checkedBody Nothing typ, symtab)
|
||||
else error "Condition in if statement must be boolean"
|
||||
|
||||
--------------------------------------------------
|
||||
-- If Else Statement
|
||||
--------------------------------------------------
|
||||
|
||||
-- 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"
|
||||
let
|
||||
checkedCond = typeCheckExpr cond symtab cls
|
||||
(checkedBody, _) = typeCheckStmt body symtab cls
|
||||
(checkedElse, _) = typeCheckStmt elseBody symtab cls
|
||||
|
||||
typ =
|
||||
upperBound
|
||||
(getTypeFromTypedStmt checkedBody)
|
||||
(getTypeFromTypedStmt checkedElse)
|
||||
in
|
||||
if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then (TAST.If checkedCond checkedBody (Just checkedElse) typ, symtab)
|
||||
else error "Condition in if statement must be boolean"
|
||||
|
||||
--------------------------------------------------
|
||||
-- While Statement
|
||||
--------------------------------------------------
|
||||
|
||||
-- While-Statement
|
||||
typeCheckStmt (AST.While cond body) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt 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"
|
||||
in
|
||||
if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then (TAST.While checkedCond checkedBody typ, symtab)
|
||||
else error "Condition in while statement must be boolean"
|
||||
|
||||
--------------------------------------------------
|
||||
-- Return Statement
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckStmt (AST.Return maybeExpr) symtab cls =
|
||||
let checkedExpr = fmap (\expr -> typeCheckExpr expr symtab cls) maybeExpr
|
||||
let
|
||||
checkedExpr = fmap (\expr -> typeCheckExpr expr symtab cls) maybeExpr
|
||||
typ = maybe "void" getTypeFromTypedExpr checkedExpr
|
||||
in TAST.Return checkedExpr typ
|
||||
in
|
||||
(TAST.Return checkedExpr typ, symtab)
|
||||
|
||||
--------------------------------------------------
|
||||
-- Local Variable Declaration
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckStmt (AST.LocalVarDecl typ str) _ _ =
|
||||
TAST.LocalVarDecl typ str typ
|
||||
typeCheckStmt (AST.LocalVarDecl typ str maybeExpr) symtab cls =
|
||||
if any (\(name, _) -> name == str) symtab
|
||||
then error $ "Variable already defined: " ++ str
|
||||
else
|
||||
let
|
||||
checkedExpr = fmap (\expr -> typeCheckExpr expr symtab cls) maybeExpr
|
||||
exprTyp = fmap getTypeFromTypedExpr checkedExpr
|
||||
in
|
||||
case exprTyp of
|
||||
Just actualTyp
|
||||
| actualTyp /= typ ->
|
||||
error $ "Type mismatch: Expected " ++ typ ++ " found " ++ actualTyp
|
||||
_ ->
|
||||
()
|
||||
`seq` (TAST.LocalVarDecl typ str typ, (str, typ) : symtab)
|
||||
|
||||
--------------------------------------------------
|
||||
-- Block Statement
|
||||
--------------------------------------------------
|
||||
|
||||
-- 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
|
||||
let
|
||||
(checkedStmts, _) = typeCheckStmtList stmts symtab cls
|
||||
typ =
|
||||
case reverse checkedStmts of
|
||||
[] -> "void"
|
||||
(lastStmt : _) ->
|
||||
getTypeFromTypedStmt lastStmt
|
||||
in (TAST.Block checkedStmts typ, symtab)
|
||||
|
||||
--------------------------------------------------
|
||||
-- Expression Statement
|
||||
--------------------------------------------------
|
||||
|
||||
-- Expression statement
|
||||
typeCheckStmt (AST.StmtExprStmt stmtExpr) symtab cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtab cls
|
||||
let
|
||||
checkedStmtExpr = typeCheckStmtExpr stmtExpr symtab cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprStmt checkedStmtExpr typ
|
||||
in
|
||||
(TAST.StmtExprStmt checkedStmtExpr typ, symtab)
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement List Helper
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckStmtList :: [AST.Stmt] -> Symtab -> [AST.Class] -> ([TAST.TypedStmt], Symtab)
|
||||
|
||||
typeCheckStmtList [] symtab _ =
|
||||
([], symtab)
|
||||
|
||||
typeCheckStmtList (stmt:rest) symtab cls =
|
||||
let
|
||||
(checkedStmt, newSymtab) = typeCheckStmt stmt symtab cls
|
||||
(checkedRest, finalSymtab) = typeCheckStmtList rest newSymtab cls
|
||||
in
|
||||
(checkedStmt : checkedRest, finalSymtab)
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmtExpr :: AST.StmtExpr -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmtExpr
|
||||
|
||||
typeCheckStmtExpr :: AST.StmtExpr -> Symtab -> [AST.Class] -> TAST.TypedStmtExpr
|
||||
|
||||
--------------------------------------------------
|
||||
-- Assign
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckStmtExpr (AST.Assign lhs rhs) symtab cls =
|
||||
let checkedLhs = typeCheckExpr lhs 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"
|
||||
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
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckStmtExpr (AST.New typ args) symtab cls =
|
||||
let
|
||||
checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
in
|
||||
TAST.New typ checkedArgs typ
|
||||
|
||||
--------------------------------------------------
|
||||
-- Method Call
|
||||
--------------------------------------------------
|
||||
|
||||
-- Method call
|
||||
typeCheckStmtExpr (AST.MethodCall target methodName args) symtab cls =
|
||||
let checkedTarget = typeCheckExpr target 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
|
||||
in
|
||||
TAST.MethodCall checkedTarget methodName checkedArgs returnType
|
||||
|
||||
--------------------------------------------------
|
||||
-- Helper Methods
|
||||
--------------------------------------------------
|
||||
|
||||
-- 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
|
||||
Nothing ->
|
||||
error $
|
||||
"Method not found: " ++ methodName ++ " in class " ++ classType
|
||||
|
||||
findMethod :: Type -> String -> [AST.Class] -> Maybe Type
|
||||
findMethod classType methodName classes =
|
||||
@@ -170,96 +272,142 @@ 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
|
||||
case find (\(AST.Field t name _) -> name == fieldName) fields of
|
||||
Just (AST.Field t _ _) -> t
|
||||
Nothing -> error $ "Field not found: " ++ fieldName ++ " in class " ++ classType
|
||||
Nothing ->
|
||||
error $ "Field not found: " ++ fieldName ++ " in class " ++ classType
|
||||
Nothing -> error $ "Class not found: " ++ classType
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckExpr :: AST.Expr -> [(String, Type)] -> [AST.Class] -> TAST.TypedExpr
|
||||
|
||||
-- boolean literals
|
||||
typeCheckExpr (AST.Bool value) symtbl cls =
|
||||
typeCheckExpr :: AST.Expr -> Symtab -> [AST.Class] -> TAST.TypedExpr
|
||||
|
||||
--------------------------------------------------
|
||||
-- Boolean Literals
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.Bool value) _ _ =
|
||||
TAST.Bool value "boolean"
|
||||
|
||||
-- integer literals
|
||||
typeCheckExpr (AST.Integer value) symtbl cls =
|
||||
--------------------------------------------------
|
||||
-- Integer Literals
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.Integer value) _ _ =
|
||||
TAST.Integer value "int"
|
||||
|
||||
-- variable references
|
||||
typeCheckExpr (AST.LocalOrFieldVar varName) symtbl cls =
|
||||
case lookup varName symtbl of
|
||||
Just t -> TAST.LocalOrFieldVar varName t
|
||||
--------------------------------------------------
|
||||
-- Variable References
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.LocalOrFieldVar varName) symtab _ =
|
||||
case lookup varName symtab of
|
||||
Just t ->
|
||||
TAST.LocalOrFieldVar varName t
|
||||
Nothing -> error $ "Undefined variable: " ++ varName
|
||||
|
||||
-- this reference
|
||||
--------------------------------------------------
|
||||
-- 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
|
||||
--------------------------------------------------
|
||||
-- Field Access
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.InstVar obj fieldName) symtab cls =
|
||||
let
|
||||
checkedObj = typeCheckExpr obj symtab cls
|
||||
objType = getTypeFromTypedExpr checkedObj
|
||||
fieldType = lookupFieldType objType fieldName cls
|
||||
in TAST.InstVar checkedObj fieldName fieldType
|
||||
in
|
||||
TAST.InstVar checkedObj fieldName fieldType
|
||||
|
||||
-- unary operators
|
||||
typeCheckExpr (AST.Unary op expr) symtbl cls =
|
||||
let checkedExpr = typeCheckExpr expr symtbl cls
|
||||
--------------------------------------------------
|
||||
-- Unary Operators
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.Unary op expr) symtab cls =
|
||||
let
|
||||
checkedExpr = typeCheckExpr expr symtab 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"
|
||||
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
|
||||
--------------------------------------------------
|
||||
-- Binary Operators
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.Binary op left right) symtab cls =
|
||||
let
|
||||
checkedLeft = typeCheckExpr left symtab cls
|
||||
checkedRight = typeCheckExpr right symtab 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
|
||||
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
|
||||
--------------------------------------------------
|
||||
|
||||
-- character literals
|
||||
typeCheckExpr (AST.Char value) _ _ =
|
||||
TAST.Char value "char"
|
||||
|
||||
-- null literal
|
||||
--------------------------------------------------
|
||||
-- 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
|
||||
--------------------------------------------------
|
||||
-- Statement Expression
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckExpr (AST.StmtExprExpr stmtExpr) symtab cls =
|
||||
let
|
||||
checkedStmtExpr = typeCheckStmtExpr stmtExpr symtab cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in
|
||||
TAST.StmtExprExpr checkedStmtExpr typ
|
||||
|
||||
--------------------------------------------------
|
||||
-- Expression Helper Methods
|
||||
--------------------------------------------------
|
||||
|
||||
checkIntOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
|
||||
-- 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"
|
||||
@@ -272,18 +420,31 @@ checkBoolOp leftType rightType left right op =
|
||||
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"
|
||||
if leftType == rightType
|
||||
|| leftType == "null"
|
||||
|| rightType == "null"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error $ "Equality operator: cannot compare " ++ leftType ++ " with " ++ rightType
|
||||
else
|
||||
error $
|
||||
"Equality operator: cannot compare "
|
||||
++ leftType
|
||||
++ " with "
|
||||
++ rightType
|
||||
|
||||
--------------------------------------------------
|
||||
-- Upper Bound
|
||||
--------------------------------------------------
|
||||
|
||||
upperBound :: Type -> Type -> Type
|
||||
upperBound t1 t2
|
||||
| t1 == t2 = t1
|
||||
| otherwise = "Object"
|
||||
| otherwise = "Object"
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# run all tests: ./test.sh 2>&1 | grep -E "Testing|Results"
|
||||
# inspect a diff: diff <(javap -c out/compoiler-ref/FieldsTest.class) <(javap -c out/compoiler-out/FieldsTest.class)
|
||||
|
||||
JAVA_DIR="src/Testsuite/javaFiles"
|
||||
REF_DIR="./out/compoiler-ref"
|
||||
OUT_DIR="./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_DIR/${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