Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8416f1b9c8 | |||
| 245e70169e | |||
| 8497b949a4 | |||
| 251d8daa0f | |||
| 1ad70c0b00 | |||
| 7f6e8f5cb5 | |||
| 88e9b545eb | |||
| 11fda5eaa7 | |||
| 1a153200fa | |||
| 815388b842 | |||
| 22e238317c | |||
| 5d48938221 | |||
| 44c1032e15 | |||
| 842149be5e | |||
| 48b6270fd0 | |||
| b668fc94ff | |||
| a829814cd6 | |||
| 66e0efba81 | |||
| 31248ecc97 | |||
| ac60aeba3e | |||
| b833d6ebe0 | |||
| 3f0e00ac63 | |||
| 4370ec3c1f | |||
| 162bed1b12 | |||
| e91fac7060 | |||
| 0eb8c64fc6 | |||
| f96627fcc9 | |||
| b5ddd0b5c9 | |||
| 531f5d7f7f | |||
| 5a16f08ccc | |||
| e5eb3a9792 | |||
| 8e3b978ba4 | |||
| 1b2ae2ae22 | |||
| d9b7ba40bc | |||
| 03e0571f4e | |||
| 4978de5220 | |||
| b80b71936a | |||
| 22de50b77e | |||
| e84550025c | |||
| 8470ab2d94 | |||
| 1ad8aface9 | |||
| daa741a090 | |||
| e81ceb454f | |||
| 87db9f6e23 | |||
| a447fad905 | |||
| b964bdda1b | |||
| 2f908d1df1 | |||
| 40e84e45f6 | |||
| 56d43bac38 | |||
| 9230042b25 | |||
| 76c4cb677c |
@@ -31,3 +31,5 @@ cabal.project.local~
|
||||
hie.yaml
|
||||
src/Grammar/Scanner.hs
|
||||
src/Grammar/Parser.hs
|
||||
src/Grammar/Parser.info
|
||||
src/Main
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
# Compilerbau
|
||||
|
||||
# – Prüfungsleistung
|
||||
|
||||
## Spezifikation
|
||||
|
||||
Deklarationen: • Σ: Eingabe–Alphabet
|
||||
|
||||
- JC: Menge aller syntaktisch korrekten Java–Klassen 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 Lambda–Expressions
|
||||
- BC: Menge aller Bytecode–Files
|
||||
|
||||
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 Java–Compiler 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 (1–2 Personen)
|
||||
Scannen: alex–File oder Scanner von Hand programmieren
|
||||
Grammatik (nur bei Bearbeitung durch 2 Personen):Erstellen einer Mini-Java-Grammatik an Hand der Spezifikation
|
||||
Parsen:Erstellen des happy–Files oder des Kombinator–Parsers 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 Java–Files, die alle implementierten Features abdecken.
|
||||
- Händische Übersetzung aller Java-Files der Testsuite in die abstrakte Syntax (als Test–Eingaben für den Typ-Checker)
|
||||
- Händische Übersetzung aller Testfälle der abstrakten Syntax in getypte abstrakte Syntax (als Test–Eingaben 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 Java–Programmen, 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
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
module Codegen.ClassFile where
|
||||
|
||||
import Codegen.ConstPool
|
||||
( generateConstPool,
|
||||
lookupClassIndex,
|
||||
lookupUtf8Index,
|
||||
)
|
||||
import Codegen.Types
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Grammar.AST (Type)
|
||||
import Grammar.TAST
|
||||
( TypedClass (..),
|
||||
TypedFieldDecl (..),
|
||||
TypedMethodDecl (..),
|
||||
)
|
||||
|
||||
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 Index = Int
|
||||
|
||||
type Interfaces = [Word16] -- constant pool indices
|
||||
|
||||
type Field_Infos = [Field_Info]
|
||||
|
||||
type Method_Infos = [Method_Info]
|
||||
|
||||
type Attribute_Infos = [Attribute_Info]
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
generateClassFile :: TypedClass -> ClassFile
|
||||
generateClassFile cls =
|
||||
let constPool = makeConstpool cls
|
||||
in ClassFile
|
||||
{ magic = Magic 0xCAFEBABE,
|
||||
minorVersion = MinorVersion 0,
|
||||
majorVersion = MajorVersion 48,
|
||||
constantPool = constPool,
|
||||
accessFlags = makeAccessFlags,
|
||||
thisClass = makeThisClass constPool cls,
|
||||
superClass = makeSuperClass constPool,
|
||||
interfaces = makeInterface,
|
||||
fields = makeFields constPool cls,
|
||||
methods = makeMethods constPool cls,
|
||||
attributes = makeAttributes constPool
|
||||
}
|
||||
|
||||
makeConstpool :: TypedClass -> CP_Infos
|
||||
makeConstpool = generateConstPool
|
||||
|
||||
makeAccessFlags :: AccessFlags
|
||||
makeAccessFlags = AccessFlags [ACC_PUBLIC, ACC_SUPER]
|
||||
|
||||
makeThisClass :: CP_Infos -> TypedClass -> ThisClass
|
||||
makeThisClass constPool (Class name _ _) =
|
||||
ThisClass (lookupClassIndex constPool name)
|
||||
|
||||
makeSuperClass :: CP_Infos -> SuperClass
|
||||
makeSuperClass constPool =
|
||||
SuperClass (lookupClassIndex constPool "java/lang/Object")
|
||||
|
||||
makeInterface :: Interfaces
|
||||
makeInterface = []
|
||||
|
||||
makeFields :: CP_Infos -> TypedClass -> Field_Infos
|
||||
makeFields constPool (Class _ fields _) = map (makeField constPool) fields
|
||||
|
||||
makeField :: CP_Infos -> TypedFieldDecl -> Field_Info
|
||||
makeField constPool (Field fieldType name _) =
|
||||
Field_Info
|
||||
{ fieldAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
fieldNameIndex = lookupUtf8Index constPool name,
|
||||
fieldDescIndex = lookupUtf8Index constPool (typeDescriptor fieldType),
|
||||
fieldAttributes = []
|
||||
}
|
||||
|
||||
makeAttributes :: CP_Infos -> Attribute_Infos
|
||||
makeAttributes _ = []
|
||||
|
||||
makeMethods :: CP_Infos -> TypedClass -> Method_Infos
|
||||
makeMethods constPool (Class _ _ methods) = map (makeMethod constPool) methods
|
||||
|
||||
makeMethod :: CP_Infos -> TypedMethodDecl -> Method_Info
|
||||
makeMethod constPool (Method returnType name params _body) =
|
||||
Method_Info
|
||||
{ methodAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
methodNameIndex = lookupUtf8Index constPool name,
|
||||
methodDescIndex = lookupUtf8Index constPool (methodDescriptor params returnType),
|
||||
methodAttributes = []
|
||||
}
|
||||
|
||||
methodDescriptor :: [(Type, String)] -> Type -> String
|
||||
methodDescriptor params returnType =
|
||||
"(" ++ concatMap (typeDescriptor . fst) params ++ ")" ++ typeDescriptor returnType
|
||||
|
||||
typeDescriptor :: Type -> String
|
||||
typeDescriptor t = case t of
|
||||
"int" -> "I"
|
||||
"boolean" -> "Z"
|
||||
"char" -> "C"
|
||||
"void" -> "V"
|
||||
_ -> "L" ++ t ++ ";"
|
||||
@@ -0,0 +1,232 @@
|
||||
module Codegen.ConstPool where
|
||||
|
||||
import Codegen.Types
|
||||
import Data.List (isSuffixOf)
|
||||
import qualified Grammar.TAST as TAST
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Recursive extraction of Tags from an already-built pool
|
||||
------------------------------------------------------------------------
|
||||
|
||||
extractTags :: CP_Infos -> [Tag]
|
||||
extractTags [] = []
|
||||
extractTags (x : xs) = tag_cp x : extractTags xs
|
||||
|
||||
countEntries :: CP_Infos -> Int
|
||||
countEntries [] = 0
|
||||
countEntries (_ : xs) = 1 + countEntries xs
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Helper constructors
|
||||
------------------------------------------------------------------------
|
||||
|
||||
mkClassInfo :: IndexConstantPool -> String -> CP_Info
|
||||
mkClassInfo = ClassInfo TagClass
|
||||
|
||||
mkFieldRefInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkFieldRefInfo = FieldRefInfo TagFieldRef
|
||||
|
||||
mkMethodRefInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkMethodRefInfo = MethodRefInfo TagMethodRef
|
||||
|
||||
mkStringInfo :: IndexConstantPool -> String -> CP_Info
|
||||
mkStringInfo = StringInfo TagString
|
||||
|
||||
mkIntegerInfo :: Int -> String -> CP_Info
|
||||
mkIntegerInfo = IntegerInfo TagInteger
|
||||
|
||||
mkNameAndTypeInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkNameAndTypeInfo = NameAndTypeInfo TagNameAndType
|
||||
|
||||
mkUtf8Info :: String -> String -> CP_Info
|
||||
mkUtf8Info str = Utf8Info TagUtf8 (length str) str
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Tag utilities
|
||||
------------------------------------------------------------------------
|
||||
|
||||
tagToByte :: Tag -> Int
|
||||
tagToByte TagClass = 7
|
||||
tagToByte TagFieldRef = 9
|
||||
tagToByte TagMethodRef = 10
|
||||
tagToByte TagString = 8
|
||||
tagToByte TagInteger = 3
|
||||
tagToByte TagNameAndType = 12
|
||||
tagToByte TagUtf8 = 1
|
||||
|
||||
tagToString :: Tag -> String
|
||||
tagToString TagClass = "Class"
|
||||
tagToString TagFieldRef = "FieldRef"
|
||||
tagToString TagMethodRef = "MethodRef"
|
||||
tagToString TagString = "String"
|
||||
tagToString TagInteger = "Integer"
|
||||
tagToString TagNameAndType = "NameAndType"
|
||||
tagToString TagUtf8 = "Utf8"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Descriptor helpers
|
||||
------------------------------------------------------------------------
|
||||
|
||||
typeToDescriptor :: String -> String
|
||||
typeToDescriptor "int" = "I"
|
||||
typeToDescriptor "boolean" = "Z"
|
||||
typeToDescriptor "char" = "C"
|
||||
typeToDescriptor "void" = "V"
|
||||
typeToDescriptor t = "L" ++ t ++ ";"
|
||||
|
||||
methodDescriptor :: [(String, String)] -> String -> String
|
||||
methodDescriptor params ret =
|
||||
"(" ++ paramsDesc params ++ ")" ++ typeToDescriptor ret
|
||||
where
|
||||
paramsDesc [] = ""
|
||||
paramsDesc ((pty, _) : ps) = typeToDescriptor pty ++ paramsDesc ps
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Recursive traversal of TAST to collect CP_Info entries
|
||||
------------------------------------------------------------------------
|
||||
|
||||
collectFromExprs :: [TAST.TypedExpr] -> CP_Infos
|
||||
collectFromExprs [] = []
|
||||
collectFromExprs (e : es) = collectFromTypedExpr e ++ collectFromExprs es
|
||||
|
||||
collectFromTypedExpr :: TAST.TypedExpr -> CP_Infos
|
||||
collectFromTypedExpr (TAST.Integer n _) =
|
||||
[mkIntegerInfo (fromIntegral n) ("int " ++ show n)]
|
||||
collectFromTypedExpr (TAST.Bool _ _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.Char c _) =
|
||||
[mkIntegerInfo (fromEnum c) ("char " ++ show c)]
|
||||
collectFromTypedExpr (TAST.Null _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.This _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.LocalOrFieldVar _ _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.InstVar obj name typ) =
|
||||
collectFromTypedExpr obj
|
||||
++ [ mkUtf8Info name ("field name: " ++ name),
|
||||
mkUtf8Info (typeToDescriptor typ) ("field descriptor: " ++ typeToDescriptor typ),
|
||||
mkNameAndTypeInfo 0 0 ("field NameAndType: " ++ name),
|
||||
mkFieldRefInfo 0 0 ("field ref: " ++ name)
|
||||
]
|
||||
collectFromTypedExpr (TAST.Unary _ e _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedExpr (TAST.Binary _ e1 e2 _) =
|
||||
collectFromTypedExpr e1 ++ collectFromTypedExpr e2
|
||||
collectFromTypedExpr (TAST.StmtExprExpr se _) =
|
||||
collectFromTypedStmtExpr se
|
||||
|
||||
collectFromTypedStmtExpr :: TAST.TypedStmtExpr -> CP_Infos
|
||||
collectFromTypedStmtExpr (TAST.Assign lhs rhs _) =
|
||||
collectFromTypedExpr lhs ++ collectFromTypedExpr rhs
|
||||
collectFromTypedStmtExpr (TAST.New t args _) =
|
||||
collectFromExprs args
|
||||
++ [ mkUtf8Info t ("class name: " ++ t),
|
||||
mkClassInfo 0 ("class: " ++ t)
|
||||
]
|
||||
collectFromTypedStmtExpr (TAST.MethodCall obj name args retTyp) =
|
||||
collectFromTypedExpr obj
|
||||
++ collectFromExprs args
|
||||
++ [ mkUtf8Info name ("method name: " ++ name),
|
||||
mkUtf8Info (typeToDescriptor retTyp) ("method ret desc: " ++ typeToDescriptor retTyp),
|
||||
mkNameAndTypeInfo 0 0 ("method NameAndType: " ++ name),
|
||||
mkMethodRefInfo 0 0 ("method ref: " ++ name)
|
||||
]
|
||||
|
||||
collectFromStmts :: [TAST.TypedStmt] -> CP_Infos
|
||||
collectFromStmts [] = []
|
||||
collectFromStmts (s : ss) = collectFromTypedStmt s ++ collectFromStmts ss
|
||||
|
||||
collectFromTypedStmt :: TAST.TypedStmt -> CP_Infos
|
||||
collectFromTypedStmt (TAST.Block stmts _) =
|
||||
collectFromStmts stmts
|
||||
collectFromTypedStmt (TAST.Return (Just e) _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedStmt (TAST.Return Nothing _) =
|
||||
[]
|
||||
collectFromTypedStmt (TAST.While cond body _) =
|
||||
collectFromTypedExpr cond ++ collectFromTypedStmt body
|
||||
collectFromTypedStmt (TAST.LocalVarDecl _ _ _) =
|
||||
[]
|
||||
collectFromTypedStmt (TAST.If cond then_ mElse _) =
|
||||
collectFromTypedExpr cond
|
||||
++ collectFromTypedStmt then_
|
||||
++ collectFromMaybeStmt mElse
|
||||
collectFromTypedStmt (TAST.StmtExprStmt se _) =
|
||||
collectFromTypedStmtExpr se
|
||||
|
||||
collectFromMaybeStmt :: Maybe TAST.TypedStmt -> CP_Infos
|
||||
collectFromMaybeStmt Nothing = []
|
||||
collectFromMaybeStmt (Just s) = collectFromTypedStmt s
|
||||
|
||||
collectFromFields :: [TAST.TypedFieldDecl] -> CP_Infos
|
||||
collectFromFields [] = []
|
||||
collectFromFields (TAST.Field fType fName _: fs ) =
|
||||
[ mkUtf8Info fName ("field: " ++ fName),
|
||||
|
||||
mkUtf8Info (typeToDescriptor fType) ("field desc: " ++ fType)
|
||||
]
|
||||
++ collectFromFields fs
|
||||
|
||||
collectFromMethods :: [TAST.TypedMethodDecl] -> CP_Infos
|
||||
collectFromMethods [] = []
|
||||
collectFromMethods (TAST.Method mType mName params body : ms) =
|
||||
[ mkUtf8Info mName ("method: " ++ mName),
|
||||
mkUtf8Info (methodDescriptor params mType) ("method desc: " ++ mName)
|
||||
]
|
||||
++ collectFromTypedStmt body
|
||||
++ collectFromMethods ms
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Build the complete constant pool for a class
|
||||
------------------------------------------------------------------------
|
||||
|
||||
generateConstPool :: TAST.TypedClass -> CP_Infos
|
||||
generateConstPool (TAST.Class cName fields methods) =
|
||||
[ mkUtf8Info cName ("this class: " ++ cName),
|
||||
mkUtf8Info "java/lang/Object" "super class",
|
||||
mkClassInfo 0 ("this class: " ++ cName),
|
||||
mkClassInfo 0 "super class"
|
||||
]
|
||||
++ collectFromFields fields
|
||||
++ collectFromMethods methods
|
||||
|
||||
lookupClassIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupClassIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
ClassInfo {desc = descVal}
|
||||
| name == "java/lang/Object" -> descVal == "super class" || isSuffixOf name descVal
|
||||
| otherwise -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
lookupUtf8Index :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupUtf8Index pool value =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
Utf8Info {cad_cp = cadVal, desc = descVal} -> cadVal == value || isSuffixOf value descVal
|
||||
_ -> False
|
||||
|
||||
lookupNameAndTypeIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupNameAndTypeIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> lookupUtf8Index pool name
|
||||
where
|
||||
matches entry = case entry of
|
||||
NameAndTypeInfo {desc = descVal} -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
findIndex :: (CP_Info -> Bool) -> CP_Infos -> Maybe IndexConstantPool
|
||||
findIndex predicate pool = go 1 pool
|
||||
where
|
||||
go _ [] = Nothing
|
||||
go i (x : xs)
|
||||
| predicate x = Just i
|
||||
| otherwise = go (i + 1) xs
|
||||
@@ -0,0 +1,139 @@
|
||||
module Codegen.Lowerer where
|
||||
|
||||
import Codegen.ClassFile
|
||||
import Codegen.Types (CP_Info (Utf8Info), CP_Infos)
|
||||
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 (Utf8Info {cad_cp = 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
|
||||
@@ -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>: 0x2A–0x2D for 0–3, 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>: 0x1A–0x1D for 0–3, else wide form 0x15 <index>
|
||||
ILoad 0 -> [0x1A]
|
||||
ILoad 1 -> [0x1B]
|
||||
ILoad 2 -> [0x1C]
|
||||
ILoad 3 -> [0x1D]
|
||||
ILoad n -> [0x15, fromIntegral n]
|
||||
-- astore_<n>: 0x4B–0x4E for 0–3, else 0x3A <index>
|
||||
AStore 0 -> [0x4B]
|
||||
AStore 1 -> [0x4C]
|
||||
AStore 2 -> [0x4D]
|
||||
AStore 3 -> [0x4E]
|
||||
AStore n -> [0x3A, fromIntegral n]
|
||||
-- istore_<n>: 0x3B–0x3E for 0–3, 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
|
||||
@@ -0,0 +1,57 @@
|
||||
module Codegen.Types where
|
||||
|
||||
type IndexConstantPool = Int
|
||||
|
||||
type CP_Infos = [CP_Info]
|
||||
|
||||
data CP_Info
|
||||
= ClassInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| FieldRefInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_nameandtype_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| MethodRefInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_nameandtype_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| StringInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| IntegerInfo
|
||||
{ tag_cp :: Tag,
|
||||
numi_cp :: Int,
|
||||
desc :: String
|
||||
}
|
||||
| NameAndTypeInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_descr_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| Utf8Info
|
||||
{ tag_cp :: Tag,
|
||||
tam_cp :: Int,
|
||||
cad_cp :: String,
|
||||
desc :: String
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data Tag
|
||||
= TagClass
|
||||
| TagFieldRef
|
||||
| TagMethodRef
|
||||
| TagString
|
||||
| TagInteger
|
||||
| TagNameAndType
|
||||
| TagUtf8
|
||||
deriving (Eq, Ord, Show)
|
||||
+2
-2
@@ -6,7 +6,7 @@ type Program = [Class]
|
||||
|
||||
data Class = Class Type [FieldDecl] [MethodDecl] deriving (Show, Eq)
|
||||
|
||||
data FieldDecl = Field Type String deriving (Show, Eq)
|
||||
data FieldDecl = Field Type String (Maybe Expr) deriving (Show, Eq)
|
||||
|
||||
data MethodDecl = Method Type String [(Type, String)] Stmt deriving (Show, Eq)
|
||||
|
||||
@@ -19,7 +19,7 @@ data Expr
|
||||
| Integer Integer
|
||||
| Bool Bool
|
||||
| Char Char
|
||||
| Jnull
|
||||
| Null
|
||||
| StmtExprExpr StmtExpr
|
||||
deriving (Show, Eq)
|
||||
|
||||
|
||||
+19
-5
@@ -10,6 +10,8 @@ import Grammar.AST
|
||||
%error { parseError }
|
||||
|
||||
%token
|
||||
public { TokenVisibility Public }
|
||||
private { TokenVisibility Private }
|
||||
class { TokenClass }
|
||||
void { TokenVoid }
|
||||
return { TokenReturn }
|
||||
@@ -68,15 +70,26 @@ Classes : Classes ClassDecl { $2 : $1 }
|
||||
| ClassDecl { [$1] }
|
||||
|
||||
ClassDecl : class id '{' Decls '}' { Class $2 (fst $4) (snd $4) }
|
||||
| public class id '{' Decls '}' { Class $3 (fst $5) (snd $5) }
|
||||
| private class id '{' Decls '}' { Class $3 (fst $5) (snd $5) }
|
||||
|
||||
Decls : FieldDecl Decls { ($1 : fst $2, snd $2) }
|
||||
| MethodDecl Decls { (fst $2, $1 : snd $2) }
|
||||
| {- leer -} { ([], []) }
|
||||
|
||||
FieldDecl : Type id ';' { Field $1 $2 }
|
||||
FieldDecl : Type id ';' { Field $1 $2 Nothing }
|
||||
| public Type id ';' { Field $2 $3 Nothing }
|
||||
| private Type id ';' { Field $2 $3 Nothing }
|
||||
| Type id '=' Expr ';' { Field $1 $2 (Just $4) }
|
||||
| public Type id '=' Expr ';' { Field $2 $3 (Just $5) }
|
||||
| private Type id '=' Expr ';' { Field $2 $3 (Just $5) }
|
||||
|
||||
MethodDecl : Type id '(' Params ')' Block { Method $1 $2 $4 $6 }
|
||||
| void id '(' Params ')' Block { Method "void" $2 $4 $6 }
|
||||
MethodDecl : Type id '(' Params ')' Block { Method $1 $2 $4 $6 }
|
||||
| public Type id '(' Params ')' Block { Method $2 $3 $5 $7 }
|
||||
| private Type id '(' Params ')' Block { Method $2 $3 $5 $7 }
|
||||
| void id '(' Params ')' Block { Method "void" $2 $4 $6 }
|
||||
| public void id '(' Params ')' Block { Method "void" $3 $5 $7 }
|
||||
| private void id '(' Params ')' Block { Method "void" $3 $5 $7 }
|
||||
|
||||
Params : ParamList { $1 }
|
||||
| {- leer -} { [] }
|
||||
@@ -97,7 +110,8 @@ Stmts : Stmts Stmt { $2 : $1 }
|
||||
| {- leer -} { [] }
|
||||
|
||||
Stmt : Block { $1 }
|
||||
| return Expr ';' { Return $2 }
|
||||
| return Expr ';' { Return (Just $2) }
|
||||
| return ';' { Return Nothing }
|
||||
| while '(' Expr ')' Stmt { While $3 $5 }
|
||||
| if '(' Expr ')' Stmt else Stmt { If $3 $5 (Just $7) }
|
||||
| if '(' Expr ')' Stmt { If $3 $5 Nothing }
|
||||
@@ -141,5 +155,5 @@ ExprList : Expr { [$1] }
|
||||
|
||||
{
|
||||
parseError :: [Token] -> a
|
||||
parseError tokens = error $ "Lexical error or syntax error at: " ++ show tokens
|
||||
parseError tokens = error $ "abc Lexical error or syntax error at: " ++ show tokens
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ tokens :-
|
||||
"/*"([^\*]|(\*+([^\*\/])))*\*+\/ ; -- multi-line comments
|
||||
|
||||
-- key words
|
||||
public { \_ -> TokenVisibility Public }
|
||||
private { \_ -> TokenVisibility Private }
|
||||
class { \_ -> TokenClass }
|
||||
void { \_ -> TokenVoid }
|
||||
return { \_ -> TokenReturn }
|
||||
@@ -49,8 +51,14 @@ tokens :-
|
||||
$alpha [$alpha $digit]* { \s -> TokenIdent s }
|
||||
|
||||
{
|
||||
data Visibility
|
||||
= Public
|
||||
| Private
|
||||
deriving (Eq, Show)
|
||||
|
||||
data Token
|
||||
= TokenClass
|
||||
= TokenVisibility Visibility
|
||||
| TokenClass
|
||||
| TokenStatic
|
||||
| TokenVoid
|
||||
| TokenReturn
|
||||
|
||||
+30
-6
@@ -1,13 +1,37 @@
|
||||
module Grammar.TAST where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.AST (BinaryOperator, Type, UnaryOperator)
|
||||
|
||||
data TypedExpr = TypedExpr Expr Type
|
||||
data TypedClass = Class Type [TypedFieldDecl] [TypedMethodDecl] deriving (Show, Eq)
|
||||
|
||||
data TypedStmtExpr = TypedStmtExpr StmtExpr Type
|
||||
data TypedFieldDecl = Field Type String (Maybe TypedExpr) deriving (Show, Eq)
|
||||
|
||||
data TypedStmt = TypedStmt Stmt Type
|
||||
data TypedMethodDecl = Method Type String [(Type, String)] TypedStmt deriving (Show, Eq)
|
||||
|
||||
data TypedBinaryOperator = TypedBinaryOperator BinaryOperator 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 TypedUnaryOperator = TypedUnaryOperator UnaryOperator Type
|
||||
data TypedStmtExpr
|
||||
= Assign TypedExpr TypedExpr Type
|
||||
| New Type [TypedExpr] Type
|
||||
| MethodCall TypedExpr String [TypedExpr] Type
|
||||
deriving (Show, Eq)
|
||||
|
||||
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)
|
||||
|
||||
+40
-5
@@ -1,10 +1,45 @@
|
||||
module Main where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.TAST
|
||||
import Typecheck.SemanticChecker
|
||||
import Codegen.ClassFile (ClassFile, generateClassFile)
|
||||
import Grammar.AST (Program)
|
||||
import Grammar.Parser (parse)
|
||||
import Grammar.Scanner (Token, alexScanTokens)
|
||||
import Grammar.TAST (TypedClass (Class))
|
||||
import System.Environment (getArgs)
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let t = getTypeFromExpr $ TypedExpr This "myType"
|
||||
putStrLn t
|
||||
args <- getArgs
|
||||
case args of
|
||||
[path] -> runPipeline path
|
||||
_ -> putStrLn "Usage: compoiler <input.java>"
|
||||
|
||||
runPipeline :: FilePath -> IO ()
|
||||
runPipeline path = do
|
||||
source <- readFile path
|
||||
let tokens = scan source
|
||||
let ast = parseProgram tokens
|
||||
let typedClasses = typeCheckProgram ast
|
||||
let classFiles = map generateClassFile typedClasses
|
||||
reportSuccess ast typedClasses classFiles
|
||||
|
||||
scan :: String -> [Token]
|
||||
scan = alexScanTokens
|
||||
|
||||
parseProgram :: [Token] -> Program
|
||||
parseProgram = parse
|
||||
|
||||
typeCheckProgram :: Program -> [TypedClass]
|
||||
typeCheckProgram classes =
|
||||
map (\cls -> typeCheckClass cls [] classes) classes
|
||||
|
||||
reportSuccess :: Program -> [TypedClass] -> [ClassFile] -> IO ()
|
||||
reportSuccess ast typed classFiles = do
|
||||
putStrLn ("Parsed classes: " ++ show (length ast))
|
||||
putStrLn ("Typed classes: " ++ show (length typed))
|
||||
putStrLn ("Generated class files: " ++ show (length classFiles))
|
||||
putStrLn ("Class names: " ++ unwords (map className typed))
|
||||
|
||||
className :: TypedClass -> String
|
||||
className (Class name _ _) = name
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
module Testsuite.AstFiles.ArithmeticTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"ArithmeticTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"basic"
|
||||
[("int", "a"), ("int", "b"), ("int", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Subtract
|
||||
(Binary Add (LocalOrFieldVar "a") (LocalOrFieldVar "b"))
|
||||
( Binary
|
||||
Modulo
|
||||
( Binary
|
||||
Divide
|
||||
(Binary Multiply (LocalOrFieldVar "c") (LocalOrFieldVar "a"))
|
||||
(LocalOrFieldVar "b")
|
||||
)
|
||||
(LocalOrFieldVar "c")
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
Method
|
||||
"boolean"
|
||||
"logic"
|
||||
[("boolean", "a"), ("boolean", "b"), ("boolean", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
And
|
||||
(Unary Not (LocalOrFieldVar "a"))
|
||||
(Binary Or (LocalOrFieldVar "c") (LocalOrFieldVar "b"))
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
Class "EmptyTest" [] []
|
||||
]
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
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 @@
|
||||
[("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 @@
|
||||
[]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class ArithmeticTest {
|
||||
|
||||
public int basic(int a, int b, int c) {
|
||||
return a + b - (((c * a) / b) % c);
|
||||
}
|
||||
|
||||
public boolean logic(boolean a, boolean b, boolean c) {
|
||||
return !a && (c || b);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstructorTest
|
||||
{
|
||||
public int a = -1;
|
||||
|
||||
public ConstructorTest(int initial_value)
|
||||
{
|
||||
a = initial_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public class EmptyTest
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class FieldsTest {
|
||||
|
||||
public int a;
|
||||
public int b = 42;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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();
|
||||
AllSyntaxTest allSyntax = new AllSyntaxTest(0);
|
||||
CombinedControlTest combined = new CombinedControlTest(6);
|
||||
ExpressionTest expression = new ExpressionTest();
|
||||
IfTest ifTest = new IfTest();
|
||||
MultiClassTest multiClass = new MultiClassTest();
|
||||
SingletonTest singleton = new SingletonTest();
|
||||
WhileTest whileTest = new WhileTest();
|
||||
|
||||
// constructing a basic class works
|
||||
System.out.println("test empty non-null. Expected: non-null, Real: " + (empty != null));
|
||||
// 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());
|
||||
// AllSyntaxTest tests
|
||||
System.out.println("test AllSyntaxTest.inc(). Expected: 1, Real: " + allSyntax.inc());
|
||||
System.out.println("test AllSyntaxTest.inc(4). Expected: 5, Real: " + allSyntax.inc(4));
|
||||
System.out.println("test AllSyntaxTest.sumUpTo(5). Expected: 15, Real: " + AllSyntaxTest.sumUpTo(5));
|
||||
System.out.println("test AllSyntaxTest.predicate('a'). Expected: true, Real: " + allSyntax.predicate('a'));
|
||||
// CombinedControlTest tests
|
||||
System.out.println("test CombinedControlTest.compute(). Expected: -3, Real: " + combined.compute());
|
||||
// ExpressionTest tests
|
||||
System.out.println("test ExpressionTest.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));
|
||||
// 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));
|
||||
// SingletonTest tests
|
||||
System.out.println("test SingletonTest.getInstance(). Expected: non-null, Real: " + (singleton.getInstance() != null));
|
||||
// WhileTest tests
|
||||
System.out.println("test WhileTest.whileLoopTest(5). Expected: 15, Real: " + WhileTest.whileLoopTest(5));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class MethodOverloadTest {
|
||||
|
||||
public int MethodOverload() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
public int MethodOverload(int a) {
|
||||
return 42 + a;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class MultipleClassesTest
|
||||
{
|
||||
public AnotherTestClass a = new AnotherTestClass();
|
||||
}
|
||||
|
||||
class AnotherTestClass
|
||||
{
|
||||
public int a = 42;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
class ShenaniganceTest {
|
||||
|
||||
int testAssignment() {
|
||||
int x = 1;
|
||||
int y = x = 5;
|
||||
return y;
|
||||
}
|
||||
|
||||
int divEqual() {
|
||||
int x = 234_343_000;
|
||||
x /= 4;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
public class SingletonTest {
|
||||
|
||||
SingletonTest instance;
|
||||
|
||||
SingletonTest() {
|
||||
}
|
||||
|
||||
public SingletonTest getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new SingletonTest();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
[
|
||||
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"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
Class "EmptyTest" [] []
|
||||
]
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
class ClassWithInt {
|
||||
Integer i;
|
||||
}
|
||||
@@ -1,60 +1,289 @@
|
||||
module Typecheck.SemanticChecker where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.TAST
|
||||
import Data.List (find)
|
||||
import Grammar.AST as AST
|
||||
import Grammar.TAST as TAST
|
||||
|
||||
--------------------------------------------------
|
||||
-- Helper functions
|
||||
-- Get type 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
|
||||
|
||||
getTypeFromStmt :: TypedStmt -> Type
|
||||
getTypeFromStmt (TypedStmt _ typ) = typ
|
||||
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
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Typechecking Class
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckClass :: AST.Class -> [(String, Type)] -> [AST.Class] -> TAST.TypedClass
|
||||
typeCheckClass (AST.Class typ fields methods) symtab cls =
|
||||
let checkedFields = map (\f -> typeCheckField f symtab cls) fields
|
||||
checkedMethods = map (\m -> typeCheckMethod m symtab cls) methods
|
||||
in TAST.Class typ checkedFields checkedMethods
|
||||
|
||||
typeCheckField :: AST.FieldDecl -> [(String, Type)] -> [AST.Class] -> TAST.TypedFieldDecl
|
||||
typeCheckField (AST.Field typ name Nothing) symtab cls =
|
||||
TAST.Field typ name Nothing
|
||||
typeCheckField (AST.Field expectedTyp name (Just expr)) symtab cls =
|
||||
let checkedExpr = typeCheckExpr expr symtab cls
|
||||
actualTyp = getTypeFromTypedExpr checkedExpr
|
||||
in if expectedTyp == actualTyp
|
||||
then TAST.Field expectedTyp name (Just checkedExpr)
|
||||
else error $ "Type mismatch: Expected " ++ expectedTyp ++ " found " ++ actualTyp
|
||||
|
||||
|
||||
|
||||
typeCheckMethod :: AST.MethodDecl -> [(String, Type)] -> [AST.Class] -> TAST.TypedMethodDecl
|
||||
typeCheckMethod (AST.Method typ name params stmt) symtab cls =
|
||||
let checkedStmt = typeCheckStmt stmt symtab cls
|
||||
stmtTyp = getTypeFromTypedStmt checkedStmt
|
||||
in if typ == stmtTyp || stmtTyp == "void"
|
||||
then TAST.Method typ name params checkedStmt
|
||||
else error $ "Return type mismatch: expected " ++ typ ++ ", got " ++ stmtTyp
|
||||
|
||||
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
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmt :: Stmt -> [(String, Type)] -> [Class] -> TypedStmt
|
||||
-- If Statement
|
||||
typeCheckStmt (If cond body Nothing) symtab cls =
|
||||
typeCheckStmt :: AST.Stmt -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmt
|
||||
|
||||
-- If-Statement
|
||||
typeCheckStmt (AST.If cond body Nothing) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
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 =
|
||||
typ = getTypeFromTypedStmt checkedBody
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody Nothing typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- If-Else-Statement
|
||||
typeCheckStmt (AST.If cond body (Just elseBody)) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
checkedElse = fmap (\e -> typeCheckStmt e symtab cls) elseBranch
|
||||
in if getTypeFromExpr checkedCond == "boolean"
|
||||
then TypedStmt (If checkedCond checkedBody checkedElse) (upperBound (getTypeFromStmt checkedBody) (getTypeFromStmt checkedElse))
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
checkedElse = Just (typeCheckStmt elseBody symtab cls)
|
||||
typ = upperBound (getTypeFromTypedStmt checkedBody) (getTypeFromTypedStmt (maybe checkedBody id checkedElse))
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody checkedElse typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- While-Statement
|
||||
typeCheckStmt (AST.While cond body) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
typ = getTypeFromTypedStmt checkedBody
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.While checkedCond checkedBody typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- Return Statement
|
||||
typeCheckStmt (AST.Return maybeExpr) symtab cls =
|
||||
let checkedExpr = fmap (\expr -> typeCheckExpr expr symtab cls) maybeExpr
|
||||
typ = maybe "void" getTypeFromTypedExpr checkedExpr
|
||||
in TAST.Return checkedExpr typ
|
||||
|
||||
|
||||
typeCheckStmt (AST.LocalVarDecl typ str) _ _ =
|
||||
TAST.LocalVarDecl typ str typ
|
||||
|
||||
-- Block statement
|
||||
typeCheckStmt (AST.Block stmts) symtab cls =
|
||||
let checkedStmts = map (\s -> typeCheckStmt s symtab cls) stmts
|
||||
typ = case reverse checkedStmts of
|
||||
[] -> "void"
|
||||
(lastStmt : _) -> getTypeFromTypedStmt lastStmt
|
||||
in TAST.Block checkedStmts typ
|
||||
|
||||
-- Expression statement
|
||||
typeCheckStmt (AST.StmtExprStmt stmtExpr) symtab cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtab cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprStmt checkedStmtExpr typ
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmtExpr :: AST.StmtExpr -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmtExpr
|
||||
|
||||
-- Assign
|
||||
typeCheckStmtExpr (AST.Assign lhs rhs) symtab cls =
|
||||
let checkedLhs = typeCheckExpr lhs symtab cls
|
||||
checkedRhs = typeCheckExpr rhs symtab cls
|
||||
lhsType = getTypeFromTypedExpr checkedLhs
|
||||
rhsType = getTypeFromTypedExpr checkedRhs
|
||||
in if lhsType == rhsType || rhsType == "null"
|
||||
then TAST.Assign checkedLhs checkedRhs lhsType
|
||||
else error "Type mismatch in assignment"
|
||||
|
||||
-- New
|
||||
typeCheckStmtExpr (AST.New typ args) symtab cls =
|
||||
let checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
in TAST.New typ checkedArgs typ
|
||||
|
||||
-- Method call
|
||||
typeCheckStmtExpr (AST.MethodCall target methodName args) symtab cls =
|
||||
let checkedTarget = typeCheckExpr target symtab cls
|
||||
checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
targetType = getTypeFromTypedExpr checkedTarget
|
||||
returnType = lookupMethodReturnType targetType methodName cls
|
||||
in TAST.MethodCall checkedTarget methodName checkedArgs returnType
|
||||
|
||||
-- Helper methods
|
||||
lookupMethodReturnType :: Type -> String -> [AST.Class] -> Type
|
||||
lookupMethodReturnType classType methodName classes =
|
||||
case findMethod classType methodName classes of
|
||||
Just t -> t
|
||||
Nothing -> error $ "Method not found: " ++ methodName ++ " in class " ++ classType
|
||||
|
||||
findMethod :: Type -> String -> [AST.Class] -> Maybe Type
|
||||
findMethod classType methodName classes =
|
||||
case find (\(AST.Class clsName _ _) -> clsName == classType) classes of
|
||||
Just (AST.Class _ _ methods) ->
|
||||
case find (\(AST.Method ret name _ _) -> name == methodName) methods of
|
||||
Just (AST.Method ret _ _ _) -> Just ret
|
||||
Nothing -> Nothing
|
||||
Nothing -> Nothing
|
||||
|
||||
lookupFieldType :: Type -> String -> [AST.Class] -> Type
|
||||
lookupFieldType classType fieldName classes =
|
||||
case find (\(AST.Class clsName _ _) -> clsName == classType) classes of
|
||||
Just (AST.Class _ fields _) ->
|
||||
case find (\(AST.Field _ name _) -> name == fieldName) fields of
|
||||
Just (AST.Field t _ _) -> t
|
||||
Nothing -> error $ "Field not found: " ++ fieldName ++ " in class " ++ classType
|
||||
Nothing -> error $ "Class not found: " ++ classType
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckExpr :: Expr -> [(String, Type)] -> [Class] -> TypedExpr
|
||||
typeCheckExpr :: AST.Expr -> [(String, Type)] -> [AST.Class] -> TAST.TypedExpr
|
||||
|
||||
-- boolean literals
|
||||
typeCheckExpr (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
|
||||
|
||||
-- this reference
|
||||
typeCheckExpr AST.This _ cls =
|
||||
case cls of
|
||||
(AST.Class className _ _ : _) -> TAST.This className
|
||||
[] -> error "No class context for this expression"
|
||||
|
||||
-- field access on an object
|
||||
typeCheckExpr (AST.InstVar obj fieldName) symtbl cls =
|
||||
let checkedObj = typeCheckExpr obj symtbl cls
|
||||
objType = getTypeFromTypedExpr checkedObj
|
||||
fieldType = lookupFieldType objType fieldName cls
|
||||
in TAST.InstVar checkedObj fieldName fieldType
|
||||
|
||||
-- unary operators
|
||||
typeCheckExpr (AST.Unary op expr) symtbl cls =
|
||||
let checkedExpr = typeCheckExpr expr symtbl cls
|
||||
exprType = getTypeFromTypedExpr checkedExpr
|
||||
in case op of
|
||||
AST.Not -> if exprType == "boolean"
|
||||
then TAST.Unary AST.Not checkedExpr "boolean"
|
||||
else error "Operator ! requires boolean operand"
|
||||
AST.Negate -> if exprType == "int"
|
||||
then TAST.Unary AST.Negate checkedExpr "int"
|
||||
else error "Unary - requires integer operand"
|
||||
|
||||
-- binary operators
|
||||
typeCheckExpr (AST.Binary op left right) symtbl cls =
|
||||
let checkedLeft = typeCheckExpr left symtbl cls
|
||||
checkedRight = typeCheckExpr right symtbl cls
|
||||
leftType = getTypeFromTypedExpr checkedLeft
|
||||
rightType = getTypeFromTypedExpr checkedRight
|
||||
in case op of
|
||||
AST.Add -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Subtract -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Multiply -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Divide -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Modulo -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.CompLessThan -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompLessOrEqual -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompGreaterThan -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompGreaterOrEqual -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompEqual -> checkEquality leftType rightType checkedLeft checkedRight op
|
||||
AST.CompNotEqual -> checkEquality leftType rightType checkedLeft checkedRight op
|
||||
AST.BitAnd -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.BitOr -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.BitXor -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.And -> checkBoolOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Or -> checkBoolOp leftType rightType checkedLeft checkedRight op
|
||||
|
||||
-- character literals
|
||||
typeCheckExpr (AST.Char value) _ _ =
|
||||
TAST.Char value "char"
|
||||
|
||||
-- null literal
|
||||
typeCheckExpr AST.Null _ _ =
|
||||
TAST.Null "null"
|
||||
|
||||
-- statement expression
|
||||
typeCheckExpr (AST.StmtExprExpr stmtExpr) symtbl cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtbl cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprExpr checkedStmtExpr typ
|
||||
|
||||
-- Helper methods
|
||||
checkIntOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkIntOp leftType rightType left right op =
|
||||
if leftType == "int" && rightType == "int"
|
||||
then TAST.Binary op left right "int"
|
||||
else error "Integer binary operator requires int operands"
|
||||
|
||||
checkBoolOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkBoolOp leftType rightType left right op =
|
||||
if leftType == "boolean" && rightType == "boolean"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error "Boolean binary operator requires boolean operands"
|
||||
|
||||
checkIntComparison :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkIntComparison leftType rightType left right op =
|
||||
if leftType == "int" && rightType == "int"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error "Comparison operator requires int operands"
|
||||
|
||||
checkEquality :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkEquality leftType rightType left right op =
|
||||
if leftType == rightType || leftType == "null" || rightType == "null"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error $ "Equality operator: cannot compare " ++ leftType ++ " with " ++ rightType
|
||||
|
||||
upperBound :: Type -> Type -> Type
|
||||
upperBound t1 t2
|
||||
| t1 == t2 = t1
|
||||
| otherwise = "Object"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
func Main.main : int
|
||||
entry:
|
||||
t0 = 0
|
||||
return t0
|
||||
@@ -1,11 +0,0 @@
|
||||
[
|
||||
Class "Main" []
|
||||
[
|
||||
Method "int" "main" []
|
||||
(Block
|
||||
[
|
||||
Return (Integer 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
class Main {
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[
|
||||
Class "Main" []
|
||||
[
|
||||
Method "int" "main" []
|
||||
(TypedStmt
|
||||
(Block
|
||||
[
|
||||
TypedStmt
|
||||
(Return (Integer 0))
|
||||
"int"
|
||||
]
|
||||
)
|
||||
"int"
|
||||
)
|
||||
]
|
||||
]
|
||||
Reference in New Issue
Block a user