From 7c40a50196d4142f9807a1be1888829a73e8aa03 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 05:23:09 +0100 Subject: [PATCH 01/16] add intellij config files --- .idea/.gitignore | 8 ++++++ .idea/compiler.xml | 16 +++++++++++ .idea/encodings.xml | 8 ++++++ .idea/jarRepositories.xml | 20 +++++++++++++ .../Maven__org_antlr_antlr4_runtime_4_7_2.xml | 13 +++++++++ ..._org_apiguardian_apiguardian_api_1_1_0.xml | 13 +++++++++ ..._junit_jupiter_junit_jupiter_api_5_6_0.xml | 13 +++++++++ ...nit_jupiter_junit_jupiter_engine_5_6_0.xml | 13 +++++++++ ..._platform_junit_platform_commons_1_6_0.xml | 13 +++++++++ ...t_platform_junit_platform_engine_1_6_0.xml | 13 +++++++++ ...Maven__org_opentest4j_opentest4j_1_2_0.xml | 13 +++++++++ .idea/misc.xml | 28 +++++++++++++++++++ .idea/modules.xml | 8 ++++++ .idea/vcs.xml | 6 ++++ klang.iml | 22 +++++++++++++++ 15 files changed, 207 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/libraries/Maven__org_antlr_antlr4_runtime_4_7_2.xml create mode 100644 .idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml create mode 100644 .idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_6_0.xml create mode 100644 .idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_6_0.xml create mode 100644 .idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_6_0.xml create mode 100644 .idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_6_0.xml create mode 100644 .idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 klang.iml 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 From 7af815042bd62463ce33fa51ab066b97ea292589 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 15:55:41 +0100 Subject: [PATCH 02/16] WIP: Add enum support --- .../antlr4/de/hsrm/compiler/Klang/Klang.g4 | 7 +- .../hsrm/compiler/Klang/ContextAnalysis.java | 40 ++-- .../hsrm/compiler/Klang/GetDefinitions.java | 218 ++++++++++++++++++ .../de/hsrm/compiler/Klang/GetFunctions.java | 54 ----- .../hsrm/compiler/Klang/GetStructNames.java | 37 --- .../de/hsrm/compiler/Klang/GetStructs.java | 70 ------ .../java/de/hsrm/compiler/Klang/Klang.java | 50 ++-- .../compiler/Klang/nodes/EnumDefinition.java | 19 ++ .../de/hsrm/compiler/Klang/nodes/Program.java | 9 +- .../hsrm/compiler/Klang/types/EnumType.java | 48 ++++ .../compiler/Klang/visitors/EvalVisitor.java | 12 +- .../hsrm/compiler/Klang/visitors/GenASM.java | 13 +- .../hsrm/compiler/Klang/visitors/GetVars.java | 5 + .../Klang/visitors/PrettyPrintVisitor.java | 22 ++ .../hsrm/compiler/Klang/visitors/Visitor.java | 8 +- src/test/java/AndTest.java | 3 +- src/test/java/ConstructorCallTest.java | 9 +- src/test/java/DestroyStatementTest.java | 3 +- src/test/java/FieldAssignmentTest.java | 6 +- src/test/java/FunctionCallTest.java | 9 +- src/test/java/FunctionDefinitionTest.java | 6 +- src/test/java/Helper.java | 16 +- src/test/java/ModuloTest.java | 3 +- src/test/java/OrTest.java | 3 +- src/test/java/StructFieldAccessTest.java | 6 +- src/test/java/VariableAssignmentTest.java | 3 +- src/test/java/VariableDeclarationTest.java | 6 +- src/test/java/VariableTest.java | 6 +- 28 files changed, 436 insertions(+), 255 deletions(-) create mode 100644 src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java delete mode 100644 src/main/java/de/hsrm/compiler/Klang/GetFunctions.java delete mode 100644 src/main/java/de/hsrm/compiler/Klang/GetStructNames.java delete mode 100644 src/main/java/de/hsrm/compiler/Klang/GetStructs.java create mode 100644 src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java create mode 100644 src/main/java/de/hsrm/compiler/Klang/types/EnumType.java diff --git a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 index a1a176a..a6fb2e0 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 @@ -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..53dd7fd 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -15,8 +15,9 @@ import de.hsrm.compiler.Klang.types.Type; public class ContextAnalysis extends KlangBaseVisitor { Map vars = new HashMap<>(); - Map funcs; - Map structs; + Map functionDefs; + Map structDefs; + Map enumDefs; Type currentDeclaredReturnType; String currentFunctionDefinitionName; @@ -27,9 +28,14 @@ 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 @@ -41,7 +47,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } Expression expression = (Expression) this.visit(ctx.expression()); - Program result = new Program(funcs, this.structs, expression); + Program result = new Program(funcs, structDefs, enumDefs, expression); result.type = expression.type; result.line = ctx.start.getLine(); result.col = ctx.start.getCharPositionInLine(); @@ -67,7 +73,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 +95,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; } @@ -177,7 +181,7 @@ public class ContextAnalysis extends KlangBaseVisitor { int col = ctx.start.getCharPositionInLine(); Type declaredType = Type.getByName(ctx.type_annotation().type().getText()); - if (!declaredType.isPrimitiveType() && this.structs.get(declaredType.getName()) == null) { + if (!declaredType.isPrimitiveType() && this.structDefs.get(declaredType.getName()) == null) { String error = "Type " + declaredType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } @@ -291,7 +295,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()); } @@ -338,7 +342,7 @@ public class ContextAnalysis extends KlangBaseVisitor { String structName = variableDef.type.getName(); Type resultType; try { - resultType = Helper.drillType(this.structs, structName, path, 0); + resultType = Helper.drillType(this.structDefs, structName, path, 0); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } @@ -708,7 +712,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(); @@ -733,7 +737,7 @@ public class ContextAnalysis extends KlangBaseVisitor { this.currentDeclaredReturnType = returnType; this.currentFunctionDefinitionName = name; - if (!returnType.isPrimitiveType() && this.structs.get(returnType.getName()) == null) { + if (!returnType.isPrimitiveType() && this.structDefs.get(returnType.getName()) == null) { String error = "Type " + returnType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } @@ -779,7 +783,7 @@ public class ContextAnalysis extends KlangBaseVisitor { int col = ctx.start.getCharPositionInLine(); Type type = Type.getByName(ctx.type_annotation().type().getText()); - if (!type.isPrimitiveType() && this.structs.get(type.getName()) == null) { + if (!type.isPrimitiveType() && this.structDefs.get(type.getName()) == null) { String error = "Type " + type.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } @@ -797,7 +801,7 @@ public class ContextAnalysis extends KlangBaseVisitor { int line = ctx.start.getLine(); int col = ctx.start.getCharPositionInLine(); - FunctionInformation func = this.funcs.get(name); + FunctionInformation func = this.functionDefs.get(name); if (func == null) { String error = "Function with name \"" + name + "\" not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); @@ -835,7 +839,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..405cf97 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -0,0 +1,218 @@ +package de.hsrm.compiler.Klang; + +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.types.EnumType; +import de.hsrm.compiler.Klang.types.StructType; +import de.hsrm.compiler.Klang.types.Type; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +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 enumFields = new HashSet(); + for (int i = 1; i < ctx.IDENT().size(); i++) { + var currentEnumField = ctx.IDENT(i); + var currentEnumFieldName = currentEnumField.getText(); + if (enumFields.contains(currentEnumFieldName)) { + var line = currentEnumField.getSymbol().getLine(); + var col = currentEnumField.getSymbol().getCharPositionInLine(); + var error = " Duplicate enum value " + currentEnumFieldName + " in enum " + enumName + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + enumFields.add(currentEnumFieldName); + } + + var enumDef = new EnumDefinition(enumName, enumFields.toArray(new String[0])); + enumDef.line = ctx.start.getLine(); + enumDef.col = ctx.start.getCharPositionInLine(); + enumDef.type = new EnumType(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 StructField[structFieldCount]; + var structFieldNames = new HashSet(); + 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 structFieldTypeName = currentStructField.type_annotation().type().getText(); + var structFieldLine = currentStructField.start.getLine(); + var structFieldCol = currentStructField.start.getCharPositionInLine(); + + if (structFieldNames.contains(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(structFieldTypeName); + structField.line = structFieldLine; + structField.col = structFieldCol; + + structFieldNames.add(structFieldName); + structFields[i] = structField; + } + + var structDef = new StructDefinition(structName, structFields); + structDef.line = line; + structDef.col = col; + structDef.type = new StructType(structName); + structDefs.put(structName, structDef); + + return null; + } + + @Override + public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { + var funcName = ctx.funcName.getText(); + var returnType = Type.getByName(ctx.returnType.getText()); + var parameters = new TreeMap(); + var paramCount = ctx.params.parameter().size(); + var signature = new Type[paramCount]; + + // Check that there isn't a struct or enum with the same name + if (structNames.contains(funcName) || enumNames.contains(funcName)) { + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + 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 paramType = Type.getByName(currentParam.type_annotation().type().getText()); + var paramName = currentParam.IDENT().getText(); + + if (parameters.containsKey(paramName)) { + var paramLine = currentParam.start.getLine(); + var paramCol = currentParam.start.getCharPositionInLine(); + var error = "Duplicate parameter name " + paramName + " in function " + funcName + "."; + throw new RuntimeException(Helper.getErrorPrefix(paramLine, paramCol) + error); + } + + parameters.put(paramName, paramType); + signature[i] = paramType; + } + + var information = new FunctionInformation(funcName, returnType, parameters, signature); + functionDefs.put(funcName, information); + 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..136630b 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.helper.FunctionInformation; +import de.hsrm.compiler.Klang.nodes.EnumDefinition; 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/nodes/EnumDefinition.java b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java new file mode 100644 index 0000000..7631ef1 --- /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 String[] enums; + + public EnumDefinition(String name, String[] 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/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/types/EnumType.java b/src/main/java/de/hsrm/compiler/Klang/types/EnumType.java new file mode 100644 index 0000000..292a6fb --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/types/EnumType.java @@ -0,0 +1,48 @@ +package de.hsrm.compiler.Klang.types; + +public class EnumType extends Type { + + public String name; + + public EnumType(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 enum " + getName() + " and " + that.getName()); + } + + @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 EnumType) { + var thatType = (EnumType) that; + return getName().equals(thatType.getName()); + } + + return false; + } +} 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..b1738d7 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; @@ -471,6 +466,11 @@ public class EvalVisitor implements Visitor { return null; } + @Override + public Value visit(EnumDefinition e) { + return null; + } + @Override public Value visit(StructDefinition e) { // We get these from a previous visitor 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..f9afa96 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(); @@ -778,6 +774,11 @@ public class GenASM implements Visitor { return null; } + @Override + public Void visit(EnumDefinition e) { + return null; + } + @Override public Void visit(StructDefinition e) { // We get these from a previous visitor 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..6e66d0e 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,11 @@ class GetVars implements Visitor { return null; } + @Override + public Void visit(EnumDefinition e) { + return null; + } + @Override public Void visit(StructDefinition 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..5a35800 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,22 @@ public class PrettyPrintVisitor implements Visitor { return null; } + @Override + public Void visit(EnumDefinition e) { + ex.write("enum " + e.name + " { "); + var first = true; + for(var enumName: e.enums) { + if (!first) { + ex.write(", "); + } else { + first = false; + } + ex.write(enumName); + } + ex.write(" }"); + return null; + } + @Override public Void visit(StructDefinition e) { ex.write("struct " + e.name + " {"); 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..f8d7e77 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,6 +37,7 @@ public interface Visitor { R visit(FunctionCall e); R visit(Program e); R visit(Parameter e); + R visit(EnumDefinition e); R visit(StructDefinition e); R visit(StructField e); R visit(StructFieldAccessExpression 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..665b6e9 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -13,7 +13,8 @@ public class FunctionDefinitionTest { 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); + 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:0 Type schwurbel not defined.", e.getMessage()); @@ -24,7 +25,8 @@ public class FunctionDefinitionTest { 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); + 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:0 Function foo has to return something of type int.", e.getMessage()); diff --git a/src/test/java/Helper.java b/src/test/java/Helper.java index 68b0ff6..e364ac1 100644 --- a/src/test/java/Helper.java +++ b/src/test/java/Helper.java @@ -18,19 +18,19 @@ public class Helper { public static Map getFuncs(ParseTree tree) { var functionDefinitions = new HashMap(); - new GetFunctions(functionDefinitions).visit(tree); + 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/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..acc1d1c 100644 --- a/src/test/java/VariableDeclarationTest.java +++ b/src/test/java/VariableDeclarationTest.java @@ -12,7 +12,8 @@ public class VariableDeclarationTest { 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); + 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 Type unk not defined.", e.getMessage()); @@ -24,7 +25,8 @@ public class VariableDeclarationTest { 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); + 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 Redeclaration of variable with name \"x\".", e.getMessage()); 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()); From 6e4431652c8cd15f5cf281150fb642624de21960 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 16:19:42 +0100 Subject: [PATCH 03/16] Remove FunctionInformation and replace it with FunctionDefinition. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 23 +++++---- .../hsrm/compiler/Klang/GetDefinitions.java | 48 +++++++++---------- .../java/de/hsrm/compiler/Klang/Klang.java | 4 +- .../Klang/helper/FunctionInformation.java | 19 -------- src/test/java/Helper.java | 4 +- 5 files changed, 39 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/de/hsrm/compiler/Klang/helper/FunctionInformation.java diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 53dd7fd..ced5fc1 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,9 +9,12 @@ 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.HashMap; +import java.util.Map; + public class ContextAnalysis extends KlangBaseVisitor { Map vars = new HashMap<>(); - Map functionDefs; + Map functionDefs; Map structDefs; Map enumDefs; Type currentDeclaredReturnType; @@ -29,7 +28,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } public ContextAnalysis( - Map functionDefs, + Map functionDefs, Map structDefs, Map enumDefs ) { @@ -801,15 +800,15 @@ public class ContextAnalysis extends KlangBaseVisitor { int line = ctx.start.getLine(); int col = ctx.start.getCharPositionInLine(); - FunctionInformation func = this.functionDefs.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); @@ -819,14 +818,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; diff --git a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java index 405cf97..7ce8cee 100644 --- a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -1,19 +1,18 @@ package de.hsrm.compiler.Klang; -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.types.EnumType; import de.hsrm.compiler.Klang.types.StructType; import de.hsrm.compiler.Klang.types.Type; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.TreeMap; public class GetDefinitions extends KlangBaseVisitor { - private final Map functionDefs; + private final Map functionDefs; private final Map structDefs; private final Map enumDefs; @@ -22,7 +21,7 @@ public class GetDefinitions extends KlangBaseVisitor { private Set enumNames; public GetDefinitions( - Map functionDefs, + Map functionDefs, Map structDefs, Map enumDefs ) { @@ -138,8 +137,7 @@ public class GetDefinitions extends KlangBaseVisitor { public Node visitStructDef(KlangParser.StructDefContext ctx) { var structName = ctx.structName.getText(); var structFieldCount = ctx.structField().size(); - var structFields = new StructField[structFieldCount]; - var structFieldNames = new HashSet(); + var structFields = new HashMap(); var line = ctx.start.getLine(); var col = ctx.start.getCharPositionInLine(); @@ -152,25 +150,23 @@ public class GetDefinitions extends KlangBaseVisitor { for (int i = 0; i < structFieldCount; i++) { var currentStructField = ctx.structField(i); var structFieldName = currentStructField.IDENT().getText(); - var structFieldTypeName = currentStructField.type_annotation().type().getText(); var structFieldLine = currentStructField.start.getLine(); var structFieldCol = currentStructField.start.getCharPositionInLine(); - if (structFieldNames.contains(structFieldName)) { + 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(structFieldTypeName); + structField.type = Type.getByName(currentStructField.type_annotation().type().getText()); structField.line = structFieldLine; structField.col = structFieldCol; - structFieldNames.add(structFieldName); - structFields[i] = structField; + structFields.put(structFieldName, structField); } - var structDef = new StructDefinition(structName, structFields); + var structDef = new StructDefinition(structName, structFields.values().toArray(new StructField[0])); structDef.line = line; structDef.col = col; structDef.type = new StructType(structName); @@ -182,37 +178,41 @@ public class GetDefinitions extends KlangBaseVisitor { @Override public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { var funcName = ctx.funcName.getText(); - var returnType = Type.getByName(ctx.returnType.getText()); - var parameters = new TreeMap(); var paramCount = ctx.params.parameter().size(); - var signature = new Type[paramCount]; + var parameters = new HashMap(); + 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 line = ctx.start.getLine(); - var col = ctx.start.getCharPositionInLine(); 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 paramType = Type.getByName(currentParam.type_annotation().type().getText()); var paramName = currentParam.IDENT().getText(); + var paramLine = currentParam.start.getLine(); + var paramCol = currentParam.start.getCharPositionInLine(); if (parameters.containsKey(paramName)) { - var paramLine = currentParam.start.getLine(); - var paramCol = currentParam.start.getCharPositionInLine(); var error = "Duplicate parameter name " + paramName + " in function " + funcName + "."; throw new RuntimeException(Helper.getErrorPrefix(paramLine, paramCol) + error); } - parameters.put(paramName, paramType); - signature[i] = paramType; + 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 information = new FunctionInformation(funcName, returnType, parameters, signature); - functionDefs.put(funcName, information); + var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null); + functionDef.type = Type.getByName(ctx.returnType.getText()); + functionDef.line = line; + functionDef.col = col; + functionDefs.put(funcName, functionDef); return null; } } diff --git a/src/main/java/de/hsrm/compiler/Klang/Klang.java b/src/main/java/de/hsrm/compiler/Klang/Klang.java index 136630b..ca8094c 100644 --- a/src/main/java/de/hsrm/compiler/Klang/Klang.java +++ b/src/main/java/de/hsrm/compiler/Klang/Klang.java @@ -1,7 +1,7 @@ package de.hsrm.compiler.Klang; -import de.hsrm.compiler.Klang.helper.FunctionInformation; 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.EvalVisitor; @@ -90,7 +90,7 @@ public class Klang { // Context Analysis and DAST generation Node root; - var functionDefs = new HashMap(); + var functionDefs = new HashMap(); var structDefs = new HashMap(); var enumDefs = new HashMap(); try { 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/test/java/Helper.java b/src/test/java/Helper.java index e364ac1..eef2a8c 100644 --- a/src/test/java/Helper.java +++ b/src/test/java/Helper.java @@ -16,8 +16,8 @@ public class Helper { return parser.parse(); } - public static Map getFuncs(ParseTree tree) { - var functionDefinitions = new HashMap(); + public static Map getFuncs(ParseTree tree) { + var functionDefinitions = new HashMap(); new GetDefinitions(functionDefinitions, new HashMap<>(), new HashMap<>()).visit(tree); return functionDefinitions; } From 9a58afb550c010fa82f6f838217385f5676650da Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 17:21:35 +0100 Subject: [PATCH 04/16] Implement StructField type checking in ContextAnalysis. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 61 +++++++++++++++--- src/test/java/ParameterTest.java | 14 ----- src/test/java/StructDefinitionTest.java | 62 +++++++++++++++++++ 3 files changed, 115 insertions(+), 22 deletions(-) delete mode 100644 src/test/java/ParameterTest.java create mode 100644 src/test/java/StructDefinitionTest.java diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index ced5fc1..4ba5b6f 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -39,18 +39,23 @@ public class ContextAnalysis extends KlangBaseVisitor { @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, structDefs, enumDefs, 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 @@ -727,6 +732,46 @@ public class ContextAnalysis extends KlangBaseVisitor { return n; } + @Override + 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(); + + 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 super.visitStructDef(ctx); + } + + @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); + } + + var structField = new StructField(structFieldName); + structField.type = structFieldType; + structField.line = line; + structField.col = col; + + return structField; + } + @Override public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { String name = ctx.funcName.getText(); 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..b3150f3 --- /dev/null +++ b/src/test/java/StructDefinitionTest.java @@ -0,0 +1,62 @@ +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 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 From 3b928d621babfdaef1e7e508674be50b39d05044 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 17:47:58 +0100 Subject: [PATCH 05/16] Refactor FunctionDefinition and Parameter context analysis and extend the type check to include enums. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 71 ++++++++------- src/test/java/FunctionDefinitionTest.java | 90 ++++++++++++++----- 2 files changed, 106 insertions(+), 55 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 4ba5b6f..825a5bc 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -774,69 +774,72 @@ 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; + var name = ctx.funcName.getText(); + var returnType = Type.getByName(ctx.returnType.type().getText()); + currentDeclaredReturnType = returnType; + currentFunctionDefinitionName = name; - if (!returnType.isPrimitiveType() && this.structDefs.get(returnType.getName()) == null) { - String error = "Type " + returnType.getName() + " not defined."; + 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.structDefs.get(type.getName()) == null) { - String error = "Type " + type.getName() + " not defined."; + 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(); + String error = "Type " + parameterType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - Parameter result = new Parameter(name); - result.type = type; - result.line = line; - result.col = col; - return result; + var parameter = new Parameter(parameterName); + parameter.type = parameterType; + parameter.line = ctx.start.getLine(); + parameter.col = ctx.start.getCharPositionInLine(); + + return parameter; } @Override diff --git a/src/test/java/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java index 665b6e9..0d79c6a 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -1,34 +1,82 @@ -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; 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); - 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: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();"); + 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); - 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: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()); } } \ No newline at end of file From 6fd3f5a2e69d74c57304b6cf1cd158b3cfc8116c Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 19:14:04 +0100 Subject: [PATCH 06/16] Make it possible to use an enum in an expression (i.e. selecting one of the enum values: Foo.A) --- .../antlr4/de/hsrm/compiler/Klang/Klang.g4 | 2 +- .../hsrm/compiler/Klang/ContextAnalysis.java | 62 +++++++++++++------ .../hsrm/compiler/Klang/GetDefinitions.java | 9 ++- .../expressions/EnumAccessExpression.java | 18 ++++++ ...ssion.java => MemberAccessExpression.java} | 4 +- .../types/{EnumType.java => NamedType.java} | 11 ++-- .../hsrm/compiler/Klang/types/StructType.java | 54 ---------------- .../de/hsrm/compiler/Klang/types/Type.java | 4 +- .../compiler/Klang/visitors/EvalVisitor.java | 7 ++- .../hsrm/compiler/Klang/visitors/GenASM.java | 7 ++- .../hsrm/compiler/Klang/visitors/GetVars.java | 7 ++- .../Klang/visitors/PrettyPrintVisitor.java | 11 +++- .../hsrm/compiler/Klang/visitors/Visitor.java | 3 +- src/test/java/FunctionDefinitionTest.java | 2 +- 14 files changed, 107 insertions(+), 94 deletions(-) create mode 100644 src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java rename src/main/java/de/hsrm/compiler/Klang/nodes/expressions/{StructFieldAccessExpression.java => MemberAccessExpression.java} (69%) rename src/main/java/de/hsrm/compiler/Klang/types/{EnumType.java => NamedType.java} (78%) delete mode 100644 src/main/java/de/hsrm/compiler/Klang/types/StructType.java diff --git a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 index a6fb2e0..efe054d 100644 --- a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 +++ b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 @@ -82,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 diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 825a5bc..f2478c3 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -9,8 +9,7 @@ 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.HashMap; -import java.util.Map; +import java.util.*; public class ContextAnalysis extends KlangBaseVisitor { Map vars = new HashMap<>(); @@ -319,43 +318,68 @@ 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); + if (Arrays.stream(enumDef.enums).noneMatch(e -> e.equals(enumValueName))) { + var error = "Unknown enum value " + enumValueName + " of enum " + enumDef.name + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + var enumAccessExpression = new EnumAccessExpression(baseName, enumValueName); + 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.structDefs, 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 @@ -749,7 +773,7 @@ public class ContextAnalysis extends KlangBaseVisitor { structDef.line = line; structDef.col = col; - return super.visitStructDef(ctx); + return structDef; } @Override diff --git a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java index 7ce8cee..13136e9 100644 --- a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -2,8 +2,7 @@ package de.hsrm.compiler.Klang; import de.hsrm.compiler.Klang.helper.Helper; import de.hsrm.compiler.Klang.nodes.*; -import de.hsrm.compiler.Klang.types.EnumType; -import de.hsrm.compiler.Klang.types.StructType; +import de.hsrm.compiler.Klang.types.NamedType; import de.hsrm.compiler.Klang.types.Type; import java.util.HashMap; @@ -127,7 +126,7 @@ public class GetDefinitions extends KlangBaseVisitor { var enumDef = new EnumDefinition(enumName, enumFields.toArray(new String[0])); enumDef.line = ctx.start.getLine(); enumDef.col = ctx.start.getCharPositionInLine(); - enumDef.type = new EnumType(enumName); + enumDef.type = new NamedType(enumName); enumDefs.put(enumName, enumDef); return null; @@ -169,7 +168,7 @@ public class GetDefinitions extends KlangBaseVisitor { var structDef = new StructDefinition(structName, structFields.values().toArray(new StructField[0])); structDef.line = line; structDef.col = col; - structDef.type = new StructType(structName); + structDef.type = new NamedType(structName); structDefs.put(structName, structDef); return null; @@ -209,7 +208,7 @@ public class GetDefinitions extends KlangBaseVisitor { } var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null); - functionDef.type = Type.getByName(ctx.returnType.getText()); + functionDef.type = Type.getByName(ctx.returnType.type().getText()); functionDef.line = line; functionDef.col = col; functionDefs.put(funcName, functionDef); 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..476f9a5 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java @@ -0,0 +1,18 @@ +package de.hsrm.compiler.Klang.nodes.expressions; + +import de.hsrm.compiler.Klang.visitors.Visitor; + +public class EnumAccessExpression extends Expression { + public String enumName; + public String enumValueName; + + public EnumAccessExpression(String enumName, String enumValueName) { + this.enumName = enumName; + this.enumValueName = enumValueName; + } + + @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/EnumType.java b/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java similarity index 78% rename from src/main/java/de/hsrm/compiler/Klang/types/EnumType.java rename to src/main/java/de/hsrm/compiler/Klang/types/NamedType.java index 292a6fb..d3e27b1 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/EnumType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java @@ -1,10 +1,9 @@ package de.hsrm.compiler.Klang.types; -public class EnumType extends Type { - +public class NamedType extends Type { public String name; - public EnumType(String name) { + public NamedType(String name) { this.name = name; } @@ -19,7 +18,7 @@ public class EnumType extends Type { return this; } - throw new RuntimeException("Type mismatch: cannot combine enum " + getName() + " and " + that.getName()); + throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName()); } @Override @@ -38,8 +37,8 @@ public class EnumType extends Type { return true; } - if (that instanceof EnumType) { - var thatType = (EnumType) that; + if (that instanceof NamedType) { + var thatType = (NamedType) that; return getName().equals(thatType.getName()); } 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..3a40d18 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,7 @@ package de.hsrm.compiler.Klang.types; +import java.util.Set; + public abstract class Type { // Returns an instance of IntegerType @@ -26,7 +28,7 @@ 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); } } 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 b1738d7..81ec6c7 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -484,7 +484,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 +496,11 @@ public class EvalVisitor implements Visitor { return currentValue; } + @Override + public Value visit(EnumAccessExpression e) { + return null; + } + @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 f9afa96..d824e56 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -792,7 +792,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); @@ -817,6 +817,11 @@ public class GenASM implements Visitor { return null; } + @Override + public Void visit(EnumAccessExpression e) { + 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 6e66d0e..15f94ca 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java @@ -250,7 +250,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 5a35800..fdd89be 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java @@ -420,7 +420,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("."); @@ -429,6 +429,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 f8d7e77..4337ee5 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java @@ -40,7 +40,8 @@ public interface Visitor { R visit(EnumDefinition 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/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java index 0d79c6a..79299a1 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -10,7 +10,7 @@ public class FunctionDefinitionTest { @Test void shouldNotThrowIfReturnTypeIsReferringToAnEnum() { // given - var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: int): bar { return bar.A; } foo();"); + 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), From 22634c9652af57d5c6ca12d3bf7c26e0b17765c3 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 23:08:38 +0100 Subject: [PATCH 07/16] Use LinkedHashMaps and LinkedHashSets to preserve the order of parameters and struct fields. --- .../java/de/hsrm/compiler/Klang/GetDefinitions.java | 11 ++++------- src/test/java/FunctionDefinitionTest.java | 4 +--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java index 13136e9..fd7ccf6 100644 --- a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -5,10 +5,7 @@ import de.hsrm.compiler.Klang.nodes.*; import de.hsrm.compiler.Klang.types.NamedType; import de.hsrm.compiler.Klang.types.Type; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; public class GetDefinitions extends KlangBaseVisitor { private final Map functionDefs; @@ -110,7 +107,7 @@ public class GetDefinitions extends KlangBaseVisitor { } // IDENT() includes the enumName as the first entry, which we skip - var enumFields = new HashSet(); + var enumFields = new LinkedHashSet(); for (int i = 1; i < ctx.IDENT().size(); i++) { var currentEnumField = ctx.IDENT(i); var currentEnumFieldName = currentEnumField.getText(); @@ -136,7 +133,7 @@ public class GetDefinitions extends KlangBaseVisitor { public Node visitStructDef(KlangParser.StructDefContext ctx) { var structName = ctx.structName.getText(); var structFieldCount = ctx.structField().size(); - var structFields = new HashMap(); + var structFields = new LinkedHashMap(); var line = ctx.start.getLine(); var col = ctx.start.getCharPositionInLine(); @@ -178,7 +175,7 @@ public class GetDefinitions extends KlangBaseVisitor { public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) { var funcName = ctx.funcName.getText(); var paramCount = ctx.params.parameter().size(); - var parameters = new HashMap(); + var parameters = new LinkedHashMap(); var line = ctx.start.getLine(); var col = ctx.start.getCharPositionInLine(); diff --git a/src/test/java/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java index 79299a1..85295be 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -1,7 +1,5 @@ -import org.antlr.v4.runtime.tree.ParseTree; -import org.junit.jupiter.api.Test; - import de.hsrm.compiler.Klang.ContextAnalysis; +import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; From f77d6a002d458319cb74eafb9e1c7824e3f94f9d Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 23:17:43 +0100 Subject: [PATCH 08/16] Check that a parameter name of a function definition does not shadow an enum definition. --- .../de/hsrm/compiler/Klang/ContextAnalysis.java | 16 +++++++++++++++- src/test/java/FunctionDefinitionTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index f2478c3..9dc5fe6 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -851,10 +851,24 @@ public class ContextAnalysis extends KlangBaseVisitor { var parameterName = ctx.IDENT().getText(); var parameterType = Type.getByName(ctx.type_annotation().type().getText()); + 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); + } + + 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(); - String error = "Type " + parameterType.getName() + " not defined."; + var error = "Type " + parameterType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } diff --git a/src/test/java/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java index 85295be..b6086fc 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -77,4 +77,19 @@ public class FunctionDefinitionTest { 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 From 30dfbbbbba21acb58badbaf4c66bb8ddb3dd1b68 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 23:33:12 +0100 Subject: [PATCH 09/16] Check that a variable name of variable declaration does not shadow an enum definition. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 47 +++++--- src/test/java/VariableDeclarationTest.java | 104 ++++++++++++++---- 2 files changed, 111 insertions(+), 40 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 9dc5fe6..49deeb3 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -179,43 +179,54 @@ 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.structDefs.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); + 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 diff --git a/src/test/java/VariableDeclarationTest.java b/src/test/java/VariableDeclarationTest.java index acc1d1c..5dba488 100644 --- a/src/test/java/VariableDeclarationTest.java +++ b/src/test/java/VariableDeclarationTest.java @@ -1,34 +1,94 @@ -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); - var enums = Helper.getEnums(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); - - 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; 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); - var enums = Helper.getEnums(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); - - 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()); } } \ No newline at end of file From 2768b4429c6ee3116ebca19490d7ca70ce8a61e5 Mon Sep 17 00:00:00 2001 From: nitrix Date: Wed, 15 Mar 2023 23:48:57 +0100 Subject: [PATCH 10/16] Check that a struct field name of a struct declaration does not shadow an enum or a struct definition. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 10 ++++++ src/test/java/StructDefinitionTest.java | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 49deeb3..c4d3da4 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -799,6 +799,16 @@ public class ContextAnalysis extends KlangBaseVisitor { 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; diff --git a/src/test/java/StructDefinitionTest.java b/src/test/java/StructDefinitionTest.java index b3150f3..11dce9c 100644 --- a/src/test/java/StructDefinitionTest.java +++ b/src/test/java/StructDefinitionTest.java @@ -27,6 +27,38 @@ public class StructDefinitionTest { 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 From 55a5b8f54aa04ed9531703895829371cec23e745 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 16 Mar 2023 00:01:31 +0100 Subject: [PATCH 11/16] Make sure that a variable that references an enum has to be initialized. --- .../de/hsrm/compiler/Klang/ContextAnalysis.java | 5 +++++ src/test/java/VariableDeclarationTest.java | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index c4d3da4..9cf7dec 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -216,6 +216,11 @@ public class ContextAnalysis extends KlangBaseVisitor { variableDeclaration = new VariableDeclaration(variableName, (Expression) expression); variableDeclaration.initialized = true; } else { + 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); } diff --git a/src/test/java/VariableDeclarationTest.java b/src/test/java/VariableDeclarationTest.java index 5dba488..7727159 100644 --- a/src/test/java/VariableDeclarationTest.java +++ b/src/test/java/VariableDeclarationTest.java @@ -21,7 +21,7 @@ public class VariableDeclarationTest { @Test void shouldNotThrowIfDeclaredTypeIsAnEnum() { // given - var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let a: bar; return 1; } foo();"); + 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), @@ -91,4 +91,19 @@ public class VariableDeclarationTest { 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 From 77fe360ffa7d693f8ec641305ba192d9f3652d81 Mon Sep 17 00:00:00 2001 From: nitrix Date: Mon, 20 Mar 2023 19:10:40 +0100 Subject: [PATCH 12/16] Evaluate: Implement evaluation for enums. --- .../compiler/Klang/types/BooleanType.java | 6 +++++ .../hsrm/compiler/Klang/types/FloatType.java | 6 +++++ .../compiler/Klang/types/IntegerType.java | 7 ++++++ .../hsrm/compiler/Klang/types/NamedType.java | 12 ++++++++++ .../hsrm/compiler/Klang/types/NullType.java | 7 ++++++ .../de/hsrm/compiler/Klang/types/Type.java | 8 ++++++- .../compiler/Klang/visitors/EvalVisitor.java | 24 ++++--------------- 7 files changed, 50 insertions(+), 20 deletions(-) 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 index d3e27b1..8ea4bdf 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/NamedType.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 class NamedType extends Type { public String name; @@ -21,6 +28,11 @@ public class NamedType extends Type { 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; 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/Type.java b/src/main/java/de/hsrm/compiler/Klang/types/Type.java index 3a40d18..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,6 +1,11 @@ package de.hsrm.compiler.Klang.types; -import java.util.Set; +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 { @@ -34,6 +39,7 @@ public abstract class Type { 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 81ec6c7..55af3ad 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -46,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 @@ -498,7 +484,7 @@ public class EvalVisitor implements Visitor { @Override public Value visit(EnumAccessExpression e) { - return null; + return new Value(e.enumValueName, e.type); } @Override From 05945421677138e89be9f96297e0e5dc2ddf7be8 Mon Sep 17 00:00:00 2001 From: nitrix Date: Mon, 20 Mar 2023 19:30:07 +0100 Subject: [PATCH 13/16] Enums: Make EnumDefinition use EnumValues instead of Strings as children. This allows us to store the index of the enum value along the name. The index can be used to compare two enum values in assembler. Later on this might be used to enable users of KLang to set arbitrary values as the index of an enum value. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 2 +- .../de/hsrm/compiler/Klang/GetDefinitions.java | 18 ++++++++++++------ .../compiler/Klang/nodes/EnumDefinition.java | 4 ++-- .../hsrm/compiler/Klang/nodes/EnumValue.java | 18 ++++++++++++++++++ .../compiler/Klang/visitors/EvalVisitor.java | 5 +++++ .../hsrm/compiler/Klang/visitors/GenASM.java | 5 +++++ .../hsrm/compiler/Klang/visitors/GetVars.java | 5 +++++ .../Klang/visitors/PrettyPrintVisitor.java | 10 ++++++++-- .../hsrm/compiler/Klang/visitors/Visitor.java | 1 + 9 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 src/main/java/de/hsrm/compiler/Klang/nodes/EnumValue.java diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 9cf7dec..b3c62fe 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -355,7 +355,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } var enumValueName = path.get(0); - if (Arrays.stream(enumDef.enums).noneMatch(e -> e.equals(enumValueName))) { + if (Arrays.stream(enumDef.enums).noneMatch(e -> e.value.equals(enumValueName))) { var error = "Unknown enum value " + enumValueName + " of enum " + enumDef.name + "."; 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 index fd7ccf6..d5cf9e4 100644 --- a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -107,20 +107,26 @@ public class GetDefinitions extends KlangBaseVisitor { } // IDENT() includes the enumName as the first entry, which we skip - var enumFields = new LinkedHashSet(); + var enumValues = new LinkedHashMap(); for (int i = 1; i < ctx.IDENT().size(); i++) { var currentEnumField = ctx.IDENT(i); var currentEnumFieldName = currentEnumField.getText(); - if (enumFields.contains(currentEnumFieldName)) { - var line = currentEnumField.getSymbol().getLine(); - var col = currentEnumField.getSymbol().getCharPositionInLine(); + 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); } - enumFields.add(currentEnumFieldName); + + var enumValue = new EnumValue(currentEnumFieldName, i - 1); + enumValue.line = line; + enumValue.col = col; + + enumValues.put(currentEnumFieldName, enumValue); } - var enumDef = new EnumDefinition(enumName, enumFields.toArray(new String[0])); + 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); diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java index 7631ef1..4971b2d 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/EnumDefinition.java @@ -5,9 +5,9 @@ import de.hsrm.compiler.Klang.visitors.Visitor; public class EnumDefinition extends Node { public String name; - public String[] enums; + public EnumValue[] enums; - public EnumDefinition(String name, String[] enums) { + public EnumDefinition(String name, EnumValue[] enums) { this.name = name; this.enums = enums; } 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/visitors/EvalVisitor.java b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java index 55af3ad..ed24dbc 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -457,6 +457,11 @@ public class EvalVisitor implements Visitor { return null; } + @Override + public Value visit(EnumValue e) { + return null; + } + @Override public Value visit(StructDefinition e) { // We get these from a previous visitor 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 d824e56..e9572c6 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -779,6 +779,11 @@ public class GenASM implements Visitor { return null; } + @Override + public Void visit(EnumValue e) { + return null; + } + @Override public Void visit(StructDefinition e) { // We get these from a previous visitor 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 15f94ca..33e6b6b 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java @@ -239,6 +239,11 @@ class GetVars implements Visitor { return null; } + @Override + public Void visit(EnumValue e) { + return null; + } + @Override public Void visit(StructDefinition 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 fdd89be..252797b 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java @@ -387,18 +387,24 @@ public class PrettyPrintVisitor implements Visitor { public Void visit(EnumDefinition e) { ex.write("enum " + e.name + " { "); var first = true; - for(var enumName: e.enums) { + for(var enumValue: e.enums) { if (!first) { ex.write(", "); } else { first = false; } - ex.write(enumName); + 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 + " {"); 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 4337ee5..f56e8b6 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java @@ -38,6 +38,7 @@ public interface Visitor { 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(MemberAccessExpression e); From 198bd74a47ab6cf7e1da2a5dba9fe62c45cc7508 Mon Sep 17 00:00:00 2001 From: nitrix Date: Mon, 20 Mar 2023 19:54:48 +0100 Subject: [PATCH 14/16] Enums: Make the EnumAccessExpression save a reference to the EnumValue it is referencing. This can be used during assembler generation to easily find the correct EnumValue for a given EnumAccessExpression. --- .../de/hsrm/compiler/Klang/ContextAnalysis.java | 13 ++++++++----- .../nodes/expressions/EnumAccessExpression.java | 10 +++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index b3c62fe..f752bce 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -355,12 +355,15 @@ public class ContextAnalysis extends KlangBaseVisitor { } var enumValueName = path.get(0); - if (Arrays.stream(enumDef.enums).noneMatch(e -> e.value.equals(enumValueName))) { - var error = "Unknown enum value " + enumValueName + " of enum " + enumDef.name + "."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } + 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); + var enumAccessExpression = new EnumAccessExpression(baseName, enumValueName, enumValue); enumAccessExpression.type = enumDef.type; enumAccessExpression.line = line; enumAccessExpression.col = col; 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 index 476f9a5..d46c715 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/EnumAccessExpression.java @@ -1,14 +1,22 @@ 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) { + public EnumAccessExpression( + String enumName, + String enumValueName, + EnumValue enumValue + ) { this.enumName = enumName; this.enumValueName = enumValueName; + this.enumValue = enumValue; } @Override From ea1c04ae0a7204e25d54abd20d61b05ead6c7256 Mon Sep 17 00:00:00 2001 From: nitrix Date: Mon, 20 Mar 2023 19:55:37 +0100 Subject: [PATCH 15/16] Build: Add a main manifest attribute to the generated jar. This makes it possible to directly execute the packaged jar. --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) 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 + + From e835bd0f06b8f37f41ad957e6b126edd4baabbf8 Mon Sep 17 00:00:00 2001 From: nitrix Date: Mon, 20 Mar 2023 21:05:24 +0100 Subject: [PATCH 16/16] GenASM: Make GenASM quietly rewrite a user's function if it's called main. We generate our own main function that executes the user's specified expression at the end of his file. This auto generated function has to be called "main" in order for it to be executed on startup. If the user chooses to call one of his own functions "main" our auto generated function would collide with the user's function. That's why we quietly rewrite the user's function to "main_by_user". This way we prevent a collision. Note for the future: We should change our Syntax to allow for no expression as the last definition of a file. This way the user can choose if a particular source file needs to contain a main function or not (just like c does it). This is also one of the requirements for modules to work. --- .../de/hsrm/compiler/Klang/visitors/GenASM.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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 e9572c6..5bc32f2 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -567,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; @@ -744,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; } @@ -769,7 +777,7 @@ 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; } @@ -824,6 +832,9 @@ public class GenASM implements Visitor { @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; }