14 Commits

Author SHA1 Message Date
Vectabyte 22de50b77e Merge branch 'main' into feature/test-cases 2026-05-04 17:18:29 +00:00
Vectabyte e84550025c Initial Test Cases 2026-05-04 19:16:34 +02:00
Silas 8470ab2d94 new ConstPool first steps 2026-04-29 16:29:28 +02:00
Konstantin Fastovski 1ad8aface9 Merge branch 'feature/create-codegen-skeleton-and-add-'return-int'-as-example' 2026-04-29 16:26:16 +02:00
Konstantin Fastovski daa741a090 Implement Classfile datatype, Lowerer and (untested) Serializer 2026-04-29 16:25:01 +02:00
Konstantin Fastovski e81ceb454f feat: moved ai generated files to subdirectory 2026-04-29 15:07:00 +02:00
LeonProgrammiert 87db9f6e23 Merge branch 'main' of ssh://gitea.hb.dhbw-stuttgart.de:2222/mo/Compoiler 2026-04-29 14:21:25 +02:00
LeonProgrammiert a447fad905 feat: updated AST, TAST and getType Methods 2026-04-29 14:21:22 +02:00
Felix b964bdda1b Merge branch 'feature/operators' 2026-04-29 11:28:04 +02:00
timb 2f908d1df1 feat: update typed abstract syntax 2026-04-29 11:13:37 +02:00
Silas 40e84e45f6 Merge branch 'main' of https://gitea.hb.dhbw-stuttgart.de/mo/Compoiler 2026-04-29 11:04:47 +02:00
tym 56d43bac38 Merge pull request 'Added operators' (#2) from feature/operators into main
Reviewed-on: #2
2026-04-29 08:05:11 +00:00
Silas 9230042b25 feat: add Konstantenpool 2026-04-29 09:29:12 +02:00
Konstantin Fastovski 76c4cb677c feat: codegen skeleton 2026-04-27 16:03:04 +02:00
34 changed files with 1027 additions and 41 deletions
+1
View File
@@ -31,3 +31,4 @@ cabal.project.local~
hie.yaml
src/Grammar/Scanner.hs
src/Grammar/Parser.hs
src/Grammar/Parser.info
+81
View File
@@ -0,0 +1,81 @@
# Compilerbau
# Prüfungsleistung
## Spezifikation
Deklarationen: • Σ: EingabeAlphabet
- JC: Menge aller syntaktisch korrekten JavaKlassen mit folgenden Einschränkungen:
- keine generischen Klassen
- keine abstrakten Klassen
- keine Vererbung
- keine Interfaces
- keine Threads
- keine Exceptions
- keine Arrays
- als Basistypen sind nur int, boolean und char zugelassen
- keine Packages
- keine Imports
- keine LambdaExpressions
- BC: Menge aller BytecodeFiles
Eingabe: p∈Σ∗
Vorbedingung: ∅
Ausgabe: bc∈BC∗∪{error}
Nachbedingungen: • falls p∈(JC), so ist bc∈(BC)und p wird nach bc übersetzt wie es durch die Sprache Java definiert ist.
- falls p !∈(JC), so ist bc=error.
## Vorgehen
Arbeitsteam:Der JavaCompiler wird in einem Team von 5-7 Personen erstellt. Das
Team wird nochmals unterteilt:
- Gemeinsame Aufgabe
GIT-Repository: Einrichten eines GIT-Repositories auf den DHBW GITEA-Server
Abstrakte Syntax: Aufbau der abstrakten Syntax aus dem Parsetree
Dokumentation: Erstellen der Dokumentation
- Scannen/Parsen/Grammatik (12 Personen)
Scannen: alexFile oder Scanner von Hand programmieren
Grammatik (nur bei Bearbeitung durch 2 Personen):Erstellen einer Mini-Java-Grammatik an Hand der Spezifikation
Parsen:Erstellen des happyFiles oder des KombinatorParsers und Aufbau des abstrakten Syntaxbaums
- Semantische Analyse:Typisierung der abstrakten Syntax(1 Person)
Typisierung: Typisierung der abstrakten Syntax
- Codeerzeugung (2-3 Personen):
- Aufbau eines abstrakten ClassFiles (1 Person)
- Konstantenpool (1 Person)
- Nur bei Bearbeitung durch 3 Personen:Umwandlung des ClassFiles in Bytecode (1 Person)
- Tester (1 Person)
- Testsuite von JavaFiles, die alle implementierten Features abdecken.
- Händische Übersetzung aller Java-Files der Testsuite in die abstrakte Syntax (als TestEingaben für den Typ-Checker)
- Händische Übersetzung aller Testfälle der abstrakten Syntax in getypte abstrakte Syntax (als TestEingaben für den Code-Generierer).
- Händische Übersetzung aller Testfälle der getypten abstrakten Syntax in abstrakten Bytecode.
- Automatische Tests, die die jeweiligen Testsuite mit den implementierten Funktionen des Teams vergleichen
## Prüfungsleistung
Die Arbeitsleistung wird bewertet an Hand
- des Gesamtergebnis des Teams
- der Arbeitsleistung jeder/s Studierenden
an Hand folgender Kriterien:
- Projektergebnis
- wöchentlicher Projektfortschritt
- Mitarbeit im Team
Das Projektergebnis muss folgendes beinhalten:
- (Kurz)dokumentation aus der hervorgeht welche Leistung der jeweiligen Studierende erbrachten hat.
- Im Teilprojekt muss folgendes vorliegen:
- Eine Testsuite von JavaProgrammen, für die der Compilerteil funktioniert.
- Präsentation des Programms an Hand der erstellen Testsuites.
- Durchgehendes Beispiel, fur das der gesamte Compiler funktioniert.
- Abgabetermin: Letzte Semesterwoche
+130
View File
@@ -0,0 +1,130 @@
module Codegen.ClassFile where
import Data.Word (Word16, Word32, Word8)
data ClassFile = ClassFile
{ magic :: Magic,
minorVersion :: MinorVersion,
majorVersion :: MajorVersion,
constantPool :: CP_Infos,
accessFlags :: AccessFlags,
thisClass :: ThisClass,
superClass :: SuperClass,
interfaces :: Interfaces,
fields :: Field_Infos,
methods :: Method_Infos,
attributes :: Attribute_Infos
}
deriving (Show, Eq)
-- Counts are derivable from list lengths, so no need to store them separately
type CP_Infos = [CP_Info]
type Interfaces = [Word16] -- constant pool indices
type Field_Infos = [Field_Info]
type Method_Infos = [Method_Info]
type Attribute_Infos = [Attribute_Info]
type Index = Int -- constant pool index (1-based)
newtype Magic = Magic Word32 deriving (Show, Eq) -- always 0xCAFEBABE
newtype MinorVersion = MinorVersion Int deriving (Show, Eq)
newtype MajorVersion = MajorVersion Int deriving (Show, Eq) -- 52 = Java 8
newtype AccessFlags = AccessFlags [AccessFlag] deriving (Show, Eq)
newtype ThisClass = ThisClass Index deriving (Show, Eq)
newtype SuperClass = SuperClass Index deriving (Show, Eq)
data AccessFlag
= ACC_PUBLIC
| ACC_PRIVATE
| ACC_PROTECTED
| ACC_STATIC
| ACC_FINAL
| ACC_SUPER -- always set on classes
| ACC_INTERFACE
| ACC_ABSTRACT
| ACC_SYNTHETIC
| ACC_ENUM
deriving (Show, Eq)
-- Duplicate field names fixed by prefixing per constructor
data CP_Info
= Class_Info
{ci_nameIndex :: Index}
| FieldRef_Info
{ fr_classIndex :: Index,
fr_nameAndTypeIndex :: Index
}
| MethodRef_Info
{ mr_classIndex :: Index,
mr_nameAndTypeIndex :: Index
}
| InterfaceMethodRef_Info
{ imr_classIndex :: Index,
imr_nameAndTypeIndex :: Index
}
| String_Info
{si_stringIndex :: Index}
| Integer_Info
{ii_value :: Int}
| Float_Info
{fi_value :: Float}
| Long_Info
{li_value :: Integer} -- Long fits in Haskell Integer
| Double_Info
{di_value :: Double}
| NameAndType_Info
{ nt_nameIndex :: Index,
nt_descriptorIndex :: Index
}
| Utf8_Info
{u8_value :: String}
deriving (Show, Eq)
data Field_Info = Field_Info
{ fieldAccessFlags :: AccessFlags,
fieldNameIndex :: Index,
fieldDescIndex :: Index,
fieldAttributes :: Attribute_Infos
}
deriving (Show, Eq)
data Method_Info = Method_Info
{ methodAccessFlags :: AccessFlags,
methodNameIndex :: Index,
methodDescIndex :: Index,
methodAttributes :: Attribute_Infos
}
deriving (Show, Eq)
data Attribute_Info
= Code_Attribute
{ codeNameIndex :: Index,
maxStack :: Int,
maxLocals :: Int,
codeBody :: [Word8], -- raw serialized bytecode
exceptionTable :: [ExceptionEntry],
codeAttributes :: Attribute_Infos
}
| Generic_Attribute
{ attrNameIndex :: Index,
attrData :: [Word8]
}
deriving (Show, Eq)
data ExceptionEntry = ExceptionEntry
{ startPc :: Int,
endPc :: Int,
handlerPc :: Int,
catchType :: Index -- 0 means catches all (finally)
}
deriving (Show, Eq)
+54
View File
@@ -0,0 +1,54 @@
module ConstantPool where
data Index_Constant_Pool
data CPInfo =
ClassInfo
{ tag_cp :: Tag
, index_cp :: Index_Constant_Pool
, desc :: String
}
| FieldRefInfo
{ tag_cp :: Tag
, index_name_cp :: Index_Constant_Pool
, index_nameandtype_cp :: Index_Constant_Pool
, desc :: String
}
| MethodRefInfo
{ tag_cp :: Tag
, index_name_cp :: Index_Constant_Pool
, index_nameandtype_cp :: Index_Constant_Pool
, desc :: String
}
| StringInfo
{ tag_cp :: Tag
, index_cp :: Index_Constant_Pool
, desc :: String
}
| IntegerInfo
{ tag_cp :: Tag
, numi_cp :: Int
, desc :: String
}
| NameAndTypeInfo
{ tag_cp :: Tag
, index_name_cp :: Index_Constant_Pool
, index_descr_cp :: Index_Constant_Pool
, desc :: String
}
| Utf8Info
{ tag_cp :: Tag
, tam_cp :: Int
, cad_cp :: String
, desc :: String
}
deriving ()
data Tag =
TagClass
| TagFieldRef
| TagMethodRef
| TagString
| TagInteger
| TagNameAndType
| TagUtf8
deriving (Eq, Ord, Show)
+138
View File
@@ -0,0 +1,138 @@
module Codegen.Lowerer where
import Codegen.ClassFile
import Data.Word (Word8)
import Numeric (showHex)
data Instruction
= ALoad Int
| ALoad0
| ILoad Int
| AStore Int
| IStore Int
| GetField Int
| PutField Int
| InvokeSpecial Int
| InvokeVirtual Int
| InvokeStatic Int
| IConst Int
| AConstNull
| Ldc Int
| IAdd
| ISub
| IMul
| IfEq Int
| IfNe Int
| Goto Int
| Return
| IReturn
| AReturn
deriving (Show, Eq)
data BtcLine = BtcLine
{ lineNumber :: Integer,
instruction :: Instruction
}
deriving (Show, Eq)
newtype BtcProgram = BtcProgram [BtcLine]
deriving (Show, Eq)
-- Lower a full ClassFile into a BtcProgram per method
-- Returns one BtcProgram per method, paired with its name
lowerClassFile :: ClassFile -> [(String, BtcProgram)]
lowerClassFile cf =
[ (methodName cf m, lowerMethod m)
| m <- methods cf
]
-- Extract method name from the constant pool
methodName :: ClassFile -> Method_Info -> String
methodName cf m =
case lookupPool (constantPool cf) (methodNameIndex m) of
Just (Utf8_Info name) -> name
_ -> "<unknown>"
-- Lower a single method's Code attribute into a BtcProgram
lowerMethod :: Method_Info -> BtcProgram
lowerMethod m =
case findCodeAttr (methodAttributes m) of
Just attr -> lowerCode attr
Nothing -> BtcProgram [] -- abstract/native method, no body
findCodeAttr :: Attribute_Infos -> Maybe Attribute_Info
findCodeAttr = foldr step Nothing
where
step attr@Code_Attribute {} _ = Just attr
step _ acc = acc
-- Reconstruct BtcProgram from raw code bytes
-- This is the tricky direction — bytes → instructions
lowerCode :: Attribute_Info -> BtcProgram
lowerCode attr = BtcProgram (decodeBytes 0 (codeBody attr))
decodeBytes :: Int -> [Word8] -> [BtcLine]
decodeBytes _ [] = []
decodeBytes pos (b : bs) = case b of
0x2A -> line ALoad0 pos 1 bs
0x2B -> line (ALoad 1) pos 1 bs
0x2C -> line (ALoad 2) pos 1 bs
0x2D -> line (ALoad 3) pos 1 bs
0x19 -> withByte bs pos $ \n rest -> line (ALoad n) pos 2 rest
0x1A -> line (ILoad 0) pos 1 bs
0x1B -> line (ILoad 1) pos 1 bs
0x1C -> line (ILoad 2) pos 1 bs
0x1D -> line (ILoad 3) pos 1 bs
0x15 -> withByte bs pos $ \n rest -> line (ILoad n) pos 2 rest
0x4B -> line (AStore 0) pos 1 bs
0x4C -> line (AStore 1) pos 1 bs
0x4D -> line (AStore 2) pos 1 bs
0x4E -> line (AStore 3) pos 1 bs
0x3A -> withByte bs pos $ \n rest -> line (AStore n) pos 2 rest
0x3B -> line (IStore 0) pos 1 bs
0x3C -> line (IStore 1) pos 1 bs
0x3D -> line (IStore 2) pos 1 bs
0x3E -> line (IStore 3) pos 1 bs
0x36 -> withByte bs pos $ \n rest -> line (IStore n) pos 2 rest
0xB4 -> withIndex bs pos $ \i rest -> line (GetField i) pos 3 rest
0xB5 -> withIndex bs pos $ \i rest -> line (PutField i) pos 3 rest
0xB7 -> withIndex bs pos $ \i rest -> line (InvokeSpecial i) pos 3 rest
0xB6 -> withIndex bs pos $ \i rest -> line (InvokeVirtual i) pos 3 rest
0xB8 -> withIndex bs pos $ \i rest -> line (InvokeStatic i) pos 3 rest
0x02 -> line (IConst (-1)) pos 1 bs
0x03 -> line (IConst 0) pos 1 bs
0x04 -> line (IConst 1) pos 1 bs
0x05 -> line (IConst 2) pos 1 bs
0x06 -> line (IConst 3) pos 1 bs
0x07 -> line (IConst 4) pos 1 bs
0x08 -> line (IConst 5) pos 1 bs
0x01 -> line AConstNull pos 1 bs
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
0x99 -> withIndex bs pos $ \i rest -> line (IfEq i) pos 3 rest
0x9A -> withIndex bs pos $ \i rest -> line (IfNe 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
0xB0 -> line AReturn pos 1 bs
unknown -> error $ "Unknown opcode 0x" ++ showHex unknown "" ++ " at position " ++ show pos
-- Helpers
line :: Instruction -> Int -> Int -> [Word8] -> [BtcLine]
line instr pos size rest = BtcLine (toInteger pos) instr : decodeBytes (pos + size) rest
withByte :: [Word8] -> Int -> (Int -> [Word8] -> [BtcLine]) -> [BtcLine]
withByte (b : rest) _ f = f (fromIntegral b) rest
withByte [] pos _ = error $ "Unexpected end of bytecode at " ++ show pos
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
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
+79
View File
@@ -0,0 +1,79 @@
module Codegen.Serializer where
import Data.Bits (shiftR, (.&.))
import Data.Word (Word8)
-- Split a 16-bit Int into two bytes (big-endian), as JVM expects
indexBytes :: Int -> [Word8]
indexBytes n =
[ fromIntegral (n `shiftR` 8 .&. 0xFF),
fromIntegral (n .&. 0xFF)
]
serializeInstruction :: Instruction -> [Word8]
serializeInstruction instr = case instr of
-- aload_<n>: 0x2A0x2D for 03, else wide form 0x19 <index>
ALoad 0 -> [0x2A]
ALoad 1 -> [0x2B]
ALoad 2 -> [0x2C]
ALoad 3 -> [0x2D]
ALoad n -> [0x19, fromIntegral n]
ALoad0 -> [0x2A] -- shorthand for ALoad 0
-- iload_<n>: 0x1A0x1D for 03, else wide form 0x15 <index>
ILoad 0 -> [0x1A]
ILoad 1 -> [0x1B]
ILoad 2 -> [0x1C]
ILoad 3 -> [0x1D]
ILoad n -> [0x15, fromIntegral n]
-- astore_<n>: 0x4B0x4E for 03, else 0x3A <index>
AStore 0 -> [0x4B]
AStore 1 -> [0x4C]
AStore 2 -> [0x4D]
AStore 3 -> [0x4E]
AStore n -> [0x3A, fromIntegral n]
-- istore_<n>: 0x3B0x3E for 03, else 0x36 <index>
IStore 0 -> [0x3B]
IStore 1 -> [0x3C]
IStore 2 -> [0x3D]
IStore 3 -> [0x3E]
IStore n -> [0x36, fromIntegral n]
-- Field access: opcode + 2-byte constant pool index
GetField i -> 0xB4 : indexBytes i
PutField i -> 0xB5 : indexBytes i
-- Method invocation: opcode + 2-byte constant pool index
InvokeSpecial i -> 0xB7 : indexBytes i
InvokeVirtual i -> 0xB6 : indexBytes i
InvokeStatic i -> 0xB8 : indexBytes i
-- iconst_<n>: -1 through 5 have dedicated opcodes
IConst (-1) -> [0x02]
IConst 0 -> [0x03]
IConst 1 -> [0x04]
IConst 2 -> [0x05]
IConst 3 -> [0x06]
IConst 4 -> [0x07]
IConst 5 -> [0x08]
IConst n -> error $ "IConst out of range (-1..5): " ++ show n
AConstNull -> [0x01]
-- ldc: 1-byte index (use ldc_w 0x13 for large pool indices > 255)
Ldc i
| i <= 255 -> [0x12, fromIntegral i]
| otherwise -> 0x13 : indexBytes i -- ldc_w
-- Arithmetic
IAdd -> [0x60]
ISub -> [0x64]
IMul -> [0x68]
-- Control flow: 2-byte signed branch offset
IfEq offset -> 0x99 : indexBytes offset
IfNe offset -> 0x9A : indexBytes offset
Goto offset -> 0xA7 : indexBytes offset
-- Return
Return -> [0xB1]
IReturn -> [0xAC]
AReturn -> [0xB0]
-- Serialize a full program — concatenate all instruction bytes
serializeProgram :: BtcProgram -> [Word8]
serializeProgram (BtcProgram lines) =
concatMap (serializeInstruction . instruction) lines
+1 -1
View File
@@ -19,7 +19,7 @@ data Expr
| Integer Integer
| Bool Bool
| Char Char
| Jnull
| Null
| StmtExprExpr StmtExpr
deriving (Show, Eq)
+26 -8
View File
@@ -1,13 +1,31 @@
module Grammar.TAST where
import Grammar.AST
import Grammar.AST (BinaryOperator, Type, UnaryOperator)
data TypedExpr = TypedExpr Expr Type
data TypedExpr
= This Type
| LocalOrFieldVar String Type
| InstVar TypedExpr String Type
| Unary UnaryOperator TypedExpr Type
| Binary BinaryOperator TypedExpr TypedExpr Type
| Integer Integer Type
| Bool Bool Type
| Char Char Type
| Null Type
| StmtExprExpr TypedStmtExpr Type
deriving (Show, Eq)
data TypedStmtExpr = TypedStmtExpr StmtExpr Type
data TypedStmtExpr
= Assign TypedExpr TypedExpr Type
| New Type [TypedExpr] Type
| MethodCall TypedExpr String [TypedExpr] Type
deriving (Show, Eq)
data TypedStmt = TypedStmt Stmt Type
data TypedBinaryOperator = TypedBinaryOperator BinaryOperator Type
data TypedUnaryOperator = TypedUnaryOperator UnaryOperator Type
data TypedStmt
= Block [TypedStmt] Type
| Return (Maybe TypedExpr) Type
| While TypedExpr TypedStmt Type
| LocalVarDecl Type String Type
| If TypedExpr TypedStmt (Maybe TypedStmt) Type
| StmtExprStmt TypedStmtExpr Type
deriving (Show, Eq)
+38 -27
View File
@@ -1,46 +1,57 @@
module Typecheck.SemanticChecker where
import Grammar.AST
import Grammar.TAST
import Grammar.AST as AST
import Grammar.TAST as TAST
--------------------------------------------------
-- Helper functions
--------------------------------------------------
getExpr :: TypedExpr -> Expr
getExpr (TypedExpr e _) = e
getTypeFromTypedExpr :: TypedExpr -> Type
getTypeFromTypedExpr (TAST.This t) = t
getTypeFromTypedExpr (TAST.LocalOrFieldVar _ t) = t
getTypeFromTypedExpr (TAST.InstVar _ _ t) = t
getTypeFromTypedExpr (TAST.Unary _ _ t) = t
getTypeFromTypedExpr (TAST.Binary _ _ _ t) = t
getTypeFromTypedExpr (TAST.Integer _ t) = t
getTypeFromTypedExpr (TAST.Bool _ t) = t
getTypeFromTypedExpr (TAST.Char _ t) = t
getTypeFromTypedExpr (TAST.Null t) = t
getTypeFromTypedExpr (TAST.StmtExprExpr _ t) = t
getStmt :: TypedStmt -> Stmt
getStmt (TypedStmt s _) = s
getTypeFromExpr :: TypedExpr -> Type
getTypeFromExpr (TypedExpr _ typ) = typ
getTypeFromTypedStmtExpr :: TypedStmtExpr -> Type
getTypeFromTypedStmtExpr (TAST.Assign _ _ t) = t
getTypeFromTypedStmtExpr (TAST.New _ _ t) = t
getTypeFromTypedStmtExpr (TAST.MethodCall _ _ _ t) = t
getTypeFromTypedStmt :: TypedStmt -> Type
getTypeFromTypedStmt (TAST.Block _ t) = t
getTypeFromTypedStmt (TAST.Return _ t) = t
getTypeFromTypedStmt (TAST.While _ _ t) = t
getTypeFromTypedStmt (TAST.LocalVarDecl _ _ t) = t
getTypeFromTypedStmt (TAST.If _ _ _ t) = t
getTypeFromTypedStmt (TAST.StmtExprStmt _ t) = t
getTypeFromStmt :: TypedStmt -> Type
getTypeFromStmt (TypedStmt _ typ) = typ
upperBound :: Type -> Type -> Type
upperBound t1 t2
| t1 == t2 = t1
| otherwise = "Object" -- // TODO: implement proper class hierarchy and find the least common ancestor of t1 and t2 in that hierarchy
| otherwise = "Object" -- // Or: Throw an error because types don't match
--------------------------------------------------
-- Statement Typechecking
--------------------------------------------------
typeCheckStmt :: Stmt -> [(String, Type)] -> [Class] -> TypedStmt
-- If Statement
typeCheckStmt (If cond body Nothing) symtab cls =
let checkedCond = typeCheckExpr cond symtab cls
checkedBody = typeCheckStmt body symtab cls
in if getTypeFromExpr checkedCond == "boolean"
then TypedStmt (If checkedCond checkedBody Nothing) (getTypeFromStmt checkedBody)
else error "Condition in if statement must be of type boolean"
-- If-Else Statement
typeCheckStmt (If cond body (Just elseBranch)) symtab cls =
typeCheckStmt (AST.If cond body elseBranch) symtab cls =
let checkedCond = typeCheckExpr cond symtab cls
checkedBody = typeCheckStmt body symtab cls
checkedElse = fmap (\e -> typeCheckStmt e symtab cls) elseBranch
in if getTypeFromExpr checkedCond == "boolean"
then TypedStmt (If checkedCond checkedBody checkedElse) (upperBound (getTypeFromStmt checkedBody) (getTypeFromStmt checkedElse))
typ = case checkedElse of
Nothing -> getTypeFromTypedStmt checkedBody
Just ce -> upperBound (getTypeFromTypedStmt checkedBody) (getTypeFromTypedStmt ce)
in if getTypeFromTypedExpr checkedCond == "boolean"
then TAST.If checkedCond checkedBody checkedElse typ
else error "Condition in if statement must be of type boolean"
--------------------------------------------------
@@ -48,13 +59,13 @@ typeCheckStmt (If cond body (Just elseBranch)) symtab cls =
--------------------------------------------------
typeCheckExpr :: Expr -> [(String, Type)] -> [Class] -> TypedExpr
-- boolean literals
typeCheckExpr (Bool boolean) symtbl cls =
TypedExpr (Bool boolean) "boolean"
typeCheckExpr (AST.Bool value) symtbl cls =
TAST.Bool value "boolean"
-- integer literals
typeCheckExpr (Integer integer) symtbl cls =
TypedExpr (Integer integer) "int"
typeCheckExpr (AST.Integer value) symtbl cls =
TAST.Integer value "int"
-- variable references
typeCheckExpr (LocalOrFieldVar varName) symtbl cls =
typeCheckExpr (AST.LocalOrFieldVar varName) symtbl cls =
case lookup varName symtbl of
Just t -> TypedExpr (LocalOrFieldVar varName) t
Just t -> TAST.LocalOrFieldVar varName t
Nothing -> error $ "Undefined variable: " ++ varName
+40
View File
@@ -0,0 +1,40 @@
public class AllSyntaxTest {
public int x;
private static int counter = 0;
public AllSyntaxTest(int initial) {
x = initial;
counter = counter + 1;
}
public int inc() {
x = x + 1;
return x;
}
public int inc(int delta) {
x = x + delta;
return x;
}
public static int sumUpTo(int n) {
int s = 0;
for (int i = 1; i <= n; i = i + 1) {
s = s + i;
}
return s;
}
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
}
}
+11
View File
@@ -0,0 +1,11 @@
public class ArithmeticTest {
public int basic(int a, int b, int c)
{
return a + b - c * a / b % c;
}
public boolean logic(boolean a, boolean b, boolean c)
{
return !a && (c || b);
}
}
+95
View File
@@ -0,0 +1,95 @@
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
+27
View File
@@ -0,0 +1,27 @@
public class CombinedControlTest {
int field;
public CombinedControlTest(int v) {
field = v;
}
public int compute() {
int i = 0;
int acc = 0;
while (i < field) {
if (i % 2 == 0) {
acc = acc + i;
} else {
acc = acc - i;
}
i = i + 1;
}
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());
}
}
@@ -0,0 +1,12 @@
public class ConstructorOverloadTest {
public int a = 42;
ConstructorOverloadTest() {
// nothing here, so a will assume the default value 42.
}
ConstructorOverloadTest(int a) {
this.a = a;
}
}
+9
View File
@@ -0,0 +1,9 @@
public class ConstructorTest
{
public int a = -1;
public ConstructorTest(int initial_value)
{
a = initial_value;
}
}
+4
View File
@@ -0,0 +1,4 @@
public class EmptyTest
{
}
+18
View File
@@ -0,0 +1,18 @@
public class ExpressionTest {
public static boolean shortCircuit(int a, int b) {
// short-circuit: when a==0 the right side must not be evaluated
boolean res = (a != 0) && ((10 / a) > b);
return res;
}
public static char charArithmetic(char c, int offset) {
char d = (char)(c + offset);
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'
}
}
+5
View File
@@ -0,0 +1,5 @@
public class FieldsTest
{
public int a;
public int b = 42;
}
+17
View File
@@ -0,0 +1,17 @@
public class IfTest {
public static boolean ifElseTest(int x) {
if (x < 0) {
return false;
} else if (x == 0) {
return true;
} else {
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
}
}
+19
View File
@@ -0,0 +1,19 @@
public class LoopTest {
public int factorial(int n)
{
int tally = 1;
for(int i = 1; i <= n; i++)
{
tally *= i;
}
return tally;
}
int weirdFor() {
int k = 0;
for (; k < 5; k++) {
}
return k;
}
}
+58
View File
@@ -0,0 +1,58 @@
// compile all test files using:
// ls Test/JavaSources/*.java | grep -v ".*Main.java" | xargs -I {} cabal run compiler {}
// compile (in project root) using:
// pushd Test/JavaSources; javac -g:none Main.java; popd
// afterwards, run using
// java -ea -cp Test/JavaSources/ Main
public class Main {
public static void main(String[] args)
{
EmptyTest empty = new EmptyTest();
FieldsTest fields = new FieldsTest();
ConstructorTest constructor = new ConstructorTest(42);
ArithmeticTest arithmetic = new ArithmeticTest();
MultipleClassesTest multipleClasses = new MultipleClassesTest();
RecursionTest recursion = new RecursionTest(10);
MaliciousTest malicious = new MaliciousTest();
LoopTest loop = new LoopTest();
MethodOverloadTest overload = new MethodOverloadTest();
ShenaniganceTest shenanigance = new ShenaniganceTest();
// constructing a basic class works
System.out.println("test empty non-null. Expected: non-null, Real: " + (empty != null));
// initializers (and default initializers to 0/null) work
System.out.println("test fields initializers. Expected: a==0 and b==42, Real: a=" + fields.a + " and b=" + fields.b);
// constructor parameters override initializers
System.out.println("test constructor a. Expected result: 42, Real result: " + constructor.a);
// basic arithmetics
System.out.println("test arithmetic basic(1,2,3). Expected: 2, Real: " + arithmetic.basic(1, 2, 3));
// we have boolean logic as well
System.out.println("test arithmetic logic(false,false,true). Expected: true, Real: " + arithmetic.logic(false, false, true));
// multiple classes within one file work. Referencing another classes fields/methods works.
System.out.println("test multiple classes field. Expected: 42, Real: " + multipleClasses.a.a);
// self-referencing classes work.
System.out.println("test nested child value. Expected: 5, Real: " + recursion.child.child.child.child.child.value);
// self-referencing methods work.
System.out.println("test recursion fibonacci(15). Expected: 610, Real: " + recursion.fibonacci(15));
System.out.println("test factorial(5). Expected: 120, Real: " + loop.factorial(5));
System.out.println("test weirdFor(). Expected: 5, Real: " + loop.weirdFor());
// methods with the same name but different parameters work
System.out.println("test MethodOverload(). Expected: 42, Real: " + overload.MethodOverload());
System.out.println("test MethodOverload(15). Expected: 42+15, Real: " + overload.MethodOverload(15));
// constructor overloading works, too.
System.out.println("test ctor overload default. Expected: 42, Real: " + (new ConstructorOverloadTest()).a);
System.out.println("test ctor overload with arg. Expected: 12, Real: " + (new ConstructorOverloadTest(12)).a);
// intentionally dodgy expressions work
System.out.println("test assignNegativeIncrement(42). Expected: -42, Real: " + malicious.assignNegativeIncrement(42));
System.out.println("test tripleAddition(1,2,3). Expected: 6, Real: " + malicious.tripleAddition(1, 2, 3));
for(int i = 0; i < 3; i++)
{
System.out.println("test cursedFormatting i=" + i + ". Expected: " + i + ", Real: " + malicious.cursedFormatting(i));
}
// other syntactic sugar
System.out.println("test shenanigance.testAssignment(). Expected: 5, Real: " + shenanigance.testAssignment());
System.out.println("test shenanigance.divEqual(). Expected: " + (234_343_000 / 4) + ", Real: " + shenanigance.divEqual());
System.out.println("test shenanigance.testIf(5). Expected: true, Real: " + shenanigance.testIf(5));
}
}
+41
View File
@@ -0,0 +1,41 @@
public class MaliciousTest {
public int assignNegativeIncrement(int n)
{
return n=-++n+1;
}
public int tripleAddition(int a, int b, int c)
{
return a+++b+++c++;
}
public int cursedFormatting(int n)
{
if
(n == 0)
{
return ((((0))));
}
else
if(n ==
1)
{
return
1;
}else {
return
2
;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
public class MethodOverloadTest {
public int MethodOverload() {
return 42;
}
public int MethodOverload(int a) {
return 42 + a;
}
}
+12
View File
@@ -0,0 +1,12 @@
public class MultiClassTest {
public static void main(String[] args) {
Helper h = new Helper(3);
System.out.println(h.doubleIt()); // expect 6
}
}
class Helper {
int v;
Helper(int v0) { v = v0; }
int doubleIt() { return v * 2; }
}
+9
View File
@@ -0,0 +1,9 @@
public class MultipleClassesTest
{
public AnotherTestClass a = new AnotherTestClass();
}
class AnotherTestClass
{
public int a = 42;
}
+34
View File
@@ -0,0 +1,34 @@
public class RecursionTest {
public int value = 0;
public RecursionTest child = null;
public RecursionTest(int n)
{
this.value = n;
if(n > 0)
{
child = new RecursionTest(n - 1);
}
}
public int fibonacci(int n)
{
if(n < 2)
{
return n;
}
else
{
return fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
public int ackermann(int m, int n)
{
if (m == 0) return n + 1;
if (n == 0) return ackermann(m - 1, 1);
return ackermann(m - 1, ackermann(m, n - 1));
}
}
+25
View File
@@ -0,0 +1,25 @@
class ShenaniganceTest {
int testAssignment() {
int x = 1;
int y = x = 5;
return y;
}
int divEqual() {
int x = 234_343_000;
x /= 4;
return x;
}
boolean testIf(int x) {
if (true && x < 8) {
char f = 'c';
return f > x ;
}
ifn't {
return false;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
public class SingletonTest {
SingletonTest instance;
SingletonTest() {
}
public SingletonTest getInstance() {
if (instance == null) {
instance = new SingletonTest();
}
return instance;
}
}
+15
View File
@@ -0,0 +1,15 @@
public class WhileTest {
public static int whileLoopTest(int n) {
int sum = 0;
while (n > 0) {
sum = sum + n;
n = n - 1;
}
return sum;
}
public static void main(String[] args) {
// Expect 1+2+3+4+5 = 15
System.out.println(whileLoopTest(5));
}
}
-5
View File
@@ -1,5 +0,0 @@
class Main {
int main() {
return 0;
}
}
@@ -0,0 +1,3 @@
class ClassWithInt {
Integer i;
}
Binary file not shown.