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 0b2a74a..22fab9d 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(); @@ -428,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; @@ -448,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; @@ -468,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; @@ -490,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; @@ -515,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; @@ -537,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; @@ -572,7 +558,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } checkNumeric(lhs, rhs, line, col); - + result.line = line; result.col = col; return result; @@ -606,7 +592,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) { @@ -614,7 +600,7 @@ public class ContextAnalysis extends KlangBaseVisitor { } checkNumeric(lhs, rhs, line, col); - + result.line = line; result.col = col; return result; @@ -640,7 +626,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; @@ -653,7 +639,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); @@ -731,6 +717,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(); @@ -884,55 +881,60 @@ 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(); 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/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/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..e4ee34f --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java @@ -0,0 +1,47 @@ +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)) { + return this; + } + + if (that.equals(Type.getIntegerType())) { + return this; + } + + throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName()); + } + + @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/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/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 ee6193a..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); @@ -404,7 +408,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..80b47dc 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; @@ -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())) { @@ -543,9 +549,23 @@ public class GenASM implements Visitor { @Override public Void visit(Block e) { - for (var statement : e.statements) { - 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 +578,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"); @@ -644,17 +666,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; } @@ -675,10 +697,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; } 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 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..07dd92b --- /dev/null +++ b/src/test/java/CharTest.java @@ -0,0 +1,164 @@ +import de.hsrm.compiler.Klang.ContextAnalysis; +import org.junit.jupiter.api.Test; + +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 + 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(""" + struct foo { f: float; } + function bar(): int { + let c: foo = '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 3:4 Type mismatch: cannot combine foo and char", e.getMessage()); + } + + @Test + void shouldThrowWhenReturningCharFromOtherTypedFunction() { + // given + var tree = Helper.prepareParser(""" + function bar(): float { + 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 float 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: bool; } + function bar(): void { + let x: foo = create foo(false); + 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 bool and char", e.getMessage()); + } +}