Compare commits

...

10 Commits

Author SHA1 Message Date
bcc9f29ff5 Merge pull request 'feature/char-support' (#3) from feature/char-support into master
Reviewed-on: #3
2023-03-24 17:03:23 +01:00
259ac49981 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.
2023-03-24 00:17:38 +01:00
2aff9b3d0d 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.
2023-03-23 23:04:09 +01:00
a7e93f4f01 GenASM: Set the bytesToClearFromStack variable earlier
If you set the variable after visiting the function body all children will see bytesToClearFromStack == 0.
2023-03-23 22:52:52 +01:00
5f0e84198a 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.
2023-03-23 22:49:36 +01:00
aef2c84fdc 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.
2023-03-23 21:49:59 +01:00
4219f93021 Merge pull request 'Add support for void types' (#2) from 31-void-type into master
Reviewed-on: #2
2023-03-23 14:28:24 +01:00
7965c89a60 VoidType: Add tests and fix some bugs 2023-03-23 13:10:20 +01:00
26eff47057 Fix typos. 2023-03-23 13:09:52 +01:00
Marvin Kaiser
53976615e1 31: Add void type 2023-03-23 12:47:22 +01:00
23 changed files with 640 additions and 443 deletions

View File

@@ -33,7 +33,7 @@ parameter
;
braced_block
: OBRK statement+ CBRK
: OBRK (statement | functionCall SCOL)+ CBRK
;
@@ -73,7 +73,7 @@ field_assignment
;
return_statement
: RETURN expression SCOL
: RETURN expression? SCOL
;
destroy_statement
@@ -107,6 +107,7 @@ atom
: INTEGER_LITERAL #intAtom
| BOOLEAN_LITERAL #boolAtom
| FLOAT_LITERAL #floatAtom
| CHAR_LITERAL #charAtom
| NULL # nullAtom
| IDENT #variable
;
@@ -119,7 +120,9 @@ type
: INTEGER
| BOOLEAN
| FLOAT
| CHAR
| IDENT
| VOID
;
functionCall
@@ -183,9 +186,13 @@ SUB: '-';
MOD: '%';
DIV: '/';
SQUOT: '\'';
BOOLEAN: 'bool';
INTEGER: 'int';
FLOAT: 'float';
CHAR: 'char';
VOID: 'void';
INTEGER_LITERAL
: [0-9]+
@@ -200,6 +207,10 @@ BOOLEAN_LITERAL
| 'false'
;
CHAR_LITERAL
: SQUOT [ -~] SQUOT
;
IDENT
: [a-zA-Z][a-zA-Z0-9]*
;

View File

@@ -68,25 +68,32 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitBraced_block(KlangParser.Braced_blockContext ctx) {
int actualStatementCount = 0;
int declaredStatementCount = ctx.statement().size();
boolean hasReturn = false;
Statement[] statements = new Statement[declaredStatementCount];
var statementsOrFunctionCalls = new ArrayList<Node>();
var hasReturn = false;
for (int i = 0; i < declaredStatementCount; i++) {
Node currentStatement = this.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 {
this.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,19 +103,12 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
}
}
// If there was unreachable code in this block,
// create a shorter statements array and copy the statements to there
if (actualStatementCount < declaredStatementCount) {
Statement[] 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
Block 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();
@@ -186,6 +186,11 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
if (declaredType.equals(Type.getVoidType())) {
var error = "Type " + declaredType.getName() + " can not be used to declare variables.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
if (!declaredType.isPrimitiveType() && !structDefs.containsKey(declaredType.getName()) && !enumDefs.containsKey(declaredType.getName())) {
var error = "Type " + declaredType.getName() + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
@@ -266,19 +271,31 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitReturn_statement(KlangParser.Return_statementContext ctx) {
Expression expression = (Expression) this.visit(ctx.expression());
ReturnStatement result = new ReturnStatement(expression);
if (currentDeclaredReturnType.equals(Type.getVoidType())) {
var result = new ReturnStatement();
result.type = Type.getVoidType();
result.line = ctx.start.getLine();
result.col = ctx.start.getCharPositionInLine();
if (ctx.expression() != null) {
var error = "Cannot return an expression from a void function.";
throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error);
}
return result;
}
var expression = (Expression) visit(ctx.expression());
// Check if this expression is a tail recursion
if (expression instanceof FunctionCall funCall) {
if (funCall.name.equals(this.currentFunctionDefinitionName)) {
if (funCall.name.equals(currentFunctionDefinitionName)) {
// Flag this function call
funCall.isTailRecursive = true;
}
}
result.type = expression.type;
var result = new ReturnStatement(expression);
result.line = ctx.start.getLine();
result.type = expression.type;
result.col = ctx.start.getCharPositionInLine();
return result;
}
@@ -410,18 +427,21 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@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;
@@ -430,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@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;
@@ -450,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@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;
@@ -472,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@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;
@@ -497,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@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;
@@ -519,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@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;
@@ -554,7 +558,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
}
checkNumeric(lhs, rhs, line, col);
result.line = line;
result.col = col;
return result;
@@ -588,7 +592,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
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) {
@@ -596,7 +600,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
}
checkNumeric(lhs, rhs, line, col);
result.line = line;
result.col = col;
return result;
@@ -622,7 +626,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
String error = "Only integers are allowed for modulo.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
result.line = line;
result.col = col;
return result;
@@ -635,7 +639,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
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);
@@ -713,6 +717,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
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();
@@ -749,6 +764,11 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
if (structFieldType.equals(Type.getVoidType())) {
var error = "Type void can not be used as a struct field type.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
if (!structFieldType.isPrimitiveType() && !structDefs.containsKey(structFieldType.getName()) && !enumDefs.containsKey(structFieldType.getName())) {
var error = "Type " + structFieldType.getName() + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
@@ -779,7 +799,8 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
currentDeclaredReturnType = returnType;
currentFunctionDefinitionName = name;
if (!returnType.isPrimitiveType() && !structDefs.containsKey(returnType.getName()) && !enumDefs.containsKey(returnType.getName())) {
var typeNotDefined = !returnType.isPrimitiveType() && !structDefs.containsKey(returnType.getName()) && !enumDefs.containsKey(returnType.getName());
if (!returnType.equals(Type.getVoidType()) && typeNotDefined) {
var line = ctx.returnType.start.getLine();
var col = ctx.returnType.start.getCharPositionInLine();
var error = "Type " + returnType.getName() + " not defined.";
@@ -807,7 +828,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
// Visit the block, make sure that a return value is guaranteed
var block = visit(ctx.braced_block());
if (block.type == null) {
if (block.type == null && !returnType.equals(Type.getVoidType())) {
var line = ctx.braced_block().start.getLine();
var col = ctx.braced_block().start.getCharPositionInLine();
var error = "Function " + name + " has to return something of type " + returnType.getName() + ".";
@@ -826,26 +847,29 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
public Node visitParameter(KlangParser.ParameterContext ctx) {
var parameterName = ctx.IDENT().getText();
var parameterType = Type.getByName(ctx.type_annotation().type().getText());
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
if (parameterType.equals(Type.getVoidType())) {
var error = "Type " + parameterType.getName() + " cannot be used to declare a parameter.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
if (structDefs.containsKey(parameterName)) {
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
var error = "Parameter name " + parameterName + " duplicates a struct of the same name.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
if (enumDefs.containsKey(parameterName)) {
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
var error = "Parameter name " + parameterName + " duplicates an enum of the same name.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
if (!parameterType.isPrimitiveType() && !structDefs.containsKey(parameterType.getName()) && !enumDefs.containsKey(parameterType.getName())) {
var line = ctx.type_annotation().start.getLine();
var col = ctx.type_annotation().start.getCharPositionInLine();
var typeLine = ctx.type_annotation().start.getLine();
var typeCol = ctx.type_annotation().start.getCharPositionInLine();
var error = "Type " + parameterType.getName() + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(typeLine, typeCol) + error);
}
var parameter = new Parameter(parameterName);
@@ -857,55 +881,60 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
}
@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();
@@ -983,4 +1012,4 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
}
return path;
}
}
}

View File

@@ -1,9 +1,11 @@
package de.hsrm.compiler.Klang;
import de.hsrm.compiler.Klang.helper.*;
import de.hsrm.compiler.Klang.nodes.EnumDefinition;
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
import de.hsrm.compiler.Klang.nodes.Node;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import de.hsrm.compiler.Klang.types.Type;
import de.hsrm.compiler.Klang.visitors.EvalVisitor;
import de.hsrm.compiler.Klang.visitors.GenASM;
import de.hsrm.compiler.Klang.visitors.PrettyPrintVisitor;
@@ -120,7 +122,11 @@ public class Klang {
System.out.println("\nEvaluating the source code:");
EvalVisitor evalVisitor = new EvalVisitor(structDefs);
Value result = root.welcome(evalVisitor);
generateOutput(out, "Result was: " + result.asObject().toString());
if (result.type.equals(Type.getVoidType())) {
generateOutput(out, "Result was void");
} else {
generateOutput(out, "Result was: " + result.asObject().toString());
}
return;
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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> R welcome(Visitor<R> v) {
return v.visit(this);
}
}

View File

@@ -11,6 +11,10 @@ public class ReturnStatement extends Statement {
this.expression = expression;
}
public ReturnStatement() {
this.expression = null;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);

View File

@@ -32,7 +32,7 @@ public class BooleanType extends PrimitiveType {
}
// Every remaining type will throw a RuntimeException
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override

View File

@@ -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;
}
}

View File

@@ -36,7 +36,7 @@ public class FloatType extends NumericType {
}
// Every remaining type will throw a RuntimeException
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override

View File

@@ -35,8 +35,12 @@ 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 missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override

View File

@@ -23,7 +23,7 @@ public class NullType extends Type {
public Type combine(Type that) {
// You can not combine null with a primitive type
if (that.isPrimitiveType()) {
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
// Everything else combines with null to the type it was before

View File

@@ -23,16 +23,26 @@ public abstract class Type {
return FloatType.getType();
}
public static CharType getCharType() {
return CharType.getType();
}
public static NullType getNullType() {
return NullType.getType();
}
public static VoidType getVoidType() {
return VoidType.getType();
}
public static Type getByName(String name) {
switch (name) {
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);
}
}
@@ -42,4 +52,4 @@ public abstract class Type {
public abstract boolean valuesEqual(Value a, Value b);
public abstract boolean isPrimitiveType();
public abstract boolean isNumericType();
}
}

View File

@@ -0,0 +1,45 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class VoidType extends Type {
private static VoidType instance;
public static VoidType getType() {
if (instance != null) {
return instance;
}
instance = new VoidType();
return instance;
}
@Override
public String getName() {
return "void";
}
@Override
public Type combine(Type that) {
if (that.equals(this)) {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
throw new RuntimeException("Can not compare void types.");
}
@Override
public boolean isPrimitiveType() {
return false;
}
@Override
public boolean isNumericType() {
return false;
}
}

View File

@@ -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<Value> {
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);
@@ -396,12 +400,15 @@ public class EvalVisitor implements Visitor<Value> {
@Override
public Value visit(ReturnStatement e) {
if (e.expression == null) {
return new Value(null, Type.getVoidType());
}
return e.expression.welcome(this);
}
@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;

View File

@@ -71,7 +71,7 @@ public class GenASM implements Visitor<Void> {
private int lCount = 0;
private int currentFunctionStartLabel = 0;
private long bytesToClearFromTheStack = 0;
private Parameter[] currentFunctionParams;
private FunctionDefinition currentFunctionDef;
public GenASM(String mainName, Map<String, StructDefinition> structs) {
this.mainName = mainName;
@@ -102,6 +102,12 @@ public class GenASM implements Visitor<Void> {
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())) {
@@ -525,7 +531,9 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(ReturnStatement e) {
e.expression.welcome(this);
if (e.expression != null) {
e.expression.welcome(this);
}
// The ReturnStatement visitor is kindly removing the
// stack space that was allocated by the FunctionDefinition
@@ -541,9 +549,23 @@ public class GenASM implements Visitor<Void> {
@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;
}
@@ -556,9 +578,11 @@ public class GenASM implements Visitor<Void> {
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");
@@ -642,17 +666,17 @@ public class GenASM implements Visitor<Void> {
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;
}
@@ -673,10 +697,10 @@ public class GenASM implements Visitor<Void> {
// 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;
}
@@ -1020,4 +1044,4 @@ public class GenASM implements Visitor<Void> {
return false;
}
}
}
}

View File

@@ -1,286 +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<Void> {
public Set<String> vars;
public Map<String, Type> types;
public GetVars(Set<String> vars, Map<String, Type> 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) {
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;
}
}

View File

@@ -97,6 +97,14 @@ public class PrettyPrintVisitor implements Visitor<Void> {
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("(");
@@ -310,8 +318,11 @@ public class PrettyPrintVisitor implements Visitor<Void> {
@Override
public Void visit(ReturnStatement e) {
ex.write("return ");
e.expression.welcome(this);
ex.write("return");
if (e.expression != null) {
ex.write(" ");
e.expression.welcome(this);
}
ex.write(";");
return null;
}
@@ -320,7 +331,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
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) {

View File

@@ -12,6 +12,7 @@ public interface Visitor<R> {
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);

164
src/test/java/CharTest.java Normal file
View File

@@ -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());
}
}

View File

@@ -21,7 +21,7 @@ public class ConstructorCallTest {
}
@Test
void numConstructorParameterMissmatch() {
void numConstructorParametermismatch() {
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
@@ -41,6 +41,6 @@ public class ConstructorCallTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:63 argument 0 Type missmatch: cannot combine bool and int", e.getMessage());
assertEquals("Error in line 1:63 argument 0 Type mismatch: cannot combine bool and int", e.getMessage());
}
}

View File

@@ -33,7 +33,7 @@ public class FunctionCallTest {
}
@Test
void parameterTypeMissmatch() {
void parameterTypeMismatch() {
ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);

101
src/test/java/VoidTest.java Normal file
View File

@@ -0,0 +1,101 @@
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class VoidTest {
@Test
void shouldNotThrowIfVoidIsUsedAsReturnType() {
// given
var tree = Helper.prepareParser("function foo(): void { let a: int = 0; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfVoidFunctionIsCalled() {
// given
var tree = Helper.prepareParser("""
function foo(): void {
let a: int = 0;
}
function bar(): int {
foo();
return 1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldThrowIfVoidFunctionIsAssignedToVariable() {
// given
var tree = Helper.prepareParser("""
function foo(): void {
let a: int = 0;
}
function bar(): int {
let a: int = foo();
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 5:4 Type mismatch: cannot combine int and void", e.getMessage());
}
@Test
void shouldThrowIfValueIsReturnedFromVoidFunction() {
// given
var tree = Helper.prepareParser("function foo(): void { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:23 Cannot return an expression from a void function.", e.getMessage());
}
@Test
void shouldThrowIfVoidIsUsedAsParameterType() {
// given
var tree = Helper.prepareParser("function foo(a: void): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:13 Type void cannot be used to declare a parameter.", e.getMessage());
}
@Test
void shouldThrowIfVoidIsUsedAsVariableType() {
// given
var tree = Helper.prepareParser("function foo(): int { let a: void; return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:22 Type void can not be used to declare variables.", e.getMessage());
}
@Test
void shouldThrowIfVoidIsUsedAsStructFieldType() {
// given
var tree = Helper.prepareParser("struct bar { a: void; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:13 Type void can not be used as a struct field type.", e.getMessage());
}
}