feature/char-support #3
@@ -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]*
|
||||
;
|
||||
|
||||
@@ -571,7 +571,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
}
|
||||
|
||||
checkNumeric(lhs, rhs, line, col);
|
||||
|
||||
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
return result;
|
||||
@@ -605,7 +605,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) {
|
||||
@@ -613,7 +613,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
}
|
||||
|
||||
checkNumeric(lhs, rhs, line, col);
|
||||
|
||||
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
return result;
|
||||
@@ -639,7 +639,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;
|
||||
@@ -652,7 +652,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);
|
||||
@@ -730,6 +730,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();
|
||||
@@ -929,14 +940,14 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
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();
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
43
src/main/java/de/hsrm/compiler/Klang/types/CharType.java
Normal file
43
src/main/java/de/hsrm/compiler/Klang/types/CharType.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package de.hsrm.compiler.Klang.types;
|
||||
|
||||
import de.hsrm.compiler.Klang.Value;
|
||||
|
||||
public class CharType extends PrimitiveType {
|
||||
private static CharType instance = null;
|
||||
|
||||
public static CharType getType() {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
instance = new CharType();
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "char";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type combine(Type that) {
|
||||
if (!this.equals(that)) {
|
||||
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + getName());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean valuesEqual(Value a, Value b) {
|
||||
return ((int) a.asChar()) == ((int) b.asChar());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
if (this == that) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return that instanceof CharType;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
125
src/test/java/CharTest.java
Normal file
125
src/test/java/CharTest.java
Normal file
@@ -0,0 +1,125 @@
|
||||
import de.hsrm.compiler.Klang.ContextAnalysis;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class CharTest {
|
||||
|
||||
@Test
|
||||
void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): int {
|
||||
let c: char = 'c';
|
||||
return 1;
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
assertDoesNotThrow(() -> ctxAnal.visit(tree));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowIfCharLiteralIsReturnedFromFunction() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): char {
|
||||
return 'c';
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
assertDoesNotThrow(() -> ctxAnal.visit(tree));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowIfCharIsUsedInStruct() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
struct myChar { c: char; }
|
||||
function bar(): myChar {
|
||||
let x: myChar = create myChar('c');
|
||||
return x;
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
assertDoesNotThrow(() -> ctxAnal.visit(tree));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIfCharIsAssignedToOtherTypedVariable() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): int {
|
||||
let c: int = 'c';
|
||||
return 1;
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 2:4 Type mismatch: cannot combine int and char", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenReturningCharFromOtherTypedFunction() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): int {
|
||||
return 'c';
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 2:4 Type mismatch: cannot combine int and char", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowPassingCharToOtherTypedFunctionParameter() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function foo(a: float): void {
|
||||
let x: bool = false;
|
||||
}
|
||||
function bar(): void {
|
||||
foo('c');
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 5:4 argument 0 Expected float but got: char", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenAssigningCharToOtherTypedStructField() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
struct foo { a: int; }
|
||||
function bar(): void {
|
||||
let x: foo = create foo(1);
|
||||
x.a = 'c';
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 4:4 Type mismatch: cannot combine int and char", e.getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user