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:
@@ -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]*
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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();
|
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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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())) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
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