Compare commits

...

6 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
14 changed files with 412 additions and 415 deletions

View File

@@ -107,6 +107,7 @@ atom
: INTEGER_LITERAL #intAtom : INTEGER_LITERAL #intAtom
| BOOLEAN_LITERAL #boolAtom | BOOLEAN_LITERAL #boolAtom
| FLOAT_LITERAL #floatAtom | FLOAT_LITERAL #floatAtom
| CHAR_LITERAL #charAtom
| NULL # nullAtom | NULL # nullAtom
| IDENT #variable | IDENT #variable
; ;
@@ -119,6 +120,7 @@ type
: INTEGER : INTEGER
| BOOLEAN | BOOLEAN
| FLOAT | FLOAT
| CHAR
| IDENT | IDENT
| VOID | VOID
; ;
@@ -184,9 +186,12 @@ SUB: '-';
MOD: '%'; MOD: '%';
DIV: '/'; DIV: '/';
SQUOT: '\'';
BOOLEAN: 'bool'; BOOLEAN: 'bool';
INTEGER: 'int'; INTEGER: 'int';
FLOAT: 'float'; FLOAT: 'float';
CHAR: 'char';
VOID: 'void'; VOID: 'void';
INTEGER_LITERAL INTEGER_LITERAL
@@ -202,6 +207,10 @@ BOOLEAN_LITERAL
| 'false' | 'false'
; ;
CHAR_LITERAL
: SQUOT [ -~] SQUOT
;
IDENT IDENT
: [a-zA-Z][a-zA-Z0-9]* : [a-zA-Z][a-zA-Z0-9]*
; ;

View File

@@ -68,25 +68,32 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitBraced_block(KlangParser.Braced_blockContext ctx) { public Node visitBraced_block(KlangParser.Braced_blockContext ctx) {
var actualStatementCount = 0; var statementsOrFunctionCalls = new ArrayList<Node>();
var declaredStatementCount = ctx.statement().size();
var hasReturn = false; var hasReturn = false;
var statements = new Statement[declaredStatementCount];
for (int i = 0; i < declaredStatementCount; i++) { for (var child: ctx.children) {
var currentStatement = visit(ctx.statement(i)); var statementOrFunctionCall = visit(child);
statements[i] = (Statement) currentStatement;
actualStatementCount += 1;
// We use the existence of a type to indicate that this statement returns // The children array contains more than just the statements or function calls
// something for which the VariableDeclaration is an exception // but everything else evaluates to null, so we can skip it.
if (currentStatement.type != null && !(currentStatement instanceof VariableDeclaration)) { if (statementOrFunctionCall == null) {
continue;
}
statementsOrFunctionCalls.add(statementOrFunctionCall);
if (
statementOrFunctionCall.type != null
&& !(statementOrFunctionCall instanceof VariableDeclaration)
&& !statementOrFunctionCall.type.equals(Type.getVoidType())
) {
// check whether the type matches // check whether the type matches
try { try {
currentDeclaredReturnType.combine(currentStatement.type); currentDeclaredReturnType.combine(statementOrFunctionCall.type);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException( var line = statementOrFunctionCall.line;
Helper.getErrorPrefix(currentStatement.line, currentStatement.col) + e.getMessage()); var col = statementOrFunctionCall.col;
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
// since we have a return guaranteed, every statement // since we have a return guaranteed, every statement
@@ -96,20 +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) {
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, // 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 // 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) { if (hasReturn) {
result.type = this.currentDeclaredReturnType; result.type = currentDeclaredReturnType;
} }
result.line = ctx.start.getLine(); result.line = ctx.start.getLine();
@@ -428,18 +427,21 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) { public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); 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 { try {
lhs.type.combine(rhs.type); lhs.type.combine(rhs.type);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); 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.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -448,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) { public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
NotEqualityExpression result = new NotEqualityExpression((Expression) lhs, (Expression) rhs); var result = new NotEqualityExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -468,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) { public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
checkNumeric(lhs, rhs, line, col); var result = new LTExpression((Expression) lhs, (Expression) rhs);
LTExpression result = new LTExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -490,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) { public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} 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.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); 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.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -515,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) { public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
checkNumeric(lhs, rhs, line, col); var result = new LTEExpression((Expression) lhs, (Expression) rhs);
LTEExpression result = new LTEExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -537,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) { public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
checkNumeric(lhs, rhs, line, col); var result = new GTEExpression((Expression) lhs, (Expression) rhs);
GTEExpression result = new GTEExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -572,7 +558,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
} }
checkNumeric(lhs, rhs, line, col); checkNumeric(lhs, rhs, line, col);
result.line = line; result.line = line;
result.col = col; result.col = col;
return result; return result;
@@ -606,7 +592,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
int line = ctx.start.getLine(); int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); int col = ctx.start.getCharPositionInLine();
DivisionExpression result = new DivisionExpression((Expression) lhs, (Expression) rhs); DivisionExpression result = new DivisionExpression((Expression) lhs, (Expression) rhs);
try { try {
result.type = lhs.type.combine(rhs.type); result.type = lhs.type.combine(rhs.type);
} catch (Exception e) { } catch (Exception e) {
@@ -614,7 +600,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
} }
checkNumeric(lhs, rhs, line, col); checkNumeric(lhs, rhs, line, col);
result.line = line; result.line = line;
result.col = col; result.col = col;
return result; return result;
@@ -640,7 +626,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
String error = "Only integers are allowed for modulo."; String error = "Only integers are allowed for modulo.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
} }
result.line = line; result.line = line;
result.col = col; result.col = col;
return result; return result;
@@ -653,7 +639,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
result.type = expression.type; result.type = expression.type;
result.line = ctx.start.getLine(); result.line = ctx.start.getLine();
result.col = ctx.start.getCharPositionInLine(); result.col = ctx.start.getCharPositionInLine();
if (!result.type.isNumericType()) { if (!result.type.isNumericType()) {
String error = "Only numeric types are allowed for this expression."; String error = "Only numeric types are allowed for this expression.";
throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error); throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error);
@@ -731,6 +717,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
return n; 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 @Override
public Node visitNullAtom(KlangParser.NullAtomContext ctx) { public Node visitNullAtom(KlangParser.NullAtomContext ctx) {
Node n = new NullExpression(); Node n = new NullExpression();
@@ -884,55 +881,60 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
} }
@Override @Override
public Node visitFunctionCallExpression(KlangParser.FunctionCallExpressionContext ctx) { public Node visitFunctionCall(KlangParser.FunctionCallContext ctx) {
String name = ctx.functionCall().IDENT().getText(); var name = ctx.IDENT().getText();
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
var functionDef = this.functionDefs.get(name); var functionDef = functionDefs.get(name);
if (functionDef == null) { 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); throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
} }
// Make sure the number of arguments matches the number of parameters // Make sure the number of arguments matches the number of parameters
int argCount = ctx.functionCall().arguments().expression().size(); var argCount = ctx.arguments().expression().size();
int paramCount = functionDef.parameters.length; var paramCount = functionDef.parameters.length;
if (argCount != paramCount) { 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); throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
} }
// Evaluate every argument // Evaluate every argument
Expression[] args = new Expression[argCount]; var args = new Expression[argCount];
for (int i = 0; i < argCount; i++) { 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)) { 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()); throw new RuntimeException(Helper.getErrorPrefix(line, col) + "argument " + i + " Expected " + functionDef.parameters[i].type.getName() + " but got: " + expression.type.getName());
} }
args[i] = expression; args[i] = expression;
} }
FunctionCall result = new FunctionCall(name, args); var result = new FunctionCall(name, args);
result.type = functionDef.type; result.type = functionDef.type;
result.line = line; result.line = line;
result.col = col; result.col = col;
return result; return result;
} }
@Override
public Node visitFunctionCallExpression(KlangParser.FunctionCallExpressionContext ctx) {
return visit(ctx.functionCall());
}
@Override @Override
public Node visitConstructorCallExpression(KlangParser.ConstructorCallExpressionContext ctx) { public Node visitConstructorCallExpression(KlangParser.ConstructorCallExpressionContext ctx) {
String name = ctx.IDENT().getText(); String name = ctx.IDENT().getText();
int line = ctx.start.getLine(); int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); int col = ctx.start.getCharPositionInLine();
// Get the corresponding struct definition // Get the corresponding struct definition
var struct = this.structDefs.get(name); var struct = this.structDefs.get(name);
if (struct == null) { if (struct == null) {
String error = "Struct with name \"" + name + "\" not defined."; String error = "Struct with name \"" + name + "\" not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
} }
// Make sure the number of arguments match the number of struct fields // Make sure the number of arguments match the number of struct fields
int fieldCount = struct.fields.length; int fieldCount = struct.fields.length;
int argCount = ctx.arguments().expression().size(); int argCount = ctx.arguments().expression().size();

View File

@@ -4,8 +4,8 @@ import de.hsrm.compiler.Klang.types.Type;
import java.util.Map; import java.util.Map;
public class Value { public class Value {
private final Object value;
public Type type; public Type type;
private Object value;
public Value(Object value) { public Value(Object value) {
this.value = value; this.value = value;
@@ -17,19 +17,23 @@ public class Value {
} }
public Object asObject() { public Object asObject() {
return this.value; return value;
} }
public int asInteger() { public int asInteger() {
return (int) this.value; return (int) value;
} }
public double asFloat() { public double asFloat() {
return (double) this.value; return (double) value;
} }
public boolean asBoolean() { public boolean asBoolean() {
return (boolean) this.value; return (boolean) value;
}
public char asChar() {
return (char) value;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@@ -1,14 +1,13 @@
package de.hsrm.compiler.Klang.nodes; package de.hsrm.compiler.Klang.nodes;
import de.hsrm.compiler.Klang.nodes.statements.Statement;
import de.hsrm.compiler.Klang.visitors.Visitor; import de.hsrm.compiler.Klang.visitors.Visitor;
public class Block extends Node { public class Block extends Node {
public Statement[] statements; public Node[] statementsOrFunctionCalls;
public Block(Statement[] statements) { public Block(Node[] statements) {
this.statements = statements; this.statementsOrFunctionCalls = statements;
} }
@Override @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

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

@@ -35,6 +35,10 @@ public class IntegerType extends NumericType {
return Type.getFloatType(); return Type.getFloatType();
} }
if (that.equals(Type.getCharType())) {
return Type.getCharType();
}
// Every remaining type will throw a RuntimeException // Every remaining type will throw a RuntimeException
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
} }

View File

@@ -23,6 +23,10 @@ public abstract class Type {
return FloatType.getType(); return FloatType.getType();
} }
public static CharType getCharType() {
return CharType.getType();
}
public static NullType getNullType() { public static NullType getNullType() {
return NullType.getType(); return NullType.getType();
} }
@@ -36,6 +40,7 @@ public abstract class Type {
case "bool": return getBooleanType(); case "bool": return getBooleanType();
case "int": return getIntegerType(); case "int": return getIntegerType();
case "float": return getFloatType(); case "float": return getFloatType();
case "char": return getCharType();
case "null": return getNullType(); case "null": return getNullType();
case "void": return getVoidType(); case "void": return getVoidType();
default: return new NamedType(name); default: return new NamedType(name);

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.ForLoop;
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop; import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*; 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.NullType;
import de.hsrm.compiler.Klang.types.Type; import de.hsrm.compiler.Klang.types.Type;
@@ -46,6 +45,11 @@ public class EvalVisitor implements Visitor<Value> {
return result; return result;
} }
@Override
public Value visit(CharExpression e) {
return new Value(e.c, Type.getCharType());
}
@Override @Override
public Value visit(EqualityExpression e) { public Value visit(EqualityExpression e) {
var lhs = e.lhs.welcome(this); var lhs = e.lhs.welcome(this);
@@ -404,7 +408,7 @@ public class EvalVisitor implements Visitor<Value> {
@Override @Override
public Value visit(Block e) { public Value visit(Block e) {
for (var stmt : e.statements) { for (var stmt : e.statementsOrFunctionCalls) {
var result = stmt.welcome(this); var result = stmt.welcome(this);
if (result != null) { if (result != null) {
return result; return result;

View File

@@ -71,7 +71,7 @@ public class GenASM implements Visitor<Void> {
private int lCount = 0; private int lCount = 0;
private int currentFunctionStartLabel = 0; private int currentFunctionStartLabel = 0;
private long bytesToClearFromTheStack = 0; private long bytesToClearFromTheStack = 0;
private Parameter[] currentFunctionParams; private FunctionDefinition currentFunctionDef;
public GenASM(String mainName, Map<String, StructDefinition> structs) { public GenASM(String mainName, Map<String, StructDefinition> structs) {
this.mainName = mainName; this.mainName = mainName;
@@ -102,6 +102,12 @@ public class GenASM implements Visitor<Void> {
return null; return null;
} }
@Override
public Void visit(CharExpression e) {
asm.mov("q", "$" + ((int) e.c), "%rax");
return null;
}
@Override @Override
public Void visit(Variable e) { public Void visit(Variable e) {
if (e.type.equals(Type.getFloatType())) { if (e.type.equals(Type.getFloatType())) {
@@ -543,9 +549,23 @@ public class GenASM implements Visitor<Void> {
@Override @Override
public Void visit(Block e) { public Void visit(Block e) {
for (var statement : e.statements) { for (var statementOrFunctionCall : e.statementsOrFunctionCalls) {
statement.welcome(this); 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; return null;
} }
@@ -558,9 +578,11 @@ public class GenASM implements Visitor<Void> {
e.name = "main_by_user"; e.name = "main_by_user";
} }
// Remember the current function definition so everyone below us knows where they belong to.
currentFunctionDef = e;
var lblStart = ++lCount; var lblStart = ++lCount;
currentFunctionStartLabel = lblStart; currentFunctionStartLabel = lblStart;
currentFunctionParams = e.parameters;
asm.functionHead(e.name); asm.functionHead(e.name);
asm.push("q", "%rbp"); asm.push("q", "%rbp");
asm.mov("q", "%rsp", "%rbp"); asm.mov("q", "%rsp", "%rbp");
@@ -644,17 +666,17 @@ public class GenASM implements Visitor<Void> {
asm.sub("q", "$8", "%rsp"); 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 // 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 // ret uses to return to the caller but code that gets generated after visiting the
// reached since the block node is guaranteed to contain a return node that results // function body will never be reached since the block node is guaranteed to
// in a ret command being executed before this code would be reached. // contain a return node that results in a ret command being executed before this
// As a workaround (and a dirty, dirty hack) I indicate to the return node visitor // code would be reached. As a workaround (and a dirty, dirty hack) I indicate
// how many bytes need to be cleared from the stack. // to the return node visitor how many bytes need to be cleared from the stack.
var wasStackPadded = (registerParameters.size() + e.localVariables.length) % 2 != 0; var wasStackPadded = (registerParameters.size() + e.localVariables.length) % 2 != 0;
bytesToClearFromTheStack = 8L * (registerParameters.size() + e.localVariables.length + (wasStackPadded ? 1 : 0)); bytesToClearFromTheStack = 8L * (registerParameters.size() + e.localVariables.length + (wasStackPadded ? 1 : 0));
e.block.welcome(this);
return null; return null;
} }
@@ -675,10 +697,10 @@ public class GenASM implements Visitor<Void> {
// push args into local var locations, last arg is on top of the stack // push args into local var locations, last arg is on top of the stack
for (int i = e.arguments.length - 1; i >= 0; i--) { 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; return null;
} }

View File

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

View File

@@ -97,6 +97,14 @@ public class PrettyPrintVisitor implements Visitor<Void> {
return null; return null;
} }
@Override
public Void visit(CharExpression e) {
ex.write("'");
ex.write(e.c);
ex.write("'");
return null;
}
@Override @Override
public Void visit(EqualityExpression e) { public Void visit(EqualityExpression e) {
ex.write("("); ex.write("(");
@@ -323,7 +331,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
public Void visit(Block e) { public Void visit(Block e) {
ex.write("{"); ex.write("{");
ex.addIndent(); ex.addIndent();
for (Statement stmt : e.statements) { for (var stmt : e.statementsOrFunctionCalls) {
ex.nl(); ex.nl();
stmt.welcome(this); stmt.welcome(this);
if (stmt.getClass() == VariableAssignment.class || stmt.getClass() == VariableDeclaration.class) { 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(IntegerExpression e);
R visit(FloatExpression e); R visit(FloatExpression e);
R visit(BooleanExpression e); R visit(BooleanExpression e);
R visit(CharExpression e);
R visit(Variable e); R visit(Variable e);
R visit(AdditionExpression e); R visit(AdditionExpression e);
R visit(EqualityExpression 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());
}
}