diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index f752bce..a2323a9 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -8,10 +8,12 @@ import de.hsrm.compiler.Klang.nodes.loops.ForLoop; import de.hsrm.compiler.Klang.nodes.loops.WhileLoop; import de.hsrm.compiler.Klang.nodes.statements.*; import de.hsrm.compiler.Klang.types.Type; +import org.antlr.v4.runtime.tree.TerminalNode; import java.util.*; public class ContextAnalysis extends KlangBaseVisitor { + Map params = new HashMap<>(); Map vars = new HashMap<>(); Map functionDefs; Map structDefs; @@ -199,8 +201,8 @@ public class ContextAnalysis extends KlangBaseVisitor { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - if (vars.get(variableName) != null) { - var error = "Redeclaration of variable with name \"" + variableName + "\"."; + if (vars.get(variableName) != null || params.get(variableName) != null) { + var error = "Redeclaration of variable or parameter with name \"" + variableName + "\"."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } @@ -236,31 +238,27 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitVariable_assignment(KlangParser.Variable_assignmentContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var name = ctx.IDENT().getText(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - VariableDeclaration var = this.vars.get(name); - if (var == null) { - String error = "Variable with name \"" + name + "\" not defined."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } + var variableOrParameter = getVariableOrParameter(name, line, col); // Evaluate the expression - Expression expression = (Expression) this.visit(ctx.expression()); + var expression = (Expression) visit(ctx.expression()); // Make sure expression can be assigned to the variable try { - expression.type.combine(var.type); + expression.type.combine(variableOrParameter.type); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } // Since we assigned a value to this variable, we can consider it initialized - var.initialized = true; + variableOrParameter.initialized = true; // Create a new node and add the type of the expression to it - Node result = new VariableAssignment(name, expression); + var result = new VariableAssignment(name, expression); result.line = line; result.col = col; return result; @@ -272,8 +270,7 @@ public class ContextAnalysis extends KlangBaseVisitor { ReturnStatement result = new ReturnStatement(expression); // Check if this expression is a tail recursion - if (expression instanceof FunctionCall) { - var funCall = (FunctionCall) expression; + if (expression instanceof FunctionCall funCall) { if (funCall.name.equals(this.currentFunctionDefinitionName)) { // Flag this function call funCall.isTailRecursive = true; @@ -288,46 +285,24 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitField_assignment(KlangParser.Field_assignmentContext 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]; + var varName = ctx.IDENT(0).getText(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + var path = createStructPath(ctx.IDENT()); - for (int i = 1; i < ctx.IDENT().size(); i++) { - path[i - 1] = ctx.IDENT(i).getText(); - } + var variableOrParameter = getVariableOrParameter(varName, line, col); + ensureReferencesStruct(variableOrParameter, line, col); + var fieldType = drillType(variableOrParameter, path, line, col); - // Get the referenced variable, make sure it is defined - var variableDef = this.vars.get(varName); - if (variableDef == null) { - String error = "Variable with name " + varName + " 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() + "."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } - - // Get the type of the result of this expression - String structName = variableDef.type.getName(); - Type fieldType; - try { - fieldType = Helper.drillType(this.structDefs, structName, path, 0); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); - } - - // Get the expression and make sure the type combines properly - Expression expression = (Expression) this.visit(ctx.expression()); + // Visit the expression and make sure the type combines properly + var expression = (Expression) visit(ctx.expression()); try { fieldType.combine(expression.type); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } - Node result = new FieldAssignment(varName, structName, path, expression); + var result = new FieldAssignment(varName, variableOrParameter.type.getName(), path, expression); result.col = col; result.line = line; return result; @@ -338,23 +313,17 @@ public class ContextAnalysis extends KlangBaseVisitor { 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.add(ctx.IDENT(i).getText()); - } + var path = createStructPath(ctx.IDENT()); // Determine if the base name points to an enum or a variable var enumDef = enumDefs.get(baseName); if (enumDef != null) { - if (path.size() != 1) { + if (path.length != 1) { var error = "Illegal access to enum " + enumDef.name + "."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - var enumValueName = path.get(0); + var enumValueName = path[0]; var enumValue = Arrays.stream(enumDef.enums) .filter(e -> e.value.equals(enumValueName)) .findFirst() @@ -371,29 +340,11 @@ public class ContextAnalysis extends KlangBaseVisitor { return enumAccessExpression; } - // Get the referenced variable, make sure it is defined - var variableDef = vars.get(baseName); - if (variableDef == null) { - var error = "Variable with name " + baseName + " not defined."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } + var variableOrParameter = getVariableOrParameter(baseName, line, col); + ensureReferencesStruct(variableOrParameter, line, col); + var resultType = drillType(variableOrParameter, path, line, col); - // Make sure it references a struct - if (variableDef.type.isPrimitiveType()) { - 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 - var structName = variableDef.type.getName(); - Type resultType; - try { - resultType = Helper.drillType(structDefs, structName, path.toArray(new String[0]), 0); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); - } - - var memberAccessExpression = new MemberAccessExpression(baseName, structName, path.toArray(new String[0])); + var memberAccessExpression = new MemberAccessExpression(baseName, variableOrParameter.type.getName(), path); memberAccessExpression.type = resultType; memberAccessExpression.line = line; memberAccessExpression.col = col; @@ -711,24 +662,20 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitVariable(KlangParser.VariableContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var variableName = ctx.IDENT().getText(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - VariableDeclaration var = this.vars.get(name); - if (var == null) { - String error = "Variable with name \"" + name + "\" not defined."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } + var variableOrParameter = getVariableOrParameter(variableName, line, col); // Make sure the variable has been initialized before it can be used - if (!var.initialized) { - String error = "Variable with name \"" + name + "\" has not been initialized."; + if (!variableOrParameter.initialized) { + var error = "Variable with name \"" + variableName + "\" has not been initialized."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - Variable result = new Variable(ctx.IDENT().getText()); - result.type = var.type; + var result = new Variable(ctx.IDENT().getText()); + result.type = variableOrParameter.type; result.line = line; result.col = col; return result; @@ -839,23 +786,23 @@ public class ContextAnalysis extends KlangBaseVisitor { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - // Create a new set for the variables of the current function + // Create a new set for the variables and parameters of the current function // this will be filled in the variable declaration visitor as well vars = new HashMap<>(); + params = new HashMap<>(); + - // Process the parameter list by visiting every parameter in it var paramCount = ctx.params.parameter().size(); - var params = new Parameter[paramCount]; + var functionParameters = new Parameter[paramCount]; // the list of parameters that get passed to FunctionDefinition for (int i = 0; i < paramCount; i++) { - // Add the parameter to the list of parameters - var param = (Parameter) visit(ctx.params.parameter(i)); - params[i] = param; + functionParameters[i] = (Parameter) visit(ctx.params.parameter(i)); - // add the param as a variable - var var = new VariableDeclaration(param.name); - var.initialized = true; // parameters can always be considered initialized - var.type = param.type; - vars.put(param.name, var); + // add the param as a variable to the global parameter map so that + // child nodes can access them. + var param = new VariableDeclaration(functionParameters[i].name); + param.initialized = true; // parameters can always be considered initialized + param.type = functionParameters[i].type; + params.put(param.name, param); } // Visit the block, make sure that a return value is guaranteed @@ -867,7 +814,7 @@ public class ContextAnalysis extends KlangBaseVisitor { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - var functionDef = new FunctionDefinition(name, params, (Block) block); + var functionDef = new FunctionDefinition(name, functionParameters, vars.values().toArray(new VariableDeclaration[0]), (Block) block); functionDef.type = returnType; functionDef.line = ctx.start.getLine(); functionDef.col = ctx.start.getCharPositionInLine(); @@ -988,19 +935,52 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitDestroy_statement(KlangParser.Destroy_statementContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - - VariableDeclaration var = this.vars.get(name); - if (var == null) { - String error = "Variable with name \"" + name + "\" not defined."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); - } + var varName = ctx.IDENT().getText(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - Node result = new DestructorCall(name); + var variableOrParameter = getVariableOrParameter(varName, line, col); + ensureReferencesStruct(variableOrParameter, line, col); + + var result = new DestructorCall(varName); result.line = line; result.col = col; return result; } + + private VariableDeclaration getVariableOrParameter(String name, int line, int col) { + var variable = vars.get(name); + var parameter = params.get(name); + if (variable == null && parameter == null) { + var error = "Variable or parameter with name \"" + name + "\" not defined."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + return variable != null ? variable : parameter; + } + + private void ensureReferencesStruct(VariableDeclaration variableOrParameter, int line, int col) { + if (variableOrParameter.type.isPrimitiveType() || enumDefs.containsKey(variableOrParameter.type.getName())) { + var error = "Variable or parameter must reference a struct but references " + variableOrParameter.type.getName() + "."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + } + + private Type drillType(VariableDeclaration variableOrParameter, String[] path, int line, int col) { + try { + var structName = variableOrParameter.type.getName(); + return Helper.drillType(structDefs, structName, path, 0); + } catch (Exception e) { + throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); + } + } + + private String[] createStructPath(List identifiers) { + // Create a list of member names. This excludes the first entry as it is the base name. + var path = new String[identifiers.size() - 1]; + for (int i = 1; i < identifiers.size(); i++) { + path[i - 1] = identifiers.get(i).getText(); + } + return path; + } } \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java index d5cf9e4..7445d23 100644 --- a/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java +++ b/src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java @@ -210,7 +210,7 @@ public class GetDefinitions extends KlangBaseVisitor { parameters.put(paramName, parameter); } - var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null); + var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null, null); functionDef.type = Type.getByName(ctx.returnType.type().getText()); functionDef.line = line; functionDef.col = col; diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/FunctionDefinition.java b/src/main/java/de/hsrm/compiler/Klang/nodes/FunctionDefinition.java index 45c1ca8..e008174 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/FunctionDefinition.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/FunctionDefinition.java @@ -1,17 +1,20 @@ package de.hsrm.compiler.Klang.nodes; +import de.hsrm.compiler.Klang.nodes.statements.VariableDeclaration; import de.hsrm.compiler.Klang.visitors.Visitor; public class FunctionDefinition extends Node { public String name; public Parameter[] parameters; + public VariableDeclaration[] localVariables; public Block block; - public FunctionDefinition(String name, Parameter[] parameters, Block block) { + public FunctionDefinition(String name, Parameter[] parameters, VariableDeclaration[] localVariables, Block block) { this.name = name; this.parameters = parameters; this.block = block; + this.localVariables = localVariables; } @Override 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 3dd028a..888be8f 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/NamedType.java @@ -1,11 +1,6 @@ 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; @@ -49,8 +44,7 @@ public class NamedType extends Type { return true; } - if (that instanceof NamedType) { - var thatType = (NamedType) that; + if (that instanceof NamedType thatType) { return getName().equals(thatType.getName()); } diff --git a/src/test/java/DestroyStatementTest.java b/src/test/java/DestroyStatementTest.java index 523c4b5..2051bcd 100644 --- a/src/test/java/DestroyStatementTest.java +++ b/src/test/java/DestroyStatementTest.java @@ -17,6 +17,6 @@ public class DestroyStatementTest { 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()); + assertEquals("Error in line 1:45 Variable or parameter with name \"x\" not defined.", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/FieldAssignmentTest.java b/src/test/java/FieldAssignmentTest.java index 32eeacb..651e290 100644 --- a/src/test/java/FieldAssignmentTest.java +++ b/src/test/java/FieldAssignmentTest.java @@ -17,7 +17,7 @@ public class FieldAssignmentTest { 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()); + assertEquals("Error in line 1:46 Variable or parameter with name \"str\" not defined.", e.getMessage()); } @Test @@ -29,6 +29,6 @@ public class FieldAssignmentTest { 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()); + assertEquals("Error in line 1:62 Variable or parameter must reference a struct but references int.", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/FunctionDefinitionTest.java b/src/test/java/FunctionDefinitionTest.java index b6086fc..a1ff3cf 100644 --- a/src/test/java/FunctionDefinitionTest.java +++ b/src/test/java/FunctionDefinitionTest.java @@ -92,4 +92,28 @@ public class FunctionDefinitionTest { 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()); } + + @Test + void shouldResetVariablesAndParameterEnvironment() { + // given + var tree = Helper.prepareParser(""" + function foo(x: int): int { + return 1; + } + function bar(): int { + let x: int = 0; + return x; + } + + bar(); + """); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } } \ No newline at end of file diff --git a/src/test/java/StructFieldAccessTest.java b/src/test/java/StructFieldAccessTest.java index 9bda8f3..51c97c2 100644 --- a/src/test/java/StructFieldAccessTest.java +++ b/src/test/java/StructFieldAccessTest.java @@ -16,7 +16,7 @@ public class StructFieldAccessTest { 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()); + assertEquals("Error in line 1:53 Variable or parameter with name \"str\" not defined.", e.getMessage()); } @Test @@ -28,6 +28,6 @@ public class StructFieldAccessTest { 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()); + assertEquals("Error in line 1:69 Variable or parameter must reference a struct but references int.", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/VariableAssignmentTest.java b/src/test/java/VariableAssignmentTest.java index 292e5f4..1c08a33 100644 --- a/src/test/java/VariableAssignmentTest.java +++ b/src/test/java/VariableAssignmentTest.java @@ -17,6 +17,6 @@ public class VariableAssignmentTest { 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()); + assertEquals("Error in line 1:22 Variable or parameter with name \"x\" not defined.", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/VariableTest.java b/src/test/java/VariableTest.java index c9d2d39..8135774 100644 --- a/src/test/java/VariableTest.java +++ b/src/test/java/VariableTest.java @@ -17,7 +17,7 @@ public class VariableTest { 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()); + assertEquals("Error in line 1:29 Variable or parameter with name \"x\" not defined.", e.getMessage()); } @Test