From 259ac499810349702060f5d1233d7fe8313e3283 Mon Sep 17 00:00:00 2001 From: nitrix Date: Fri, 24 Mar 2023 00:17:38 +0100 Subject: [PATCH] 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. --- .../hsrm/compiler/Klang/ContextAnalysis.java | 107 ++++++++---------- .../hsrm/compiler/Klang/types/CharType.java | 10 +- .../compiler/Klang/types/IntegerType.java | 4 + src/test/java/CharTest.java | 53 +++++++-- 4 files changed, 104 insertions(+), 70 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 0fed5bb..22fab9d 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -427,18 +427,21 @@ public class ContextAnalysis extends KlangBaseVisitor { @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; @@ -447,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @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; @@ -467,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @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; @@ -489,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @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; @@ -514,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @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; @@ -536,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor { @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; diff --git a/src/main/java/de/hsrm/compiler/Klang/types/CharType.java b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java index f339fe8..e4ee34f 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/CharType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/CharType.java @@ -20,11 +20,15 @@ public class CharType extends PrimitiveType { @Override public Type combine(Type that) { - if (!this.equals(that)) { - throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + getName()); + if (this.equals(that)) { + return this; } - return this; + if (that.equals(Type.getIntegerType())) { + return this; + } + + throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName()); } @Override diff --git a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java index f413d5b..3673020 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java @@ -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()); } diff --git a/src/test/java/CharTest.java b/src/test/java/CharTest.java index cd334f1..07dd92b 100644 --- a/src/test/java/CharTest.java +++ b/src/test/java/CharTest.java @@ -5,6 +5,44 @@ 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 @@ -57,8 +95,9 @@ public class CharTest { void shouldThrowIfCharIsAssignedToOtherTypedVariable() { // given var tree = Helper.prepareParser(""" + struct foo { f: float; } function bar(): int { - let c: int = 'c'; + let c: foo = 'c'; return 1; } bar(); @@ -67,14 +106,14 @@ public class CharTest { // 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()); + 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(): int { + function bar(): float { return 'c'; } bar(); @@ -83,7 +122,7 @@ public class CharTest { // 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()); + assertEquals("Error in line 2:4 Type mismatch: cannot combine float and char", e.getMessage()); } @Test @@ -109,9 +148,9 @@ public class CharTest { void shouldThrowWhenAssigningCharToOtherTypedStructField() { // given var tree = Helper.prepareParser(""" - struct foo { a: int; } + struct foo { a: bool; } function bar(): void { - let x: foo = create foo(1); + let x: foo = create foo(false); x.a = 'c'; } bar(); @@ -120,6 +159,6 @@ public class CharTest { // 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()); + assertEquals("Error in line 4:4 Type mismatch: cannot combine bool and char", e.getMessage()); } }