From e4ea85c0ced3beb63ecafd7858358dab890caa97 Mon Sep 17 00:00:00 2001
From: Niklas Fors <niklas.fors@cs.lth.se>
Date: Fri, 6 Jul 2018 16:30:18 +0200
Subject: [PATCH] Generate method violateLowerBounds() and fix minor things
 with lists/opts

---
 spec/jastadd/Analysis.jrag          |  10 +++
 spec/jastadd/Backend.jadd           | 100 +++++++++++++++++++++++++++-
 spec/parser/RelAstBase.parser       |   4 +-
 spec/scanner/RelAst.flex            |   2 +
 tests/valid/.gitignore              |   6 +-
 tests/valid/AbstractTests.java      |  25 +++++++
 tests/valid/{Test.java => All.java} |  29 +-------
 tests/valid/LowerBounds.java        |  47 +++++++++++++
 tests/valid/LowerBounds.relast      |   8 +++
 tests/valid/Makefile                |  17 +++--
 10 files changed, 206 insertions(+), 42 deletions(-)
 create mode 100644 tests/valid/AbstractTests.java
 rename tests/valid/{Test.java => All.java} (93%)
 create mode 100644 tests/valid/LowerBounds.java
 create mode 100644 tests/valid/LowerBounds.relast

diff --git a/spec/jastadd/Analysis.jrag b/spec/jastadd/Analysis.jrag
index 6ee9e64..a9d3711 100644
--- a/spec/jastadd/Analysis.jrag
+++ b/spec/jastadd/Analysis.jrag
@@ -85,6 +85,16 @@ aspect ComponentAnalysis {
 		when !isTargetOfDirectedRelation() && toTypeDecl() != null
 		to TypeDecl.relationComponents()
 		for toTypeDecl();
+
+	syn Set<OneRelationComponent> TypeDecl.oneRelationComponents() {
+		Set<OneRelationComponent> set = new HashSet<>();
+		for (RelationComponent rc: relationComponents()) {
+			if (rc instanceof OneRelationComponent) {
+				set.add((OneRelationComponent) rc);
+			}
+		}
+		return set;
+	}
 }
 
 aspect Constructors {
diff --git a/spec/jastadd/Backend.jadd b/spec/jastadd/Backend.jadd
index 763091d..f2bb942 100644
--- a/spec/jastadd/Backend.jadd
+++ b/spec/jastadd/Backend.jadd
@@ -75,6 +75,7 @@ aspect BackendAspect {
 		sb.append("import java.util.ArrayList;\n");
 		sb.append("import java.util.Collections;\n");
 		sb.append("aspect RelAstAPI {\n");
+
 		for (TypeDecl td: getTypeDecls()) {
 			if (td.needsConstructor()) {
 				td.generateConstructor(sb);
@@ -84,7 +85,9 @@ aspect BackendAspect {
 			r.generateAPI(sb);
 		}
 
-		sb.append(ind(1) + "public void ASTNode.assertNotNull(Object obj) {\n");
+		generateLowerBoundCheck(sb);
+
+		sb.append(ind(1) + "public static void ASTNode.assertNotNull(Object obj) {\n");
 		sb.append(ind(2) + "if (obj == null) {\n");
 		sb.append(ind(3) + "throw new NullPointerException();\n");
 		sb.append(ind(2) + "}\n");
@@ -96,17 +99,35 @@ aspect BackendAspect {
 		sb.append(ind(1) + "public " + getID() + "." + getID() + "(");
 		int i = 0;
 		for (Component c: componentsTransitive()) {
-			sb.append(c.getTypeUse() + " " + c.getID());
+			sb.append(c.constructorParameter());
 			if (++i < componentsTransitive().size()) {
 				sb.append(", ");
 			}
 		}
 		sb.append(") {\n");
 		for (Component c: componentsTransitive()) {
-			sb.append(ind(2) + "set" + c.getID() + "(" + c.getID() + ");\n");
+			sb.append(ind(2) + c.constructorSetMethod() + "(" + c.getID() + ");\n");
 		}
 		sb.append(ind(1) + "}\n");
 	}
+	public String Component.constructorParameter() {
+		return getTypeUse() + " " + getID();
+	}
+	public String ListComponent.constructorParameter() {
+		return "List<" + getTypeUse() + "> " + getID();
+	}
+	public String OptComponent.constructorParameter() {
+		return "Opt<" + getTypeUse() + "> " + getID();
+	}
+	public String Component.constructorSetMethod() {
+		return "set" + getID();
+	}
+	public String ListComponent.constructorSetMethod() {
+		return "set" + getID() + "List";
+	}
+	public String OptComponent.constructorSetMethod() {
+		return "set" + getID() + "Opt";
+	}
 }
 
 aspect BackendAPI {
@@ -403,6 +424,79 @@ aspect BackendBidirectionalAPI {
 	}
 }
 
+aspect LowerBoundCheck {
+	public void Program.generateLowerBoundCheck(StringBuilder sb) {
+		sb.append(ind(1) + "public boolean ASTNode.violateLowerBounds() {\n");
+		sb.append(ind(2) + "return !getLowerBoundsViolations().isEmpty();\n");
+		sb.append(ind(1) + "}\n");
+
+		sb.append(ind(1) + "public java.util.List<Pair<ASTNode, String>> "
+			+ "ASTNode.getLowerBoundsViolations() {\n");
+		sb.append(ind(2) + "ArrayList<Pair<ASTNode, String>> list = new ArrayList<>();\n");
+		sb.append(ind(2) + "computeLowerBoundsViolations(list);\n");
+		sb.append(ind(2) + "return list;\n");
+		sb.append(ind(1) + "}\n");
+
+		sb.append(ind(1) + "public void ASTNode.computeLowerBoundsViolations("
+			+ "java.util.List<Pair<ASTNode, String>> list) {\n");
+		sb.append(ind(2) + "for (int i = 0; i < getNumChildNoTransform(); i++) {\n");
+		sb.append(ind(3) + "getChildNoTransform(i).computeLowerBoundsViolations(list);\n");
+		sb.append(ind(2) + "}\n");
+		sb.append(ind(1) + "}\n");
+
+		for (TypeDecl td: getTypeDecls()) {
+			td.generateLowerBoundCheck(sb);
+		}
+
+		generatePairClass(sb);
+	}
+
+	public void TypeDecl.generateLowerBoundCheck(StringBuilder sb) {
+		if (!oneRelationComponents().isEmpty()) {
+			sb.append(ind(1) + "public void " + getID() + ".computeLowerBoundsViolations(" +
+				"java.util.List<Pair<ASTNode, String>> list) {\n");
+			for (OneRelationComponent o: oneRelationComponents()) {
+				o.generateLowerBoundCheck(sb);
+			}
+			sb.append(ind(2) + "super.computeLowerBoundsViolations(list);\n");
+			sb.append(ind(1) + "}\n");
+		}
+	}
+
+	public void OneRelationComponent.generateLowerBoundCheck(StringBuilder sb) {
+		sb.append(ind(2) + "if (" + name() + "() == null) {\n");
+		sb.append(ind(3) + "list.add(new Pair<>(this, \"" + name() + "\"));\n");
+		sb.append(ind(2) + "}\n");
+	}
+
+
+	public void Program.generatePairClass(StringBuilder sb) {
+		sb.append(ind(1) + "public class Pair<T1, T2> {\n");
+		sb.append(ind(2) + "public final T1 _1;\n");
+		sb.append(ind(2) + "public final T2 _2;\n");
+		// Constructor
+		sb.append(ind(2) + "public Pair(T1 _1, T2 _2) {\n");
+		sb.append(ind(3) + "ASTNode.assertNotNull(_1);\n");
+		sb.append(ind(3) + "ASTNode.assertNotNull(_2);\n");
+		sb.append(ind(3) + "this._1 = _1;\n");
+		sb.append(ind(3) + "this._2 = _2;\n");
+		sb.append(ind(2) + "}\n");
+		// equals
+		sb.append(ind(2) + "public boolean equals(Object other) {\n");
+		sb.append(ind(3) + "if (other instanceof Pair) {\n");
+		sb.append(ind(4) + "Pair<?,?> p = (Pair<?,?>) other;\n");
+		sb.append(ind(4) + "return _1.equals(p._1) && _2.equals(p._2);\n");
+		sb.append(ind(3) + "} else {\n");
+		sb.append(ind(4) + "return false;\n");
+		sb.append(ind(3) + "}\n");
+		sb.append(ind(2) + "}\n");
+		// hashCode
+		sb.append(ind(2) + "public int hashCode() {\n");
+		sb.append(ind(3) + "return 31*_1.hashCode() + _2.hashCode();\n");
+		sb.append(ind(2) + "}\n");
+		sb.append(ind(1) + "}\n");
+	}
+}
 
 aspect PrettyPrint {
 	public String Relation.prettyPrint() {
diff --git a/spec/parser/RelAstBase.parser b/spec/parser/RelAstBase.parser
index 6b99a76..15fca6a 100644
--- a/spec/parser/RelAstBase.parser
+++ b/spec/parser/RelAstBase.parser
@@ -53,8 +53,8 @@ Component component =
 	| ID COL s_type_use.u STAR {: return new ListComponent(ID, u); :}
 	| s_type_use.u STAR {: return new ListComponent(u.getID(), u); :}
 	// Opt
-	| ID COL s_type_use.u QUESTION_MARK {: return new OptComponent(ID, u); :}
-	| s_type_use.u QUESTION_MARK {: return new OptComponent(u.getID(), u); :}
+	| LBRACKET ID COL s_type_use.u RBRACKET {: return new OptComponent(ID, u); :}
+	| LBRACKET s_type_use.u RBRACKET {: return new OptComponent(u.getID(), u); :}
 	// Token
 	| LT ID COL type_use.u GT {: return new TokenComponent(ID, u); :}
 	| LT ID GT {: return new TokenComponent(ID, new SimpleTypeUse("String")); :}
diff --git a/spec/scanner/RelAst.flex b/spec/scanner/RelAst.flex
index 44ec2a2..27d068b 100644
--- a/spec/scanner/RelAst.flex
+++ b/spec/scanner/RelAst.flex
@@ -60,6 +60,8 @@ ID = [a-zA-Z$_][a-zA-Z0-9$_]*
 	","				{ return sym(Terminals.COMMA); }
 	"<"				{ return sym(Terminals.LT); }
 	">"				{ return sym(Terminals.GT); }
+	"["				{ return sym(Terminals.LBRACKET); }
+	"]"				{ return sym(Terminals.RBRACKET); }
 	"?"				{ return sym(Terminals.QUESTION_MARK); }
 	"->"			{ return sym(Terminals.RIGHT); }
 	"<->"			{ return sym(Terminals.BIDIRECTIONAL); }
diff --git a/tests/valid/.gitignore b/tests/valid/.gitignore
index 12f898e..670d442 100644
--- a/tests/valid/.gitignore
+++ b/tests/valid/.gitignore
@@ -1,5 +1,3 @@
 /AST/*
-/AllGen.ast
-/AllGen.jadd
-/AllGenGen.ast
-/AllGenGen.jadd
+/*Gen.ast
+/*Gen.jadd
diff --git a/tests/valid/AbstractTests.java b/tests/valid/AbstractTests.java
new file mode 100644
index 0000000..3b86096
--- /dev/null
+++ b/tests/valid/AbstractTests.java
@@ -0,0 +1,25 @@
+public class AbstractTests {
+	protected void assertException() {
+		check(false, "should throw exception");
+	}
+	protected void assertTrue(boolean b) {
+		check(b, "value should be true (is false)");
+	}
+	protected void assertFalse(boolean b) {
+		check(!b, "value should be flase (is true)");
+	}
+	protected void assertNull(Object obj) {
+		check(obj == null, "Object not null: " + obj);
+	}
+	protected void assertSame(Object o1, Object o2) {
+		check(o1 == o2, "Objects not same: " + o1 + ", " + o2);
+	}
+	protected void assertEquals(Object o1, Object o2) {
+		check(o1.equals(o2), "Objects not equals: " + o1 + ", " + o2);
+	}
+	protected void check(boolean b, String message) {
+		if (!b) {
+			throw new RuntimeException(message);
+		}
+	}
+}
\ No newline at end of file
diff --git a/tests/valid/Test.java b/tests/valid/All.java
similarity index 93%
rename from tests/valid/Test.java
rename to tests/valid/All.java
index c2f0665..3771423 100644
--- a/tests/valid/Test.java
+++ b/tests/valid/All.java
@@ -1,7 +1,7 @@
 import AST.*;
 import java.util.*;
 
-public class Test {
+public class All extends AbstractTests {
 	private Root r;
 	private A a1;
 	private A a2;
@@ -11,7 +11,7 @@ public class Test {
 	private B b3;
 
 	public static void main(String args[]) {
-		new Test().test();
+		new All().test();
 	}
 
 	public void test() {
@@ -551,29 +551,4 @@ public class Test {
 		r.addB(b2);
 		r.addB(b3);
 	}
-
-	private void assertException() {
-		check(false, "should throw exception");
-	}
-	private void assertTrue(boolean b) {
-		check(b, "value should be true (is false)");
-	}
-	private void assertFalse(boolean b) {
-		check(!b, "value should be flase (is true)");
-	}
-	private void assertNull(Object obj) {
-		check(obj == null, "Object not null: " + obj);
-	}
-	private void assertSame(Object o1, Object o2) {
-		check(o1 == o2, "Objects not same: " + o1 + ", " + o2);
-	}
-	private void assertEquals(Object o1, Object o2) {
-		check(o1.equals(o2), "Objects not equals: " + o1 + ", " + o2);
-	}
-	private void check(boolean b, String message) {
-		if (!b) {
-			throw new RuntimeException(message);
-		}
-
-	}
 }
\ No newline at end of file
diff --git a/tests/valid/LowerBounds.java b/tests/valid/LowerBounds.java
new file mode 100644
index 0000000..b2f7832
--- /dev/null
+++ b/tests/valid/LowerBounds.java
@@ -0,0 +1,47 @@
+import AST.*;
+
+public class LowerBounds extends AbstractTests {
+	public static void main(String args[]) {
+		new LowerBounds().test();
+	}
+
+	/*
+	 * Root ::= A* B*;
+	 * A ::= <Name> [C];
+	 * B ::= <Name>;
+	 * C ::= <Name>;
+	 * rel A.b -> B;
+	 * rel B.c <-> C.b;
+	 * rel Root.aa? -> A;
+	 */
+	public void test() {
+		Root r = new Root();
+		C c1 = new C("c1");
+		C c2 = new C("c2");
+		A a1 = new A("a1", new Opt<>(c1));
+		A a2 = new A("a2", new Opt<>(c2));
+		B b1 = new B("b1");
+		B b2 = new B("b2");
+		r.addA(a1);
+		r.addA(a2);
+		r.addB(b1);
+		r.addB(b2);
+
+		assertTrue(r.violateLowerBounds());
+
+		a1.setB(b1);
+		a2.setB(b2);
+		b1.setC(c1);
+		b2.setC(c2);
+
+		assertFalse(r.violateLowerBounds());
+
+		b2.setC(c1);
+
+		assertTrue(r.violateLowerBounds());
+
+		b1.setC(c2);
+
+		assertFalse(r.violateLowerBounds());
+	}
+}
\ No newline at end of file
diff --git a/tests/valid/LowerBounds.relast b/tests/valid/LowerBounds.relast
new file mode 100644
index 0000000..43b2340
--- /dev/null
+++ b/tests/valid/LowerBounds.relast
@@ -0,0 +1,8 @@
+Root ::= A* B*;
+A ::= <Name> [C];
+B ::= <Name>;
+C ::= <Name>;
+
+rel A.b -> B;
+rel B.c <-> C.b;
+rel Root.aa? -> A;
diff --git a/tests/valid/Makefile b/tests/valid/Makefile
index 786fe1f..6db0a7e 100644
--- a/tests/valid/Makefile
+++ b/tests/valid/Makefile
@@ -1,17 +1,22 @@
 all: build-jar test
-test: compile run check-idempotent
+test: test1 test2
 	@echo "#"
 	@echo "# VALID TESTS OK"
 	@echo "#"
 
 build-jar:
 	(cd ../../ && ant jar)
-compile:
+test1:
 	java -jar ../../relast-compiler.jar All.relast --file
+	rm -rf AST
 	java -jar ../../tools/jastadd2.jar --package=AST AllGen.ast AllGen.jadd Utils.jadd
-run:
-	javac AST/*.java Test.java
-	java Test
-check-idempotent:
+	javac AST/*.java All.java
+	java All
 	java -jar ../../relast-compiler.jar AllGen.ast --file
 	diff AllGen.ast AllGenGen.ast
+test2:
+	java -jar ../../relast-compiler.jar LowerBounds.relast --file
+	rm -rf AST
+	java -jar ../../tools/jastadd2.jar --package=AST LowerBoundsGen.ast LowerBoundsGen.jadd Utils.jadd
+	javac AST/*.java LowerBounds.java
+	java LowerBounds
-- 
GitLab