diff --git a/src/main/java/ast/expressions/unaryexpressions/MemberAccessNode.java b/src/main/java/ast/expressions/unaryexpressions/MemberAccessNode.java index aa85286..e171f79 100644 --- a/src/main/java/ast/expressions/unaryexpressions/MemberAccessNode.java +++ b/src/main/java/ast/expressions/unaryexpressions/MemberAccessNode.java @@ -1,11 +1,14 @@ package ast.expressions.unaryexpressions; import ast.ASTNode; +import semantic.SemanticVisitor; +import typechecker.TypeCheckResult; +import visitor.Visitable; import java.util.ArrayList; import java.util.List; -public class MemberAccessNode implements ASTNode { +public class MemberAccessNode implements ASTNode, Visitable { public Boolean thisExpr; public List identifiers = new ArrayList<>(); @@ -17,4 +20,9 @@ public class MemberAccessNode implements ASTNode { identifiers.add(identifier); } + @Override + public TypeCheckResult accept(SemanticVisitor visitor) { + return visitor.analyze(this); + } + } diff --git a/src/main/java/ast/statementexpressions/methodcallstatementnexpressions/TargetNode.java b/src/main/java/ast/statementexpressions/methodcallstatementnexpressions/TargetNode.java index 01e2344..01a1d78 100644 --- a/src/main/java/ast/statementexpressions/methodcallstatementnexpressions/TargetNode.java +++ b/src/main/java/ast/statementexpressions/methodcallstatementnexpressions/TargetNode.java @@ -3,8 +3,11 @@ package ast.statementexpressions.methodcallstatementnexpressions; import ast.ASTNode; import ast.expressions.unaryexpressions.MemberAccessNode; import ast.statementexpressions.NewDeclarationNode; +import semantic.SemanticVisitor; +import typechecker.TypeCheckResult; +import visitor.Visitable; -public class TargetNode implements ASTNode { +public class TargetNode implements ASTNode, Visitable { public Boolean thisTar; public MemberAccessNode memberAccess; public NewDeclarationNode newDeclaration; @@ -25,4 +28,11 @@ public class TargetNode implements ASTNode { public TargetNode(String identifier) { this.identifier = identifier; } + + @Override + public TypeCheckResult accept(SemanticVisitor visitor) { + return visitor.analyze(this); + } + + } \ No newline at end of file diff --git a/src/main/java/semantic/SemanticAnalyzer.java b/src/main/java/semantic/SemanticAnalyzer.java index 6bdd578..2e7e496 100644 --- a/src/main/java/semantic/SemanticAnalyzer.java +++ b/src/main/java/semantic/SemanticAnalyzer.java @@ -9,6 +9,7 @@ import java.util.Objects; import ast.*; import ast.expressions.IExpressionNode; import ast.expressions.binaryexpressions.*; +import ast.expressions.unaryexpressions.MemberAccessNode; import ast.expressions.unaryexpressions.UnaryNode; import ast.members.ConstructorNode; import ast.members.FieldNode; @@ -21,7 +22,9 @@ import ast.statementexpressions.NewDeclarationNode; import ast.statementexpressions.crementexpressions.DecrementNode; import ast.statementexpressions.crementexpressions.IncrementNode; import ast.statementexpressions.methodcallstatementnexpressions.MethodCallNode; +import ast.statementexpressions.methodcallstatementnexpressions.TargetNode; import ast.statements.*; +import ast.type.EnumAccessModifierNode; import ast.type.type.*; import semantic.context.Context; import semantic.exceptions.*; @@ -150,7 +153,7 @@ public class SemanticAnalyzer implements SemanticVisitor { if (resultType == null) { resultType = new BaseType(TypeEnum.VOID); } - if(methodNode.getType() == null){ + if (methodNode.getType() == null) { methodNode.setType(new BaseType(TypeEnum.VOID)); } if (!resultType.equals(methodNode.getType())) { @@ -185,7 +188,7 @@ public class SemanticAnalyzer implements SemanticVisitor { if (toCheck.expression != null) { var result = toCheck.expression.accept(this); return new TypeCheckResult(true, result.getType()); - } else if(toCheck.voidReturn){ + } else if (toCheck.voidReturn) { return new TypeCheckResult(false, new BaseType(TypeEnum.VOID)); } return new TypeCheckResult(true, null); @@ -223,7 +226,7 @@ public class SemanticAnalyzer implements SemanticVisitor { @Override public TypeCheckResult analyze(AssignableNode toCheck) { - if ( currentFields.get(toCheck.identifier) != null){ + if (currentFields.get(toCheck.identifier) != null) { return new TypeCheckResult(true, currentFields.get(toCheck.identifier)); } else if (currentScope.getLocalVar(toCheck.identifier) != null) { return new TypeCheckResult(true, currentScope.getLocalVar(toCheck.identifier)); @@ -264,19 +267,16 @@ public class SemanticAnalyzer implements SemanticVisitor { var rResult = rExpression.accept(this); var variable = currentScope.getLocalVar(toCheck.assignable.identifier); - if(variable == null){ + if (variable == null) { variable = currentFields.get(toCheck.assignable.identifier); } - if (!Objects.equals(variable, rExpression.getType())) { + if (!Objects.equals(variable, rResult.getType())) { errors.add(new TypeMismatchException( "Mismatch types in Assign-Statement: cannot convert from \"" + lResult.getType() + "\" to \"" + rResult.getType() + "\"")); valid = false; } -// else { -// toCheck.setType(assignable.getType()); -// } valid = valid && lResult.isValid() && rResult.isValid(); currentNullType = null; return new TypeCheckResult(valid, null); // return type is null to get the return type sufficently @@ -295,25 +295,43 @@ public class SemanticAnalyzer implements SemanticVisitor { @Override public TypeCheckResult analyze(MethodCallNode toCheck) { - var targetType = currentScope.getLocalVar(toCheck.target.identifier); - if(targetType == null){ - targetType = currentFields.get(toCheck.target.identifier); + if (toCheck.target.identifier != null) { + var targetType = currentScope.getLocalVar(toCheck.target.identifier); + if (targetType == null) { + targetType = currentFields.get(toCheck.target.identifier); + } + if (targetType instanceof ReferenceType reference) { + return new TypeCheckResult(true, getTypeFromMethod(toCheck, reference)); + } + } else { + if(toCheck.target.thisTar){ + return new TypeCheckResult(true, getTypeFromMethod(toCheck, new ReferenceType(currentClass.identifier))); + } else { + var result = toCheck.target.accept(this); + if (result.getType() instanceof ReferenceType reference) { + return new TypeCheckResult(true, getTypeFromMethod(toCheck, reference)); + } + } } - if(targetType instanceof ReferenceType reference){ - var classContext = context.getClass(reference.getIdentifier()); + return null; + } - if(classContext == null){ - errors.add(new NotDefinedException(toCheck.target.identifier + "is not Defined")); - }else { - var methods = classContext.getMethods(); - for (var method : methods) { - if(toCheck.identifier.equals(method.getIdentifier()) && method.getParameters().size() == toCheck.parameters.size()){ - return new TypeCheckResult(true, method.getType()); + private ITypeNode getTypeFromMethod(MethodCallNode toCheck, ReferenceType reference) { + var classContext = context.getClass(reference.getIdentifier()); + + if (classContext == null) { + errors.add(new NotDeclaredException(toCheck.target.identifier + "is not Defined")); + } else { + var methods = classContext.getMethods(); + for (var method : methods) { + if (toCheck.identifier.equals(method.getIdentifier()) && method.getParameters().size() == toCheck.parameters.size()) { + if(method.accesModifier.accessType == EnumAccessModifierNode.PUBLIC){ + return method.getType(); + }else { + errors.add(new NotVisibleException("This Method is not Visible")); } } } - } else { - } return null; } @@ -348,7 +366,7 @@ public class SemanticAnalyzer implements SemanticVisitor { @Override public TypeCheckResult analyze(NewDeclarationNode toCheck) { - if(context.containsClass(toCheck.identifier)) { + if (context.containsClass(toCheck.identifier)) { return new TypeCheckResult(true, new ReferenceType(toCheck.identifier)); } @@ -404,6 +422,48 @@ public class SemanticAnalyzer implements SemanticVisitor { errors.add(new NotDeclaredException("Var is not Declared")); } return new TypeCheckResult(valid, null); -} + } + + @Override + public TypeCheckResult analyze(MemberAccessNode memberAccessNode) { + + ITypeNode currentType = null; + + for (String s : memberAccessNode.identifiers) { + if (currentType == null) { + if (currentScope.getLocalVar(s) != null) { + currentType = currentScope.getLocalVar(s); + + } else if (currentFields.get(s) != null) { + currentType = currentFields.get(s); + } else { + errors.add(new NotDeclaredException(s + "Not Declared")); + return new TypeCheckResult(false, null); + } + + } else { + if(currentType instanceof ReferenceType reference) { + var currentTypeClass = context.getClass(reference.getIdentifier()); + + var currentField = currentTypeClass.getField(s); + currentType = currentField.getType(); + } + } + + } + + return new TypeCheckResult(true, currentType); + } + + @Override + public TypeCheckResult analyze(TargetNode targetNode) { + + if (targetNode.memberAccess != null) { + return targetNode.memberAccess.accept(this); + } + return null; + } + + } \ No newline at end of file diff --git a/src/main/java/semantic/SemanticVisitor.java b/src/main/java/semantic/SemanticVisitor.java index 974d1de..87ba85a 100644 --- a/src/main/java/semantic/SemanticVisitor.java +++ b/src/main/java/semantic/SemanticVisitor.java @@ -2,6 +2,7 @@ package semantic; import ast.*; import ast.expressions.binaryexpressions.*; +import ast.expressions.unaryexpressions.MemberAccessNode; import ast.expressions.unaryexpressions.UnaryNode; import ast.members.*; import ast.parameters.ParameterNode; @@ -11,6 +12,7 @@ import ast.statementexpressions.NewDeclarationNode; import ast.statementexpressions.crementexpressions.DecrementNode; import ast.statementexpressions.crementexpressions.IncrementNode; import ast.statementexpressions.methodcallstatementnexpressions.MethodCallNode; +import ast.statementexpressions.methodcallstatementnexpressions.TargetNode; import ast.statements.*; import typechecker.TypeCheckResult; @@ -38,8 +40,6 @@ public interface SemanticVisitor { TypeCheckResult analyze(ElseNode toCheck); - //TypeCheckResult analyze(ForNode toCheck); - TypeCheckResult analyze(AssignNode toCheck); TypeCheckResult analyze(DecrementNode toCheck); @@ -66,4 +66,8 @@ public interface SemanticVisitor { TypeCheckResult analyze(UnaryNode toCheck); + TypeCheckResult analyze(MemberAccessNode toCheck); + + TypeCheckResult analyze(TargetNode toCheck); + } \ No newline at end of file diff --git a/src/main/java/semantic/exceptions/NotVisibleException.java b/src/main/java/semantic/exceptions/NotVisibleException.java new file mode 100644 index 0000000..efd7444 --- /dev/null +++ b/src/main/java/semantic/exceptions/NotVisibleException.java @@ -0,0 +1,9 @@ +package semantic.exceptions; + +public class NotVisibleException extends RuntimeException { + + public NotVisibleException(String message) { + super(message); + } + +} diff --git a/src/test/java/semantic/EndToTypedAstTest.java b/src/test/java/semantic/EndToTypedAstTest.java index 9bf5485..7d13f07 100644 --- a/src/test/java/semantic/EndToTypedAstTest.java +++ b/src/test/java/semantic/EndToTypedAstTest.java @@ -26,11 +26,37 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.*; public class EndToTypedAstTest { private static final Map> exceptionMap = new HashMap<>(); + @Test + public void testOnlyOneFile() { + SemanticAnalyzer.clearAnalyzer(); + + CharStream codeCharStream = null; + try { + codeCharStream = CharStreams.fromPath(Paths.get("src/test/resources/input/typedAstFeaturesTests/CorrectTest.java")); + } catch (IOException e) { + throw new RuntimeException(e); + } + SimpleJavaLexer lexer = new SimpleJavaLexer(codeCharStream); + CommonTokenStream tokenStream = new CommonTokenStream(lexer); + + SimpleJavaParser parser = new SimpleJavaParser(tokenStream); + ParseTree parseTree = parser.program(); // parse the input + + /* ------------------------- AST builder -> AST ------------------------- */ + ASTBuilder astBuilder = new ASTBuilder(); + ProgramNode abstractSyntaxTree = (ProgramNode) astBuilder.visit(parseTree); + + var result = SemanticAnalyzer.generateTast(abstractSyntaxTree); + + assertTrue(SemanticAnalyzer.errors.isEmpty()); + } + @Test public void exceptionsTest() { String directoryPath = "src/test/resources/input/typedAstExceptionsTests"; @@ -88,7 +114,7 @@ public class EndToTypedAstTest { } @Test - public void featureTest(){ + public void featureTest() { String directoryPath = "src/test/resources/input/typedAstFeaturesTests"; File folder = new File(directoryPath); if (folder.isDirectory()) { @@ -127,9 +153,8 @@ public class EndToTypedAstTest { } - - // ------------------ Helpers ------------------ + /** * This method is used to extract the expected exception from a given file. * It reads the file line by line and uses a regular expression to match the expected exception annotation. @@ -158,25 +183,25 @@ public class EndToTypedAstTest { } /** - * This method is used to retrieve the Class object associated with a given exception name. - * It first prints the original exception name, then appends the package name to the exception name and prints it. - * It then retrieves the Class object from the exceptionMap using the fully qualified exception name. - * If the Class object is not found in the exceptionMap, it throws a RuntimeException. - * - * @param exceptionName The name of the exception for which the Class object is to be retrieved. - * @return The Class object associated with the given exception name. - * @throws RuntimeException If the Class object for the given exception name is not found in the exceptionMap. - */ -private Class getExceptionClass(String exceptionName) { - System.out.println(exceptionName); - exceptionName = "semantic.exceptions." + exceptionName; - System.out.println(exceptionName); - Class exceptionClass = exceptionMap.get(exceptionName); - if (exceptionClass == null) { - throw new RuntimeException("Exception class not found: " + exceptionName); + * This method is used to retrieve the Class object associated with a given exception name. + * It first prints the original exception name, then appends the package name to the exception name and prints it. + * It then retrieves the Class object from the exceptionMap using the fully qualified exception name. + * If the Class object is not found in the exceptionMap, it throws a RuntimeException. + * + * @param exceptionName The name of the exception for which the Class object is to be retrieved. + * @return The Class object associated with the given exception name. + * @throws RuntimeException If the Class object for the given exception name is not found in the exceptionMap. + */ + private Class getExceptionClass(String exceptionName) { + System.out.println(exceptionName); + exceptionName = "semantic.exceptions." + exceptionName; + System.out.println(exceptionName); + Class exceptionClass = exceptionMap.get(exceptionName); + if (exceptionClass == null) { + throw new RuntimeException("Exception class not found: " + exceptionName); + } + return exceptionClass; } - return exceptionClass; -} /** * This method is used to load custom exceptions from a specified package. diff --git a/src/test/resources/input/featureTests/BooleanOperations.class b/src/test/resources/input/featureTests/BooleanOperations.class new file mode 100644 index 0000000..cd8092a Binary files /dev/null and b/src/test/resources/input/featureTests/BooleanOperations.class differ diff --git a/src/test/resources/input/featureTests/CharManipulation.class b/src/test/resources/input/featureTests/CharManipulation.class new file mode 100644 index 0000000..e47890d Binary files /dev/null and b/src/test/resources/input/featureTests/CharManipulation.class differ diff --git a/src/test/resources/input/featureTests/ConditionalStatements.class b/src/test/resources/input/featureTests/ConditionalStatements.class new file mode 100644 index 0000000..7fa39b3 Binary files /dev/null and b/src/test/resources/input/featureTests/ConditionalStatements.class differ diff --git a/src/test/resources/input/featureTests/EmptyClassExample.class b/src/test/resources/input/featureTests/EmptyClassExample.class new file mode 100644 index 0000000..a6a129d Binary files /dev/null and b/src/test/resources/input/featureTests/EmptyClassExample.class differ diff --git a/src/test/resources/input/featureTests/LoopExamples.class b/src/test/resources/input/featureTests/LoopExamples.class new file mode 100644 index 0000000..2a398c6 Binary files /dev/null and b/src/test/resources/input/featureTests/LoopExamples.class differ diff --git a/src/test/resources/input/featureTests/MethodOverloading.class b/src/test/resources/input/featureTests/MethodOverloading.class new file mode 100644 index 0000000..2357744 Binary files /dev/null and b/src/test/resources/input/featureTests/MethodOverloading.class differ diff --git a/src/test/resources/input/typedAstExceptionsTests/MethodNotVisible.java b/src/test/resources/input/typedAstExceptionsTests/MethodNotVisible.java new file mode 100644 index 0000000..2631c3e --- /dev/null +++ b/src/test/resources/input/typedAstExceptionsTests/MethodNotVisible.java @@ -0,0 +1,21 @@ +// @expected: NotVisibleException +public class Test { + + public int firstInt; + public Car ca; + + public int speed(){ + return ca.getSpeed(); + } + +} + +public class Car{ + + private int speed; + + private int getSpeed(){ + return speed; + } + +} \ No newline at end of file diff --git a/src/test/resources/input/typedAstExceptionsTests/WrongIfClause.java b/src/test/resources/input/typedAstExceptionsTests/WrongIfClause.java deleted file mode 100644 index e1b7c27..0000000 --- a/src/test/resources/input/typedAstExceptionsTests/WrongIfClause.java +++ /dev/null @@ -1,8 +0,0 @@ -// @expected: TypeMismatchException -public class Example { - - public static void testMethod(int x){ - - } - -} diff --git a/src/test/resources/input/typedAstFeaturesTests/CallMethodFromObjekt.java b/src/test/resources/input/typedAstFeaturesTests/CallMethodFromObjekt.java new file mode 100644 index 0000000..61b94f2 --- /dev/null +++ b/src/test/resources/input/typedAstFeaturesTests/CallMethodFromObjekt.java @@ -0,0 +1,20 @@ +public class Test { + + public int firstInt; + public Car ca; + + public int speed(){ + return ca.getSpeed(); + } + +} + +public class Car{ + + private int speed; + + public int getSpeed(){ + return speed; + } + +} \ No newline at end of file diff --git a/src/test/resources/input/typedAstFeaturesTests/CorrectTest.java b/src/test/resources/input/typedAstFeaturesTests/CorrectTest.java index 957dc26..1f51430 100644 --- a/src/test/resources/input/typedAstFeaturesTests/CorrectTest.java +++ b/src/test/resources/input/typedAstFeaturesTests/CorrectTest.java @@ -1,41 +1,8 @@ -public class Example { - - public int a; - - public Car c; - - public void test(){ - c = new Car(); - int speed = c.getSpeed(); - } - - public static int testReturnMethod(int b, boolean bo){ - a = b; - return a; - } - - public void testOjektMethod(int testInt){ - Test testClass = new Test(); - int b = testClass.getFirstInt(testInt); - } - -} - -public class Test { - - public int firstInt; - - public int getFirstInt(int b){ - return firstInt--; - } - -} - public class Car{ private int speed; - public int getSpeed(){ + public int getSpeed(boolean boo){ return speed; } diff --git a/src/test/resources/input/typedAstFeaturesTests/ThisDotMethod.java b/src/test/resources/input/typedAstFeaturesTests/ThisDotMethod.java new file mode 100644 index 0000000..36bcade --- /dev/null +++ b/src/test/resources/input/typedAstFeaturesTests/ThisDotMethod.java @@ -0,0 +1,13 @@ +public class Car{ + + private int speed; + + public int getSpeed(){ + return speed; + } + + public int test(){ + return this.getSpeed(); + } + +} \ No newline at end of file