aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-semantics/src
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-17 02:32:26 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-17 02:43:55 +0200
commiteb5da232b5954895b449957c73e35d0b36e3a902 (patch)
treea8714116cfe3102659e9c5e5b90131e7ec248492 /subprojects/language-semantics/src
parentchore(deps): bump dependencies (diff)
downloadrefinery-eb5da232b5954895b449957c73e35d0b36e3a902.tar.gz
refinery-eb5da232b5954895b449957c73e35d0b36e3a902.tar.zst
refinery-eb5da232b5954895b449957c73e35d0b36e3a902.zip
feat: basic semantics mapping and visualization
Diffstat (limited to 'subprojects/language-semantics/src')
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java337
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java31
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java20
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java9
-rw-r--r--subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java11
5 files changed, 366 insertions, 42 deletions
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
index fe67ed2c..93c7c8e5 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
@@ -9,66 +9,298 @@ import com.google.inject.Inject;
9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps; 9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; 10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
11import tools.refinery.language.model.problem.*; 11import tools.refinery.language.model.problem.*;
12import tools.refinery.language.semantics.model.internal.DecisionTree;
13import tools.refinery.language.utils.BuiltinSymbols;
12import tools.refinery.language.utils.ProblemDesugarer; 14import tools.refinery.language.utils.ProblemDesugarer;
13import tools.refinery.store.representation.Symbol; 15import tools.refinery.language.utils.ProblemUtil;
16import tools.refinery.store.model.ModelStoreBuilder;
17import tools.refinery.store.reasoning.ReasoningAdapter;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.seed.ModelSeed;
20import tools.refinery.store.reasoning.seed.Seed;
21import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
22import tools.refinery.store.reasoning.translator.metamodel.Metamodel;
23import tools.refinery.store.reasoning.translator.metamodel.MetamodelBuilder;
24import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator;
25import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
26import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
27import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
28import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
14import tools.refinery.store.representation.TruthValue; 29import tools.refinery.store.representation.TruthValue;
30import tools.refinery.store.representation.cardinality.CardinalityInterval;
31import tools.refinery.store.representation.cardinality.CardinalityIntervals;
32import tools.refinery.store.representation.cardinality.UpperCardinalities;
15import tools.refinery.store.tuple.Tuple; 33import tools.refinery.store.tuple.Tuple;
16 34
17import java.util.HashMap; 35import java.util.ArrayList;
36import java.util.LinkedHashMap;
18import java.util.Map; 37import java.util.Map;
19 38
20public class ModelInitializer { 39public class ModelInitializer {
21 @Inject 40 @Inject
22 private ProblemDesugarer desugarer; 41 private ProblemDesugarer desugarer;
23 42
43 @Inject
44 private SemanticsUtils semanticsUtils;
45
46 private Problem problem;
47
48 private BuiltinSymbols builtinSymbols;
49
50 private PartialRelation nodeRelation;
51
24 private final MutableObjectIntMap<Node> nodeTrace = ObjectIntMaps.mutable.empty(); 52 private final MutableObjectIntMap<Node> nodeTrace = ObjectIntMaps.mutable.empty();
25 53
26 private final Map<tools.refinery.language.model.problem.Relation, Symbol<TruthValue>> relationTrace = 54 private final Map<Relation, RelationInfo> relationInfoMap = new LinkedHashMap<>();
27 new HashMap<>();
28 55
29 private int nodeCount = 0; 56 private Map<Relation, PartialRelation> relationTrace;
57
58 private final MetamodelBuilder metamodelBuilder = Metamodel.builder();
59
60 public int getNodeCount() {
61 return nodeTrace.size();
62 }
63
64 public MutableObjectIntMap<Node> getNodeTrace() {
65 return nodeTrace;
66 }
30 67
31 /*public void createModel(Problem problem) { 68 public Map<Relation, PartialRelation> getRelationTrace() {
32 var builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException( 69 return relationTrace;
70 }
71
72 public ModelSeed createModel(Problem problem, ModelStoreBuilder builder) {
73 this.problem = problem;
74 builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException(
33 "Problem has no builtin library")); 75 "Problem has no builtin library"));
34 var collectedSymbols = desugarer.collectSymbols(problem); 76 var nodeInfo = collectPartialRelation(builtinSymbols.node(), 1, TruthValue.TRUE, TruthValue.TRUE);
35 for (var node : collectedSymbols.nodes().keySet()) { 77 nodeRelation = nodeInfo.partialRelation();
36 nodeTrace.put(node, nodeCount); 78 metamodelBuilder.type(nodeRelation, true);
37 nodeCount += 1; 79 relationInfoMap.put(builtinSymbols.exists(), new RelationInfo(ReasoningAdapter.EXISTS_SYMBOL, null,
38 } 80 TruthValue.TRUE));
39 for (var pair : collectedSymbols.relations().entrySet()) { 81 relationInfoMap.put(builtinSymbols.equals(), new RelationInfo(ReasoningAdapter.EQUALS_SYMBOL, (TruthValue) null,
40 var relation = pair.getKey(); 82 null));
41 var relationInfo = pair.getValue(); 83 relationInfoMap.put(builtinSymbols.contained(), new RelationInfo(ContainmentHierarchyTranslator.CONTAINED_SYMBOL,
42 var isEqualsRelation = relation == builtinSymbols.equals(); 84 null, TruthValue.UNKNOWN));
43 var decisionTree = mergeAssertions(relationInfo, isEqualsRelation); 85 relationInfoMap.put(builtinSymbols.contains(), new RelationInfo(ContainmentHierarchyTranslator.CONTAINS_SYMBOL,
44 var defaultValue = isEqualsRelation ? TruthValue.FALSE : TruthValue.UNKNOWN; 86 null, TruthValue.UNKNOWN));
45 relationTrace.put(relation, Symbol.of( 87 relationInfoMap.put(builtinSymbols.invalidNumberOfContainers(),
46 relationInfo.name(), relationInfo.arity(), TruthValue.class, defaultValue)); 88 new RelationInfo(ContainmentHierarchyTranslator.INVALID_NUMBER_OF_CONTAINERS, TruthValue.FALSE,
47 } 89 TruthValue.FALSE));
48 } 90 collectNodes();
49 91 collectPartialSymbols();
50 private DecisionTree mergeAssertions(RelationInfo relationInfo, boolean isEqualsRelation) { 92 collectAssertions();
51 var arity = relationInfo.arity(); 93 var metamodel = metamodelBuilder.build();
52 var defaultAssertions = new DecisionTree(arity, isEqualsRelation ? null : TruthValue.UNKNOWN); 94 builder.with(ReasoningAdapter.builder());
53 var assertions = new DecisionTree(arity); 95 builder.with(new MultiObjectTranslator());
54 for (var assertion : relationInfo.assertions()) { 96 builder.with(new MetamodelTranslator(metamodel));
55 var tuple = getTuple(assertion); 97 relationTrace = new LinkedHashMap<>(relationInfoMap.size());
56 var value = getTruthValue(assertion.getValue()); 98 int nodeCount = getNodeCount();
57 if (assertion.isDefault()) { 99 var modelSeedBuilder = ModelSeed.builder(nodeCount);
58 defaultAssertions.mergeValue(tuple, value); 100 for (var entry : relationInfoMap.entrySet()) {
101 var relation = entry.getKey();
102 var info = entry.getValue();
103 var partialRelation = info.partialRelation();
104 relationTrace.put(relation, partialRelation);
105 modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount));
106 }
107 return modelSeedBuilder.build();
108 }
109
110 private void collectNodes() {
111 for (var statement : problem.getStatements()) {
112 if (statement instanceof IndividualDeclaration individualDeclaration) {
113 for (var individual : individualDeclaration.getNodes()) {
114 collectNode(individual);
115 }
116 } else if (statement instanceof ClassDeclaration classDeclaration) {
117 var newNode = classDeclaration.getNewNode();
118 if (newNode != null) {
119 collectNode(newNode);
120 }
121 } else if (statement instanceof EnumDeclaration enumDeclaration) {
122 for (var literal : enumDeclaration.getLiterals()) {
123 collectNode(literal);
124 }
125 }
126 }
127 for (var node : problem.getNodes()) {
128 collectNode(node);
129 }
130 }
131
132 private void collectNode(Node node) {
133 nodeTrace.getIfAbsentPut(node, this::getNodeCount);
134 }
135
136 private void collectPartialSymbols() {
137 for (var statement : problem.getStatements()) {
138 if (statement instanceof ClassDeclaration classDeclaration) {
139 collectClassDeclaration(classDeclaration);
140 } else if (statement instanceof EnumDeclaration enumDeclaration) {
141 collectPartialRelation(enumDeclaration, 1, null, TruthValue.FALSE);
142 } else if (statement instanceof PredicateDefinition predicateDefinition) {
143 // TODO Implement predicate definitions
144 }
145 }
146 }
147
148 private void collectClassDeclaration(ClassDeclaration classDeclaration) {
149 collectPartialRelation(classDeclaration, 1, null, TruthValue.UNKNOWN);
150 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
151 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
152 collectPartialRelation(referenceDeclaration, 2, null, TruthValue.UNKNOWN);
153 var invalidMultiplicityConstraint = referenceDeclaration.getInvalidMultiplicity();
154 if (invalidMultiplicityConstraint != null) {
155 collectPartialRelation(invalidMultiplicityConstraint, 1, TruthValue.FALSE, TruthValue.FALSE);
156 }
59 } else { 157 } else {
60 assertions.mergeValue(tuple, value); 158 throw new IllegalArgumentException("Unknown feature declaration: " + featureDeclaration);
159 }
160 }
161 }
162
163 private RelationInfo collectPartialRelation(Relation relation, int arity, TruthValue value,
164 TruthValue defaultValue) {
165 return relationInfoMap.computeIfAbsent(relation, key -> {
166 var name = getName(relation);
167 return new RelationInfo(name, arity, value, defaultValue);
168 });
169 }
170
171 private String getName(Relation relation) {
172 return semanticsUtils.getName(relation).orElseGet(() -> "#" + relationInfoMap.size());
173 }
174
175 private void collectAssertions() {
176 for (var statement : problem.getStatements()) {
177 if (statement instanceof ClassDeclaration classDeclaration) {
178 collectClassDeclarationAssertions(classDeclaration);
179 } else if (statement instanceof EnumDeclaration enumDeclaration) {
180 collectEnumAssertions(enumDeclaration);
181 } else if (statement instanceof IndividualDeclaration individualDeclaration) {
182 for (var individual : individualDeclaration.getNodes()) {
183 collectIndividualAssertions(individual);
184 }
185 } else if (statement instanceof Assertion assertion) {
186 collectAssertion(assertion);
61 } 187 }
62 } 188 }
63 defaultAssertions.overwriteValues(assertions); 189 }
64 if (isEqualsRelation) { 190
65 for (int i = 0; i < nodeCount; i++) { 191 private void collectClassDeclarationAssertions(ClassDeclaration classDeclaration) {
66 defaultAssertions.setIfMissing(Tuple.of(i, i), TruthValue.TRUE); 192 var superTypes = classDeclaration.getSuperTypes();
193 var partialSuperTypes = new ArrayList<PartialRelation>(superTypes.size() + 1);
194 partialSuperTypes.add(nodeRelation);
195 for (var superType : superTypes) {
196 partialSuperTypes.add(getRelationInfo(superType).partialRelation());
197 }
198 var info = getRelationInfo(classDeclaration);
199 metamodelBuilder.type(info.partialRelation(), classDeclaration.isAbstract(),
200 partialSuperTypes);
201 var newNode = classDeclaration.getNewNode();
202 if (newNode != null) {
203 var newNodeId = getNodeId(newNode);
204 collectCardinalityAssertions(newNodeId, TruthValue.UNKNOWN);
205 mergeValue(classDeclaration, Tuple.of(newNodeId), TruthValue.TRUE);
206 }
207 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
208 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
209 collectReferenceDeclarationAssertions(classDeclaration, referenceDeclaration);
210 } else {
211 throw new IllegalArgumentException("Unknown feature declaration: " + featureDeclaration);
67 } 212 }
68 defaultAssertions.setAllMissing(TruthValue.FALSE);
69 } 213 }
70 return defaultAssertions; 214 }
71 }*/ 215
216 private void collectReferenceDeclarationAssertions(ClassDeclaration classDeclaration,
217 ReferenceDeclaration referenceDeclaration) {
218 var relation = getRelationInfo(referenceDeclaration).partialRelation();
219 var source = getRelationInfo(classDeclaration).partialRelation();
220 var target = getRelationInfo(referenceDeclaration.getReferenceType()).partialRelation();
221 boolean containment = referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT;
222 var opposite = referenceDeclaration.getOpposite();
223 PartialRelation oppositeRelation = null;
224 if (opposite != null) {
225 oppositeRelation = getRelationInfo(opposite).partialRelation();
226 }
227 var multiplicity = getMultiplicityConstraint(referenceDeclaration);
228 metamodelBuilder.reference(relation, source, containment, multiplicity, target, oppositeRelation);
229 }
230
231 private Multiplicity getMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) {
232 if (!ProblemUtil.hasMultiplicityConstraint(referenceDeclaration)) {
233 return UnconstrainedMultiplicity.INSTANCE;
234 }
235 var problemMultiplicity = referenceDeclaration.getMultiplicity();
236 CardinalityInterval interval;
237 if (problemMultiplicity == null) {
238 interval = CardinalityIntervals.LONE;
239 } else if (problemMultiplicity instanceof ExactMultiplicity exactMultiplicity) {
240 interval = CardinalityIntervals.exactly(exactMultiplicity.getExactValue());
241 } else if (problemMultiplicity instanceof RangeMultiplicity rangeMultiplicity) {
242 var upperBound = rangeMultiplicity.getUpperBound();
243 interval = CardinalityIntervals.between(rangeMultiplicity.getLowerBound(),
244 upperBound < 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(upperBound));
245 } else {
246 throw new IllegalArgumentException("Unknown multiplicity: " + problemMultiplicity);
247 }
248 var constraint = getRelationInfo(referenceDeclaration.getInvalidMultiplicity()).partialRelation();
249 return ConstrainedMultiplicity.of(interval, constraint);
250 }
251
252 private void collectEnumAssertions(EnumDeclaration enumDeclaration) {
253 var info = getRelationInfo(enumDeclaration);
254 metamodelBuilder.type(info.partialRelation(), nodeRelation);
255 var overlay = new DecisionTree(1, null);
256 for (var literal : enumDeclaration.getLiterals()) {
257 collectIndividualAssertions(literal);
258 var nodeId = getNodeId(literal);
259 overlay.mergeValue(Tuple.of(nodeId), TruthValue.TRUE);
260 }
261 info.assertions().overwriteValues(overlay);
262 }
263
264 private void collectIndividualAssertions(Node node) {
265 var nodeId = getNodeId(node);
266 collectCardinalityAssertions(nodeId, TruthValue.TRUE);
267 }
268
269 private void collectCardinalityAssertions(int nodeId, TruthValue value) {
270 mergeValue(builtinSymbols.exists(), Tuple.of(nodeId), value);
271 mergeValue(builtinSymbols.equals(), Tuple.of(nodeId, nodeId), value);
272 }
273
274 private void collectAssertion(Assertion assertion) {
275 var relation = assertion.getRelation();
276 var tuple = getTuple(assertion);
277 var value = getTruthValue(assertion.getValue());
278 if (assertion.isDefault()) {
279 mergeDefaultValue(relation, tuple, value);
280 } else {
281 mergeValue(relation, tuple, value);
282 }
283 }
284
285 private void mergeValue(Relation relation, Tuple key, TruthValue value) {
286 getRelationInfo(relation).assertions().mergeValue(key, value);
287 }
288
289 private void mergeDefaultValue(Relation relation, Tuple key, TruthValue value) {
290 getRelationInfo(relation).defaultAssertions().mergeValue(key, value);
291 }
292
293 private RelationInfo getRelationInfo(Relation relation) {
294 var info = relationInfoMap.get(relation);
295 if (info == null) {
296 throw new IllegalArgumentException("Unknown relation: " + relation);
297 }
298 return info;
299 }
300
301 private int getNodeId(Node node) {
302 return nodeTrace.getOrThrow(node);
303 }
72 304
73 private Tuple getTuple(Assertion assertion) { 305 private Tuple getTuple(Assertion assertion) {
74 var arguments = assertion.getArguments(); 306 var arguments = assertion.getArguments();
@@ -77,7 +309,7 @@ public class ModelInitializer {
77 for (int i = 0; i < arity; i++) { 309 for (int i = 0; i < arity; i++) {
78 var argument = arguments.get(i); 310 var argument = arguments.get(i);
79 if (argument instanceof NodeAssertionArgument nodeArgument) { 311 if (argument instanceof NodeAssertionArgument nodeArgument) {
80 nodes[i] = nodeTrace.getOrThrow(nodeArgument.getNode()); 312 nodes[i] = getNodeId(nodeArgument.getNode());
81 } else if (argument instanceof WildcardAssertionArgument) { 313 } else if (argument instanceof WildcardAssertionArgument) {
82 nodes[i] = -1; 314 nodes[i] = -1;
83 } else { 315 } else {
@@ -98,4 +330,27 @@ public class ModelInitializer {
98 case ERROR -> TruthValue.ERROR; 330 case ERROR -> TruthValue.ERROR;
99 }; 331 };
100 } 332 }
333
334 private record RelationInfo(PartialRelation partialRelation, DecisionTree assertions,
335 DecisionTree defaultAssertions) {
336 public RelationInfo(String name, int arity, TruthValue value, TruthValue defaultValue) {
337 this(new PartialRelation(name, arity), value, defaultValue);
338 }
339
340 public RelationInfo(PartialRelation partialRelation, TruthValue value, TruthValue defaultValue) {
341 this(partialRelation, new DecisionTree(partialRelation.arity(), value),
342 new DecisionTree(partialRelation.arity(), defaultValue));
343 }
344
345 public Seed<TruthValue> toSeed(int nodeCount) {
346 defaultAssertions.overwriteValues(assertions);
347 if (partialRelation.equals(ReasoningAdapter.EQUALS_SYMBOL)) {
348 for (int i = 0; i < nodeCount; i++) {
349 defaultAssertions.setIfMissing(Tuple.of(i, i), TruthValue.TRUE);
350 }
351 defaultAssertions.setAllMissing(TruthValue.FALSE);
352 }
353 return defaultAssertions;
354 }
355 }
101} 356}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java
new file mode 100644
index 00000000..47c89e9b
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.model;
7
8import com.google.inject.Inject;
9import com.google.inject.Singleton;
10import org.eclipse.emf.ecore.EObject;
11import org.eclipse.xtext.naming.IQualifiedNameConverter;
12import org.eclipse.xtext.naming.IQualifiedNameProvider;
13
14import java.util.Optional;
15
16@Singleton
17public class SemanticsUtils {
18 @Inject
19 private IQualifiedNameProvider qualifiedNameProvider;
20
21 @Inject
22 private IQualifiedNameConverter qualifiedNameConverter;
23
24 public Optional<String> getName(EObject eObject) {
25 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(eObject);
26 if (qualifiedName == null) {
27 return Optional.empty();
28 }
29 return Optional.of(qualifiedNameConverter.toString(qualifiedName));
30 }
31}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java
index c1afecf9..d693dec3 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java
@@ -7,10 +7,11 @@ package tools.refinery.language.semantics.model.internal;
7 7
8import org.eclipse.collections.api.factory.primitive.IntObjectMaps; 8import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
9import tools.refinery.store.map.Cursor; 9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.reasoning.seed.Seed;
10import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.representation.TruthValue; 12import tools.refinery.store.representation.TruthValue;
12 13
13public class DecisionTree { 14public class DecisionTree implements Seed<TruthValue> {
14 private final int levels; 15 private final int levels;
15 16
16 private final DecisionTreeNode root; 17 private final DecisionTreeNode root;
@@ -29,6 +30,22 @@ public class DecisionTree {
29 this(levels, null); 30 this(levels, null);
30 } 31 }
31 32
33 @Override
34 public int arity() {
35 return levels;
36 }
37
38 @Override
39 public Class<TruthValue> valueType() {
40 return TruthValue.class;
41 }
42
43 @Override
44 public TruthValue reducedValue() {
45 return root.getReducedValue().getTruthValue();
46 }
47
48 @Override
32 public TruthValue get(Tuple tuple) { 49 public TruthValue get(Tuple tuple) {
33 return root.getValue(levels - 1, tuple).getTruthValue(); 50 return root.getValue(levels - 1, tuple).getTruthValue();
34 } 51 }
@@ -60,6 +77,7 @@ public class DecisionTree {
60 return reducedValue == null ? null : reducedValue.getTruthValue(); 77 return reducedValue == null ? null : reducedValue.getTruthValue();
61 } 78 }
62 79
80 @Override
63 public Cursor<Tuple, TruthValue> getCursor(TruthValue defaultValue, int nodeCount) { 81 public Cursor<Tuple, TruthValue> getCursor(TruthValue defaultValue, int nodeCount) {
64 return new DecisionTreeCursor(levels, defaultValue, nodeCount, root); 82 return new DecisionTreeCursor(levels, defaultValue, nodeCount, root);
65 } 83 }
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java
index 9a1e15a3..a9fc644a 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java
@@ -67,6 +67,15 @@ class DecisionTreeCursor implements Cursor<Tuple, TruthValue> {
67 67
68 @Override 68 @Override
69 public boolean move() { 69 public boolean move() {
70 while (moveOne()) {
71 if (!value.equals(defaultValue)) {
72 return true;
73 }
74 }
75 return false;
76 }
77
78 private boolean moveOne() {
70 boolean found = false; 79 boolean found = false;
71 if (path.isEmpty() && !terminated) { 80 if (path.isEmpty() && !terminated) {
72 found = root.moveNext(levels - 1, this); 81 found = root.moveNext(levels - 1, this);
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java
index b3fcbabb..3c43d3bd 100644
--- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java
+++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java
@@ -134,6 +134,17 @@ class DecisionTreeTests {
134 } 134 }
135 135
136 @Test 136 @Test
137 void overwriteIterationTest() {
138 var sut = new DecisionTree(1, TruthValue.TRUE);
139 var overwrite = new DecisionTree(1, null);
140 overwrite.mergeValue(Tuple.of(0), TruthValue.UNKNOWN);
141 sut.overwriteValues(overwrite);
142 var map = iterateAll(sut, TruthValue.UNKNOWN, 2);
143 assertThat(map.keySet(), hasSize(1));
144 assertThat(map, hasEntry(Tuple.of(1), TruthValue.TRUE));
145 }
146
147 @Test
137 void overwriteNothingTest() { 148 void overwriteNothingTest() {
138 var sut = new DecisionTree(2, TruthValue.UNKNOWN); 149 var sut = new DecisionTree(2, TruthValue.UNKNOWN);
139 var values = new DecisionTree(2, null); 150 var values = new DecisionTree(2, null);