Merge pull request 'feature/char-support' (#3) from feature/char-support into master

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
2023-03-24 17:03:23 +01:00
14 changed files with 412 additions and 415 deletions

View File

@@ -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]*
;

View File

@@ -68,25 +68,32 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitBraced_block(KlangParser.Braced_blockContext ctx) {
var actualStatementCount = 0;
var declaredStatementCount = ctx.statement().size();
var statementsOrFunctionCalls = new ArrayList<Node>();
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<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,
// 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<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;
@@ -448,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;
@@ -468,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;
@@ -490,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;
@@ -515,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;
@@ -537,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;
@@ -731,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();
@@ -884,42 +881,47 @@ 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();

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

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

View File

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

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);
@@ -404,7 +408,7 @@ public class EvalVisitor implements Visitor<Value> {
@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())) {
@@ -543,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;
}
@@ -558,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");
@@ -644,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;
}
@@ -675,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;
}

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