Compare commits

..

3 Commits

Author SHA1 Message Date
ab7077d8f0 add public private to typecheck 2024-07-04 16:59:52 +02:00
53061fb73d add public private to parser 2024-07-04 16:59:37 +02:00
bac2a534b6 adjust AST for Modifier 2024-07-04 16:03:38 +02:00
8 changed files with 128 additions and 172 deletions

Binary file not shown.

View File

@ -16,39 +16,6 @@
filecolor=magenta,
urlcolor=cyan,
}
%for code listings
\usepackage{listings}
\usepackage{xcolor}
\definecolor{ListingBackground}{HTML}{F8F8F8}
\lstdefinestyle{mystyle}{
language=Java, % default language
numbers=left, % position of line numbers (left, right)
stepnumber=1, % set number to each line
numbersep=5pt, % 5pt between number and source code
numberstyle=\tiny, % letter size of numbers
breaklines=true, % break lines if necessary (true, false)
breakautoindent=true, % indenting after break line (true, false)
postbreak=\space, % break line after space
tabsize=2, % tabulator size
basicstyle=\ttfamily\footnotesize, % font style
showspaces=false, % show space (true, false)
extendedchars=true, % show all Latin1 characters (true, false)
captionpos=b, % sets the caption-position to bottom
backgroundcolor=\color{ListingBackground}, % source code background
xleftmargin=10pt, % margin left
xrightmargin=5pt, % margin right
frame=single, % border settings
frameround=ffff,
rulecolor=\color{darkgray}, % border color
fillcolor=\color{ListingBackground},
aboveskip=20pt,
keywordstyle=\color[rgb]{0.133,0.133,0.6},
commentstyle=\color[rgb]{0.133,0.545,0.133},
stringstyle=\color[rgb]{0.627,0.126,0.941}
}
\lstset{style=mystyle}
\let\clearpage\relax
\begin{document}

View File

@ -15,7 +15,7 @@
\item Binar-Operationen (\texttt{\&, |, \^})
\item Pre/Post-Inkrement \& Dekrement
\item Kontrollflussstrukturen:
\begin{itemize}[noitemsep]
\begin{itemize}
\item If/Else
\item While
\item For
@ -31,5 +31,4 @@
\item Deklaration und Zuweisung in einer Anweisung
\item Beliebig verschachtelte Blöcke
\item Überladung von Methoden \& Konstruktoren
\item Parameter mit Standardwerten
\end{itemize}

View File

@ -7,14 +7,13 @@ Die komplexeren Tokens haben Unittests, welche mit dem Testframework HUnit gesch
\subsection{Parser}
Der Parser wurde mit dem Happy tool implementiert. Er baut aus einer Liste von Tokens einen ungetypten AST. Wir haben bereits eine Grammatik bekommen und mussten für die einzelnen Regeln noch die Rückgabewerte angeben.
Der Parser wurde mit dem Happy tool implementiert. Er baut aus einer Liste von Tokens einen ungetypten AST. Wir haben bereits eine Grammatik bekommen und mussten diese noch in den AST umwandeln.
Um den Parser aufzubauen wurde zuerst ein Großteil der Grammatik auskommentiert und Stück für Stück wurden die Rückgabewerte hinzugefügt. Immer wenn ein neues Feature umgesetzt wurde, wurde dafür ein weiterer Unit Test geschrieben. Es gibt also für jede komplexe Ableitungsregel mindestens einen Unittest.
Um den Parser aufzubauen wurde zuerst ein Großteil der Grammatik auskommentiert und Stück für Stück wurden die Umwandlungen hinzugefügt. Immer wenn ein neues Feature umgesetzt wurde, wurde dafür ein weiterer Unit Test geschrieben. Es gibt also für jede komplexe Ableitungsregel mindestens einen Unittest.
\subsubsection{Klassenaufbau}
Als erstes wurden leere Konstruktoren Methoden und Felder umgesetzt. Da in Java Konstruktoren, Methoden und Felder durcheinander vorkommen können geben die Ableitungsregeln einen Datentyp namens MemberDeclaration zurück, welcher eines von den drei enthalten kann. Die \verb|classbodydeclarations| Regel baut dann einen 3-Tupel mit einer Liste aus Konstruktoren, einer aus Methoden und einer aus Feldern. Über Pattern Matching werden diese Listen dann erweitert und in der darüberliegenden Regel schließlich extrahiert.
Als erstes wurden leere Konstruktoren Methoden und Felder umgesetzt. Da in Java Konstruktoren, Methoden und Felder durcheinander vorkommen können geben die Ableitungsregeln einen Datentyp namens `MemberDeclaration` zurück. Die classbodydeclarations Regel baut dann einen 3-Tupel mit einer Liste aus Konstruktoren, einer aus Methoden und einer aus Feldern. Über pattern matching werden diese Listen dann erweitert und in der darüberliegenden Regel schließlich extrahiert.
\pagebreak
Bei folgender Klasse:
\begin{lstlisting}[language=Java]
class TestClass {
@ -42,13 +41,12 @@ Class "TestClass"
\end{lstlisting}
Das Nothing ist in diesem Fall ein Platzhalter für eine Zuweisung, da unser Compiler auch Zuweisung bei der Felddeklaration unterstützt.
In Java ist es möglich mehrere Variablen in einer Zeile zu deklarieren (Bsp.: `int x, y;`). Beim Parsen ergiebt sich dann die Schwierigkeit, dass man in dem Moment, wo man die Variable parst nicht weiß welchen Datentyp diese hat. Aus diesem Grund gibt es den Datentyp Declarator, welcher nur den Identifier und eventuell eine Zuweisung enthält. In den darüberliegenden Regeln fielddeclaration und localvariabledeclaration wird dann die Typinformation hinzugefügt mithilfe der Funktion convertDeclarator.
\subsubsection{Syntactic Sugar}
In Java ist es möglich mehrere Variablen in einer Zeile zu deklarieren (Bsp.: \verb|int x, y;|). Beim Parsen ergibt sich dann die Schwierigkeit, dass man in dem Moment, in dem man die Variable parst, nicht weiß welchen Datentyp diese hat. Aus diesem Grund gibt es den Datentyp Declarator, welcher nur den Identifier und eventuell eine Zuweisung enthält. In den darüberliegenden Regeln \verb|fielddeclaration| und \verb|localvariabledeclaration| wird dann die Typinformation hinzugefügt mithilfe der Funktion \verb|convertDeclarator|.
Für die Zuweisung wird auch die Kombination mit Rechenoperatoren unterstützt. Das ganze ist durch Syntactic Sugar im Parser umgesetzt. Wenn es einen Zuweisungsoperator gibt, dann wird der Ausdruck in eine Zuweisung und Rechnung aufgeteilt. Bsp.: \verb|x += 3;| wird umgewandelt in \verb|x = x + 3|.
For-Schleifen wurden auch rein im Parser durch Syntactic Sugar implementiert. Eine For-Schleife wird dabei in eine While-Schleife umgewandelt. Dafür wird zuerst ein Block erstellt, sodass die deklarierten Variablen auch nur für den Bereich der Schleife gültig sind. Die Bedingung der For-Schleife kann in die While-Schleife übernommen werden. Innerhalb der While-Schleife folgen zuerst die Statements, die im Block der For-Schleife waren und danach die Update-Statements.
For-Schleifen wurde auch rein im Parser durch Syntactic Sugar implementiert. Eine For-Schleife wird dabei in eine While-Schleife umgewandelt. Dafür wird zuerst ein Block erstellt, sodass die deklarierten Variablen auch nur für den Bereich der Schleife gültig sind. Die Bedingung der For-Schleife kann in die While-Schleife übernommen werden. Innerhalb der While-Schleife folgen zuerst die Statements, die im Block der For-Schleife waren und danach die Update-Statements.
\begin{lstlisting}[language=Java]
for (int i = 0; i < 9; i++) {
@ -65,24 +63,3 @@ wird umgewandelt in:
}
}
\end{lstlisting}
Es wurden auch Parameter mit Standardwerten im Parser implementiert. Dieses Feature ist in der aktuellen Java Version (Java 22) noch nicht implementiert. Der Parser macht sich dafür das Überladen von Methoden zunutze. Er generiert für jedes Parameter mit Standardwert eine weitere Funktion, welche die ursprüngliche Funktion mit einem Standardwert aufruft.
%\lstinputlisting[language=Java,firstline=7,lastline=9]{../Test/JavaSources/TestOptionalParameter.java}
\begin{lstlisting}[language=Java]
int normalAndOptional(int a, int b = 2, int c = 3) {
return a + b + c;
}
\end{lstlisting}
wird umgewandelt in:
\begin{lstlisting}
int normalAndOptional(int a) {
return normalAndOptional(a, 2);
}
int normalAndOptional(int a, int b) {
return normalAndOptional(a, b, 3);
}
int normalAndOptional(int a, int b, int c) {
return a + b + c;
}
\end{lstlisting}

View File

@ -3,12 +3,13 @@ module Ast where
type CompilationUnit = [Class]
type DataType = String
type Identifier = String
type Modifier = String
data ParameterDeclaration = ParameterDeclaration DataType Identifier deriving (Show, Eq)
data VariableDeclaration = VariableDeclaration DataType Identifier (Maybe Expression) deriving (Show, Eq)
data Class = Class DataType [ConstructorDeclaration] [MethodDeclaration] [VariableDeclaration] deriving (Show, Eq)
data MethodDeclaration = MethodDeclaration DataType Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
data ConstructorDeclaration = ConstructorDeclaration Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
data VariableDeclaration = VariableDeclaration DataType Modifier Identifier (Maybe Expression) deriving (Show, Eq)
data Class = Class DataType Modifier [ConstructorDeclaration] [MethodDeclaration] [VariableDeclaration] deriving (Show, Eq)
data MethodDeclaration = MethodDeclaration DataType Modifier Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
data ConstructorDeclaration = ConstructorDeclaration Modifier Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
data Statement
= If Expression Statement (Maybe Statement)

View File

@ -252,11 +252,10 @@ assembleStatement (constants, ops, lvars) (TypedStatement _ (LocalVariableDeclar
in
(constants_init, ops_init ++ storeLocal, lvars ++ [name])
assembleStatement (constants, ops, lvars) (TypedStatement dtype (StatementExpressionStatement expr)) = let
assembleStatement (constants, ops, lvars) (TypedStatement _ (StatementExpressionStatement expr)) = let
(constants_e, ops_e, lvars_e) = assembleStatementExpression (constants, ops, lvars) expr
in case dtype of
"void" -> (constants_e, ops_e, lvars_e)
_ -> (constants_e, ops_e ++ [Oppop], lvars_e)
in
(constants_e, ops_e ++ [Oppop], lvars_e)
assembleStatement _ stmt = error ("Unknown statement: " ++ show stmt)

View File

@ -93,14 +93,14 @@ qualifiedname : name DOT IDENTIFIER { BinaryOperation NameResolution $1 (Ref
simplename : IDENTIFIER { $1 }
classdeclaration : CLASS IDENTIFIER classbody { case $3 of (constructors, methods, fields) -> Class $2 constructors methods fields }
| modifiers CLASS IDENTIFIER classbody { case $4 of (constructors, methods, fields) -> Class $3 constructors methods fields }
classdeclaration : CLASS IDENTIFIER classbody { case $3 of (constructors, methods, fields) -> Class $2 "public" constructors methods fields }
| modifiers CLASS IDENTIFIER classbody { case $4 of (constructors, methods, fields) -> Class $3 (head $1) constructors methods fields }
classbody : LBRACKET RBRACKET { ([], [], []) }
| LBRACKET classbodydeclarations RBRACKET { $2 }
modifiers : modifier { }
| modifiers modifier { }
modifiers : modifier { [$1] }
| modifiers modifier { $1 ++ [$2] }
classbodydeclarations : classbodydeclaration {
case $1 of
@ -115,11 +115,11 @@ classbodydeclarations : classbodydeclaration {
((constructors, methods, fields), FieldDecls newFields) -> (constructors, methods, (fields ++ newFields))
}
modifier : PUBLIC { }
| PROTECTED { }
| PRIVATE { }
| STATIC { }
| ABSTRACT { }
modifier : PUBLIC { "public" }
| PROTECTED { "protected" }
| PRIVATE { "private" }
| STATIC { "static" }
| ABSTRACT { "abstract" }
classtype : classorinterfacetype { $1 }
@ -131,13 +131,13 @@ classorinterfacetype : simplename { $1 }
classmemberdeclaration : fielddeclaration { $1 }
| methoddeclaration { $1 }
constructordeclaration : constructordeclarator constructorbody { case $1 of (identifier, parameters) -> ConstructorDecl $ ConstructorDeclaration identifier parameters $2 }
| modifiers constructordeclarator constructorbody { case $2 of (identifier, parameters) -> ConstructorDecl $ ConstructorDeclaration identifier parameters $3 }
constructordeclaration : constructordeclarator constructorbody { case $1 of (identifier, parameters) -> ConstructorDecl $ ConstructorDeclaration "public" identifier parameters $2 }
| modifiers constructordeclarator constructorbody { case $2 of (identifier, parameters) -> ConstructorDecl $ ConstructorDeclaration (head $1) identifier parameters $3 }
fielddeclaration : type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator $1) $2 }
| modifiers type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator $2) $3 }
fielddeclaration : type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator "public" $1) $2 }
| modifiers type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator (head $1) $2) $3 }
methoddeclaration : methodheader methodbody { case $1 of (returnType, (name, (parameters, optionalparameters))) -> MethodDecl (MethodDeclarationWithOptionals returnType name parameters optionalparameters $2) }
methoddeclaration : methodheader methodbody { case $1 of (returnType, modifier, (name, (parameters, optionalparameters))) -> MethodDecl (MethodDeclarationWithOptionals returnType modifier name parameters optionalparameters $2) }
block : LBRACKET RBRACKET { Block [] }
| LBRACKET blockstatements RBRACKET { Block $2 }
@ -150,10 +150,10 @@ constructorbody : LBRACKET RBRACKET { Block [] }
| LBRACKET blockstatements RBRACKET { Block $2 }
-- | LBRACKET explicitconstructorinvocation blockstatements RBRACKET { }
methodheader : type methoddeclarator { ($1, $2) }
| modifiers type methoddeclarator { ($2, $3) }
| VOID methoddeclarator { ("void", $2) }
| modifiers VOID methoddeclarator { ("void", $3)}
methodheader : type methoddeclarator { ($1, "public", $2) }
| modifiers type methoddeclarator { ($2, head $1, $3) }
| VOID methoddeclarator { ("void", "public", $2) }
| modifiers VOID methoddeclarator { ("void", head $1, $3) }
type : primitivetype { $1 }
| referencetype { $1 }
@ -165,7 +165,7 @@ methodbody : block { $1 }
| SEMICOLON { Block [] }
blockstatements : blockstatement { $1 }
| blockstatements blockstatement { $1 ++ $2}
| blockstatements blockstatement { $1 ++ $2 }
formalandoptionalparameterlist : formalparameterlist { ($1, []) }
| formalparameterlist COMMA optionalparameterlist { ($1, $3) }
@ -193,7 +193,6 @@ primitivetype : BOOLEAN { "boolean" }
referencetype : classorinterfacetype { $1 }
variabledeclarator : variabledeclaratorid { Declarator $1 Nothing }
| variabledeclaratorid ASSIGN variableinitializer { Declarator $1 (Just $3) }
@ -225,7 +224,8 @@ expression : assignmentexpression { $1 }
integraltype : INT { "int" }
| CHAR { "char" }
localvariabledeclaration : type variabledeclarators { map LocalVariableDeclaration $ map (convertDeclarator $1) $2 }
localvariabledeclaration : type variabledeclarators { map (LocalVariableDeclaration . convertDeclarator "public" $1) $2 }
| modifiers type variabledeclarators { map (LocalVariableDeclaration . convertDeclarator (unwords $1) $2) $3 }
statementwithouttrailingsubstatement : block { [$1] }
| emptystatement { [] }
@ -402,28 +402,33 @@ data MemberDeclaration = MethodDecl MethodDeclarationWithOptionals
data Declarator = Declarator Identifier (Maybe Expression)
convertDeclarator :: DataType -> Declarator -> VariableDeclaration
convertDeclarator dataType (Declarator id assigment) = VariableDeclaration dataType id assigment
convertDeclarator :: Modifier -> DataType -> Declarator -> VariableDeclaration
convertDeclarator modifier dataType (Declarator id assignment) = VariableDeclaration dataType modifier id assignment
extractFunctionName :: Expression -> (Expression, Identifier)
extractFunctionName (BinaryOperation NameResolution exp (Reference functionname)) = (exp, functionname)
extractFunctionName (Reference functionname) = ((Reference "this"), functionname)
data OptionalParameter = OptionalParameter DataType Identifier Expression deriving (Show)
data MethodDeclarationWithOptionals = MethodDeclarationWithOptionals DataType Identifier [ParameterDeclaration] [OptionalParameter] Statement deriving (Show)
data MethodDeclarationWithOptionals = MethodDeclarationWithOptionals DataType String Identifier [ParameterDeclaration] [OptionalParameter] Statement deriving (Show)
convertMethodDeclarationWithOptionals :: MethodDeclarationWithOptionals -> [MethodDeclaration]
convertMethodDeclarationWithOptionals (MethodDeclarationWithOptionals returnType id param [] stmt) = [MethodDeclaration returnType id param stmt]
convertMethodDeclarationWithOptionals (MethodDeclarationWithOptionals returnType id param (opt : optRest) stmt) = generateHelperMethod returnType id param opt : convertMethodDeclarationWithOptionals (generateBaseMethod returnType id param opt optRest stmt)
convertMethodDeclarationWithOptionals (MethodDeclarationWithOptionals returnType modifier id param [] stmt) = [MethodDeclaration returnType modifier id param stmt]
convertMethodDeclarationWithOptionals (MethodDeclarationWithOptionals returnType modifier id param (opt : optRest) stmt) = generateHelperMethod returnType modifier id param opt : convertMethodDeclarationWithOptionals (generateBaseMethod returnType modifier id param opt optRest stmt)
convertOptionalParameter :: OptionalParameter -> ParameterDeclaration
convertOptionalParameter (OptionalParameter dtype id exp) = ParameterDeclaration dtype id
generateHelperMethod :: DataType -> Identifier -> [ParameterDeclaration] -> OptionalParameter -> MethodDeclaration
generateHelperMethod returnType methodName params (OptionalParameter dtype id exp) =
let references = ((map (\(ParameterDeclaration paramType ident) -> (Reference ident)) params) ++ [exp])
methodcall = (MethodCall (Reference "this") methodName references)
lastStatement = if returnType == "void" then StatementExpressionStatement methodcall else Return $ Just $ StatementExpressionExpression methodcall
in MethodDeclaration returnType methodName params $ Block [lastStatement]
generateBaseMethod :: DataType -> Identifier -> [ParameterDeclaration] -> OptionalParameter -> [OptionalParameter] -> Statement -> MethodDeclarationWithOptionals
generateBaseMethod returnType methodName params (OptionalParameter dtype id exp) optRest stmt = MethodDeclarationWithOptionals returnType methodName (params ++ [ParameterDeclaration dtype id]) optRest stmt
generateHelperMethod :: DataType -> Modifier -> Identifier -> [ParameterDeclaration] -> OptionalParameter -> MethodDeclaration
generateHelperMethod returnType modifier methodName params (OptionalParameter dtype id exp) =
let references = ((map (\(ParameterDeclaration paramType ident) -> (Reference ident)) params) ++ [exp])
methodcall = (MethodCall (Reference "this") methodName references)
lastStatement = if returnType == "void" then StatementExpressionStatement methodcall else Return $ Just $ StatementExpressionExpression methodcall
in MethodDeclaration returnType modifier methodName params $ Block [lastStatement]
generateBaseMethod :: DataType -> Modifier -> Identifier -> [ParameterDeclaration] -> OptionalParameter -> [OptionalParameter] -> Statement -> MethodDeclarationWithOptionals
generateBaseMethod returnType modifier methodName params (OptionalParameter dtype id exp) optRest stmt = MethodDeclarationWithOptionals returnType modifier methodName (params ++ [ParameterDeclaration dtype id]) optRest stmt
parseError :: ([Token], [String]) -> a
parseError (errortoken, expected) = error ("parse error on token: " ++ show errortoken ++ "\nexpected one of: " ++ show expected)

View File

@ -1,19 +1,19 @@
module Typecheck where
import Data.List (find)
import Data.Maybe
import Ast
typeCheckCompilationUnit :: CompilationUnit -> CompilationUnit
typeCheckCompilationUnit classes =
let
-- Helper function to add a default constructor if none are present
ensureDefaultConstructor :: Class -> Class
ensureDefaultConstructor (Class className constructors methods fields) =
ensureDefaultConstructor (Class className modifier constructors methods fields) =
let
defaultConstructor = ConstructorDeclaration className [] (Block [])
defaultConstructor = ConstructorDeclaration modifier className [] (Block [])
constructorsWithDefault = if null constructors then [defaultConstructor] else constructors
in Class className constructorsWithDefault methods fields
in Class className modifier constructorsWithDefault methods fields
-- Inject default constructors into all classes
classesWithDefaultConstructors = map ensureDefaultConstructor classes
@ -21,7 +21,7 @@ typeCheckCompilationUnit classes =
in map (`typeCheckClass` classesWithDefaultConstructors) classesWithDefaultConstructors
typeCheckClass :: Class -> [Class] -> Class
typeCheckClass (Class className constructors methods fields) classes =
typeCheckClass (Class className modifier constructors methods fields) classes =
let
-- Fields and methods dont need to be added to the symtab because they are looked upon automatically under "this"
-- if its not a declared local variable. Also shadowing wouldnt be possible then.
@ -29,10 +29,10 @@ typeCheckClass (Class className constructors methods fields) classes =
checkedConstructors = map (\constructor -> typeCheckConstructorDeclaration constructor initalSymTab classes) constructors
checkedMethods = map (\method -> typeCheckMethodDeclaration method initalSymTab classes) methods
checkedFields = map (\field -> typeCheckVariableDeclaration field initalSymTab classes) fields
in Class className checkedConstructors checkedMethods checkedFields
in Class className modifier checkedConstructors checkedMethods checkedFields
typeCheckConstructorDeclaration :: ConstructorDeclaration -> [(Identifier, DataType)] -> [Class] -> ConstructorDeclaration
typeCheckConstructorDeclaration (ConstructorDeclaration name params body) symtab classes =
typeCheckConstructorDeclaration (ConstructorDeclaration modifier name params body) symtab classes =
let
constructorParams = [(identifier, dataType) | ParameterDeclaration dataType identifier <- params]
initialSymtab = symtab ++ constructorParams
@ -41,24 +41,23 @@ typeCheckConstructorDeclaration (ConstructorDeclaration name params body) symtab
bodyType = getTypeFromStmt checkedBody
in if name == className
then if bodyType == "void"
then ConstructorDeclaration name params checkedBody
then ConstructorDeclaration modifier name params checkedBody
else error $ "Constructor Declaration: Return type mismatch in constructor " ++ name ++ ": expected void, found " ++ bodyType
else error $ "Constructor Declaration: Constructor name " ++ name ++ " does not match class name " ++ className
typeCheckMethodDeclaration :: MethodDeclaration -> [(Identifier, DataType)] -> [Class] -> MethodDeclaration
typeCheckMethodDeclaration (MethodDeclaration retType name params body) symtab classes =
typeCheckMethodDeclaration (MethodDeclaration retType modifier name params body) symtab classes =
let
methodParams = [(identifier, dataType) | ParameterDeclaration dataType identifier <- params]
initialSymtab = ("thisMeth", retType) : symtab ++ methodParams
checkedBody = typeCheckStatement body initialSymtab classes
bodyType = getTypeFromStmt checkedBody
in if bodyType == retType || (bodyType == "void" && retType == "void") || (bodyType == "null" && isObjectType retType) || isSubtype bodyType retType classes
then MethodDeclaration retType name params checkedBody
then MethodDeclaration retType modifier name params checkedBody
else error $ "Method Declaration: Return type mismatch in method " ++ name ++ ": expected " ++ retType ++ ", found " ++ bodyType
typeCheckVariableDeclaration :: VariableDeclaration -> [(Identifier, DataType)] -> [Class] -> VariableDeclaration
typeCheckVariableDeclaration (VariableDeclaration dataType identifier maybeExpr) symtab classes =
typeCheckVariableDeclaration (VariableDeclaration dataType modifier identifier maybeExpr) symtab classes =
let
-- Ensure the type is valid (either a primitive type or a valid class name)
validType = dataType `elem` ["int", "boolean", "char"] || isUserDefinedClass dataType classes
@ -74,11 +73,10 @@ typeCheckVariableDeclaration (VariableDeclaration dataType identifier maybeExpr)
(False, _, _) -> error $ "Type '" ++ dataType ++ "' is not a valid type for variable '" ++ identifier ++ "'"
(_, True, _) -> error $ "Variable '" ++ identifier ++ "' is redefined in the same scope"
(_, _, Just t)
| t == "null" && isObjectType dataType -> VariableDeclaration dataType identifier checkedExprWithType
| t == "null" && isObjectType dataType -> VariableDeclaration dataType modifier identifier checkedExprWithType
| t /= dataType -> error $ "Type mismatch in declaration of '" ++ identifier ++ "': expected " ++ dataType ++ ", found " ++ t
| otherwise -> VariableDeclaration dataType identifier checkedExprWithType
(_, _, Nothing) -> VariableDeclaration dataType identifier checkedExprWithType
| otherwise -> VariableDeclaration dataType modifier identifier checkedExprWithType
(_, _, Nothing) -> VariableDeclaration dataType modifier identifier checkedExprWithType
-- ********************************** Type Checking: Expressions **********************************
@ -93,10 +91,10 @@ typeCheckExpression (Reference id) symtab classes =
Nothing ->
case lookup "this" symtab of
Just className ->
let classDetails = find (\(Class name _ _ _) -> name == className) classes
let classDetails = find (\(Class name _ _ _ _) -> name == className) classes
in case classDetails of
Just (Class _ _ _ fields) ->
let fieldTypes = [dt | VariableDeclaration dt fieldId _ <- fields, fieldId == id]
Just (Class _ _ _ _ fields) ->
let fieldTypes = [dt | VariableDeclaration dt modifier fieldId _ <- fields, fieldId == id]
-- this case only happens when its a field of its own class so the implicit this will be converted to explicit this
in case fieldTypes of
[fieldType] -> TypedExpression fieldType (BinaryOperation NameResolution (TypedExpression className (LocalVariable "this")) (TypedExpression fieldType (FieldVariable id)))
@ -167,35 +165,36 @@ typeCheckStatementExpression (Assignment ref expr) symtab classes =
else
error $ "Type mismatch in assignment to variable: expected " ++ type'' ++ ", found " ++ typeToAssign
typeCheckStatementExpression (ConstructorCall className args) symtab classes =
case find (\(Class name _ _ _) -> name == className) classes of
case find (\(Class name _ _ _ _) -> name == className) classes of
Nothing -> error $ "Class '" ++ className ++ "' not found."
Just (Class _ constructors _ _) ->
let
matchParams (ParameterDeclaration paramType _) arg =
let argTyped = typeCheckExpression arg symtab classes
argType = getTypeFromExpr argTyped
in if argType == "null" && isObjectType paramType
then Just (TypedExpression paramType NullLiteral)
else if argType == paramType
then Just argTyped
else Nothing
Just (Class _ modifier constructors _ _) ->
let matchParams (ParameterDeclaration paramType _) arg =
let argTyped = typeCheckExpression arg symtab classes
argType = getTypeFromExpr argTyped
in if argType == "null" && isObjectType paramType
then Just (TypedExpression paramType NullLiteral)
else if argType == paramType
then Just argTyped
else Nothing
matchConstructor (ConstructorDeclaration name params _) =
let matchedArgs = sequence $ zipWith matchParams params args
in fmap (\checkedArgs -> (params, checkedArgs)) matchedArgs
matchConstructor (ConstructorDeclaration constructorModifier name params _)
| constructorModifier == "public" = fmap (\checkedArgs -> (ConstructorDeclaration constructorModifier name params (Block []), checkedArgs)) (sequence $ zipWith matchParams params args)
| constructorModifier == "private" = if checkAccess className (lookup "this" symtab)
then fmap (\checkedArgs -> (ConstructorDeclaration constructorModifier name params (Block []), checkedArgs)) (sequence $ zipWith matchParams params args)
else Nothing
| otherwise = Nothing
validConstructors = filter (\(params, _) -> length params == length args) $ mapMaybe matchConstructor constructors
validConstructors = filter (\(ConstructorDeclaration _ _ params _, _) -> length params == length args) $ mapMaybe matchConstructor constructors
expectedSignatures = [ map (\(ParameterDeclaration t _) -> t) params | ConstructorDeclaration _ params _ <- constructors ]
actualSignature = map (\arg -> getTypeFromExpr (typeCheckExpression arg symtab classes)) args
mismatchDetails = "Constructor not found for class '" ++ className ++ "' with given arguments.\n" ++
"Expected signatures:\n" ++ show expectedSignatures ++
"\nActual arguments:" ++ show actualSignature
expectedSignatures = [ map (\(ParameterDeclaration t _) -> t) params | ConstructorDeclaration _ _ params _ <- constructors ]
actualSignature = map (\arg -> getTypeFromExpr (typeCheckExpression arg symtab classes)) args
mismatchDetails = "Constructor not found for class '" ++ className ++ "' with given arguments.\n" ++
"Expected signatures:\n" ++ show expectedSignatures ++
"\nActual arguments:" ++ show actualSignature
in case validConstructors of
[(_, checkedArgs)] ->
[(ConstructorDeclaration _ _ params _, checkedArgs)] ->
TypedStatementExpression className (ConstructorCall className checkedArgs)
[] -> error mismatchDetails
_ -> error $ "Multiple matching constructors found for class '" ++ className ++ "' with given arguments."
@ -204,8 +203,8 @@ typeCheckStatementExpression (MethodCall expr methodName args) symtab classes =
let objExprTyped = typeCheckExpression expr symtab classes
in case objExprTyped of
TypedExpression objType _ ->
case find (\(Class className _ _ _) -> className == objType) classes of
Just (Class _ _ methods _) ->
case find (\(Class className _ _ _ _) -> className == objType) classes of
Just (Class className _ _ methods _) ->
let matchParams (ParameterDeclaration paramType _) arg =
let argTyped = typeCheckExpression arg symtab classes
argType = getTypeFromExpr argTyped
@ -215,27 +214,31 @@ typeCheckStatementExpression (MethodCall expr methodName args) symtab classes =
then Just argTyped
else Nothing
matchMethod (MethodDeclaration retType name params _) =
let matchedArgs = sequence $ zipWith matchParams params args
in fmap (\checkedArgs -> (MethodDeclaration retType name params (Block []), checkedArgs)) matchedArgs
matchMethod (MethodDeclaration retType modifier name params _)
| modifier == "public" = fmap (\checkedArgs -> (MethodDeclaration retType modifier name params (Block []), checkedArgs)) (sequence $ zipWith matchParams params args)
| modifier == "private" = if checkAccess className (lookup "this" symtab)
then fmap (\checkedArgs -> (MethodDeclaration retType modifier name params (Block []), checkedArgs)) (sequence $ zipWith matchParams params args)
else Nothing
| otherwise = Nothing
validMethods = filter (\(MethodDeclaration _ name params _, _) -> name == methodName && length params == length args) $ mapMaybe matchMethod methods
validMethods = filter (\(MethodDeclaration _ _ name params _, _) -> name == methodName && length params == length args) $ mapMaybe matchMethod methods
expectedSignatures = [ map (\(ParameterDeclaration t _) -> t) params | MethodDeclaration _ name params _ <- methods, name == methodName ]
expectedSignatures = [ map (\(ParameterDeclaration t _) -> t) params | MethodDeclaration _ _ name params _ <- methods, name == methodName ]
actualSignature = map (\arg -> getTypeFromExpr (typeCheckExpression arg symtab classes)) args
mismatchDetails = "Method not found for class '" ++ objType ++ "' with given arguments.\n" ++
"Expected signatures for method '" ++ methodName ++ "':\n" ++ unlines (map show expectedSignatures) ++
"Actual arguments:\n" ++ show actualSignature
in case validMethods of
[(MethodDeclaration retType _ params _, checkedArgs)] ->
[(MethodDeclaration retType _ _ params _, checkedArgs)] ->
TypedStatementExpression retType (MethodCall objExprTyped methodName checkedArgs)
[] -> error mismatchDetails
_ -> error $ "Multiple matching methods found for class '" ++ objType ++ "' and method '" ++ methodName ++ "' with given arguments."
Nothing -> error $ "Class for object type '" ++ objType ++ "' not found."
_ -> error "Invalid object type for method call. Object must have a class type."
typeCheckStatementExpression (PostIncrement expr) symtab classes =
let expr' = typeCheckExpression expr symtab classes
type' = getTypeFromExpr expr'
@ -292,8 +295,7 @@ typeCheckStatement (If cond thenStmt elseStmt) symtab classes =
then TypedStatement ifType (If cond' thenStmt' elseStmt')
else error "If condition must be of type boolean"
typeCheckStatement (LocalVariableDeclaration (VariableDeclaration dataType identifier maybeExpr)) symtab classes =
typeCheckStatement (LocalVariableDeclaration (VariableDeclaration dataType modifier identifier maybeExpr)) symtab classes =
-- Check for redefinition in the current scope
if any ((== identifier) . snd) symtab
then error $ "Variable '" ++ identifier ++ "' is redefined in the same scope"
@ -307,11 +309,10 @@ typeCheckStatement (LocalVariableDeclaration (VariableDeclaration dataType ident
in case exprType of
Just t
| t == "null" && isObjectType dataType ->
TypedStatement dataType (LocalVariableDeclaration (VariableDeclaration dataType identifier checkedExprWithType))
TypedStatement dataType (LocalVariableDeclaration (VariableDeclaration dataType modifier identifier checkedExprWithType))
| t /= dataType -> error $ "Type mismatch in declaration of '" ++ identifier ++ "': expected " ++ dataType ++ ", found " ++ t
| otherwise -> TypedStatement dataType (LocalVariableDeclaration (VariableDeclaration dataType identifier checkedExprWithType))
Nothing -> TypedStatement dataType (LocalVariableDeclaration (VariableDeclaration dataType identifier checkedExpr))
| otherwise -> TypedStatement dataType (LocalVariableDeclaration (VariableDeclaration dataType modifier identifier checkedExprWithType))
Nothing -> TypedStatement dataType (LocalVariableDeclaration (VariableDeclaration dataType modifier identifier checkedExpr))
typeCheckStatement (While cond stmt) symtab classes =
let cond' = typeCheckExpression cond symtab classes
@ -326,7 +327,7 @@ typeCheckStatement (Block statements) symtab classes =
let
processStatements (accSts, currentSymtab, types) stmt =
case stmt of
LocalVariableDeclaration (VariableDeclaration dataType identifier maybeExpr) ->
LocalVariableDeclaration (VariableDeclaration dataType modifier identifier maybeExpr) ->
let
alreadyDefined = any (\(id, _) -> id == identifier) currentSymtab
newSymtab = if alreadyDefined
@ -355,7 +356,6 @@ typeCheckStatement (Block statements) symtab classes =
in TypedStatement blockType (Block checkedStatements)
typeCheckStatement (Return expr) symtab classes =
let methodReturnType = fromMaybe (error "Method return type not found in symbol table") (lookup "thisMeth" symtab)
expr' = case expr of
@ -369,7 +369,6 @@ typeCheckStatement (Return expr) symtab classes =
then TypedStatement returnType (Return expr')
else error $ "Return: Return type mismatch: expected " ++ methodReturnType ++ ", found " ++ returnType
typeCheckStatement (StatementExpressionStatement stmtExpr) symtab classes =
let stmtExpr' = typeCheckStatementExpression stmtExpr symtab classes
in TypedStatement (getTypeFromStmtExpr stmtExpr') (StatementExpressionStatement stmtExpr')
@ -385,7 +384,7 @@ isSubtype subType superType classes
| otherwise = False
isUserDefinedClass :: DataType -> [Class] -> Bool
isUserDefinedClass dt classes = dt `elem` map (\(Class name _ _ _) -> name) classes
isUserDefinedClass dt classes = dt `elem` map (\(Class name _ _ _ _) -> name) classes
isObjectType :: DataType -> Bool
isObjectType dt = dt /= "int" && dt /= "boolean" && dt /= "char"
@ -446,7 +445,6 @@ checkEqualityOperation op expr1' expr2' type1 type2
error $ "Equality operation " ++ show op ++ " requires that null can only be compared with object types. Found types: " ++ type1 ++ " and " ++ type2
| otherwise = error $ "Equality operation " ++ show op ++ " encountered unexpected types: " ++ type1 ++ " and " ++ type2
checkLogicalOperation :: BinaryOperator -> Expression -> Expression -> DataType -> DataType -> Expression
checkLogicalOperation op expr1' expr2' type1 type2
| type1 == "boolean" && type2 == "boolean" =
@ -457,12 +455,22 @@ resolveNameResolution :: Expression -> Expression -> [(Identifier, DataType)] ->
resolveNameResolution expr1' (Reference ident2) symtab classes =
case getTypeFromExpr expr1' of
objType ->
case find (\(Class className _ _ _) -> className == objType) classes of
Just (Class _ _ _ fields) ->
let fieldTypes = [dt | VariableDeclaration dt id _ <- fields, id == ident2]
in case fieldTypes of
[resolvedType] -> TypedExpression resolvedType (BinaryOperation NameResolution expr1' (TypedExpression resolvedType (FieldVariable ident2)))
case find (\(Class className _ _ _ _) -> className == objType) classes of
Just (Class _ _ _ _ fields) ->
let fieldDetails = [(dt, mod) | VariableDeclaration dt mod id _ <- fields, id == ident2]
className = objType
in case fieldDetails of
[(resolvedType, mod)] ->
if mod == "public" || (mod == "private" && checkAccess objType (lookup "this" symtab))
then TypedExpression resolvedType (BinaryOperation NameResolution expr1' (TypedExpression resolvedType (FieldVariable ident2)))
else error $ "Field '" ++ ident2 ++ "' is private and cannot be accessed outside its class."
[] -> error $ "Field '" ++ ident2 ++ "' not found in class '" ++ objType ++ "'"
_ -> error $ "Ambiguous reference to field '" ++ ident2 ++ "' in class '" ++ objType ++ "'"
Nothing -> error $ "Class '" ++ objType ++ "' not found"
resolveNameResolution _ _ _ _ = error "Name resolution requires object reference and field name"
checkAccess :: DataType -> Maybe DataType -> Bool
checkAccess objType (Just thisClass) = objType == thisClass
checkAccess _ Nothing = False