diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..5b530e0 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..923da22 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_antlr_antlr4_runtime_4_7_2.xml b/.idea/libraries/Maven__org_antlr_antlr4_runtime_4_7_2.xml new file mode 100644 index 0000000..d0c6a38 --- /dev/null +++ b/.idea/libraries/Maven__org_antlr_antlr4_runtime_4_7_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml b/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml new file mode 100644 index 0000000..f854ab0 --- /dev/null +++ b/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_6_0.xml b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_6_0.xml new file mode 100644 index 0000000..be380a5 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_6_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_6_0.xml b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_6_0.xml new file mode 100644 index 0000000..227bc44 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_6_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_6_0.xml b/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_6_0.xml new file mode 100644 index 0000000..8e5d3f2 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_6_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_6_0.xml b/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_6_0.xml new file mode 100644 index 0000000..2f36d10 --- /dev/null +++ b/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_6_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml b/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml new file mode 100644 index 0000000..fbc1b16 --- /dev/null +++ b/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1f08112 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..eb129d2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/klang.iml b/klang.iml new file mode 100644 index 0000000..d75274d --- /dev/null +++ b/klang.iml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index dd5fd96..956661e 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,11 @@ jar-with-dependencies + + + de.hsrm.compiler.Klang.Klang + + diff --git a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 index a1a176a..efe054d 100644 --- a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 +++ b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 @@ -5,7 +5,11 @@ parse ; program - : (functionDef | structDef)* expression SCOL + : (functionDef | structDef | enumDef)* expression SCOL + ; + +enumDef + : ENUM enumName=IDENT OBRK (IDENT (COMMA IDENT)*)+ CBRK ; structDef @@ -78,7 +82,7 @@ destroy_statement expression : atom #atomExpression - | IDENT (PERIOD IDENT)+ #structFieldAccessExpression + | IDENT (PERIOD IDENT)+ #memberAccessExpression | OPAR expression CPAR #parenthesisExpression | lhs=expression MUL rhs=expression #multiplicationExpression | lhs=expression DIV rhs=expression #divisionExpression @@ -143,6 +147,7 @@ forLoop IF: 'if'; ELSE: 'else'; FUNC: 'function'; +ENUM: 'enum'; STRUCT: 'struct'; RETURN: 'return'; LET: 'let'; diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index ee49476..f752bce 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -1,9 +1,5 @@ package de.hsrm.compiler.Klang; -import java.util.Map; -import java.util.HashMap; - -import de.hsrm.compiler.Klang.helper.FunctionInformation; import de.hsrm.compiler.Klang.helper.Helper; import de.hsrm.compiler.Klang.nodes.*; import de.hsrm.compiler.Klang.nodes.expressions.*; @@ -13,10 +9,13 @@ import de.hsrm.compiler.Klang.nodes.loops.WhileLoop; import de.hsrm.compiler.Klang.nodes.statements.*; import de.hsrm.compiler.Klang.types.Type; +import java.util.*; + public class ContextAnalysis extends KlangBaseVisitor { Map vars = new HashMap<>(); - Map funcs; - Map structs; + Map functionDefs; + Map structDefs; + Map enumDefs; Type currentDeclaredReturnType; String currentFunctionDefinitionName; @@ -27,25 +26,35 @@ public class ContextAnalysis extends KlangBaseVisitor { } } - public ContextAnalysis(Map funcs, Map structs) { - this.funcs = funcs; - this.structs = structs; + public ContextAnalysis( + Map functionDefs, + Map structDefs, + Map enumDefs + ) { + this.functionDefs = functionDefs; + this.structDefs = structDefs; + this.enumDefs = enumDefs; } @Override public Node visitProgram(KlangParser.ProgramContext ctx) { - FunctionDefinition[] funcs = new FunctionDefinition[ctx.functionDef().size()]; + var typeCheckedFunctionDefs = new FunctionDefinition[ctx.functionDef().size()]; + var typeCheckedStructDefs = new HashMap(); for (int i = 0; i < ctx.functionDef().size(); i++) { - funcs[i] = (FunctionDefinition) this.visit(ctx.functionDef(i)); + typeCheckedFunctionDefs[i] = (FunctionDefinition) visit(ctx.functionDef(i)); } - Expression expression = (Expression) this.visit(ctx.expression()); - Program result = new Program(funcs, this.structs, expression); - result.type = expression.type; - result.line = ctx.start.getLine(); - result.col = ctx.start.getCharPositionInLine(); - return result; + for (int i = 0; i < ctx.structDef().size(); i++) { + typeCheckedStructDefs.put(ctx.structDef(i).structName.getText(), (StructDefinition) visit(ctx.structDef(i))); + } + + var expression = (Expression) visit(ctx.expression()); + var program = new Program(typeCheckedFunctionDefs, typeCheckedStructDefs, enumDefs, expression); + program.type = expression.type; + program.line = ctx.start.getLine(); + program.col = ctx.start.getCharPositionInLine(); + return program; } @Override @@ -67,7 +76,7 @@ public class ContextAnalysis extends KlangBaseVisitor { statements[i] = (Statement) currentStatement; actualStatementCount += 1; - // We use the existance of a type to indicate that this statement returns + // We use the existence of a type to indicate that this statement returns // something for which the VariableDeclaration is an exception if (currentStatement.type != null && !(currentStatement instanceof VariableDeclaration)) { // check whether the type matches @@ -89,9 +98,7 @@ public class ContextAnalysis extends KlangBaseVisitor { // create a shorter statements array and copy the statements to there if (actualStatementCount < declaredStatementCount) { Statement[] newStatements = new Statement[actualStatementCount]; - for (int i = 0; i < actualStatementCount; i++) { - newStatements[i] = statements[i]; - } + System.arraycopy(statements, 0, newStatements, 0, actualStatementCount); statements = newStatements; } @@ -172,43 +179,59 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitVariable_declaration(KlangParser.Variable_declarationContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - Type declaredType = Type.getByName(ctx.type_annotation().type().getText()); + var variableName = ctx.IDENT().getText(); + var declaredType = Type.getByName(ctx.type_annotation().type().getText()); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - if (!declaredType.isPrimitiveType() && this.structs.get(declaredType.getName()) == null) { - String error = "Type " + declaredType.getName() + " not defined."; + if (!declaredType.isPrimitiveType() && !structDefs.containsKey(declaredType.getName()) && !enumDefs.containsKey(declaredType.getName())) { + var error = "Type " + declaredType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - if (this.vars.get(name) != null) { - String error = "Redeclaration of variable with name \"" + name + "\"."; + if (structDefs.containsKey(variableName)) { + var error = "Variable name " + variableName + " shadows a struct of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (enumDefs.containsKey(variableName)) { + var error = "Variable name " + variableName + " shadows an enum of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (vars.get(variableName) != null) { + var error = "Redeclaration of variable with name \"" + variableName + "\"."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Create the appropriate instance - VariableDeclaration result; + VariableDeclaration variableDeclaration; if (ctx.expression() != null) { - Node expression = this.visit(ctx.expression()); + var expression = visit(ctx.expression()); try { declaredType.combine(expression.type); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } - result = new VariableDeclaration(name, (Expression) expression); - result.initialized = true; + variableDeclaration = new VariableDeclaration(variableName, (Expression) expression); + variableDeclaration.initialized = true; } else { - result = new VariableDeclaration(name); + if (enumDefs.containsKey(declaredType.getName())) { + var error = "Variable " + variableName + " references an enum but is not initialized."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + variableDeclaration = new VariableDeclaration(variableName); } // Add it to the global map of variable declarations - this.vars.put(name, result); + vars.put(variableName, variableDeclaration); - result.line = line; - result.col = col; - result.type = declaredType; - return result; + variableDeclaration.line = line; + variableDeclaration.col = col; + variableDeclaration.type = declaredType; + + return variableDeclaration; } @Override @@ -291,7 +314,7 @@ public class ContextAnalysis extends KlangBaseVisitor { String structName = variableDef.type.getName(); Type fieldType; try { - fieldType = Helper.drillType(this.structs, structName, path, 0); + fieldType = Helper.drillType(this.structDefs, structName, path, 0); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } @@ -311,43 +334,71 @@ public class ContextAnalysis extends KlangBaseVisitor { } @Override - public Node visitStructFieldAccessExpression(KlangParser.StructFieldAccessExpressionContext ctx) { - String varName = ctx.IDENT(0).getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - String[] path = new String[ctx.IDENT().size() - 1]; + public Node visitMemberAccessExpression(KlangParser.MemberAccessExpressionContext ctx) { + var baseName = ctx.IDENT(0).getText(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + // Create a list of member names. This excludes + // the first entry as it is the base name. + var path = new ArrayList(); for (int i = 1; i < ctx.IDENT().size(); i++) { - path[i - 1] = ctx.IDENT(i).getText(); + path.add(ctx.IDENT(i).getText()); + } + + // Determine if the base name points to an enum or a variable + var enumDef = enumDefs.get(baseName); + if (enumDef != null) { + if (path.size() != 1) { + var error = "Illegal access to enum " + enumDef.name + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + var enumValueName = path.get(0); + var enumValue = Arrays.stream(enumDef.enums) + .filter(e -> e.value.equals(enumValueName)) + .findFirst() + .orElseThrow(() -> { + var error = "Unknown enum value " + enumValueName + " of enum " + enumDef.name + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + }); + + var enumAccessExpression = new EnumAccessExpression(baseName, enumValueName, enumValue); + enumAccessExpression.type = enumDef.type; + enumAccessExpression.line = line; + enumAccessExpression.col = col; + + return enumAccessExpression; } // Get the referenced variable, make sure it is defined - var variableDef = this.vars.get(varName); + var variableDef = vars.get(baseName); if (variableDef == null) { - String error = "Variable with name " + varName + " not defined."; + var error = "Variable with name " + baseName + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Make sure it references a struct if (variableDef.type.isPrimitiveType()) { - String error = "Variable must reference a struct but references " + variableDef.type.getName() + "."; + var error = "Variable must reference a struct but references " + variableDef.type.getName() + "."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Get the type of the result of this expression - String structName = variableDef.type.getName(); + var structName = variableDef.type.getName(); Type resultType; try { - resultType = Helper.drillType(this.structs, structName, path, 0); + resultType = Helper.drillType(structDefs, structName, path.toArray(new String[0]), 0); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } - Node result = new StructFieldAccessExpression(varName, structName, path); - result.type = resultType; - result.line = line; - result.col = col; - return result; + var memberAccessExpression = new MemberAccessExpression(baseName, structName, path.toArray(new String[0])); + memberAccessExpression.type = resultType; + memberAccessExpression.line = line; + memberAccessExpression.col = col; + + return memberAccessExpression; } @Override @@ -708,7 +759,7 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitBoolAtom(KlangParser.BoolAtomContext ctx) { - Node n = new BooleanExpression(ctx.getText().equals("true") ? true : false); + Node n = new BooleanExpression(ctx.getText().equals("true")); n.type = Type.getBooleanType(); n.line = ctx.start.getLine(); n.col = ctx.start.getCharPositionInLine(); @@ -725,70 +776,137 @@ public class ContextAnalysis extends KlangBaseVisitor { } @Override - public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { - String name = ctx.funcName.getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - Type returnType = Type.getByName(ctx.returnType.type().getText()); - this.currentDeclaredReturnType = returnType; - this.currentFunctionDefinitionName = name; + public Node visitStructDef(KlangParser.StructDefContext ctx) { + var structName = ctx.structName.getText(); + var structFieldCount = ctx.structField().size(); + var structFields = new StructField[structFieldCount]; + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - if (!returnType.isPrimitiveType() && this.structs.get(returnType.getName()) == null) { - String error = "Type " + returnType.getName() + " not defined."; + for (int i = 0; i < structFieldCount; i++) { + structFields[i] = (StructField) visit(ctx.structField(i)); + } + + var structDef = new StructDefinition(structName, structFields); + structDef.type = Type.getByName(structName); + structDef.line = line; + structDef.col = col; + + return structDef; + } + + @Override + public Node visitStructField(KlangParser.StructFieldContext ctx) { + var structFieldName = ctx.IDENT().getText(); + var structFieldType = Type.getByName(ctx.type_annotation().type().getText()); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + + if (!structFieldType.isPrimitiveType() && !structDefs.containsKey(structFieldType.getName()) && !enumDefs.containsKey(structFieldType.getName())) { + var error = "Type " + structFieldType.getName() + " not defined."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (structDefs.containsKey(structFieldName)) { + var error = "Struct field name " + structFieldName + " shadows a struct of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (enumDefs.containsKey(structFieldName)) { + var error = "Struct field name " + structFieldName + " shadows an enum of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + var structField = new StructField(structFieldName); + structField.type = structFieldType; + structField.line = line; + structField.col = col; + + return structField; + } + + @Override + public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { + var name = ctx.funcName.getText(); + var returnType = Type.getByName(ctx.returnType.type().getText()); + currentDeclaredReturnType = returnType; + currentFunctionDefinitionName = name; + + if (!returnType.isPrimitiveType() && !structDefs.containsKey(returnType.getName()) && !enumDefs.containsKey(returnType.getName())) { + var line = ctx.returnType.start.getLine(); + var col = ctx.returnType.start.getCharPositionInLine(); + var error = "Type " + returnType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Create a new set for the variables of the current function - // this will be filled in the variable declaration visitor aswell - this.vars = new HashMap<>(); + // this will be filled in the variable declaration visitor as well + vars = new HashMap<>(); - // Process the paremter list by visiting every paremter in it - int paramCount = ctx.params.parameter().size(); - Parameter[] params = new Parameter[paramCount]; + // Process the parameter list by visiting every parameter in it + var paramCount = ctx.params.parameter().size(); + var params = new Parameter[paramCount]; for (int i = 0; i < paramCount; i++) { // Add the parameter to the list of parameters - Parameter param = (Parameter) this.visit(ctx.params.parameter(i)); + var param = (Parameter) visit(ctx.params.parameter(i)); params[i] = param; // add the param as a variable - VariableDeclaration var = new VariableDeclaration(param.name); + var var = new VariableDeclaration(param.name); var.initialized = true; // parameters can always be considered initialized var.type = param.type; - this.vars.put(param.name, var); + vars.put(param.name, var); } // Visit the block, make sure that a return value is guaranteed - Node block = this.visit(ctx.braced_block()); + var block = visit(ctx.braced_block()); if (block.type == null) { - String error = "Function " + name + " has to return something of type " + returnType.getName() + "."; + var line = ctx.braced_block().start.getLine(); + var col = ctx.braced_block().start.getCharPositionInLine(); + var error = "Function " + name + " has to return something of type " + returnType.getName() + "."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - FunctionDefinition result = new FunctionDefinition(name, params, (Block) block); - result.type = returnType; + var functionDef = new FunctionDefinition(name, params, (Block) block); + functionDef.type = returnType; + functionDef.line = ctx.start.getLine(); + functionDef.col = ctx.start.getCharPositionInLine(); - result.line = ctx.start.getLine(); - result.col = ctx.start.getCharPositionInLine(); - return result; + return functionDef; } @Override public Node visitParameter(KlangParser.ParameterContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - Type type = Type.getByName(ctx.type_annotation().type().getText()); + var parameterName = ctx.IDENT().getText(); + var parameterType = Type.getByName(ctx.type_annotation().type().getText()); - if (!type.isPrimitiveType() && this.structs.get(type.getName()) == null) { - String error = "Type " + type.getName() + " not defined."; + if (structDefs.containsKey(parameterName)) { + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + var error = "Parameter name " + parameterName + " duplicates a struct of the same name."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - Parameter result = new Parameter(name); - result.type = type; - result.line = line; - result.col = col; - return result; + if (enumDefs.containsKey(parameterName)) { + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + var error = "Parameter name " + parameterName + " duplicates an enum of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (!parameterType.isPrimitiveType() && !structDefs.containsKey(parameterType.getName()) && !enumDefs.containsKey(parameterType.getName())) { + var line = ctx.type_annotation().start.getLine(); + var col = ctx.type_annotation().start.getCharPositionInLine(); + var error = "Type " + parameterType.getName() + " not defined."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + var parameter = new Parameter(parameterName); + parameter.type = parameterType; + parameter.line = ctx.start.getLine(); + parameter.col = ctx.start.getCharPositionInLine(); + + return parameter; } @Override @@ -797,15 +915,15 @@ public class ContextAnalysis extends KlangBaseVisitor { int line = ctx.start.getLine(); int col = ctx.start.getCharPositionInLine(); - FunctionInformation func = this.funcs.get(name); - if (func == null) { + var functionDef = this.functionDefs.get(name); + if (functionDef == null) { String error = "Function with name \"" + name + "\" not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Make sure the number of arguments matches the number of parameters int argCount = ctx.functionCall().arguments().expression().size(); - int paramCount = func.parameters.size(); + int paramCount = functionDef.parameters.length; if (argCount != paramCount) { String error = "Function \"" + name + "\" expects " + paramCount + " parameters, but got " + argCount + "."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); @@ -815,14 +933,14 @@ public class ContextAnalysis extends KlangBaseVisitor { Expression[] args = new Expression[argCount]; for (int i = 0; i < argCount; i++) { Expression expression = (Expression) this.visit(ctx.functionCall().arguments().expression(i)); - if (!expression.type.equals(func.signature[i])) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + "argument " + i + " Expected " + func.signature[i].getName() + " but got: " + expression.type.getName()); + if (!expression.type.equals(functionDef.parameters[i].type)) { + throw new RuntimeException(Helper.getErrorPrefix(line, col) + "argument " + i + " Expected " + functionDef.parameters[i].type.getName() + " but got: " + expression.type.getName()); } args[i] = expression; } FunctionCall result = new FunctionCall(name, args); - result.type = func.returnType; + result.type = functionDef.type; result.line = line; result.col = col; return result; @@ -835,7 +953,7 @@ public class ContextAnalysis extends KlangBaseVisitor { int col = ctx.start.getCharPositionInLine(); // Get the corresponding struct definition - var struct = this.structs.get(name); + var struct = this.structDefs.get(name); if (struct == null) { String error = "Struct with name \"" + name + "\" not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); diff --git a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java new file mode 100644 index 0000000..d5cf9e4 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -0,0 +1,220 @@ +package de.hsrm.compiler.Klang; + +import de.hsrm.compiler.Klang.helper.Helper; +import de.hsrm.compiler.Klang.nodes.*; +import de.hsrm.compiler.Klang.types.NamedType; +import de.hsrm.compiler.Klang.types.Type; + +import java.util.*; + +public class GetDefinitions extends KlangBaseVisitor { + private final Map functionDefs; + private final Map structDefs; + private final Map enumDefs; + + private Set functionNames; + private Set structNames; + private Set enumNames; + + public GetDefinitions( + Map functionDefs, + Map structDefs, + Map enumDefs + ) { + this.functionDefs = functionDefs; + this.structDefs = structDefs; + this.enumDefs = enumDefs; + } + + private Set collectFunctionNames(KlangParser.ProgramContext ctx) { + var result = new HashSet(); + for (int i = 0; i < ctx.functionDef().size(); i++) { + var currentFunctionDef = ctx.functionDef(i); + var funcName = currentFunctionDef.funcName.getText(); + if (result.contains(funcName)) { + var line = currentFunctionDef.funcName.getLine(); + var col = currentFunctionDef.funcName.getCharPositionInLine(); + var error = "Function " + funcName + " defined multiple times."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + result.add(funcName); + } + return result; + } + + private Set collectStructNames(KlangParser.ProgramContext ctx) { + var result = new HashSet(); + for (int i = 0; i < ctx.structDef().size(); i++) { + var currentStructDef = ctx.structDef(i); + var structName = currentStructDef.structName.getText(); + if (result.contains(structName)) { + var line = currentStructDef.structName.getLine(); + var col = currentStructDef.structName.getCharPositionInLine(); + var error = "Struct " + structName + " defined multiple times."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + result.add(structName); + } + return result; + } + + private Set collectEnumNames(KlangParser.ProgramContext ctx) { + var result = new HashSet(); + for (int i = 0; i < ctx.enumDef().size(); i++) { + var currentEnumDef = ctx.enumDef(i); + var enumName = currentEnumDef.enumName.getText(); + if (result.contains(enumName)) { + var line = currentEnumDef.enumName.getLine(); + var col = currentEnumDef.enumName.getCharPositionInLine(); + var error = "Enum " + enumName + " defined multiple times."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + result.add(enumName); + } + return result; + } + + @Override + public Node visitProgram(KlangParser.ProgramContext ctx) { + functionNames = collectFunctionNames(ctx); + structNames = collectStructNames(ctx); + enumNames = collectEnumNames(ctx); + + for (int i = 0; i < ctx.functionDef().size(); i++) { + visit(ctx.functionDef(i)); + } + + for (int i = 0; i < ctx.structDef().size(); i++) { + visit(ctx.structDef(i)); + } + + for (int i = 0; i < ctx.enumDef().size(); i++) { + visit(ctx.enumDef(i)); + } + + return null; + } + + @Override + public Node visitEnumDef(KlangParser.EnumDefContext ctx) { + // Check that there isn't a function or struct with the same name + var enumName = ctx.enumName.getText(); + if (functionNames.contains(enumName) || structNames.contains(enumName)) { + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + var error = "Duplicate use of name " + enumName + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + // IDENT() includes the enumName as the first entry, which we skip + var enumValues = new LinkedHashMap(); + for (int i = 1; i < ctx.IDENT().size(); i++) { + var currentEnumField = ctx.IDENT(i); + var currentEnumFieldName = currentEnumField.getText(); + var line = currentEnumField.getSymbol().getLine(); + var col = currentEnumField.getSymbol().getCharPositionInLine(); + + if (enumValues.containsKey(currentEnumFieldName)) { + var error = " Duplicate enum value " + currentEnumFieldName + " in enum " + enumName + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + var enumValue = new EnumValue(currentEnumFieldName, i - 1); + enumValue.line = line; + enumValue.col = col; + + enumValues.put(currentEnumFieldName, enumValue); + } + + var enumDef = new EnumDefinition(enumName, enumValues.values().toArray(new EnumValue[0])); + enumDef.line = ctx.start.getLine(); + enumDef.col = ctx.start.getCharPositionInLine(); + enumDef.type = new NamedType(enumName); + enumDefs.put(enumName, enumDef); + + return null; + } + + @Override + public Node visitStructDef(KlangParser.StructDefContext ctx) { + var structName = ctx.structName.getText(); + var structFieldCount = ctx.structField().size(); + var structFields = new LinkedHashMap(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + + // Check that there isn't a function or enum with the same name + if (functionNames.contains(structName) || enumNames.contains(structName)) { + var error = "Duplicate use of name " + structName + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + for (int i = 0; i < structFieldCount; i++) { + var currentStructField = ctx.structField(i); + var structFieldName = currentStructField.IDENT().getText(); + var structFieldLine = currentStructField.start.getLine(); + var structFieldCol = currentStructField.start.getCharPositionInLine(); + + if (structFields.containsKey(structFieldName)) { + var error = "Duplicate struct field " + structFieldName + " in struct " + structName + "."; + throw new RuntimeException(Helper.getErrorPrefix(structFieldLine, structFieldCol) + error); + } + + var structField = new StructField(structFieldName); + structField.type = Type.getByName(currentStructField.type_annotation().type().getText()); + structField.line = structFieldLine; + structField.col = structFieldCol; + + structFields.put(structFieldName, structField); + } + + var structDef = new StructDefinition(structName, structFields.values().toArray(new StructField[0])); + structDef.line = line; + structDef.col = col; + structDef.type = new NamedType(structName); + structDefs.put(structName, structDef); + + return null; + } + + @Override + public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { + var funcName = ctx.funcName.getText(); + var paramCount = ctx.params.parameter().size(); + var parameters = new LinkedHashMap(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + + // Check that there isn't a struct or enum with the same name + if (structNames.contains(funcName) || enumNames.contains(funcName)) { + var error = "Duplicate use of name " + funcName + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + for (int i = 0; i < paramCount; i++) { + var currentParam = ctx.params.parameter(i); + var paramName = currentParam.IDENT().getText(); + var paramLine = currentParam.start.getLine(); + var paramCol = currentParam.start.getCharPositionInLine(); + + if (parameters.containsKey(paramName)) { + var error = "Duplicate parameter name " + paramName + " in function " + funcName + "."; + throw new RuntimeException(Helper.getErrorPrefix(paramLine, paramCol) + error); + } + + var parameter = new Parameter(paramName); + parameter.type = Type.getByName(currentParam.type_annotation().type().getText()); + parameter.line = paramLine; + parameter.col = paramCol; + + parameters.put(paramName, parameter); + } + + var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null); + functionDef.type = Type.getByName(ctx.returnType.type().getText()); + functionDef.line = line; + functionDef.col = col; + functionDefs.put(funcName, functionDef); + return null; + } +} diff --git a/src/main/java/de/hsrm/compiler/Klang/GetFunctions.java b/src/main/java/de/hsrm/compiler/Klang/GetFunctions.java deleted file mode 100644 index f9faa5c..0000000 --- a/src/main/java/de/hsrm/compiler/Klang/GetFunctions.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.hsrm.compiler.Klang; - -import java.util.Map; -import java.util.TreeMap; - -import de.hsrm.compiler.Klang.types.*; -import de.hsrm.compiler.Klang.helper.*; - -public class GetFunctions extends KlangBaseVisitor { - - private Map funcs; - - public GetFunctions(Map funcs) { - this.funcs = funcs; - } - - @Override - public Void visitProgram(KlangParser.ProgramContext ctx) { - for (int i = 0; i < ctx.functionDef().size(); i++) { - this.visit(ctx.functionDef(i)); - } - return null; - } - - @Override - public Void visitFunctionDef(KlangParser.FunctionDefContext ctx) { - String name = ctx.funcName.getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - - if (this.funcs.containsKey(name)) { - String error = "Function " + name + " defined multiple times."; - throw new Error(Helper.getErrorPrefix(line, col) + error); - } - - Type returnType = Type.getByName(ctx.returnType.type().getText()); - - TreeMap parameters = new TreeMap(); - - // Process the paremter list by visiting every paremter in it - int paramCount = ctx.params.parameter().size(); - Type[] signature = new Type[paramCount]; - for (int i = 0; i < paramCount; i++) { - Type paramType = Type.getByName(ctx.params.parameter(i).type_annotation().type().getText()); - String paramName = ctx.params.parameter(i).IDENT().getText(); - parameters.put(paramName, paramType); - signature[i] = paramType; - } - - FunctionInformation information = new FunctionInformation(name, returnType, parameters, signature); - this.funcs.put(name, information); - return null; - } -} \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/GetStructNames.java b/src/main/java/de/hsrm/compiler/Klang/GetStructNames.java deleted file mode 100644 index 0e5fcb3..0000000 --- a/src/main/java/de/hsrm/compiler/Klang/GetStructNames.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.hsrm.compiler.Klang; - -import java.util.Set; - -import de.hsrm.compiler.Klang.helper.Helper; - -public class GetStructNames extends KlangBaseVisitor { - private Set structNames; - - public GetStructNames(Set structNames) { - this.structNames = structNames; - } - - @Override - public Void visitProgram(KlangParser.ProgramContext ctx) { - for (int i = 0; i < ctx.structDef().size(); i++) { - this.visit(ctx.structDef(i)); - } - - return null; - } - - @Override - public Void visitStructDef(KlangParser.StructDefContext ctx) { - String name = ctx.structName.getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - - if (this.structNames.contains(name)) { - String error = "Struct " + name + " defined multiple times."; - throw new Error(Helper.getErrorPrefix(line, col) + error); - } - - this.structNames.add(name); - return null; - } -} \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/GetStructs.java b/src/main/java/de/hsrm/compiler/Klang/GetStructs.java deleted file mode 100644 index ca1dc2f..0000000 --- a/src/main/java/de/hsrm/compiler/Klang/GetStructs.java +++ /dev/null @@ -1,70 +0,0 @@ -package de.hsrm.compiler.Klang; - -import java.util.Map; -import java.util.Set; - -import de.hsrm.compiler.Klang.helper.Helper; -import de.hsrm.compiler.Klang.nodes.Node; -import de.hsrm.compiler.Klang.nodes.StructDefinition; -import de.hsrm.compiler.Klang.nodes.StructField; -import de.hsrm.compiler.Klang.types.StructType; -import de.hsrm.compiler.Klang.types.Type; - -public class GetStructs extends KlangBaseVisitor { - - private Set structNames; - private Map structs; - - public GetStructs(Set structNames, Map structs) { - this.structs = structs; - this.structNames = structNames; - } - - @Override - public Node visitProgram(KlangParser.ProgramContext ctx) { - for (int i = 0; i < ctx.structDef().size(); i++) { - this.visit(ctx.structDef(i)); - } - - return null; - } - - @Override - public Node visitStructDef(KlangParser.StructDefContext ctx) { - String name = ctx.structName.getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - StructField[] fields = new StructField[ctx.structField().size()]; - - for (int i = 0; i < ctx.structField().size(); i++) { - StructField field = (StructField) this.visit(ctx.structField(i)); - fields[i] = field; - } - - StructDefinition result = new StructDefinition(name, fields); - result.line = line; - result.col = col; - result.type = new StructType(name); - this.structs.put(name, result); - return null; - } - - @Override - public Node visitStructField(KlangParser.StructFieldContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - Type type = Type.getByName(ctx.type_annotation().type().getText()); - - if (!type.isPrimitiveType() && !this.structNames.contains(type.getName())) { - String error = "Type " + type.getName() + " not defined."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } - - Node result = new StructField(name); - result.type = type; - result.line = line; - result.col = col; - return result; - } -} \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/Klang.java b/src/main/java/de/hsrm/compiler/Klang/Klang.java index 029fdd6..ca8094c 100644 --- a/src/main/java/de/hsrm/compiler/Klang/Klang.java +++ b/src/main/java/de/hsrm/compiler/Klang/Klang.java @@ -1,19 +1,22 @@ package de.hsrm.compiler.Klang; -// import ANTLR's runtime libraries -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.tree.*; - -import java.io.*; -import java.util.Arrays; -import java.util.List; -import java.util.HashMap; -import java.util.HashSet; - +import de.hsrm.compiler.Klang.nodes.EnumDefinition; +import de.hsrm.compiler.Klang.nodes.FunctionDefinition; import de.hsrm.compiler.Klang.nodes.Node; import de.hsrm.compiler.Klang.nodes.StructDefinition; -import de.hsrm.compiler.Klang.visitors.*; -import de.hsrm.compiler.Klang.helper.*; +import de.hsrm.compiler.Klang.visitors.EvalVisitor; +import de.hsrm.compiler.Klang.visitors.GenASM; +import de.hsrm.compiler.Klang.visitors.PrettyPrintVisitor; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; + +import java.io.FileWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; public class Klang { @@ -87,22 +90,15 @@ public class Klang { // Context Analysis and DAST generation Node root; - HashMap structs; + var functionDefs = new HashMap(); + var structDefs = new HashMap(); + var enumDefs = new HashMap(); try { - // Extract information about all functions - var functionDefinitions = new HashMap(); - new GetFunctions(functionDefinitions).visit(tree); - - // Extract names of all structs - var structNames = new HashSet(); - new GetStructNames(structNames).visit(tree); - - // Extract information about all structs - structs = new HashMap(); - new GetStructs(structNames, structs).visit(tree); + // Extract information about all definitions + new GetDefinitions(functionDefs, structDefs, enumDefs).visit(tree); // Create the DAST - ContextAnalysis ctxAnal = new ContextAnalysis(functionDefinitions, structs); + ContextAnalysis ctxAnal = new ContextAnalysis(functionDefs, structDefs, enumDefs); root = ctxAnal.visit(tree); } catch (Exception e) { System.err.println(e.getMessage()); @@ -122,14 +118,14 @@ public class Klang { if (evaluate) { // Evaluate the sourcecode and print the result System.out.println("\nEvaluating the source code:"); - EvalVisitor evalVisitor = new EvalVisitor(structs); + EvalVisitor evalVisitor = new EvalVisitor(structDefs); Value result = root.welcome(evalVisitor); generateOutput(out, "Result was: " + result.asObject().toString()); return; } // Generate assembler code - GenASM genasm = new GenASM(mainName, structs); + GenASM genasm = new GenASM(mainName, structDefs); root.welcome(genasm); generateOutput(out, genasm.toAsm()); } diff --git a/src/main/java/de/hsrm/compiler/Klang/helper/FunctionInformation.java b/src/main/java/de/hsrm/compiler/Klang/helper/FunctionInformation.java deleted file mode 100644 index 97508f0..0000000 --- a/src/main/java/de/hsrm/compiler/Klang/helper/FunctionInformation.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.hsrm.compiler.Klang.helper; - -import java.util.Map; - -import de.hsrm.compiler.Klang.types.Type; - -public class FunctionInformation { - public String name; - public Type returnType; - public Map parameters; - public Type[] signature; - - public FunctionInformation(String name, Type returnType, Map parameters, Type[] signature) { - this.name = name; - this.returnType = returnType; - this.parameters = parameters; - this.signature = signature; - } -} \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java new file mode 100644 index 0000000..4971b2d --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java @@ -0,0 +1,19 @@ +package de.hsrm.compiler.Klang.nodes; + +import de.hsrm.compiler.Klang.visitors.Visitor; + +public class EnumDefinition extends Node { + + public String name; + public EnumValue[] enums; + + public EnumDefinition(String name, EnumValue[] enums) { + this.name = name; + this.enums = enums; + } + + @Override + public R welcome(Visitor v) { + return v.visit(this); + } +} diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/EnumValue.java b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumValue.java new file mode 100644 index 0000000..2a20bc7 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumValue.java @@ -0,0 +1,18 @@ +package de.hsrm.compiler.Klang.nodes; + +import de.hsrm.compiler.Klang.visitors.Visitor; + +public class EnumValue extends Node { + public String value; + public int index; + + public EnumValue(String value, int index) { + this.value = value; + this.index = index; + } + + @Override + public R welcome(Visitor v) { + return v.visit(this); + } +} diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/Program.java b/src/main/java/de/hsrm/compiler/Klang/nodes/Program.java index f56d749..35e0c62 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/Program.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/Program.java @@ -9,11 +9,18 @@ public class Program extends Node { public FunctionDefinition[] funcs; public Map structs; + public Map enums; public Expression expression; - public Program(FunctionDefinition[] funcs, Map structs, Expression expression) { + public Program( + FunctionDefinition[] funcs, + Map structs, + Map enums, + Expression expression + ) { this.funcs = funcs; this.structs = structs; + this.enums = enums; this.expression = expression; } diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java new file mode 100644 index 0000000..d46c715 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java @@ -0,0 +1,26 @@ +package de.hsrm.compiler.Klang.nodes.expressions; + +import de.hsrm.compiler.Klang.nodes.EnumDefinition; +import de.hsrm.compiler.Klang.nodes.EnumValue; +import de.hsrm.compiler.Klang.visitors.Visitor; + +public class EnumAccessExpression extends Expression { + public String enumName; + public String enumValueName; + public EnumValue enumValue; + + public EnumAccessExpression( + String enumName, + String enumValueName, + EnumValue enumValue + ) { + this.enumName = enumName; + this.enumValueName = enumValueName; + this.enumValue = enumValue; + } + + @Override + public R welcome(Visitor v) { + return v.visit(this); + } +} diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/StructFieldAccessExpression.java b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/MemberAccessExpression.java similarity index 69% rename from src/main/java/de/hsrm/compiler/Klang/nodes/expressions/StructFieldAccessExpression.java rename to src/main/java/de/hsrm/compiler/Klang/nodes/expressions/MemberAccessExpression.java index 73b9ac8..3a71418 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/StructFieldAccessExpression.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/MemberAccessExpression.java @@ -2,12 +2,12 @@ package de.hsrm.compiler.Klang.nodes.expressions; import de.hsrm.compiler.Klang.visitors.Visitor; -public class StructFieldAccessExpression extends Expression { +public class MemberAccessExpression extends Expression { public String varName; public String structName; public String[] path; - public StructFieldAccessExpression(String varName, String structName, String[] path) { + public MemberAccessExpression(String varName, String structName, String[] path) { this.varName = varName; this.structName = structName; this.path = path; diff --git a/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java b/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java index 78bcb6b..8e3f27a 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java @@ -1,5 +1,7 @@ package de.hsrm.compiler.Klang.types; +import de.hsrm.compiler.Klang.Value; + public class BooleanType extends PrimitiveType { private static BooleanType instance = null; @@ -33,4 +35,8 @@ public class BooleanType extends PrimitiveType { throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); } + @Override + public boolean valuesEqual(Value a, Value b) { + return a.asBoolean() == b.asBoolean(); + } } \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java b/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java index 14f3fd7..cb383d5 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java @@ -1,5 +1,7 @@ package de.hsrm.compiler.Klang.types; +import de.hsrm.compiler.Klang.Value; + public class FloatType extends NumericType { private static FloatType instance = null; @@ -37,4 +39,8 @@ public class FloatType extends NumericType { throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); } + @Override + public boolean valuesEqual(Value a, Value b) { + return a.asFloat() == b.asFloat(); + } } \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java index 41064d1..7eccf14 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java @@ -1,5 +1,7 @@ package de.hsrm.compiler.Klang.types; +import de.hsrm.compiler.Klang.Value; + public class IntegerType extends NumericType { private static IntegerType instance = null; @@ -37,4 +39,9 @@ public class IntegerType extends NumericType { throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); } + @Override + public boolean valuesEqual(Value a, Value b) { + return a.asInteger() == b.asInteger(); + } + } \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java b/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java new file mode 100644 index 0000000..8ea4bdf --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java @@ -0,0 +1,59 @@ +package de.hsrm.compiler.Klang.types; + +import de.hsrm.compiler.Klang.Value; +import de.hsrm.compiler.Klang.nodes.EnumDefinition; +import de.hsrm.compiler.Klang.nodes.FunctionDefinition; +import de.hsrm.compiler.Klang.nodes.StructDefinition; + +import java.util.Map; + +public class NamedType extends Type { + public String name; + + public NamedType(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public Type combine(Type that) { + if(this.equals(that)) { + return this; + } + + throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName()); + } + + @Override + public boolean valuesEqual(Value a, Value b) { + return a.asObject().equals(b.asObject()); + } + + @Override + public boolean isPrimitiveType() { + return false; + } + + @Override + public boolean isNumericType() { + return false; + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + + if (that instanceof NamedType) { + var thatType = (NamedType) that; + return getName().equals(thatType.getName()); + } + + return false; + } +} diff --git a/src/main/java/de/hsrm/compiler/Klang/types/NullType.java b/src/main/java/de/hsrm/compiler/Klang/types/NullType.java index 452efd8..0e2cf62 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/NullType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/NullType.java @@ -1,5 +1,7 @@ package de.hsrm.compiler.Klang.types; +import de.hsrm.compiler.Klang.Value; + public class NullType extends Type { private static NullType instance = null; @@ -28,6 +30,11 @@ public class NullType extends Type { return that; } + @Override + public boolean valuesEqual(Value a, Value b) { + return a.asObject() == b.asObject(); + } + @Override public boolean isPrimitiveType() { return false; diff --git a/src/main/java/de/hsrm/compiler/Klang/types/StructType.java b/src/main/java/de/hsrm/compiler/Klang/types/StructType.java deleted file mode 100644 index 23cac1c..0000000 --- a/src/main/java/de/hsrm/compiler/Klang/types/StructType.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.hsrm.compiler.Klang.types; - -public class StructType extends Type { - - public String name; - - public StructType(String name) { - this.name = name; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Type combine(Type that) { - if (that.equals(this)) { - return this; - } - - // If you combine a null type with a struct type, you - // always get the struct type back. - if (that == NullType.getType()) { - return this; - } - - throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); - } - - @Override - public boolean isPrimitiveType() { - return false; - } - - @Override - public boolean equals(Object that) { - if (this == that) { - return true; - } - - if (that instanceof StructType) { - StructType thatType = (StructType) that; - return this.getName().equals(thatType.getName()); - } - - return false; - } - - @Override - public boolean isNumericType() { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/types/Type.java b/src/main/java/de/hsrm/compiler/Klang/types/Type.java index 15b2810..4ce91ca 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/Type.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/Type.java @@ -1,5 +1,12 @@ package de.hsrm.compiler.Klang.types; +import de.hsrm.compiler.Klang.Value; +import de.hsrm.compiler.Klang.nodes.EnumDefinition; +import de.hsrm.compiler.Klang.nodes.FunctionDefinition; +import de.hsrm.compiler.Klang.nodes.StructDefinition; + +import java.util.Map; + public abstract class Type { // Returns an instance of IntegerType @@ -26,12 +33,13 @@ public abstract class Type { case "int": return getIntegerType(); case "float": return getFloatType(); case "null": return getNullType(); - default: return new StructType(name); + default: return new NamedType(name); } } public abstract String getName(); public abstract Type combine(Type that); + public abstract boolean valuesEqual(Value a, Value b); public abstract boolean isPrimitiveType(); public abstract boolean isNumericType(); } \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java index dad982e..ed24dbc 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -4,12 +4,7 @@ import java.util.HashMap; import java.util.Map; import de.hsrm.compiler.Klang.Value; -import de.hsrm.compiler.Klang.nodes.Block; -import de.hsrm.compiler.Klang.nodes.FunctionDefinition; -import de.hsrm.compiler.Klang.nodes.Parameter; -import de.hsrm.compiler.Klang.nodes.Program; -import de.hsrm.compiler.Klang.nodes.StructDefinition; -import de.hsrm.compiler.Klang.nodes.StructField; +import de.hsrm.compiler.Klang.nodes.*; import de.hsrm.compiler.Klang.nodes.expressions.*; import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop; import de.hsrm.compiler.Klang.nodes.loops.ForLoop; @@ -51,25 +46,11 @@ public class EvalVisitor implements Visitor { @Override public Value visit(EqualityExpression e) { - Value lhs = e.lhs.welcome(this); - Value rhs = e.rhs.welcome(this); - Type resultType = Type.getBooleanType(); - Type combineType = lhs.type.combine(rhs.type); + var lhs = e.lhs.welcome(this); + var rhs = e.rhs.welcome(this); + var combinedType = lhs.type.combine(rhs.type); - switch(combineType.getName()) { - case "bool": { - return new Value(lhs.asBoolean() == rhs.asBoolean(), resultType); - } - case "int": { - return new Value(lhs.asInteger() == rhs.asInteger(), resultType); - } - case "float": { - return new Value(lhs.asFloat() == rhs.asFloat(), resultType); - } - default: { - return new Value(lhs.asObject() == rhs.asObject(), resultType); - } - } + return new Value(combinedType.valuesEqual(lhs, rhs), Type.getBooleanType()); } @Override @@ -471,6 +452,16 @@ public class EvalVisitor implements Visitor { return null; } + @Override + public Value visit(EnumDefinition e) { + return null; + } + + @Override + public Value visit(EnumValue e) { + return null; + } + @Override public Value visit(StructDefinition e) { // We get these from a previous visitor @@ -484,7 +475,7 @@ public class EvalVisitor implements Visitor { } @Override - public Value visit(StructFieldAccessExpression e) { + public Value visit(MemberAccessExpression e) { Value var = this.env.get(e.varName); Map struct = var.asStruct(); @@ -496,6 +487,11 @@ public class EvalVisitor implements Visitor { return currentValue; } + @Override + public Value visit(EnumAccessExpression e) { + return new Value(e.enumValueName, e.type); + } + @Override public Value visit(ConstructorCall e) { StructDefinition structDef = this.structs.get(e.structName); diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java index aad1f62..5bc32f2 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -1,11 +1,5 @@ package de.hsrm.compiler.Klang.visitors; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - import de.hsrm.compiler.Klang.asm.ASM; import de.hsrm.compiler.Klang.helper.Helper; import de.hsrm.compiler.Klang.nodes.*; @@ -16,6 +10,8 @@ import de.hsrm.compiler.Klang.nodes.loops.WhileLoop; import de.hsrm.compiler.Klang.nodes.statements.*; import de.hsrm.compiler.Klang.types.Type; +import java.util.*; + public class GenASM implements Visitor { private class FloatWriter { private StringBuilder sb = new StringBuilder(); @@ -571,6 +567,13 @@ public class GenASM implements Visitor { @Override public Void visit(FunctionDefinition e) { + // If the user chooses "main" as one of his function names then + // rename it and hope that they didn't use the renamed function name + // as well :D + if (e.name.equals("main")) { + e.name = "main_by_user"; + } + int lblStart = ++lCount; this.currentFunctionStartLabel = lblStart; this.currentFunctionParams = e.parameters; @@ -748,7 +751,8 @@ public class GenASM implements Visitor { asm.add("q", stackStartOffset, "%rsp"); } - asm.call(e.name); + // We rename a function name if it is "main" + asm.call(e.name.equals("main") ? "main_by_user": e.name); return null; } @@ -773,11 +777,21 @@ public class GenASM implements Visitor { @Override public Void visit(Parameter e) { - // The work for a paremeter node is implement + // The work for a parameter node is implement // in the function definition visitor return null; } + @Override + public Void visit(EnumDefinition e) { + return null; + } + + @Override + public Void visit(EnumValue e) { + return null; + } + @Override public Void visit(StructDefinition e) { // We get these from a previous visitor @@ -791,7 +805,7 @@ public class GenASM implements Visitor { } @Override - public Void visit(StructFieldAccessExpression e) { + public Void visit(MemberAccessExpression e) { var structDef = this.structs.get(e.structName); int offset = this.env.get(e.varName); @@ -816,6 +830,14 @@ public class GenASM implements Visitor { return null; } + @Override + public Void visit(EnumAccessExpression e) { + // Since the access to an enum simply results in an integer (i.e. the index + // of the enum value), we can just let IntegerExpression handle the expected behaviour. + new IntegerExpression(e.enumValue.index).welcome(this); + return null; + } + @Override public Void visit(ConstructorCall e) { // push arguments onto the stack diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java index 50d807d..33e6b6b 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java @@ -234,6 +234,16 @@ class GetVars implements Visitor { return null; } + @Override + public Void visit(EnumDefinition e) { + return null; + } + + @Override + public Void visit(EnumValue e) { + return null; + } + @Override public Void visit(StructDefinition e) { return null; @@ -245,7 +255,12 @@ class GetVars implements Visitor { } @Override - public Void visit(StructFieldAccessExpression e) { + public Void visit(MemberAccessExpression e) { + return null; + } + + @Override + public Void visit(EnumAccessExpression e) { return null; } diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java index 563779b..252797b 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java @@ -68,6 +68,12 @@ public class PrettyPrintVisitor implements Visitor { ex.nl(); } + for (var enumDef: e.enums.values()) { + enumDef.welcome(this); + ex.nl(); + ex.nl(); + } + e.expression.welcome(this); ex.write(";"); return null; @@ -377,6 +383,28 @@ public class PrettyPrintVisitor implements Visitor { return null; } + @Override + public Void visit(EnumDefinition e) { + ex.write("enum " + e.name + " { "); + var first = true; + for(var enumValue: e.enums) { + if (!first) { + ex.write(", "); + } else { + first = false; + } + enumValue.welcome(this); + } + ex.write(" }"); + return null; + } + + @Override + public Void visit(EnumValue e) { + ex.write(e.value); + return null; + } + @Override public Void visit(StructDefinition e) { ex.write("struct " + e.name + " {"); @@ -398,7 +426,7 @@ public class PrettyPrintVisitor implements Visitor { } @Override - public Void visit(StructFieldAccessExpression e) { + public Void visit(MemberAccessExpression e) { ex.write(e.varName); for (int i = 0; i < e.path.length; i++) { ex.write("."); @@ -407,6 +435,15 @@ public class PrettyPrintVisitor implements Visitor { return null; } + @Override + public Void visit(EnumAccessExpression e) { + ex.write(e.enumName); + ex.write("."); + ex.write(e.enumValueName); + + return null; + } + @Override public Void visit(ConstructorCall e) { ex.write("create " + e.structName + "("); diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java b/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java index cfef604..f56e8b6 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java @@ -1,11 +1,6 @@ package de.hsrm.compiler.Klang.visitors; -import de.hsrm.compiler.Klang.nodes.Block; -import de.hsrm.compiler.Klang.nodes.FunctionDefinition; -import de.hsrm.compiler.Klang.nodes.Parameter; -import de.hsrm.compiler.Klang.nodes.Program; -import de.hsrm.compiler.Klang.nodes.StructDefinition; -import de.hsrm.compiler.Klang.nodes.StructField; +import de.hsrm.compiler.Klang.nodes.*; import de.hsrm.compiler.Klang.nodes.expressions.*; import de.hsrm.compiler.Klang.nodes.loops.*; import de.hsrm.compiler.Klang.nodes.statements.*; @@ -42,9 +37,12 @@ public interface Visitor { R visit(FunctionCall e); R visit(Program e); R visit(Parameter e); + R visit(EnumDefinition e); + R visit(EnumValue e); R visit(StructDefinition e); R visit(StructField e); - R visit(StructFieldAccessExpression e); + R visit(MemberAccessExpression e); + R visit(EnumAccessExpression e); R visit(ConstructorCall e); R visit(NullExpression e); R visit(DestructorCall e); diff --git a/src/test/java/AndTest.java b/src/test/java/AndTest.java index 03f4f33..a4fc6d5 100644 --- a/src/test/java/AndTest.java +++ b/src/test/java/AndTest.java @@ -12,7 +12,8 @@ public class AndTest { ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 && 2; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:30 && is only defined for bool.", e.getMessage()); diff --git a/src/test/java/ConstructorCallTest.java b/src/test/java/ConstructorCallTest.java index 99010bc..9d21d29 100644 --- a/src/test/java/ConstructorCallTest.java +++ b/src/test/java/ConstructorCallTest.java @@ -13,7 +13,8 @@ public class ConstructorCallTest { ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create schwurbel(1); } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:52 Struct with name \"schwurbel\" not defined.", e.getMessage()); @@ -24,7 +25,8 @@ public class ConstructorCallTest { ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:52 Struct \"bar\" defined 1 fields, but got 2 constructor parameters.", e.getMessage()); @@ -35,7 +37,8 @@ public class ConstructorCallTest { ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(false); } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:63 argument 0 Type missmatch: cannot combine bool and int", e.getMessage()); diff --git a/src/test/java/DestroyStatementTest.java b/src/test/java/DestroyStatementTest.java index f157d2b..523c4b5 100644 --- a/src/test/java/DestroyStatementTest.java +++ b/src/test/java/DestroyStatementTest.java @@ -13,7 +13,8 @@ public class DestroyStatementTest { ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { destroy x; return 1; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:45 Variable with name \"x\" not defined.", e.getMessage()); diff --git a/src/test/java/FieldAssignmentTest.java b/src/test/java/FieldAssignmentTest.java index 879e4c8..32eeacb 100644 --- a/src/test/java/FieldAssignmentTest.java +++ b/src/test/java/FieldAssignmentTest.java @@ -13,7 +13,8 @@ public class FieldAssignmentTest { ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { str.a = 1; return 1; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:46 Variable with name str not defined.", e.getMessage()); @@ -24,7 +25,8 @@ public class FieldAssignmentTest { ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { let x: int = 0; x.a = 0; return 1; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:62 Variable must reference a struct but references int.", e.getMessage()); diff --git a/src/test/java/FunctionCallTest.java b/src/test/java/FunctionCallTest.java index d0e5126..ce3876f 100644 --- a/src/test/java/FunctionCallTest.java +++ b/src/test/java/FunctionCallTest.java @@ -13,7 +13,8 @@ public class FunctionCallTest { ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } bar();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:34 Function with name \"bar\" not defined.", e.getMessage()); @@ -24,7 +25,8 @@ public class FunctionCallTest { ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } foo(5);"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:34 Function \"foo\" expects 0 parameters, but got 1.", e.getMessage()); @@ -35,7 +37,8 @@ public class FunctionCallTest { ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:40 argument 0 Expected int but got: bool", e.getMessage()); diff --git a/src/test/java/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java index 442e792..b6086fc 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -1,32 +1,95 @@ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.antlr.v4.runtime.tree.ParseTree; +import de.hsrm.compiler.Klang.ContextAnalysis; import org.junit.jupiter.api.Test; -import de.hsrm.compiler.Klang.ContextAnalysis; +import static org.junit.jupiter.api.Assertions.*; public class FunctionDefinitionTest { @Test - void typeNotDefined() { - ParseTree tree = Helper.prepareParser("function foo(): schwurbel { return 1; } foo();"); - var funcs = Helper.getFuncs(tree); - var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); - - Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); - assertEquals("Error in line 1:0 Type schwurbel not defined.", e.getMessage()); + void shouldNotThrowIfReturnTypeIsReferringToAnEnum() { + // given + var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: int): bar { return bar.A; } foo(1);"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); } @Test - void noReturnExpression() { - ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; x = 0; } foo();"); - var funcs = Helper.getFuncs(tree); - var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); - - Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); - assertEquals("Error in line 1:0 Function foo has to return something of type int.", e.getMessage()); + void shouldNotThrowIfParameterTypeIsReferringToAnEnum() { + // given + var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: bar): int { return 1; } foo(bar.A);"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldThrowExceptionIfParameterTypeIsNotDefined() { + // given + var tree = Helper.prepareParser("function foo(a: schwurbel): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfReturnTypeIsNotDefined() { + // given + var tree = Helper.prepareParser("function foo(): schwurbel { return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfReturnStatementIsMissing() { + // given + var tree = Helper.prepareParser("function foo(): int { let x: int; x = 0; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:20 Function foo has to return something of type int.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfParameterNameMatchesEnumName() { + // given + var tree = Helper.prepareParser("enum Bar { A, B } function foo(Bar: int): int { return 1; } foo(1);"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:31 Parameter name Bar duplicates an enum of the same name.", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/Helper.java b/src/test/java/Helper.java index 68b0ff6..eef2a8c 100644 --- a/src/test/java/Helper.java +++ b/src/test/java/Helper.java @@ -16,21 +16,21 @@ public class Helper { return parser.parse(); } - public static Map getFuncs(ParseTree tree) { - var functionDefinitions = new HashMap(); - new GetFunctions(functionDefinitions).visit(tree); + public static Map getFuncs(ParseTree tree) { + var functionDefinitions = new HashMap(); + new GetDefinitions(functionDefinitions, new HashMap<>(), new HashMap<>()).visit(tree); return functionDefinitions; } - public static Set getStructNames(ParseTree tree) { - var structNames = new HashSet(); - new GetStructNames(structNames).visit(tree); - return structNames; - } - public static Map getStructs(ParseTree tree) { var structs = new HashMap(); - new GetStructs(getStructNames(tree), structs).visit(tree); + new GetDefinitions(new HashMap<>(), structs, new HashMap<>()).visit(tree); return structs; } + + public static Map getEnums(ParseTree tree) { + var enums = new HashMap(); + new GetDefinitions(new HashMap<>(), new HashMap<>(), enums).visit(tree); + return enums; + } } \ No newline at end of file diff --git a/src/test/java/ModuloTest.java b/src/test/java/ModuloTest.java index c11a87f..57b2f8b 100644 --- a/src/test/java/ModuloTest.java +++ b/src/test/java/ModuloTest.java @@ -12,7 +12,8 @@ public class ModuloTest { ParseTree tree = Helper.prepareParser("function foo(): float { return 1.0 % 2.3; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:31 Only integers are allowed for modulo.", e.getMessage()); diff --git a/src/test/java/OrTest.java b/src/test/java/OrTest.java index 07dfc33..c5511d5 100644 --- a/src/test/java/OrTest.java +++ b/src/test/java/OrTest.java @@ -13,7 +13,8 @@ public class OrTest { ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 || 2; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:30 || is only defined for bool.", e.getMessage()); diff --git a/src/test/java/ParameterTest.java b/src/test/java/ParameterTest.java deleted file mode 100644 index 9342ccc..0000000 --- a/src/test/java/ParameterTest.java +++ /dev/null @@ -1,14 +0,0 @@ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.antlr.v4.runtime.tree.ParseTree; -import org.junit.jupiter.api.Test; - -public class ParameterTest { - @Test - void typeNotDefined() { - ParseTree tree = Helper.prepareParser("struct test { a: schwurbel; } function foo(): int { return 1; } foo();"); - Exception e = assertThrows(RuntimeException.class, () -> Helper.getStructs(tree)); - assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage()); - } -} \ No newline at end of file diff --git a/src/test/java/StructDefinitionTest.java b/src/test/java/StructDefinitionTest.java new file mode 100644 index 0000000..11dce9c --- /dev/null +++ b/src/test/java/StructDefinitionTest.java @@ -0,0 +1,94 @@ +import de.hsrm.compiler.Klang.ContextAnalysis; +import de.hsrm.compiler.Klang.GetDefinitions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + +public class StructDefinitionTest { + @Test + void shouldNotThrowIfStructIsWellDefined() { + // given + var tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfStructFieldTypeIsReferringToEnum() { + // given + var tree = Helper.prepareParser("struct test { a: bar; } enum bar {A,B,C} function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfStructFieldTypeIsReferringToStruct() { + // given + var tree = Helper.prepareParser("struct a { hello: int; } struct b { world: a; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldThrowExceptionIfStructFieldNameShadowsAStruct() { + // given + var tree = Helper.prepareParser("struct bar { a: int; } struct baz { bar: int; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:36 Struct field name bar shadows a struct of the same name.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfStructFieldNameShadowsAnEnum() { + // given + var tree = Helper.prepareParser("enum bar { A, B } struct baz { bar: int; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:31 Struct field name bar shadows an enum of the same name.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfStructFieldTypeIsNotDefined() { + // given + var tree = Helper.prepareParser("struct test { a: schwurbel; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfStructFieldTypeIsReferringToAFunction() { + // given + var tree = Helper.prepareParser("struct test { a: foo; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:14 Type foo not defined.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfStructFieldNameIsDuplicated() { + // given + var tree = Helper.prepareParser("struct test { a: int; a: bool; } function foo(): int { return 1; } foo();"); + var getDefs = new GetDefinitions(new HashMap<>(), new HashMap<>(), new HashMap<>()); + + // when / then + var e = assertThrows(RuntimeException.class, () -> getDefs.visit(tree)); + assertEquals("Error in line 1:22 Duplicate struct field a in struct test.", e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/StructFieldAccessTest.java b/src/test/java/StructFieldAccessTest.java index 79b9766..9bda8f3 100644 --- a/src/test/java/StructFieldAccessTest.java +++ b/src/test/java/StructFieldAccessTest.java @@ -12,7 +12,8 @@ public class StructFieldAccessTest { ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return str.a; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:53 Variable with name str not defined.", e.getMessage()); @@ -23,7 +24,8 @@ public class StructFieldAccessTest { ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { let x: int = 0; return x.a; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:69 Variable must reference a struct but references int.", e.getMessage()); diff --git a/src/test/java/VariableAssignmentTest.java b/src/test/java/VariableAssignmentTest.java index faa338d..292e5f4 100644 --- a/src/test/java/VariableAssignmentTest.java +++ b/src/test/java/VariableAssignmentTest.java @@ -13,7 +13,8 @@ public class VariableAssignmentTest { ParseTree tree = Helper.prepareParser("function foo(): int { x = 1; return 1; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:22 Variable with name \"x\" not defined.", e.getMessage()); diff --git a/src/test/java/VariableDeclarationTest.java b/src/test/java/VariableDeclarationTest.java index a52ea2e..7727159 100644 --- a/src/test/java/VariableDeclarationTest.java +++ b/src/test/java/VariableDeclarationTest.java @@ -1,32 +1,109 @@ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.antlr.v4.runtime.tree.ParseTree; +import de.hsrm.compiler.Klang.ContextAnalysis; import org.junit.jupiter.api.Test; -import de.hsrm.compiler.Klang.ContextAnalysis; +import static org.junit.jupiter.api.Assertions.*; public class VariableDeclarationTest { @Test - void typeNotDefined() { - ParseTree tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();"); - var funcs = Helper.getFuncs(tree); - var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); - - Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + void shouldNotThrowIfDeclaredTypeIsAStruct() { + // given + var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let a: bar; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfDeclaredTypeIsAnEnum() { + // given + var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let a: bar = bar.A; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldThrowExceptionIfDeclaredNameShadowsEnumName() { + // given + var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let bar: int; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:40 Variable name bar shadows an enum of the same name.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfDeclaredNameShadowsStruct() { + // given + var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let bar: int; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:45 Variable name bar shadows a struct of the same name.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfDeclaredTypeIsNotDefined() { + // given + var tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:22 Type unk not defined.", e.getMessage()); } @Test - void variableRedeclaration() - { - ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();"); - var funcs = Helper.getFuncs(tree); - var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); - - Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + void shouldThrowExceptionIfVariableIsRedeclared() { + // given + var tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:34 Redeclaration of variable with name \"x\".", e.getMessage()); } + + @Test + void shouldThrowExceptionIfVariableReferencesEnumButIsNotInitialized() { + // given + var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let x: bar; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:40 Variable x references an enum but is not initialized.", e.getMessage()); + } } \ No newline at end of file diff --git a/src/test/java/VariableTest.java b/src/test/java/VariableTest.java index 763226c..c9d2d39 100644 --- a/src/test/java/VariableTest.java +++ b/src/test/java/VariableTest.java @@ -13,7 +13,8 @@ public class VariableTest { ParseTree tree = Helper.prepareParser("function foo(): int { return x; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:29 Variable with name \"x\" not defined.", e.getMessage()); @@ -24,7 +25,8 @@ public class VariableTest { ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; return x; } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs); + var enums = Helper.getEnums(tree); + ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); assertEquals("Error in line 1:41 Variable with name \"x\" has not been initialized.", e.getMessage());