From aef2c84fdc65aa400ef8e8e904912ce2edde4589 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 23 Mar 2023 21:40:22 +0100 Subject: [PATCH 1/5] Void Type: Fix a problem where calls to void functions were ignored The ContextAnalysis visitor for braced blocks was not updated during void support implementation which is why I do it now. Although we are re-using the functionCall production rule a different visitor is required for visiting function calls directly inside braced blocks. This is because the other place where functionCalls are used the functionCall is annotated with a label with wraps the functionCall context inside a label context. Fortunately this is in fact simple wrapping so we just implement a visitor for the wrapping class that unwraps the functionCall and passes it to the visitor that actually implements the functionCall. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 74 ++--- .../de/hsrm/compiler/Klang/nodes/Block.java | 7 +- .../compiler/Klang/visitors/EvalVisitor.java | 2 +- .../hsrm/compiler/Klang/visitors/GenASM.java | 2 +- .../hsrm/compiler/Klang/visitors/GetVars.java | 288 ------------------ 5 files changed, 44 insertions(+), 329 deletions(-) delete mode 100644 src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 0b2a74a..cbb923e 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -68,25 +68,32 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitBraced_block(KlangParser.Braced_blockContext ctx) { - var actualStatementCount = 0; - var declaredStatementCount = ctx.statement().size(); + var statementsOrFunctionCalls = new ArrayList(); var hasReturn = false; - var statements = new Statement[declaredStatementCount]; - for (int i = 0; i < declaredStatementCount; i++) { - var currentStatement = visit(ctx.statement(i)); - statements[i] = (Statement) currentStatement; - actualStatementCount += 1; + for (var child: ctx.children) { + var statementOrFunctionCall = visit(child); - // 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)) { + // The children array contains more than just the statements or function calls + // but everything else evaluates to null, so we can skip it. + if (statementOrFunctionCall == null) { + continue; + } + + statementsOrFunctionCalls.add(statementOrFunctionCall); + + if ( + statementOrFunctionCall.type != null + && !(statementOrFunctionCall instanceof VariableDeclaration) + && !statementOrFunctionCall.type.equals(Type.getVoidType()) + ) { // check whether the type matches try { - currentDeclaredReturnType.combine(currentStatement.type); + currentDeclaredReturnType.combine(statementOrFunctionCall.type); } catch (Exception e) { - throw new RuntimeException( - Helper.getErrorPrefix(currentStatement.line, currentStatement.col) + e.getMessage()); + var line = statementOrFunctionCall.line; + var col = statementOrFunctionCall.col; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } // since we have a return guaranteed, every statement @@ -96,20 +103,12 @@ public class ContextAnalysis extends KlangBaseVisitor { } } - // If there was unreachable code in this block, - // create a shorter statements array and copy the statements to there - if (actualStatementCount < declaredStatementCount) { - var newStatements = new Statement[actualStatementCount]; - System.arraycopy(statements, 0, newStatements, 0, actualStatementCount); - statements = newStatements; - } - // if this block contains at least one statement that guarantees a return value, // we indicate that this block guarantees a return value by setting result.type - var result = new Block(statements); + var result = new Block(statementsOrFunctionCalls.toArray(new Node[0])); if (hasReturn) { - result.type = this.currentDeclaredReturnType; + result.type = currentDeclaredReturnType; } result.line = ctx.start.getLine(); @@ -884,42 +883,47 @@ public class ContextAnalysis extends KlangBaseVisitor { } @Override - public Node visitFunctionCallExpression(KlangParser.FunctionCallExpressionContext ctx) { - String name = ctx.functionCall().IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + public Node visitFunctionCall(KlangParser.FunctionCallContext ctx) { + var name = ctx.IDENT().getText(); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - var functionDef = this.functionDefs.get(name); + var functionDef = functionDefs.get(name); if (functionDef == null) { - String error = "Function with name \"" + name + "\" not defined."; + var 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 = functionDef.parameters.length; + var argCount = ctx.arguments().expression().size(); + var paramCount = functionDef.parameters.length; if (argCount != paramCount) { - String error = "Function \"" + name + "\" expects " + paramCount + " parameters, but got " + argCount + "."; + var error = "Function \"" + name + "\" expects " + paramCount + " parameters, but got " + argCount + "."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Evaluate every argument - Expression[] args = new Expression[argCount]; + var args = new Expression[argCount]; for (int i = 0; i < argCount; i++) { - Expression expression = (Expression) this.visit(ctx.functionCall().arguments().expression(i)); + var expression = (Expression) visit(ctx.arguments().expression(i)); 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); + var result = new FunctionCall(name, args); result.type = functionDef.type; result.line = line; result.col = col; return result; } + @Override + public Node visitFunctionCallExpression(KlangParser.FunctionCallExpressionContext ctx) { + return visit(ctx.functionCall()); + } + @Override public Node visitConstructorCallExpression(KlangParser.ConstructorCallExpressionContext ctx) { String name = ctx.IDENT().getText(); diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/Block.java b/src/main/java/de/hsrm/compiler/Klang/nodes/Block.java index fe7a019..fa477b6 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/Block.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/Block.java @@ -1,14 +1,13 @@ package de.hsrm.compiler.Klang.nodes; -import de.hsrm.compiler.Klang.nodes.statements.Statement; import de.hsrm.compiler.Klang.visitors.Visitor; public class Block extends Node { - public Statement[] statements; + public Node[] statementsOrFunctionCalls; - public Block(Statement[] statements) { - this.statements = statements; + public Block(Node[] statements) { + this.statementsOrFunctionCalls = statements; } @Override 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 ee6193a..9cf28c5 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -404,7 +404,7 @@ public class EvalVisitor implements Visitor { @Override public Value visit(Block e) { - for (var stmt : e.statements) { + for (var stmt : e.statementsOrFunctionCalls) { var result = stmt.welcome(this); if (result != null) { return result; 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 4618c3d..b5e7e08 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -543,7 +543,7 @@ public class GenASM implements Visitor { @Override public Void visit(Block e) { - for (var statement : e.statements) { + for (var statement : e.statementsOrFunctionCalls) { statement.welcome(this); } return null; diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java deleted file mode 100644 index 45220bc..0000000 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java +++ /dev/null @@ -1,288 +0,0 @@ -package de.hsrm.compiler.Klang.visitors; - -import java.util.Map; -import java.util.Set; - -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; -import de.hsrm.compiler.Klang.nodes.loops.WhileLoop; -import de.hsrm.compiler.Klang.nodes.statements.*; -import de.hsrm.compiler.Klang.types.Type; - -class GetVars implements Visitor { - - public Set vars; - public Map types; - - public GetVars(Set vars, Map types) { - this.vars = vars; - this.types = types; - } - - @Override - public Void visit(IntegerExpression e) { - return null; - } - - @Override - public Void visit(FloatExpression e) { - return null; - } - - @Override - public Void visit(BooleanExpression e) { - return null; - } - - @Override - public Void visit(Variable e) { - return null; - } - - @Override - public Void visit(EqualityExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(NotEqualityExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(GTExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(GTEExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(LTExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(LTEExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(AdditionExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(SubstractionExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(MultiplicationExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(DivisionExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(ModuloExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(NegateExpression e) { - e.lhs.welcome(this); - return null; - } - - @Override - public Void visit(OrExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(AndExpression e) { - e.lhs.welcome(this); - e.rhs.welcome(this); - return null; - } - - @Override - public Void visit(NotExpression e) { - e.lhs.welcome(this); - return null; - } - - @Override - public Void visit(IfStatement e) { - e.cond.welcome(this); - e.then.welcome(this); - if (e.alt != null) { - e.alt.welcome(this); - } else if (e.elif != null) { - e.elif.welcome(this); - } - return null; - } - - @Override - public Void visit(WhileLoop e) { - e.cond.welcome(this); - e.block.welcome(this); - return null; - } - - @Override - public Void visit(DoWhileLoop e) { - e.cond.welcome(this); - e.block.welcome(this); - return null; - } - - @Override - public Void visit(ForLoop e) { - e.init.welcome(this); - e.condition.welcome(this); - e.step.welcome(this); - e.block.welcome(this); - return null; - } - - @Override - public Void visit(VariableDeclaration e) { - vars.add(e.name); - types.put(e.name, e.type); - return null; - } - - @Override - public Void visit(VariableAssignment e) { - e.expression.welcome(this); - return null; - } - - @Override - public Void visit(ReturnStatement e) { - if (e.expression != null) { - e.expression.welcome(this); - } - return null; - } - - @Override - public Void visit(Block e) { - for (var statement : e.statements) { - statement.welcome(this); - } - return null; - } - - @Override - public Void visit(FunctionDefinition e) { - e.block.welcome(this); - return null; - } - - @Override - public Void visit(FunctionCall e) { - for (var expression : e.arguments) { - expression.welcome(this); - } - return null; - } - - @Override - public Void visit(Program e) { - e.expression.welcome(this); - for (var func : e.funcs) { - func.welcome(this); - } - return null; - } - - @Override - public Void visit(Parameter e) { - return null; - } - - @Override - public Void visit(EnumDefinition e) { - return null; - } - - @Override - public Void visit(EnumValue e) { - return null; - } - - @Override - public Void visit(StructDefinition e) { - return null; - } - - @Override - public Void visit(StructField e) { - return null; - } - - @Override - public Void visit(MemberAccessExpression e) { - return null; - } - - @Override - public Void visit(EnumAccessExpression e) { - return null; - } - - @Override - public Void visit(ConstructorCall e) { - return null; - } - - @Override - public Void visit(NullExpression e) { - return null; - } - - @Override - public Void visit(DestructorCall e) { - return null; - } - - @Override - public Void visit(FieldAssignment e) { - return null; - } -} \ No newline at end of file From 5f0e84198a16ee8923daa47bf7c784e887cb4f95 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 23 Mar 2023 22:49:36 +0100 Subject: [PATCH 2/5] GenASM+Void: Generate an implicit return Void functions are allowed to be completely free of return statements. The problem is that this does not work on its own in assembler. We have to generate a return statement at the end of the function body if there is no return statement already. --- .../hsrm/compiler/Klang/visitors/GenASM.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 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 b5e7e08..7848143 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -71,7 +71,7 @@ public class GenASM implements Visitor { private int lCount = 0; private int currentFunctionStartLabel = 0; private long bytesToClearFromTheStack = 0; - private Parameter[] currentFunctionParams; + private FunctionDefinition currentFunctionDef; public GenASM(String mainName, Map structs) { this.mainName = mainName; @@ -543,9 +543,23 @@ public class GenASM implements Visitor { @Override public Void visit(Block e) { - for (var statement : e.statementsOrFunctionCalls) { - statement.welcome(this); + for (var statementOrFunctionCall : e.statementsOrFunctionCalls) { + statementOrFunctionCall.welcome(this); } + + // It's possible (and allowed -> void functions) that there is no return statement + // in the outermost block of a function body. In this case, no + // direct descendant of Block will be a return statement. + // This means we have to generate an implicit return, otherwise + // the code would fall through to the next function below :D + // We also have to clean up the stack which is the ReturnStatement's job. + + // Check if we have to generate an implicit return + var lastStatementOrFunctionCall = e.statementsOrFunctionCalls[e.statementsOrFunctionCalls.length - 1]; + if (currentFunctionDef.block == e && !(lastStatementOrFunctionCall instanceof ReturnStatement)) { + visit(new ReturnStatement()); + } + return null; } @@ -558,9 +572,11 @@ public class GenASM implements Visitor { e.name = "main_by_user"; } + // Remember the current function definition so everyone below us knows where they belong to. + currentFunctionDef = e; + var lblStart = ++lCount; currentFunctionStartLabel = lblStart; - currentFunctionParams = e.parameters; asm.functionHead(e.name); asm.push("q", "%rbp"); asm.mov("q", "%rsp", "%rbp"); @@ -675,10 +691,10 @@ public class GenASM implements Visitor { // push args into local var locations, last arg is on top of the stack for (int i = e.arguments.length - 1; i >= 0; i--) { - asm.pop("q", this.env.get(this.currentFunctionParams[i].name) + "(%rbp)"); + asm.pop("q", env.get(currentFunctionDef.parameters[i].name) + "(%rbp)"); } - asm.jmp(this.currentFunctionStartLabel); + asm.jmp(currentFunctionStartLabel); return null; } From a7e93f4f015d3459e794913ca3ad3f1e057b1979 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 23 Mar 2023 22:52:52 +0100 Subject: [PATCH 3/5] GenASM: Set the bytesToClearFromStack variable earlier If you set the variable after visiting the function body all children will see bytesToClearFromStack == 0. --- .../de/hsrm/compiler/Klang/visitors/GenASM.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 7848143..5fe6401 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -660,17 +660,17 @@ public class GenASM implements Visitor { asm.sub("q", "$8", "%rsp"); } - e.block.welcome(this); - // I need to clear the stack here so that the top most element is the old rbp that - // ret uses to return to the caller but code that gets generated here will never be - // reached since the block node is guaranteed to contain a return node that results - // in a ret command being executed before this code would be reached. - // As a workaround (and a dirty, dirty hack) I indicate to the return node visitor - // how many bytes need to be cleared from the stack. + // ret uses to return to the caller but code that gets generated after visiting the + // function body will never be reached since the block node is guaranteed to + // contain a return node that results in a ret command being executed before this + // code would be reached. As a workaround (and a dirty, dirty hack) I indicate + // to the return node visitor how many bytes need to be cleared from the stack. var wasStackPadded = (registerParameters.size() + e.localVariables.length) % 2 != 0; bytesToClearFromTheStack = 8L * (registerParameters.size() + e.localVariables.length + (wasStackPadded ? 1 : 0)); + e.block.welcome(this); + return null; } From 2aff9b3d0d6e3a4f6467241b5c33d45b7710aa51 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 23 Mar 2023 23:04:09 +0100 Subject: [PATCH 4/5] Char: Add support for chars. First try at implementing chars. There are some problems with the grammar where I was not able to separate the single quotes from the captured char, but I managed to hack it together in the ContextAnalysis. It's currently not possible to do any 'math' with chars, but I will give it a try soon. --- .../antlr4/de/hsrm/compiler/Klang/Klang.g4 | 9 ++ .../hsrm/compiler/Klang/ContextAnalysis.java | 25 +++- .../java/de/hsrm/compiler/Klang/Value.java | 14 +- .../nodes/expressions/CharExpression.java | 16 +++ .../hsrm/compiler/Klang/types/CharType.java | 43 ++++++ .../de/hsrm/compiler/Klang/types/Type.java | 5 + .../compiler/Klang/visitors/EvalVisitor.java | 6 +- .../hsrm/compiler/Klang/visitors/GenASM.java | 6 + .../Klang/visitors/PrettyPrintVisitor.java | 10 +- .../hsrm/compiler/Klang/visitors/Visitor.java | 1 + src/test/java/CharTest.java | 125 ++++++++++++++++++ 11 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 src/main/java/de/hsrm/compiler/Klang/nodes/expressions/CharExpression.java create mode 100644 src/main/java/de/hsrm/compiler/Klang/types/CharType.java create mode 100644 src/test/java/CharTest.java diff --git a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 index f752ddb..5eb15fc 100644 --- a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 +++ b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 @@ -107,6 +107,7 @@ atom : INTEGER_LITERAL #intAtom | BOOLEAN_LITERAL #boolAtom | FLOAT_LITERAL #floatAtom + | CHAR_LITERAL #charAtom | NULL # nullAtom | IDENT #variable ; @@ -119,6 +120,7 @@ type : INTEGER | BOOLEAN | FLOAT + | CHAR | IDENT | VOID ; @@ -184,9 +186,12 @@ SUB: '-'; MOD: '%'; DIV: '/'; +SQUOT: '\''; + BOOLEAN: 'bool'; INTEGER: 'int'; FLOAT: 'float'; +CHAR: 'char'; VOID: 'void'; INTEGER_LITERAL @@ -202,6 +207,10 @@ BOOLEAN_LITERAL | 'false' ; +CHAR_LITERAL + : SQUOT [ -~] SQUOT + ; + IDENT : [a-zA-Z][a-zA-Z0-9]* ; diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index cbb923e..0fed5bb 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -571,7 +571,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } checkNumeric(lhs, rhs, line, col); - + result.line = line; result.col = col; return result; @@ -605,7 +605,7 @@ public class ContextAnalysis extends KlangBaseVisitor { int line = ctx.start.getLine(); int col = ctx.start.getCharPositionInLine(); DivisionExpression result = new DivisionExpression((Expression) lhs, (Expression) rhs); - + try { result.type = lhs.type.combine(rhs.type); } catch (Exception e) { @@ -613,7 +613,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } checkNumeric(lhs, rhs, line, col); - + result.line = line; result.col = col; return result; @@ -639,7 +639,7 @@ public class ContextAnalysis extends KlangBaseVisitor { String error = "Only integers are allowed for modulo."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - + result.line = line; result.col = col; return result; @@ -652,7 +652,7 @@ public class ContextAnalysis extends KlangBaseVisitor { result.type = expression.type; result.line = ctx.start.getLine(); result.col = ctx.start.getCharPositionInLine(); - + if (!result.type.isNumericType()) { String error = "Only numeric types are allowed for this expression."; throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error); @@ -730,6 +730,17 @@ public class ContextAnalysis extends KlangBaseVisitor { return n; } + @Override + public Node visitCharAtom(KlangParser.CharAtomContext ctx) { + // I had no idea how to design the grammar so that the quotes + // are separate from the char, so I cheat here... + var n = new CharExpression(ctx.CHAR_LITERAL().getText().charAt(1)); + n.type = Type.getCharType(); + n.line = ctx.start.getLine(); + n.col = ctx.start.getCharPositionInLine(); + return n; + } + @Override public Node visitNullAtom(KlangParser.NullAtomContext ctx) { Node n = new NullExpression(); @@ -929,14 +940,14 @@ public class ContextAnalysis extends KlangBaseVisitor { String name = ctx.IDENT().getText(); int line = ctx.start.getLine(); int col = ctx.start.getCharPositionInLine(); - + // Get the corresponding struct definition 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); } - + // Make sure the number of arguments match the number of struct fields int fieldCount = struct.fields.length; int argCount = ctx.arguments().expression().size(); diff --git a/src/main/java/de/hsrm/compiler/Klang/Value.java b/src/main/java/de/hsrm/compiler/Klang/Value.java index 112d1de..dfe87a8 100644 --- a/src/main/java/de/hsrm/compiler/Klang/Value.java +++ b/src/main/java/de/hsrm/compiler/Klang/Value.java @@ -4,8 +4,8 @@ import de.hsrm.compiler.Klang.types.Type; import java.util.Map; public class Value { + private final Object value; public Type type; - private Object value; public Value(Object value) { this.value = value; @@ -17,19 +17,23 @@ public class Value { } public Object asObject() { - return this.value; + return value; } public int asInteger() { - return (int) this.value; + return (int) value; } public double asFloat() { - return (double) this.value; + return (double) value; } public boolean asBoolean() { - return (boolean) this.value; + return (boolean) value; + } + + public char asChar() { + return (char) value; } @SuppressWarnings("unchecked") diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/CharExpression.java b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/CharExpression.java new file mode 100644 index 0000000..88b656f --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/expressions/CharExpression.java @@ -0,0 +1,16 @@ +package de.hsrm.compiler.Klang.nodes.expressions; + +import de.hsrm.compiler.Klang.visitors.Visitor; + +public class CharExpression extends Expression { + public char c; + + public CharExpression(char c) { + this.c = c; + } + + @Override + public R welcome(Visitor v) { + return v.visit(this); + } +} diff --git a/src/main/java/de/hsrm/compiler/Klang/types/CharType.java b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java new file mode 100644 index 0000000..f339fe8 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java @@ -0,0 +1,43 @@ +package de.hsrm.compiler.Klang.types; + +import de.hsrm.compiler.Klang.Value; + +public class CharType extends PrimitiveType { + private static CharType instance = null; + + public static CharType getType() { + if (instance != null) { + return instance; + } + instance = new CharType(); + return instance; + } + + @Override + public String getName() { + return "char"; + } + + @Override + public Type combine(Type that) { + if (!this.equals(that)) { + throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + getName()); + } + + return this; + } + + @Override + public boolean valuesEqual(Value a, Value b) { + return ((int) a.asChar()) == ((int) b.asChar()); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + + return that instanceof CharType; + } +} 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 a8261df..752d73e 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/Type.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/Type.java @@ -23,6 +23,10 @@ public abstract class Type { return FloatType.getType(); } + public static CharType getCharType() { + return CharType.getType(); + } + public static NullType getNullType() { return NullType.getType(); } @@ -36,6 +40,7 @@ public abstract class Type { case "bool": return getBooleanType(); case "int": return getIntegerType(); case "float": return getFloatType(); + case "char": return getCharType(); case "null": return getNullType(); case "void": return getVoidType(); 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 9cf28c5..5678d2f 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -10,7 +10,6 @@ import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop; 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.NamedType; import de.hsrm.compiler.Klang.types.NullType; import de.hsrm.compiler.Klang.types.Type; @@ -46,6 +45,11 @@ public class EvalVisitor implements Visitor { return result; } + @Override + public Value visit(CharExpression e) { + return new Value(e.c, Type.getCharType()); + } + @Override public Value visit(EqualityExpression e) { var lhs = e.lhs.welcome(this); 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 5fe6401..80b47dc 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -102,6 +102,12 @@ public class GenASM implements Visitor { return null; } + @Override + public Void visit(CharExpression e) { + asm.mov("q", "$" + ((int) e.c), "%rax"); + return null; + } + @Override public Void visit(Variable e) { if (e.type.equals(Type.getFloatType())) { 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 470e3dd..8505a01 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java @@ -97,6 +97,14 @@ public class PrettyPrintVisitor implements Visitor { return null; } + @Override + public Void visit(CharExpression e) { + ex.write("'"); + ex.write(e.c); + ex.write("'"); + return null; + } + @Override public Void visit(EqualityExpression e) { ex.write("("); @@ -323,7 +331,7 @@ public class PrettyPrintVisitor implements Visitor { public Void visit(Block e) { ex.write("{"); ex.addIndent(); - for (Statement stmt : e.statements) { + for (var stmt : e.statementsOrFunctionCalls) { ex.nl(); stmt.welcome(this); if (stmt.getClass() == VariableAssignment.class || stmt.getClass() == VariableDeclaration.class) { 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 f56e8b6..1276482 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/Visitor.java @@ -12,6 +12,7 @@ public interface Visitor { R visit(IntegerExpression e); R visit(FloatExpression e); R visit(BooleanExpression e); + R visit(CharExpression e); R visit(Variable e); R visit(AdditionExpression e); R visit(EqualityExpression e); diff --git a/src/test/java/CharTest.java b/src/test/java/CharTest.java new file mode 100644 index 0000000..cd334f1 --- /dev/null +++ b/src/test/java/CharTest.java @@ -0,0 +1,125 @@ +import de.hsrm.compiler.Klang.ContextAnalysis; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CharTest { + + @Test + void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() { + // given + var tree = Helper.prepareParser(""" + function bar(): int { + let c: char = 'c'; + return 1; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfCharLiteralIsReturnedFromFunction() { + // given + var tree = Helper.prepareParser(""" + function bar(): char { + return 'c'; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfCharIsUsedInStruct() { + // given + var tree = Helper.prepareParser(""" + struct myChar { c: char; } + function bar(): myChar { + let x: myChar = create myChar('c'); + return x; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldThrowIfCharIsAssignedToOtherTypedVariable() { + // given + var tree = Helper.prepareParser(""" + function bar(): int { + let c: int = 'c'; + return 1; + } + bar(); + """); + 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 2:4 Type mismatch: cannot combine int and char", e.getMessage()); + } + + @Test + void shouldThrowWhenReturningCharFromOtherTypedFunction() { + // given + var tree = Helper.prepareParser(""" + function bar(): int { + return 'c'; + } + bar(); + """); + 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 2:4 Type mismatch: cannot combine int and char", e.getMessage()); + } + + @Test + void shouldThrowPassingCharToOtherTypedFunctionParameter() { + // given + var tree = Helper.prepareParser(""" + function foo(a: float): void { + let x: bool = false; + } + function bar(): void { + foo('c'); + } + bar(); + """); + 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 5:4 argument 0 Expected float but got: char", e.getMessage()); + } + + @Test + void shouldThrowWhenAssigningCharToOtherTypedStructField() { + // given + var tree = Helper.prepareParser(""" + struct foo { a: int; } + function bar(): void { + let x: foo = create foo(1); + x.a = 'c'; + } + bar(); + """); + 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 4:4 Type mismatch: cannot combine int and char", e.getMessage()); + } +} From 259ac499810349702060f5d1233d7fe8313e3283 Mon Sep 17 00:00:00 2001 From: nitrix Date: Fri, 24 Mar 2023 00:17:38 +0100 Subject: [PATCH 5/5] ContextAnalysis: Make comparing more generous Previously we only allowed numeric types to be compared using >,<,<= and >=. Now we allow all primitives types to be compared using the operators mentioned above. It is now also possible to compare and assign ints and chars to each other. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 107 ++++++++---------- .../hsrm/compiler/Klang/types/CharType.java | 10 +- .../compiler/Klang/types/IntegerType.java | 4 + src/test/java/CharTest.java | 53 +++++++-- 4 files changed, 104 insertions(+), 70 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 0fed5bb..22fab9d 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -427,18 +427,21 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) { - Node lhs = this.visit(ctx.lhs); - Node rhs = this.visit(ctx.rhs); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var lhs = visit(ctx.lhs); + var rhs = visit(ctx.rhs); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + // Since there are countless combinations of types that are comparable + // to each other we use Type::combine to figure out if two types + // may be compared to each other try { lhs.type.combine(rhs.type); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } - EqualityExpression result = new EqualityExpression((Expression) lhs, (Expression) rhs); + var result = new EqualityExpression((Expression) lhs, (Expression) rhs); result.type = Type.getBooleanType(); result.line = line; result.col = col; @@ -447,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) { - Node lhs = this.visit(ctx.lhs); - Node rhs = this.visit(ctx.rhs); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var lhs = visit(ctx.lhs); + var rhs = visit(ctx.rhs); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - try { - lhs.type.combine(rhs.type); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); + if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) { + var error = "Can only compare primitives."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - NotEqualityExpression result = new NotEqualityExpression((Expression) lhs, (Expression) rhs); + var result = new NotEqualityExpression((Expression) lhs, (Expression) rhs); result.type = Type.getBooleanType(); result.line = line; result.col = col; @@ -467,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) { - Node lhs = this.visit(ctx.lhs); - Node rhs = this.visit(ctx.rhs); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var lhs = visit(ctx.lhs); + var rhs = visit(ctx.rhs); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - try { - lhs.type.combine(rhs.type); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); + if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) { + var error = "Can only compare primitives."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - checkNumeric(lhs, rhs, line, col); - - LTExpression result = new LTExpression((Expression) lhs, (Expression) rhs); + var result = new LTExpression((Expression) lhs, (Expression) rhs); result.type = Type.getBooleanType(); result.line = line; result.col = col; @@ -489,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) { - Node lhs = this.visit(ctx.lhs); - Node rhs = this.visit(ctx.rhs); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var lhs = visit(ctx.lhs); + var rhs = visit(ctx.rhs); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - try { - lhs.type.combine(rhs.type); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); - } - - if (!lhs.type.isNumericType() || !rhs.type.isNumericType()) { - String error = "Only numeric types are allowed for this expression."; + if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) { + var error = "Can only compare primitives."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - GTExpression result = new GTExpression((Expression) lhs, (Expression) rhs); + var result = new GTExpression((Expression) lhs, (Expression) rhs); result.type = Type.getBooleanType(); result.line = line; result.col = col; @@ -514,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) { - Node lhs = this.visit(ctx.lhs); - Node rhs = this.visit(ctx.rhs); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var lhs = visit(ctx.lhs); + var rhs = visit(ctx.rhs); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - try { - lhs.type.combine(rhs.type); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); + if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) { + var error = "Can only compare primitives."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - checkNumeric(lhs, rhs, line, col); - - LTEExpression result = new LTEExpression((Expression) lhs, (Expression) rhs); + var result = new LTEExpression((Expression) lhs, (Expression) rhs); result.type = Type.getBooleanType(); result.line = line; result.col = col; @@ -536,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) { - Node lhs = this.visit(ctx.lhs); - Node rhs = this.visit(ctx.rhs); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); + var lhs = visit(ctx.lhs); + var rhs = visit(ctx.rhs); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - try { - lhs.type.combine(rhs.type); - } catch (Exception e) { - throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); + if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) { + var error = "Can only compare primitives."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } - checkNumeric(lhs, rhs, line, col); - - GTEExpression result = new GTEExpression((Expression) lhs, (Expression) rhs); + var result = new GTEExpression((Expression) lhs, (Expression) rhs); result.type = Type.getBooleanType(); result.line = line; result.col = col; diff --git a/src/main/java/de/hsrm/compiler/Klang/types/CharType.java b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java index f339fe8..e4ee34f 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/CharType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java @@ -20,11 +20,15 @@ public class CharType extends PrimitiveType { @Override public Type combine(Type that) { - if (!this.equals(that)) { - throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + getName()); + if (this.equals(that)) { + return this; } - return this; + if (that.equals(Type.getIntegerType())) { + return this; + } + + throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName()); } @Override 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 f413d5b..3673020 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java @@ -35,6 +35,10 @@ public class IntegerType extends NumericType { return Type.getFloatType(); } + if (that.equals(Type.getCharType())) { + return Type.getCharType(); + } + // Every remaining type will throw a RuntimeException throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); } diff --git a/src/test/java/CharTest.java b/src/test/java/CharTest.java index cd334f1..07dd92b 100644 --- a/src/test/java/CharTest.java +++ b/src/test/java/CharTest.java @@ -5,6 +5,44 @@ import static org.junit.jupiter.api.Assertions.*; public class CharTest { + @Test + void shouldNotThrowIfComparingCharsEquality() { + // given + var tree = Helper.prepareParser(""" + function bar(): int { + let b: char = 'b'; + if ('a' == b) { + return 1; + } + return -1; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfComparingCharsInequality() { + // given + var tree = Helper.prepareParser(""" + function bar(): int { + let b: char = 'b'; + if ('a' > b) { + return 1; + } + return -1; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + @Test void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() { // given @@ -57,8 +95,9 @@ public class CharTest { void shouldThrowIfCharIsAssignedToOtherTypedVariable() { // given var tree = Helper.prepareParser(""" + struct foo { f: float; } function bar(): int { - let c: int = 'c'; + let c: foo = 'c'; return 1; } bar(); @@ -67,14 +106,14 @@ public class CharTest { // when / then var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); - assertEquals("Error in line 2:4 Type mismatch: cannot combine int and char", e.getMessage()); + assertEquals("Error in line 3:4 Type mismatch: cannot combine foo and char", e.getMessage()); } @Test void shouldThrowWhenReturningCharFromOtherTypedFunction() { // given var tree = Helper.prepareParser(""" - function bar(): int { + function bar(): float { return 'c'; } bar(); @@ -83,7 +122,7 @@ public class CharTest { // when / then var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); - assertEquals("Error in line 2:4 Type mismatch: cannot combine int and char", e.getMessage()); + assertEquals("Error in line 2:4 Type mismatch: cannot combine float and char", e.getMessage()); } @Test @@ -109,9 +148,9 @@ public class CharTest { void shouldThrowWhenAssigningCharToOtherTypedStructField() { // given var tree = Helper.prepareParser(""" - struct foo { a: int; } + struct foo { a: bool; } function bar(): void { - let x: foo = create foo(1); + let x: foo = create foo(false); x.a = 'c'; } bar(); @@ -120,6 +159,6 @@ public class CharTest { // when / then var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); - assertEquals("Error in line 4:4 Type mismatch: cannot combine int and char", e.getMessage()); + assertEquals("Error in line 4:4 Type mismatch: cannot combine bool and char", e.getMessage()); } }