diff options
Diffstat (limited to 'subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java')
-rw-r--r-- | subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | 125 |
1 files changed, 107 insertions, 18 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java index 8bda4b95..d9eb5fd3 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -13,16 +13,16 @@ import com.google.inject.Inject; | |||
13 | import org.eclipse.emf.ecore.EObject; | 13 | import org.eclipse.emf.ecore.EObject; |
14 | import org.eclipse.emf.ecore.EReference; | 14 | import org.eclipse.emf.ecore.EReference; |
15 | import org.eclipse.xtext.EcoreUtil2; | 15 | import org.eclipse.xtext.EcoreUtil2; |
16 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
16 | import org.eclipse.xtext.validation.Check; | 17 | import org.eclipse.xtext.validation.Check; |
17 | import org.jetbrains.annotations.Nullable; | 18 | import org.jetbrains.annotations.Nullable; |
18 | import tools.refinery.language.model.problem.*; | 19 | import tools.refinery.language.model.problem.*; |
20 | import tools.refinery.language.naming.NamingUtil; | ||
21 | import tools.refinery.language.scoping.imports.ImportAdapter; | ||
19 | import tools.refinery.language.utils.ProblemDesugarer; | 22 | import tools.refinery.language.utils.ProblemDesugarer; |
20 | import tools.refinery.language.utils.ProblemUtil; | 23 | import tools.refinery.language.utils.ProblemUtil; |
21 | 24 | ||
22 | import java.util.ArrayList; | 25 | import java.util.*; |
23 | import java.util.LinkedHashMap; | ||
24 | import java.util.LinkedHashSet; | ||
25 | import java.util.Set; | ||
26 | 26 | ||
27 | /** | 27 | /** |
28 | * This class contains custom validation rules. | 28 | * This class contains custom validation rules. |
@@ -33,6 +33,10 @@ import java.util.Set; | |||
33 | public class ProblemValidator extends AbstractProblemValidator { | 33 | public class ProblemValidator extends AbstractProblemValidator { |
34 | private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator."; | 34 | private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator."; |
35 | 35 | ||
36 | public static final String UNEXPECTED_MODULE_NAME_ISSUE = ISSUE_PREFIX + "UNEXPECTED_MODULE_NAME"; | ||
37 | |||
38 | public static final String INVALID_IMPORT_ISSUE = ISSUE_PREFIX + "INVALID_IMPORT"; | ||
39 | |||
36 | public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE"; | 40 | public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE"; |
37 | 41 | ||
38 | public static final String NODE_CONSTANT_ISSUE = ISSUE_PREFIX + "NODE_CONSTANT_ISSUE"; | 42 | public static final String NODE_CONSTANT_ISSUE = ISSUE_PREFIX + "NODE_CONSTANT_ISSUE"; |
@@ -65,6 +69,61 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
65 | @Inject | 69 | @Inject |
66 | private ProblemDesugarer desugarer; | 70 | private ProblemDesugarer desugarer; |
67 | 71 | ||
72 | @Inject | ||
73 | private IQualifiedNameConverter qualifiedNameConverter; | ||
74 | |||
75 | @Check | ||
76 | public void checkModuleName(Problem problem) { | ||
77 | var nameString = problem.getName(); | ||
78 | if (nameString == null) { | ||
79 | return; | ||
80 | } | ||
81 | var resource = problem.eResource(); | ||
82 | if (resource == null) { | ||
83 | return; | ||
84 | } | ||
85 | var resourceSet = resource.getResourceSet(); | ||
86 | if (resourceSet == null) { | ||
87 | return; | ||
88 | } | ||
89 | var adapter = ImportAdapter.getOrInstall(resourceSet); | ||
90 | var expectedName = adapter.getQualifiedName(resource.getURI()); | ||
91 | if (expectedName == null) { | ||
92 | return; | ||
93 | } | ||
94 | var name = NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(nameString)); | ||
95 | if (!expectedName.equals(name)) { | ||
96 | var moduleKindName = switch (problem.getKind()) { | ||
97 | case PROBLEM -> "problem"; | ||
98 | case MODULE -> "module"; | ||
99 | }; | ||
100 | var message = "Expected %s to have name '%s', got '%s' instead.".formatted( | ||
101 | moduleKindName, qualifiedNameConverter.toString(expectedName), | ||
102 | qualifiedNameConverter.toString(name)); | ||
103 | error(message, problem, ProblemPackage.Literals.NAMED_ELEMENT__NAME, INSIGNIFICANT_INDEX, | ||
104 | UNEXPECTED_MODULE_NAME_ISSUE); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | @Check | ||
109 | public void checkImportStatement(ImportStatement importStatement) { | ||
110 | var importedModule = importStatement.getImportedModule(); | ||
111 | if (importedModule == null || importedModule.eIsProxy()) { | ||
112 | return; | ||
113 | } | ||
114 | String message = null; | ||
115 | var problem = EcoreUtil2.getContainerOfType(importStatement, Problem.class); | ||
116 | if (importedModule == problem) { | ||
117 | message = "A module cannot import itself."; | ||
118 | } else if (importedModule.getKind() != ModuleKind.MODULE) { | ||
119 | message = "Only modules can be imported."; | ||
120 | } | ||
121 | if (message != null) { | ||
122 | error(message, importStatement, ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE, | ||
123 | INSIGNIFICANT_INDEX, INVALID_IMPORT_ISSUE); | ||
124 | } | ||
125 | } | ||
126 | |||
68 | @Check | 127 | @Check |
69 | public void checkSingletonVariable(VariableOrNodeExpr expr) { | 128 | public void checkSingletonVariable(VariableOrNodeExpr expr) { |
70 | var variableOrNode = expr.getVariableOrNode(); | 129 | var variableOrNode = expr.getVariableOrNode(); |
@@ -84,10 +143,10 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
84 | @Check | 143 | @Check |
85 | public void checkNodeConstants(VariableOrNodeExpr expr) { | 144 | public void checkNodeConstants(VariableOrNodeExpr expr) { |
86 | var variableOrNode = expr.getVariableOrNode(); | 145 | var variableOrNode = expr.getVariableOrNode(); |
87 | if (variableOrNode instanceof Node node && !ProblemUtil.isIndividualNode(node)) { | 146 | if (variableOrNode instanceof Node node && !ProblemUtil.isAtomNode(node)) { |
88 | var name = node.getName(); | 147 | var name = node.getName(); |
89 | var message = ("Only individuals can be referenced in predicates. " + | 148 | var message = ("Only atoms can be referenced in predicates. " + |
90 | "Mark '%s' as individual with the declaration 'indiv %s.'").formatted(name, name); | 149 | "Mark '%s' as an atom with the declaration 'atom %s.'").formatted(name, name); |
91 | error(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, | 150 | error(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, |
92 | INSIGNIFICANT_INDEX, NODE_CONSTANT_ISSUE); | 151 | INSIGNIFICANT_INDEX, NODE_CONSTANT_ISSUE); |
93 | } | 152 | } |
@@ -96,16 +155,16 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
96 | @Check | 155 | @Check |
97 | public void checkUniqueDeclarations(Problem problem) { | 156 | public void checkUniqueDeclarations(Problem problem) { |
98 | var relations = new ArrayList<Relation>(); | 157 | var relations = new ArrayList<Relation>(); |
99 | var individuals = new ArrayList<Node>(); | 158 | var nodes = new ArrayList<Node>(); |
100 | for (var statement : problem.getStatements()) { | 159 | for (var statement : problem.getStatements()) { |
101 | if (statement instanceof Relation relation) { | 160 | if (statement instanceof Relation relation) { |
102 | relations.add(relation); | 161 | relations.add(relation); |
103 | } else if (statement instanceof IndividualDeclaration individualDeclaration) { | 162 | } else if (statement instanceof NodeDeclaration nodeDeclaration) { |
104 | individuals.addAll(individualDeclaration.getNodes()); | 163 | nodes.addAll(nodeDeclaration.getNodes()); |
105 | } | 164 | } |
106 | } | 165 | } |
107 | checkUniqueSimpleNames(relations); | 166 | checkUniqueSimpleNames(relations); |
108 | checkUniqueSimpleNames(individuals); | 167 | checkUniqueSimpleNames(nodes); |
109 | } | 168 | } |
110 | 169 | ||
111 | @Check | 170 | @Check |
@@ -302,6 +361,10 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
302 | @Check | 361 | @Check |
303 | public void checkTypeScope(TypeScope typeScope) { | 362 | public void checkTypeScope(TypeScope typeScope) { |
304 | checkArity(typeScope, ProblemPackage.Literals.TYPE_SCOPE__TARGET_TYPE, 1); | 363 | checkArity(typeScope, ProblemPackage.Literals.TYPE_SCOPE__TARGET_TYPE, 1); |
364 | if (typeScope.isIncrement() && ProblemUtil.isInModule(typeScope)) { | ||
365 | acceptError("Incremental type scopes are not supported in modules", typeScope, null, 0, | ||
366 | INVALID_MULTIPLICITY_ISSUE); | ||
367 | } | ||
305 | } | 368 | } |
306 | 369 | ||
307 | private void checkArity(EObject eObject, EReference reference, int expectedArity) { | 370 | private void checkArity(EObject eObject, EReference reference, int expectedArity) { |
@@ -351,9 +414,9 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
351 | } | 414 | } |
352 | 415 | ||
353 | private void checkExistsAssertion(Assertion assertion, LogicValue value) { | 416 | private void checkExistsAssertion(Assertion assertion, LogicValue value) { |
354 | if (value == LogicValue.TRUE || value == LogicValue.UNKNOWN) { | 417 | if (value == LogicValue.UNKNOWN) { |
355 | // {@code true} is always a valid value for {@code exists}, while {@code unknown} values may always be | 418 | // {@code unknown} values may always be refined to {@code true} of {@code false} if necessary (e.g., for |
356 | // refined to {@code true} if necessary (e.g., for individual nodes). | 419 | // atom nodes or removed multi-objects). |
357 | return; | 420 | return; |
358 | } | 421 | } |
359 | var arguments = assertion.getArguments(); | 422 | var arguments = assertion.getArguments(); |
@@ -361,9 +424,16 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
361 | // We already report an error on invalid arity. | 424 | // We already report an error on invalid arity. |
362 | return; | 425 | return; |
363 | } | 426 | } |
364 | var node = getNodeArgumentForMultiObjectAssertion(arguments.get(0)); | 427 | var node = getNodeArgumentForMultiObjectAssertion(arguments.getFirst()); |
365 | if (node != null && !node.eIsProxy() && ProblemUtil.isIndividualNode(node)) { | 428 | if (node == null || node.eIsProxy()) { |
366 | acceptError("Individual nodes must exist.", assertion, null, 0, UNSUPPORTED_ASSERTION_ISSUE); | 429 | return; |
430 | } | ||
431 | if (ProblemUtil.isAtomNode(node) && value != LogicValue.TRUE) { | ||
432 | acceptError("Atom nodes must exist.", assertion, null, 0, UNSUPPORTED_ASSERTION_ISSUE); | ||
433 | } | ||
434 | if (ProblemUtil.isMultiNode(node) && value != LogicValue.FALSE && ProblemUtil.isInModule(node)) { | ||
435 | acceptError("Multi-objects in modules cannot be required to exist.", assertion, null, 0, | ||
436 | UNSUPPORTED_ASSERTION_ISSUE); | ||
367 | } | 437 | } |
368 | } | 438 | } |
369 | 439 | ||
@@ -403,4 +473,23 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
403 | } | 473 | } |
404 | throw new IllegalArgumentException("Unknown assertion argument: " + argument); | 474 | throw new IllegalArgumentException("Unknown assertion argument: " + argument); |
405 | } | 475 | } |
476 | |||
477 | @Check | ||
478 | private void checkImplicitNodeInModule(Assertion assertion) { | ||
479 | if (!ProblemUtil.isInModule(assertion)) { | ||
480 | return; | ||
481 | } | ||
482 | for (var argument : assertion.getArguments()) { | ||
483 | if (argument instanceof NodeAssertionArgument nodeAssertionArgument) { | ||
484 | var node = nodeAssertionArgument.getNode(); | ||
485 | if (node != null && !node.eIsProxy() && ProblemUtil.isImplicitNode(node)) { | ||
486 | var name = node.getName(); | ||
487 | var message = ("Implicit nodes are not allowed in modules. Explicitly declare '%s' as a node " + | ||
488 | "with the declaration 'declare %s.'").formatted(name, name); | ||
489 | acceptError(message, nodeAssertionArgument, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE, | ||
490 | 0, UNSUPPORTED_ASSERTION_ISSUE); | ||
491 | } | ||
492 | } | ||
493 | } | ||
494 | } | ||
406 | } | 495 | } |