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.
This commit is contained in:
2023-03-23 23:04:09 +01:00
parent a7e93f4f01
commit 2aff9b3d0d
11 changed files with 246 additions and 14 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

@@ -571,7 +571,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;
@@ -605,7 +605,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) {
@@ -613,7 +613,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;
@@ -639,7 +639,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;
@@ -652,7 +652,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);
@@ -730,6 +730,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();
@@ -929,14 +940,14 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
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

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

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

View File

@@ -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())) {

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

125
src/test/java/CharTest.java Normal file
View 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());
}
}