diff options
author | 2023-12-25 13:57:04 +0100 | |
---|---|---|
committer | 2023-12-25 13:57:04 +0100 | |
commit | fa25d614455dd0872bfa6ff88c5be3ecfc33a6af (patch) | |
tree | 84aba7f8ce90ff5aea38c912b3999ec7810f6f44 /subprojects | |
parent | chore: upgrade to Eclipse 2023-12 (diff) | |
parent | refactor(generator): scope overrides (diff) | |
download | refinery-fa25d614455dd0872bfa6ff88c5be3ecfc33a6af.tar.gz refinery-fa25d614455dd0872bfa6ff88c5be3ecfc33a6af.tar.zst refinery-fa25d614455dd0872bfa6ff88c5be3ecfc33a6af.zip |
Merge pull request #50 from kris7t/generator-roundtrip
Round-trip model serialization and command line application
Diffstat (limited to 'subprojects')
45 files changed, 1774 insertions, 253 deletions
diff --git a/subprojects/frontend/config/graphvizUMDVitePlugin.ts b/subprojects/frontend/config/graphvizUMDVitePlugin.ts index ab726e4f..0c3c9aa0 100644 --- a/subprojects/frontend/config/graphvizUMDVitePlugin.ts +++ b/subprojects/frontend/config/graphvizUMDVitePlugin.ts | |||
@@ -27,13 +27,15 @@ export default function graphvizUMDVitePlugin(): PluginOption { | |||
27 | async buildStart() { | 27 | async buildStart() { |
28 | const issuer = | 28 | const issuer = |
29 | root === undefined ? issuerFileName : path.join(root, issuerFileName); | 29 | root === undefined ? issuerFileName : path.join(root, issuerFileName); |
30 | const resolvedPath = pnpapi.resolveRequest( | 30 | // Since https://github.com/hpcc-systems/hpcc-js-wasm/commit/15e1ace5edae7f94714e547a3ac20e0e17cd6b0c, |
31 | '@hpcc-js/wasm/graphviz', | 31 | // hpcc-js has both a `.cjs` and a `.umd.js` build. PnPAPI will find the former, but we need the latter. |
32 | issuer, | 32 | const resolvedPath = pnpapi |
33 | ); | 33 | .resolveRequest('@hpcc-js/wasm/graphviz', issuer) |
34 | if (resolvedPath === null) { | 34 | ?.replace(/\.cjs$/, '.umd.js'); |
35 | if (resolvedPath === undefined) { | ||
35 | return; | 36 | return; |
36 | } | 37 | } |
38 | console.log(resolvedPath); | ||
37 | if (command === 'serve') { | 39 | if (command === 'serve') { |
38 | url = `/@fs/${resolvedPath}`; | 40 | url = `/@fs/${resolvedPath}`; |
39 | } else { | 41 | } else { |
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index f55093ce..9dec8b32 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -33,23 +33,23 @@ | |||
33 | "@codemirror/language": "^6.9.3", | 33 | "@codemirror/language": "^6.9.3", |
34 | "@codemirror/lint": "^6.4.2", | 34 | "@codemirror/lint": "^6.4.2", |
35 | "@codemirror/search": "^6.5.5", | 35 | "@codemirror/search": "^6.5.5", |
36 | "@codemirror/state": "^6.3.2", | 36 | "@codemirror/state": "^6.3.3", |
37 | "@codemirror/view": "^6.22.1", | 37 | "@codemirror/view": "^6.22.3", |
38 | "@emotion/react": "^11.11.1", | 38 | "@emotion/react": "^11.11.3", |
39 | "@emotion/styled": "^11.11.0", | 39 | "@emotion/styled": "^11.11.0", |
40 | "@fontsource-variable/jetbrains-mono": "^5.0.18", | 40 | "@fontsource-variable/jetbrains-mono": "^5.0.19", |
41 | "@fontsource-variable/open-sans": "^5.0.18", | 41 | "@fontsource-variable/open-sans": "^5.0.21", |
42 | "@hpcc-js/wasm": "^2.15.0", | 42 | "@hpcc-js/wasm": "^2.15.3", |
43 | "@lezer/common": "^1.1.1", | 43 | "@lezer/common": "^1.1.2", |
44 | "@lezer/highlight": "^1.2.0", | 44 | "@lezer/highlight": "^1.2.0", |
45 | "@lezer/lr": "^1.3.14", | 45 | "@lezer/lr": "^1.3.14", |
46 | "@material-icons/svg": "^1.0.33", | 46 | "@material-icons/svg": "^1.0.33", |
47 | "@mui/icons-material": "5.14.19", | 47 | "@mui/icons-material": "^5.15.1", |
48 | "@mui/material": "5.14.19", | 48 | "@mui/material": "^5.15.1", |
49 | "@mui/system": "^5.14.19", | 49 | "@mui/system": "^5.15.1", |
50 | "@mui/x-data-grid": "^6.18.2", | 50 | "@mui/x-data-grid": "^6.18.6", |
51 | "ansi-styles": "^6.2.1", | 51 | "ansi-styles": "^6.2.1", |
52 | "csstype": "^3.1.2", | 52 | "csstype": "^3.1.3", |
53 | "d3": "^7.8.5", | 53 | "d3": "^7.8.5", |
54 | "d3-graphviz": "patch:d3-graphviz@npm%3A5.2.0#~/.yarn/patches/d3-graphviz-npm-5.2.0-161b1fbad4.patch", | 54 | "d3-graphviz": "patch:d3-graphviz@npm%3A5.2.0#~/.yarn/patches/d3-graphviz-npm-5.2.0-161b1fbad4.patch", |
55 | "d3-selection": "^3.0.0", | 55 | "d3-selection": "^3.0.0", |
@@ -65,7 +65,7 @@ | |||
65 | "notistack": "^3.0.1", | 65 | "notistack": "^3.0.1", |
66 | "react": "^18.2.0", | 66 | "react": "^18.2.0", |
67 | "react-dom": "^18.2.0", | 67 | "react-dom": "^18.2.0", |
68 | "react-resize-detector": "^9.1.0", | 68 | "react-resize-detector": "^9.1.1", |
69 | "xstate": "^4.38.3", | 69 | "xstate": "^4.38.3", |
70 | "zod": "^3.22.4" | 70 | "zod": "^3.22.4" |
71 | }, | 71 | }, |
@@ -75,38 +75,38 @@ | |||
75 | "@types/d3-graphviz": "^2.6.10", | 75 | "@types/d3-graphviz": "^2.6.10", |
76 | "@types/d3-selection": "^3.0.10", | 76 | "@types/d3-selection": "^3.0.10", |
77 | "@types/d3-zoom": "^3.0.8", | 77 | "@types/d3-zoom": "^3.0.8", |
78 | "@types/eslint": "^8.44.8", | 78 | "@types/eslint": "^8.56.0", |
79 | "@types/html-minifier-terser": "^7.0.2", | 79 | "@types/html-minifier-terser": "^7.0.2", |
80 | "@types/lodash-es": "^4.17.12", | 80 | "@types/lodash-es": "^4.17.12", |
81 | "@types/micromatch": "^4.0.6", | 81 | "@types/micromatch": "^4.0.6", |
82 | "@types/ms": "^0.7.34", | 82 | "@types/ms": "^0.7.34", |
83 | "@types/node": "^20.10.2", | 83 | "@types/node": "^20.10.5", |
84 | "@types/pnpapi": "^0.0.5", | 84 | "@types/pnpapi": "^0.0.5", |
85 | "@types/react": "^18.2.41", | 85 | "@types/react": "^18.2.45", |
86 | "@types/react-dom": "^18.2.17", | 86 | "@types/react-dom": "^18.2.18", |
87 | "@typescript-eslint/eslint-plugin": "^6.13.1", | 87 | "@typescript-eslint/eslint-plugin": "^6.15.0", |
88 | "@typescript-eslint/parser": "^6.13.1", | 88 | "@typescript-eslint/parser": "^6.15.0", |
89 | "@vitejs/plugin-react-swc": "^3.5.0", | 89 | "@vitejs/plugin-react-swc": "^3.5.0", |
90 | "@xstate/cli": "^0.5.11", | 90 | "@xstate/cli": "^0.5.15", |
91 | "cross-env": "^7.0.3", | 91 | "cross-env": "^7.0.3", |
92 | "eslint": "^8.55.0", | 92 | "eslint": "^8.56.0", |
93 | "eslint-config-airbnb": "^19.0.4", | 93 | "eslint-config-airbnb": "^19.0.4", |
94 | "eslint-config-airbnb-typescript": "^17.1.0", | 94 | "eslint-config-airbnb-typescript": "^17.1.0", |
95 | "eslint-config-prettier": "^9.1.0", | 95 | "eslint-config-prettier": "^9.1.0", |
96 | "eslint-import-resolver-typescript": "^3.6.1", | 96 | "eslint-import-resolver-typescript": "^3.6.1", |
97 | "eslint-plugin-import": "^2.29.0", | 97 | "eslint-plugin-import": "^2.29.1", |
98 | "eslint-plugin-jsx-a11y": "^6.8.0", | 98 | "eslint-plugin-jsx-a11y": "^6.8.0", |
99 | "eslint-plugin-mobx": "^0.0.9", | 99 | "eslint-plugin-mobx": "^0.0.9", |
100 | "eslint-plugin-prettier": "^5.0.1", | 100 | "eslint-plugin-prettier": "^5.1.1", |
101 | "eslint-plugin-react": "^7.33.2", | 101 | "eslint-plugin-react": "^7.33.2", |
102 | "eslint-plugin-react-hooks": "^4.6.0", | 102 | "eslint-plugin-react-hooks": "^4.6.0", |
103 | "html-minifier-terser": "^7.2.0", | 103 | "html-minifier-terser": "^7.2.0", |
104 | "micromatch": "^4.0.5", | 104 | "micromatch": "^4.0.5", |
105 | "pnpapi": "^0.0.0", | 105 | "pnpapi": "^0.0.0", |
106 | "prettier": "^3.1.0", | 106 | "prettier": "^3.1.1", |
107 | "typescript": "5.3.2", | 107 | "typescript": "5.3.3", |
108 | "vite": "^5.0.4", | 108 | "vite": "^5.0.10", |
109 | "vite-plugin-pwa": "^0.17.2", | 109 | "vite-plugin-pwa": "^0.17.4", |
110 | "workbox-window": "^7.0.0" | 110 | "workbox-window": "^7.0.0" |
111 | } | 111 | } |
112 | } | 112 | } |
diff --git a/subprojects/generator-cli/build.gradle.kts b/subprojects/generator-cli/build.gradle.kts new file mode 100644 index 00000000..6c681222 --- /dev/null +++ b/subprojects/generator-cli/build.gradle.kts | |||
@@ -0,0 +1,24 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-application") | ||
9 | } | ||
10 | |||
11 | dependencies { | ||
12 | implementation(project(":refinery-generator")) | ||
13 | implementation(libs.jcommander) | ||
14 | implementation(libs.slf4j.api) | ||
15 | } | ||
16 | |||
17 | application { | ||
18 | mainClass.set("tools.refinery.generator.cli.RefineryCli") | ||
19 | } | ||
20 | |||
21 | tasks.shadowJar { | ||
22 | // Silence Xtext warning. | ||
23 | append("plugin.properties") | ||
24 | } | ||
diff --git a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.java b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.java new file mode 100644 index 00000000..5de579e6 --- /dev/null +++ b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/RefineryCli.java | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.generator.cli; | ||
7 | |||
8 | import com.beust.jcommander.JCommander; | ||
9 | import com.beust.jcommander.ParameterException; | ||
10 | import com.google.inject.Inject; | ||
11 | import tools.refinery.generator.cli.commands.GenerateCommand; | ||
12 | import tools.refinery.generator.standalone.StandaloneRefinery; | ||
13 | |||
14 | import java.io.IOException; | ||
15 | |||
16 | public class RefineryCli { | ||
17 | private static final String GENERATE_COMMAND = "generate"; | ||
18 | |||
19 | @Inject | ||
20 | private GenerateCommand generateCommand; | ||
21 | |||
22 | private JCommander jCommander; | ||
23 | |||
24 | public String parseArguments(String... args) { | ||
25 | var jc = getJCommander(); | ||
26 | jc.parse(args); | ||
27 | return jc.getParsedCommand(); | ||
28 | } | ||
29 | |||
30 | public void run(String command) throws IOException { | ||
31 | switch (command) { | ||
32 | case GENERATE_COMMAND -> generateCommand.run(); | ||
33 | case null, default -> showUsageAndExit(); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | public void showUsageAndExit() { | ||
38 | getJCommander().usage(); | ||
39 | System.exit(1); | ||
40 | } | ||
41 | |||
42 | private JCommander getJCommander() { | ||
43 | if (jCommander == null) { | ||
44 | jCommander = JCommander.newBuilder() | ||
45 | .programName("refinery") | ||
46 | .addObject(this) | ||
47 | .addCommand(GENERATE_COMMAND, generateCommand) | ||
48 | .build(); | ||
49 | } | ||
50 | return jCommander; | ||
51 | } | ||
52 | |||
53 | public static void main(String[] args) throws IOException { | ||
54 | var cli = StandaloneRefinery.getInjector().getInstance(RefineryCli.class); | ||
55 | String command = null; | ||
56 | try { | ||
57 | command = cli.parseArguments(args); | ||
58 | } catch (ParameterException e) { | ||
59 | System.err.println(e.getMessage()); | ||
60 | cli.showUsageAndExit(); | ||
61 | } | ||
62 | cli.run(command); | ||
63 | } | ||
64 | } | ||
diff --git a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java new file mode 100644 index 00000000..b33fce23 --- /dev/null +++ b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java | |||
@@ -0,0 +1,87 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.generator.cli.commands; | ||
7 | |||
8 | import com.beust.jcommander.Parameter; | ||
9 | import com.beust.jcommander.Parameters; | ||
10 | import com.google.inject.Inject; | ||
11 | import org.eclipse.emf.ecore.resource.Resource; | ||
12 | import tools.refinery.generator.ModelGeneratorFactory; | ||
13 | import tools.refinery.generator.ProblemLoader; | ||
14 | |||
15 | import java.io.FileOutputStream; | ||
16 | import java.io.IOException; | ||
17 | import java.util.ArrayList; | ||
18 | import java.util.List; | ||
19 | import java.util.Map; | ||
20 | |||
21 | @Parameters(commandDescription = "Generate a model from a partial model") | ||
22 | public class GenerateCommand { | ||
23 | @Inject | ||
24 | private ProblemLoader loader; | ||
25 | |||
26 | @Inject | ||
27 | private ModelGeneratorFactory generatorFactory; | ||
28 | |||
29 | private String inputPath; | ||
30 | private String outputPath = "-"; | ||
31 | private List<String> scopes = new ArrayList<>(); | ||
32 | private List<String> overrideScopes = new ArrayList<>(); | ||
33 | private long randomSeed = 1; | ||
34 | |||
35 | @Parameter(description = "input path", required = true) | ||
36 | public void setInputPath(String inputPath) { | ||
37 | this.inputPath = inputPath; | ||
38 | } | ||
39 | |||
40 | @Parameter(names = {"-output", "-o"}, description = "Output path") | ||
41 | public void setOutputPath(String outputPath) { | ||
42 | this.outputPath = outputPath; | ||
43 | } | ||
44 | |||
45 | @Parameter(names = {"-scope", "-s"}, description = "Extra scope constraints") | ||
46 | public void setScopes(List<String> scopes) { | ||
47 | this.scopes = scopes; | ||
48 | } | ||
49 | |||
50 | @Parameter(names = {"-scope-override", "-S"}, description = "Override scope constraints") | ||
51 | public void setOverrideScopes(List<String> overrideScopes) { | ||
52 | this.overrideScopes = overrideScopes; | ||
53 | } | ||
54 | |||
55 | @Parameter(names = {"-random-seed", "-r"}, description = "Random seed") | ||
56 | public void setRandomSeed(long randomSeed) { | ||
57 | this.randomSeed = randomSeed; | ||
58 | } | ||
59 | |||
60 | public void run() throws IOException { | ||
61 | var problem = isStandardStream(inputPath) ? loader.loadStream(System.in) : loader.loadFile(inputPath); | ||
62 | problem = loader.loadScopeConstraints(problem, scopes, overrideScopes); | ||
63 | var generator = generatorFactory.createGenerator(problem); | ||
64 | generator.setRandomSeed(randomSeed); | ||
65 | generator.generate(); | ||
66 | var solution = generator.serializeSolution(); | ||
67 | var solutionResource = solution.eResource(); | ||
68 | var saveOptions = Map.of(); | ||
69 | if (isStandardStream(outputPath)) { | ||
70 | printSolution(solutionResource, saveOptions); | ||
71 | } else { | ||
72 | try (var outputStream = new FileOutputStream(outputPath)) { | ||
73 | solutionResource.save(outputStream, saveOptions); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | private boolean isStandardStream(String path) { | ||
79 | return path == null || path.equals("-"); | ||
80 | } | ||
81 | |||
82 | // We deliberately write to the standard output if no output path is specified. | ||
83 | @SuppressWarnings("squid:S106") | ||
84 | private void printSolution(Resource solutionResource, Map<?, ?> saveOptions) throws IOException { | ||
85 | solutionResource.save(System.out, saveOptions); | ||
86 | } | ||
87 | } | ||
diff --git a/subprojects/generator/build.gradle.kts b/subprojects/generator/build.gradle.kts index d87ce6de..f78fee4d 100644 --- a/subprojects/generator/build.gradle.kts +++ b/subprojects/generator/build.gradle.kts | |||
@@ -12,4 +12,5 @@ dependencies { | |||
12 | api(project(":refinery-language-semantics")) | 12 | api(project(":refinery-language-semantics")) |
13 | api(libs.eclipseCollections.api) | 13 | api(libs.eclipseCollections.api) |
14 | implementation(project(":refinery-store-query-interpreter")) | 14 | implementation(project(":refinery-store-query-interpreter")) |
15 | testImplementation(testFixtures(project(":refinery-language"))) | ||
15 | } | 16 | } |
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java index 5b44c10a..1515dceb 100644 --- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java +++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGenerator.java | |||
@@ -5,7 +5,10 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.generator; | 6 | package tools.refinery.generator; |
7 | 7 | ||
8 | import com.google.inject.Provider; | ||
9 | import tools.refinery.language.model.problem.Problem; | ||
8 | import tools.refinery.language.semantics.ProblemTrace; | 10 | import tools.refinery.language.semantics.ProblemTrace; |
11 | import tools.refinery.language.semantics.SolutionSerializer; | ||
9 | import tools.refinery.store.dse.strategy.BestFirstStoreManager; | 12 | import tools.refinery.store.dse.strategy.BestFirstStoreManager; |
10 | import tools.refinery.store.map.Version; | 13 | import tools.refinery.store.map.Version; |
11 | import tools.refinery.store.model.ModelStore; | 14 | import tools.refinery.store.model.ModelStore; |
@@ -16,21 +19,22 @@ import tools.refinery.store.reasoning.seed.ModelSeed; | |||
16 | 19 | ||
17 | public class ModelGenerator extends ModelFacade { | 20 | public class ModelGenerator extends ModelFacade { |
18 | private final Version initialVersion; | 21 | private final Version initialVersion; |
19 | 22 | private final Provider<SolutionSerializer> solutionSerializerProvider; | |
20 | private int randomSeed = 0; | 23 | private long randomSeed = 1; |
21 | |||
22 | private boolean lastGenerationSuccessful; | 24 | private boolean lastGenerationSuccessful; |
23 | 25 | ||
24 | public ModelGenerator(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed) { | 26 | ModelGenerator(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed, |
27 | Provider<SolutionSerializer> solutionSerializerProvider) { | ||
25 | super(problemTrace, store, modelSeed, Concreteness.CANDIDATE); | 28 | super(problemTrace, store, modelSeed, Concreteness.CANDIDATE); |
29 | this.solutionSerializerProvider = solutionSerializerProvider; | ||
26 | initialVersion = getModel().commit(); | 30 | initialVersion = getModel().commit(); |
27 | } | 31 | } |
28 | 32 | ||
29 | public int getRandomSeed() { | 33 | public long getRandomSeed() { |
30 | return randomSeed; | 34 | return randomSeed; |
31 | } | 35 | } |
32 | 36 | ||
33 | public void setRandomSeed(int randomSeed) { | 37 | public void setRandomSeed(long randomSeed) { |
34 | this.randomSeed = randomSeed; | 38 | this.randomSeed = randomSeed; |
35 | this.lastGenerationSuccessful = false; | 39 | this.lastGenerationSuccessful = false; |
36 | } | 40 | } |
@@ -50,7 +54,7 @@ public class ModelGenerator extends ModelFacade { | |||
50 | if (solutions.isEmpty()) { | 54 | if (solutions.isEmpty()) { |
51 | return false; | 55 | return false; |
52 | } | 56 | } |
53 | getModel().restore(solutions.get(0).version()); | 57 | getModel().restore(solutions.getFirst().version()); |
54 | lastGenerationSuccessful = true; | 58 | lastGenerationSuccessful = true; |
55 | return true; | 59 | return true; |
56 | } | 60 | } |
@@ -63,9 +67,19 @@ public class ModelGenerator extends ModelFacade { | |||
63 | 67 | ||
64 | @Override | 68 | @Override |
65 | public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) { | 69 | public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) { |
70 | checkSuccessfulGeneration(); | ||
71 | return super.getPartialInterpretation(partialSymbol); | ||
72 | } | ||
73 | |||
74 | public Problem serializeSolution() { | ||
75 | checkSuccessfulGeneration(); | ||
76 | var serializer = solutionSerializerProvider.get(); | ||
77 | return serializer.serializeSolution(getProblemTrace(), getModel()); | ||
78 | } | ||
79 | |||
80 | private void checkSuccessfulGeneration() { | ||
66 | if (!lastGenerationSuccessful) { | 81 | if (!lastGenerationSuccessful) { |
67 | throw new IllegalStateException("No generated model is available"); | 82 | throw new IllegalStateException("No generated model is available"); |
68 | } | 83 | } |
69 | return super.getPartialInterpretation(partialSymbol); | ||
70 | } | 84 | } |
71 | } | 85 | } |
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java index 6642d591..587601f2 100644 --- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java +++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelGeneratorFactory.java | |||
@@ -9,6 +9,7 @@ import com.google.inject.Inject; | |||
9 | import com.google.inject.Provider; | 9 | import com.google.inject.Provider; |
10 | import tools.refinery.language.model.problem.Problem; | 10 | import tools.refinery.language.model.problem.Problem; |
11 | import tools.refinery.language.semantics.ModelInitializer; | 11 | import tools.refinery.language.semantics.ModelInitializer; |
12 | import tools.refinery.language.semantics.SolutionSerializer; | ||
12 | import tools.refinery.store.dse.propagation.PropagationAdapter; | 13 | import tools.refinery.store.dse.propagation.PropagationAdapter; |
13 | import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter; | 14 | import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter; |
14 | import tools.refinery.store.model.ModelStore; | 15 | import tools.refinery.store.model.ModelStore; |
@@ -25,6 +26,9 @@ public final class ModelGeneratorFactory { | |||
25 | @Inject | 26 | @Inject |
26 | private Provider<ModelInitializer> initializerProvider; | 27 | private Provider<ModelInitializer> initializerProvider; |
27 | 28 | ||
29 | @Inject | ||
30 | private Provider<SolutionSerializer> solutionSerializerProvider; | ||
31 | |||
28 | private CancellationToken cancellationToken = CancellationToken.NONE; | 32 | private CancellationToken cancellationToken = CancellationToken.NONE; |
29 | 33 | ||
30 | private boolean debugPartialInterpretations; | 34 | private boolean debugPartialInterpretations; |
@@ -53,7 +57,8 @@ public final class ModelGeneratorFactory { | |||
53 | .requiredInterpretations(getRequiredInterpretations())); | 57 | .requiredInterpretations(getRequiredInterpretations())); |
54 | initializer.configureStoreBuilder(storeBuilder); | 58 | initializer.configureStoreBuilder(storeBuilder); |
55 | var store = storeBuilder.build(); | 59 | var store = storeBuilder.build(); |
56 | return new ModelGenerator(initializer.getProblemTrace(), store, initializer.getModelSeed()); | 60 | return new ModelGenerator(initializer.getProblemTrace(), store, initializer.getModelSeed(), |
61 | solutionSerializerProvider); | ||
57 | } | 62 | } |
58 | 63 | ||
59 | private Collection<Concreteness> getRequiredInterpretations() { | 64 | private Collection<Concreteness> getRequiredInterpretations() { |
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java b/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java index bc02c887..7207a509 100644 --- a/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java +++ b/subprojects/generator/src/main/java/tools/refinery/generator/ModelSemantics.java | |||
@@ -11,7 +11,7 @@ import tools.refinery.store.reasoning.literal.Concreteness; | |||
11 | import tools.refinery.store.reasoning.seed.ModelSeed; | 11 | import tools.refinery.store.reasoning.seed.ModelSeed; |
12 | 12 | ||
13 | public class ModelSemantics extends ModelFacade { | 13 | public class ModelSemantics extends ModelFacade { |
14 | public ModelSemantics(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed) { | 14 | ModelSemantics(ProblemTrace problemTrace, ModelStore store, ModelSeed modelSeed) { |
15 | super(problemTrace, store, modelSeed, Concreteness.PARTIAL); | 15 | super(problemTrace, store, modelSeed, Concreteness.PARTIAL); |
16 | } | 16 | } |
17 | } | 17 | } |
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java index 29f80714..20ea8132 100644 --- a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java +++ b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java | |||
@@ -10,20 +10,30 @@ import com.google.inject.Provider; | |||
10 | import org.eclipse.emf.common.util.URI; | 10 | import org.eclipse.emf.common.util.URI; |
11 | import org.eclipse.emf.ecore.resource.Resource; | 11 | import org.eclipse.emf.ecore.resource.Resource; |
12 | import org.eclipse.xtext.diagnostics.Severity; | 12 | import org.eclipse.xtext.diagnostics.Severity; |
13 | import org.eclipse.xtext.resource.FileExtensionProvider; | ||
13 | import org.eclipse.xtext.resource.IResourceFactory; | 14 | import org.eclipse.xtext.resource.IResourceFactory; |
14 | import org.eclipse.xtext.resource.XtextResourceSet; | 15 | import org.eclipse.xtext.resource.XtextResourceSet; |
15 | import org.eclipse.xtext.util.LazyStringInputStream; | 16 | import org.eclipse.xtext.util.LazyStringInputStream; |
16 | import org.eclipse.xtext.validation.CheckMode; | 17 | import org.eclipse.xtext.validation.CheckMode; |
17 | import org.eclipse.xtext.validation.IResourceValidator; | 18 | import org.eclipse.xtext.validation.IResourceValidator; |
18 | import tools.refinery.language.model.problem.Problem; | 19 | import tools.refinery.language.model.problem.Problem; |
20 | import tools.refinery.language.model.problem.Relation; | ||
21 | import tools.refinery.language.model.problem.ScopeDeclaration; | ||
19 | import tools.refinery.store.util.CancellationToken; | 22 | import tools.refinery.store.util.CancellationToken; |
20 | 23 | ||
24 | import java.io.ByteArrayOutputStream; | ||
21 | import java.io.File; | 25 | import java.io.File; |
22 | import java.io.IOException; | 26 | import java.io.IOException; |
23 | import java.io.InputStream; | 27 | import java.io.InputStream; |
28 | import java.nio.charset.StandardCharsets; | ||
29 | import java.util.ArrayList; | ||
30 | import java.util.HashSet; | ||
31 | import java.util.List; | ||
24 | import java.util.Map; | 32 | import java.util.Map; |
25 | 33 | ||
26 | public class ProblemLoader { | 34 | public class ProblemLoader { |
35 | private String fileExtension; | ||
36 | |||
27 | @Inject | 37 | @Inject |
28 | private Provider<XtextResourceSet> resourceSetProvider; | 38 | private Provider<XtextResourceSet> resourceSetProvider; |
29 | 39 | ||
@@ -35,25 +45,39 @@ public class ProblemLoader { | |||
35 | 45 | ||
36 | private CancellationToken cancellationToken = CancellationToken.NONE; | 46 | private CancellationToken cancellationToken = CancellationToken.NONE; |
37 | 47 | ||
48 | @Inject | ||
49 | public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) { | ||
50 | this.fileExtension = fileExtensionProvider.getPrimaryFileExtension(); | ||
51 | } | ||
52 | |||
38 | public ProblemLoader cancellationToken(CancellationToken cancellationToken) { | 53 | public ProblemLoader cancellationToken(CancellationToken cancellationToken) { |
39 | this.cancellationToken = cancellationToken; | 54 | this.cancellationToken = cancellationToken; |
40 | return this; | 55 | return this; |
41 | } | 56 | } |
42 | 57 | ||
43 | public Problem loadString(String problemString) throws IOException { | 58 | public Problem loadString(String problemString, URI uri) throws IOException { |
44 | try (var stream = new LazyStringInputStream(problemString)) { | 59 | try (var stream = new LazyStringInputStream(problemString)) { |
45 | return loadStream(stream); | 60 | return loadStream(stream, uri); |
46 | } | 61 | } |
47 | } | 62 | } |
48 | 63 | ||
49 | public Problem loadStream(InputStream inputStream) throws IOException { | 64 | public Problem loadString(String problemString) throws IOException { |
65 | return loadString(problemString, null); | ||
66 | } | ||
67 | |||
68 | public Problem loadStream(InputStream inputStream, URI uri) throws IOException { | ||
50 | var resourceSet = resourceSetProvider.get(); | 69 | var resourceSet = resourceSetProvider.get(); |
51 | var resource = resourceFactory.createResource(URI.createFileURI("__synthetic.problem")); | 70 | var resourceUri = uri == null ? URI.createFileURI("__synthetic." + fileExtension) : uri; |
71 | var resource = resourceFactory.createResource(resourceUri); | ||
52 | resourceSet.getResources().add(resource); | 72 | resourceSet.getResources().add(resource); |
53 | resource.load(inputStream, Map.of()); | 73 | resource.load(inputStream, Map.of()); |
54 | return loadResource(resource); | 74 | return loadResource(resource); |
55 | } | 75 | } |
56 | 76 | ||
77 | public Problem loadStream(InputStream inputStream) throws IOException { | ||
78 | return loadStream(inputStream, null); | ||
79 | } | ||
80 | |||
57 | public Problem loadFile(File file) throws IOException { | 81 | public Problem loadFile(File file) throws IOException { |
58 | return loadFile(file.getAbsolutePath()); | 82 | return loadFile(file.getAbsolutePath()); |
59 | } | 83 | } |
@@ -82,9 +106,63 @@ public class ProblemLoader { | |||
82 | if (!errors.isEmpty()) { | 106 | if (!errors.isEmpty()) { |
83 | throw new ValidationErrorsException(resource.getURI(), errors); | 107 | throw new ValidationErrorsException(resource.getURI(), errors); |
84 | } | 108 | } |
85 | if (resource.getContents().isEmpty() || !(resource.getContents().get(0) instanceof Problem problem)) { | 109 | if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) { |
86 | throw new IllegalArgumentException("Model generation problem not found in resource " + resource.getURI()); | 110 | throw new IllegalArgumentException("Model generation problem not found in resource " + resource.getURI()); |
87 | } | 111 | } |
88 | return problem; | 112 | return problem; |
89 | } | 113 | } |
114 | |||
115 | public Problem loadScopeConstraints(Problem problem, List<String> extraScopes, | ||
116 | List<String> overrideScopes) throws IOException { | ||
117 | var allScopes = new ArrayList<>(extraScopes); | ||
118 | allScopes.addAll(overrideScopes); | ||
119 | if (allScopes.isEmpty()) { | ||
120 | return problem; | ||
121 | } | ||
122 | int originalStatementCount = problem.getStatements().size(); | ||
123 | var builder = new StringBuilder(); | ||
124 | var problemResource = problem.eResource(); | ||
125 | try (var outputStream = new ByteArrayOutputStream()) { | ||
126 | problemResource.save(outputStream, Map.of()); | ||
127 | builder.append(outputStream.toString(StandardCharsets.UTF_8)); | ||
128 | } | ||
129 | builder.append('\n'); | ||
130 | for (var scope : allScopes) { | ||
131 | builder.append("scope ").append(scope).append(".\n"); | ||
132 | } | ||
133 | var modifiedProblem = loadString(builder.toString(), problemResource.getURI()); | ||
134 | var modifiedStatements = modifiedProblem.getStatements(); | ||
135 | int modifiedStatementCount = modifiedStatements.size(); | ||
136 | if (modifiedStatementCount != originalStatementCount + allScopes.size()) { | ||
137 | throw new IllegalArgumentException("Failed to parse scope constraints"); | ||
138 | } | ||
139 | // Override scopes remove any scope constraint from the original problem with the same target type. | ||
140 | var overriddenScopes = new HashSet<Relation>(); | ||
141 | for (int i = modifiedStatementCount - overrideScopes.size(); i < modifiedStatementCount; i++) { | ||
142 | var statement = modifiedStatements.get(i); | ||
143 | if (!(statement instanceof ScopeDeclaration scopeDeclaration)) { | ||
144 | throw new IllegalStateException("Invalid scope constraint: " + statement); | ||
145 | } | ||
146 | for (var typeScope : scopeDeclaration.getTypeScopes()) { | ||
147 | overriddenScopes.add(typeScope.getTargetType()); | ||
148 | } | ||
149 | } | ||
150 | int statementIndex = 0; | ||
151 | var iterator = modifiedStatements.iterator(); | ||
152 | // Scope overrides only affect type scopes from the original problem and leave type scopes added on the | ||
153 | // command line intact. | ||
154 | while (statementIndex < originalStatementCount && iterator.hasNext()) { | ||
155 | var statement = iterator.next(); | ||
156 | if (statement instanceof ScopeDeclaration scopeDeclaration) { | ||
157 | var typeScopes = scopeDeclaration.getTypeScopes(); | ||
158 | typeScopes.removeIf(typeScope -> overriddenScopes.contains(typeScope.getTargetType())); | ||
159 | // Scope declarations with no type scopes are invalid, so we have to remove them. | ||
160 | if (typeScopes.isEmpty()) { | ||
161 | iterator.remove(); | ||
162 | } | ||
163 | } | ||
164 | statementIndex++; | ||
165 | } | ||
166 | return modifiedProblem; | ||
167 | } | ||
90 | } | 168 | } |
diff --git a/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java b/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java new file mode 100644 index 00000000..0c0db105 --- /dev/null +++ b/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java | |||
@@ -0,0 +1,129 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.generator; | ||
7 | |||
8 | |||
9 | import com.google.inject.Inject; | ||
10 | import org.eclipse.xtext.testing.InjectWith; | ||
11 | import org.eclipse.xtext.testing.extensions.InjectionExtension; | ||
12 | import org.junit.jupiter.api.extension.ExtendWith; | ||
13 | import org.junit.jupiter.params.ParameterizedTest; | ||
14 | import org.junit.jupiter.params.provider.Arguments; | ||
15 | import org.junit.jupiter.params.provider.MethodSource; | ||
16 | import tools.refinery.language.tests.ProblemInjectorProvider; | ||
17 | |||
18 | import java.io.ByteArrayOutputStream; | ||
19 | import java.io.IOException; | ||
20 | import java.nio.charset.StandardCharsets; | ||
21 | import java.util.List; | ||
22 | import java.util.Map; | ||
23 | import java.util.stream.Stream; | ||
24 | |||
25 | import static org.hamcrest.MatcherAssert.assertThat; | ||
26 | import static org.hamcrest.Matchers.is; | ||
27 | |||
28 | @ExtendWith(InjectionExtension.class) | ||
29 | @InjectWith(ProblemInjectorProvider.class) | ||
30 | class ProblemLoaderTest { | ||
31 | private static final String PREFIX = """ | ||
32 | class Foo. | ||
33 | class Bar. | ||
34 | """; | ||
35 | |||
36 | @Inject | ||
37 | private ProblemLoader loader; | ||
38 | |||
39 | @ParameterizedTest | ||
40 | @MethodSource | ||
41 | void loadScopeConstraintsTest(String originalScopes, List<String> scopes, List<String> overrideScopes, | ||
42 | String expectedScopes) throws IOException { | ||
43 | var problem = loader.loadString(PREFIX + originalScopes); | ||
44 | var modifiedProblem = loader.loadScopeConstraints(problem, scopes, overrideScopes); | ||
45 | String serializedProblem; | ||
46 | try (var outputStream = new ByteArrayOutputStream()) { | ||
47 | modifiedProblem.eResource().save(outputStream, Map.of()); | ||
48 | serializedProblem = outputStream.toString(StandardCharsets.UTF_8); | ||
49 | } | ||
50 | assertThat(serializedProblem, is(PREFIX + expectedScopes)); | ||
51 | } | ||
52 | |||
53 | static Stream<Arguments> loadScopeConstraintsTest() { | ||
54 | return Stream.of(Arguments.of("", | ||
55 | List.of(), | ||
56 | List.of(), | ||
57 | ""), Arguments.of("", | ||
58 | List.of("node=5..10"), | ||
59 | List.of(), """ | ||
60 | |||
61 | scope node=5..10. | ||
62 | """), Arguments.of("", | ||
63 | List.of("Foo=2", "Bar=3"), | ||
64 | List.of(), """ | ||
65 | |||
66 | scope Foo=2. | ||
67 | scope Bar=3. | ||
68 | """), Arguments.of(""" | ||
69 | scope Foo = 1, Bar = 1. | ||
70 | """, | ||
71 | List.of("node=5..10"), | ||
72 | List.of(), """ | ||
73 | scope Foo = 1, Bar = 1. | ||
74 | |||
75 | scope node=5..10. | ||
76 | """), Arguments.of(""" | ||
77 | scope Foo = 0..10, Bar = 1. | ||
78 | """, | ||
79 | List.of("Foo = 5"), | ||
80 | List.of(), """ | ||
81 | scope Foo = 0..10, Bar = 1. | ||
82 | |||
83 | scope Foo = 5. | ||
84 | """), Arguments.of(""" | ||
85 | scope Foo = 1, Bar = 1. | ||
86 | """, | ||
87 | List.of(), | ||
88 | List.of("node=5..10"), """ | ||
89 | scope Foo = 1, Bar = 1. | ||
90 | |||
91 | scope node=5..10. | ||
92 | """), Arguments.of(""" | ||
93 | scope Foo = 1, Bar = 1. | ||
94 | """, | ||
95 | List.of(), | ||
96 | List.of("Foo=3..4"), """ | ||
97 | scope Bar = 1. | ||
98 | |||
99 | scope Foo=3..4. | ||
100 | """), Arguments.of(""" | ||
101 | scope Foo = 1, Bar = 1. | ||
102 | """, | ||
103 | List.of("Foo=2"), | ||
104 | List.of("Foo=3..4"), """ | ||
105 | scope Bar = 1. | ||
106 | |||
107 | scope Foo=2. | ||
108 | scope Foo=3..4. | ||
109 | """), Arguments.of(""" | ||
110 | scope Foo = 1. | ||
111 | scope Bar = 1. | ||
112 | """, | ||
113 | List.of(), | ||
114 | List.of("Bar=3..4"), """ | ||
115 | scope Foo = 1. | ||
116 | |||
117 | |||
118 | scope Bar=3..4. | ||
119 | """), Arguments.of(""" | ||
120 | scope Foo = 1, Bar = 1. | ||
121 | """, | ||
122 | List.of(), | ||
123 | List.of("Foo=3..4", "Bar=4..5"), """ | ||
124 | |||
125 | scope Foo=3..4. | ||
126 | scope Bar=4..5. | ||
127 | """)); | ||
128 | } | ||
129 | } | ||
diff --git a/subprojects/language-model/src/main/resources/model/problem.ecore b/subprojects/language-model/src/main/resources/model/problem.ecore index 204f922c..74229a89 100644 --- a/subprojects/language-model/src/main/resources/model/problem.ecore +++ b/subprojects/language-model/src/main/resources/model/problem.ecore | |||
@@ -23,7 +23,7 @@ | |||
23 | <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//ReferenceKind"/> | 23 | <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//ReferenceKind"/> |
24 | <eStructuralFeatures xsi:type="ecore:EReference" name="referenceType" eType="#//Relation"/> | 24 | <eStructuralFeatures xsi:type="ecore:EReference" name="referenceType" eType="#//Relation"/> |
25 | <eStructuralFeatures xsi:type="ecore:EReference" name="invalidMultiplicity" eType="#//Relation" | 25 | <eStructuralFeatures xsi:type="ecore:EReference" name="invalidMultiplicity" eType="#//Relation" |
26 | containment="true"/> | 26 | transient="true" containment="true"/> |
27 | </eClassifiers> | 27 | </eClassifiers> |
28 | <eClassifiers xsi:type="ecore:EClass" name="NamedElement" abstract="true"> | 28 | <eClassifiers xsi:type="ecore:EClass" name="NamedElement" abstract="true"> |
29 | <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> | 29 | <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> |
diff --git a/subprojects/language-semantics/build.gradle.kts b/subprojects/language-semantics/build.gradle.kts index 689f621e..777ef993 100644 --- a/subprojects/language-semantics/build.gradle.kts +++ b/subprojects/language-semantics/build.gradle.kts | |||
@@ -16,6 +16,7 @@ dependencies { | |||
16 | api(project(":refinery-store-reasoning")) | 16 | api(project(":refinery-store-reasoning")) |
17 | implementation(project(":refinery-store-reasoning-scope")) | 17 | implementation(project(":refinery-store-reasoning-scope")) |
18 | runtimeOnly(libs.eclipseCollections) | 18 | runtimeOnly(libs.eclipseCollections) |
19 | testImplementation(project(":refinery-generator")) | ||
19 | testImplementation(project(":refinery-store-dse-visualization")) | 20 | testImplementation(project(":refinery-store-dse-visualization")) |
20 | testImplementation(project(":refinery-store-query-interpreter")) | 21 | testImplementation(project(":refinery-store-query-interpreter")) |
21 | testImplementation(testFixtures(project(":refinery-language"))) | 22 | testImplementation(testFixtures(project(":refinery-language"))) |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java index ecaa7e0d..38bf5a61 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java | |||
@@ -27,9 +27,7 @@ import tools.refinery.store.reasoning.seed.ModelSeed; | |||
27 | import tools.refinery.store.reasoning.seed.Seed; | 27 | import tools.refinery.store.reasoning.seed.Seed; |
28 | import tools.refinery.store.reasoning.translator.TranslationException; | 28 | import tools.refinery.store.reasoning.translator.TranslationException; |
29 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | 29 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; |
30 | import tools.refinery.store.reasoning.translator.metamodel.Metamodel; | 30 | import tools.refinery.store.reasoning.translator.metamodel.*; |
31 | import tools.refinery.store.reasoning.translator.metamodel.MetamodelBuilder; | ||
32 | import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator; | ||
33 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | 31 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; |
34 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | 32 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; |
35 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 33 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
@@ -73,6 +71,10 @@ public class ModelInitializer { | |||
73 | 71 | ||
74 | private ScopePropagator scopePropagator; | 72 | private ScopePropagator scopePropagator; |
75 | 73 | ||
74 | private int nodeCount; | ||
75 | |||
76 | private ModelSeed.Builder modelSeedBuilder; | ||
77 | |||
76 | private ModelSeed modelSeed; | 78 | private ModelSeed modelSeed; |
77 | 79 | ||
78 | public void readProblem(Problem problem) { | 80 | public void readProblem(Problem problem) { |
@@ -101,13 +103,17 @@ public class ModelInitializer { | |||
101 | TruthValue.FALSE)); | 103 | TruthValue.FALSE)); |
102 | collectNodes(); | 104 | collectNodes(); |
103 | collectPartialSymbols(); | 105 | collectPartialSymbols(); |
106 | nodeCount = problemTrace.getNodeTrace().size(); | ||
107 | modelSeedBuilder = ModelSeed.builder(nodeCount); | ||
108 | collectAssertions(); | ||
104 | collectMetamodel(); | 109 | collectMetamodel(); |
105 | metamodel = metamodelBuilder.build(); | 110 | metamodel = metamodelBuilder.build(); |
106 | problemTrace.setMetamodel(metamodel); | 111 | problemTrace.setMetamodel(metamodel); |
107 | collectAssertions(); | 112 | fixClassDeclarationAssertions(); |
108 | int nodeCount = problemTrace.getNodeTrace().size(); | ||
109 | var modelSeedBuilder = ModelSeed.builder(nodeCount); | ||
110 | for (var entry : relationInfoMap.entrySet()) { | 113 | for (var entry : relationInfoMap.entrySet()) { |
114 | if (entry.getKey() instanceof ReferenceDeclaration) { | ||
115 | continue; | ||
116 | } | ||
111 | var info = entry.getValue(); | 117 | var info = entry.getValue(); |
112 | var partialRelation = info.partialRelation(); | 118 | var partialRelation = info.partialRelation(); |
113 | modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount)); | 119 | modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount)); |
@@ -296,7 +302,20 @@ public class ModelInitializer { | |||
296 | } | 302 | } |
297 | var multiplicity = getMultiplicityConstraint(referenceDeclaration); | 303 | var multiplicity = getMultiplicityConstraint(referenceDeclaration); |
298 | try { | 304 | try { |
299 | metamodelBuilder.reference(relation, source, containment, multiplicity, target, oppositeRelation); | 305 | var seed = relationInfoMap.get(referenceDeclaration).toSeed(nodeCount); |
306 | var defaultValue = seed.majorityValue(); | ||
307 | if (defaultValue.must()) { | ||
308 | defaultValue = TruthValue.FALSE; | ||
309 | } | ||
310 | modelSeedBuilder.seed(relation, seed); | ||
311 | metamodelBuilder.reference(relation, ReferenceInfo.builder() | ||
312 | .containment(containment) | ||
313 | .source(source) | ||
314 | .multiplicity(multiplicity) | ||
315 | .target(target) | ||
316 | .opposite(oppositeRelation) | ||
317 | .defaultValue(defaultValue) | ||
318 | .build()); | ||
300 | } catch (RuntimeException e) { | 319 | } catch (RuntimeException e) { |
301 | throw TracedException.addTrace(classDeclaration, e); | 320 | throw TracedException.addTrace(classDeclaration, e); |
302 | } | 321 | } |
@@ -355,10 +374,6 @@ public class ModelInitializer { | |||
355 | collectCardinalityAssertions(newNodeId, TruthValue.UNKNOWN); | 374 | collectCardinalityAssertions(newNodeId, TruthValue.UNKNOWN); |
356 | var tuple = Tuple.of(newNodeId); | 375 | var tuple = Tuple.of(newNodeId); |
357 | mergeValue(classDeclaration, tuple, TruthValue.TRUE); | 376 | mergeValue(classDeclaration, tuple, TruthValue.TRUE); |
358 | var typeInfo = metamodel.typeHierarchy().getAnalysisResult(getPartialRelation(classDeclaration)); | ||
359 | for (var subType : typeInfo.getDirectSubtypes()) { | ||
360 | partialRelationInfoMap.get(subType).assertions().mergeValue(tuple, TruthValue.FALSE); | ||
361 | } | ||
362 | } | 377 | } |
363 | 378 | ||
364 | private void collectEnumAssertions(EnumDeclaration enumDeclaration) { | 379 | private void collectEnumAssertions(EnumDeclaration enumDeclaration) { |
@@ -399,6 +414,27 @@ public class ModelInitializer { | |||
399 | } | 414 | } |
400 | } | 415 | } |
401 | 416 | ||
417 | private void fixClassDeclarationAssertions() { | ||
418 | for (var statement : problem.getStatements()) { | ||
419 | if (statement instanceof ClassDeclaration classDeclaration) { | ||
420 | fixClassDeclarationAssertions(classDeclaration); | ||
421 | } | ||
422 | } | ||
423 | } | ||
424 | |||
425 | private void fixClassDeclarationAssertions(ClassDeclaration classDeclaration) { | ||
426 | var newNode = classDeclaration.getNewNode(); | ||
427 | if (newNode == null) { | ||
428 | return; | ||
429 | } | ||
430 | var newNodeId = getNodeId(newNode); | ||
431 | var tuple = Tuple.of(newNodeId); | ||
432 | var typeInfo = metamodel.typeHierarchy().getAnalysisResult(getPartialRelation(classDeclaration)); | ||
433 | for (var subType : typeInfo.getDirectSubtypes()) { | ||
434 | partialRelationInfoMap.get(subType).assertions().mergeValue(tuple, TruthValue.FALSE); | ||
435 | } | ||
436 | } | ||
437 | |||
402 | private void mergeValue(Relation relation, Tuple key, TruthValue value) { | 438 | private void mergeValue(Relation relation, Tuple key, TruthValue value) { |
403 | getRelationInfo(relation).assertions().mergeValue(key, value); | 439 | getRelationInfo(relation).assertions().mergeValue(key, value); |
404 | } | 440 | } |
@@ -482,7 +518,7 @@ public class ModelInitializer { | |||
482 | defaultValue = TruthValue.FALSE; | 518 | defaultValue = TruthValue.FALSE; |
483 | } else { | 519 | } else { |
484 | var seed = modelSeed.getSeed(partialRelation); | 520 | var seed = modelSeed.getSeed(partialRelation); |
485 | defaultValue = seed.reducedValue() == TruthValue.FALSE ? TruthValue.FALSE : TruthValue.UNKNOWN; | 521 | defaultValue = seed.majorityValue() == TruthValue.FALSE ? TruthValue.FALSE : TruthValue.UNKNOWN; |
486 | var cursor = seed.getCursor(defaultValue, problemTrace.getNodeTrace().size()); | 522 | var cursor = seed.getCursor(defaultValue, problemTrace.getNodeTrace().size()); |
487 | // The symbol should be mutable if there is at least one non-default entry in the seed. | 523 | // The symbol should be mutable if there is at least one non-default entry in the seed. |
488 | mutable = mutable || cursor.move(); | 524 | mutable = mutable || cursor.move(); |
@@ -495,7 +531,7 @@ public class ModelInitializer { | |||
495 | var problemParameters = predicateDefinition.getParameters(); | 531 | var problemParameters = predicateDefinition.getParameters(); |
496 | int arity = problemParameters.size(); | 532 | int arity = problemParameters.size(); |
497 | var parameters = new NodeVariable[arity]; | 533 | var parameters = new NodeVariable[arity]; |
498 | var parameterMap = new HashMap<tools.refinery.language.model.problem.Variable, Variable>(arity); | 534 | var parameterMap = HashMap.<tools.refinery.language.model.problem.Variable, Variable>newHashMap(arity); |
499 | var commonLiterals = new ArrayList<Literal>(); | 535 | var commonLiterals = new ArrayList<Literal>(); |
500 | for (int i = 0; i < arity; i++) { | 536 | for (int i = 0; i < arity; i++) { |
501 | var problemParameter = problemParameters.get(i); | 537 | var problemParameter = problemParameters.get(i); |
@@ -532,7 +568,7 @@ public class ModelInitializer { | |||
532 | return existing; | 568 | return existing; |
533 | } | 569 | } |
534 | int localScopeSize = existing.size() + newVariables.size(); | 570 | int localScopeSize = existing.size() + newVariables.size(); |
535 | var localScope = new HashMap<tools.refinery.language.model.problem.Variable, Variable>(localScopeSize); | 571 | var localScope = HashMap.<tools.refinery.language.model.problem.Variable, Variable>newHashMap(localScopeSize); |
536 | localScope.putAll(existing); | 572 | localScope.putAll(existing); |
537 | for (var newVariable : newVariables) { | 573 | for (var newVariable : newVariables) { |
538 | localScope.put(newVariable, Variable.of(newVariable.getName())); | 574 | localScope.put(newVariable, Variable.of(newVariable.getName())); |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java index cc634949..f686e980 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ProblemTraceImpl.java | |||
@@ -9,7 +9,6 @@ import com.google.inject.Inject; | |||
9 | import org.eclipse.collections.api.factory.primitive.ObjectIntMaps; | 9 | import org.eclipse.collections.api.factory.primitive.ObjectIntMaps; |
10 | import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; | 10 | import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; |
11 | import org.eclipse.collections.api.map.primitive.ObjectIntMap; | 11 | import org.eclipse.collections.api.map.primitive.ObjectIntMap; |
12 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
13 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | 12 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
14 | import org.eclipse.xtext.naming.QualifiedName; | 13 | import org.eclipse.xtext.naming.QualifiedName; |
15 | import org.eclipse.xtext.scoping.IScope; | 14 | import org.eclipse.xtext.scoping.IScope; |
@@ -28,7 +27,7 @@ import java.util.HashMap; | |||
28 | import java.util.LinkedHashMap; | 27 | import java.util.LinkedHashMap; |
29 | import java.util.Map; | 28 | import java.util.Map; |
30 | 29 | ||
31 | public class ProblemTraceImpl implements ProblemTrace { | 30 | class ProblemTraceImpl implements ProblemTrace { |
32 | @Inject | 31 | @Inject |
33 | private IQualifiedNameConverter qualifiedNameConverter; | 32 | private IQualifiedNameConverter qualifiedNameConverter; |
34 | 33 | ||
@@ -164,24 +163,6 @@ public class ProblemTraceImpl implements ProblemTrace { | |||
164 | } | 163 | } |
165 | 164 | ||
166 | private <T> T getElement(IScope scope, QualifiedName qualifiedName, Class<T> type) { | 165 | private <T> T getElement(IScope scope, QualifiedName qualifiedName, Class<T> type) { |
167 | var iterator = scope.getElements(qualifiedName).iterator(); | 166 | return semanticsUtils.getElement(problem, scope, qualifiedName, type); |
168 | if (!iterator.hasNext()) { | ||
169 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
170 | throw new IllegalArgumentException("No such %s: %s" | ||
171 | .formatted(type.getName(), qualifiedNameString)); | ||
172 | } | ||
173 | var eObjectDescription = iterator.next(); | ||
174 | if (iterator.hasNext()) { | ||
175 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
176 | throw new IllegalArgumentException("Ambiguous %s: %s" | ||
177 | .formatted(type.getName(), qualifiedNameString)); | ||
178 | } | ||
179 | var eObject = EcoreUtil.resolve(eObjectDescription.getEObjectOrProxy(), getProblem()); | ||
180 | if (!type.isInstance(eObject)) { | ||
181 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
182 | throw new IllegalArgumentException("Not a %s: %s" | ||
183 | .formatted(type.getName(), qualifiedNameString)); | ||
184 | } | ||
185 | return type.cast(eObject); | ||
186 | } | 167 | } |
187 | } | 168 | } |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java index b195a8e7..b72ba697 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java | |||
@@ -8,8 +8,14 @@ package tools.refinery.language.semantics; | |||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import com.google.inject.Singleton; | 9 | import com.google.inject.Singleton; |
10 | import org.eclipse.emf.ecore.EObject; | 10 | import org.eclipse.emf.ecore.EObject; |
11 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
11 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | 12 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
12 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 13 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
14 | import org.eclipse.xtext.naming.QualifiedName; | ||
15 | import org.eclipse.xtext.scoping.IScope; | ||
16 | import org.jetbrains.annotations.NotNull; | ||
17 | import org.jetbrains.annotations.Nullable; | ||
18 | import tools.refinery.language.model.problem.Problem; | ||
13 | 19 | ||
14 | import java.util.Optional; | 20 | import java.util.Optional; |
15 | 21 | ||
@@ -28,4 +34,39 @@ public class SemanticsUtils { | |||
28 | } | 34 | } |
29 | return Optional.of(qualifiedNameConverter.toString(qualifiedName)); | 35 | return Optional.of(qualifiedNameConverter.toString(qualifiedName)); |
30 | } | 36 | } |
37 | |||
38 | @Nullable | ||
39 | public <T> T maybeGetElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { | ||
40 | if (qualifiedName == null) { | ||
41 | throw new IllegalArgumentException("Element name must not be null"); | ||
42 | } | ||
43 | var iterator = scope.getElements(qualifiedName).iterator(); | ||
44 | if (!iterator.hasNext()) { | ||
45 | return null; | ||
46 | } | ||
47 | var eObjectDescription = iterator.next(); | ||
48 | if (iterator.hasNext()) { | ||
49 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
50 | throw new IllegalArgumentException("Ambiguous %s: %s" | ||
51 | .formatted(type.getName(), qualifiedNameString)); | ||
52 | } | ||
53 | var eObject = EcoreUtil.resolve(eObjectDescription.getEObjectOrProxy(), problem); | ||
54 | if (!type.isInstance(eObject)) { | ||
55 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
56 | throw new IllegalArgumentException("Not a %s: %s" | ||
57 | .formatted(type.getName(), qualifiedNameString)); | ||
58 | } | ||
59 | return type.cast(eObject); | ||
60 | } | ||
61 | |||
62 | @NotNull | ||
63 | public <T> T getElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { | ||
64 | var element = maybeGetElement(problem, scope, qualifiedName, type); | ||
65 | if (element == null) { | ||
66 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
67 | throw new IllegalArgumentException("No such %s: %s" | ||
68 | .formatted(type.getName(), qualifiedNameString)); | ||
69 | } | ||
70 | return element; | ||
71 | } | ||
31 | } | 72 | } |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java new file mode 100644 index 00000000..57af599e --- /dev/null +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java | |||
@@ -0,0 +1,335 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.semantics; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Provider; | ||
10 | import org.eclipse.collections.api.factory.primitive.IntObjectMaps; | ||
11 | import org.eclipse.collections.api.factory.primitive.ObjectIntMaps; | ||
12 | import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; | ||
13 | import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; | ||
14 | import org.eclipse.emf.common.util.URI; | ||
15 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
16 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
17 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | ||
18 | import org.eclipse.xtext.naming.QualifiedName; | ||
19 | import org.eclipse.xtext.resource.FileExtensionProvider; | ||
20 | import org.eclipse.xtext.resource.IResourceFactory; | ||
21 | import org.eclipse.xtext.resource.XtextResource; | ||
22 | import org.eclipse.xtext.resource.XtextResourceSet; | ||
23 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
24 | import tools.refinery.language.model.problem.*; | ||
25 | import tools.refinery.language.utils.ProblemDesugarer; | ||
26 | import tools.refinery.language.utils.ProblemUtil; | ||
27 | import tools.refinery.store.model.Model; | ||
28 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
29 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
30 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
31 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
32 | import tools.refinery.store.reasoning.translator.typehierarchy.InferredType; | ||
33 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator; | ||
34 | import tools.refinery.store.representation.TruthValue; | ||
35 | import tools.refinery.store.tuple.Tuple; | ||
36 | |||
37 | import java.io.ByteArrayInputStream; | ||
38 | import java.io.ByteArrayOutputStream; | ||
39 | import java.io.IOException; | ||
40 | import java.util.Locale; | ||
41 | import java.util.Map; | ||
42 | import java.util.TreeMap; | ||
43 | import java.util.TreeSet; | ||
44 | import java.util.function.Function; | ||
45 | import java.util.stream.Collectors; | ||
46 | |||
47 | public class SolutionSerializer { | ||
48 | private String fileExtension; | ||
49 | |||
50 | @Inject | ||
51 | private Provider<XtextResourceSet> resourceSetProvider; | ||
52 | |||
53 | @Inject | ||
54 | private IResourceFactory resourceFactory; | ||
55 | |||
56 | @Inject | ||
57 | private IQualifiedNameProvider qualifiedNameProvider; | ||
58 | |||
59 | @Inject | ||
60 | private IQualifiedNameConverter qualifiedNameConverter; | ||
61 | |||
62 | @Inject | ||
63 | private SemanticsUtils semanticsUtils; | ||
64 | |||
65 | @Inject | ||
66 | private IScopeProvider scopeProvider; | ||
67 | |||
68 | @Inject | ||
69 | private ProblemDesugarer desugarer; | ||
70 | |||
71 | private ProblemTrace trace; | ||
72 | private Model model; | ||
73 | private ReasoningAdapter reasoningAdapter; | ||
74 | private PartialInterpretation<TruthValue, Boolean> existsInterpretation; | ||
75 | private Problem problem; | ||
76 | private final MutableIntObjectMap<Node> nodes = IntObjectMaps.mutable.empty(); | ||
77 | |||
78 | @Inject | ||
79 | public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) { | ||
80 | this.fileExtension = fileExtensionProvider.getPrimaryFileExtension(); | ||
81 | } | ||
82 | |||
83 | public Problem serializeSolution(ProblemTrace trace, Model model) { | ||
84 | var uri = URI.createFileURI("__synthetic" + fileExtension); | ||
85 | return serializeSolution(trace, model, uri); | ||
86 | } | ||
87 | |||
88 | public Problem serializeSolution(ProblemTrace trace, Model model, URI uri) { | ||
89 | this.trace = trace; | ||
90 | this.model = model; | ||
91 | reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
92 | existsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, | ||
93 | ReasoningAdapter.EXISTS_SYMBOL); | ||
94 | var originalProblem = trace.getProblem(); | ||
95 | problem = copyProblem(originalProblem, uri); | ||
96 | problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); | ||
97 | problem.getNodes().removeIf(this::shouldRemoveNode); | ||
98 | addExistsAssertions(); | ||
99 | addClassAssertions(); | ||
100 | addReferenceAssertions(); | ||
101 | return problem; | ||
102 | } | ||
103 | |||
104 | private static boolean shouldRemoveStatement(Statement statement) { | ||
105 | return statement instanceof Assertion || statement instanceof ScopeDeclaration; | ||
106 | } | ||
107 | |||
108 | private boolean shouldRemoveNode(Node newNode) { | ||
109 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(newNode); | ||
110 | var scope = scopeProvider.getScope(trace.getProblem(), ProblemPackage.Literals.ASSERTION__RELATION); | ||
111 | var originalNode = semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class); | ||
112 | if (originalNode == null) { | ||
113 | return false; | ||
114 | } | ||
115 | int nodeId = trace.getNodeId(originalNode); | ||
116 | return !isExistingNode(nodeId); | ||
117 | } | ||
118 | |||
119 | private boolean isExistingNode(int nodeId) { | ||
120 | var exists = existsInterpretation.get(Tuple.of(nodeId)); | ||
121 | if (!exists.isConcrete()) { | ||
122 | throw new IllegalStateException("Invalid EXISTS %s for node %d".formatted(exists, nodeId)); | ||
123 | } | ||
124 | return exists.may(); | ||
125 | } | ||
126 | |||
127 | private Problem copyProblem(Problem originalProblem, URI uri) { | ||
128 | var newResourceSet = resourceSetProvider.get(); | ||
129 | if (!fileExtension.equals(uri.fileExtension())) { | ||
130 | uri = uri.appendFileExtension(fileExtension); | ||
131 | } | ||
132 | var newResource = resourceFactory.createResource(uri); | ||
133 | newResourceSet.getResources().add(newResource); | ||
134 | var originalResource = originalProblem.eResource(); | ||
135 | if (originalResource instanceof XtextResource) { | ||
136 | byte[] bytes; | ||
137 | try { | ||
138 | try (var outputStream = new ByteArrayOutputStream()) { | ||
139 | originalResource.save(outputStream, Map.of()); | ||
140 | bytes = outputStream.toByteArray(); | ||
141 | } | ||
142 | try (var inputStream = new ByteArrayInputStream(bytes)) { | ||
143 | newResource.load(inputStream, Map.of()); | ||
144 | } | ||
145 | } catch (IOException e) { | ||
146 | throw new IllegalStateException("Failed to copy problem", e); | ||
147 | } | ||
148 | var contents = newResource.getContents(); | ||
149 | if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) { | ||
150 | return newProblem; | ||
151 | } | ||
152 | throw new IllegalStateException("Invalid contents of copied problem"); | ||
153 | } else { | ||
154 | var newProblem = EcoreUtil.copy(originalProblem); | ||
155 | newResource.getContents().add(newProblem); | ||
156 | return newProblem; | ||
157 | } | ||
158 | } | ||
159 | |||
160 | private Relation findRelation(Relation originalRelation) { | ||
161 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalRelation); | ||
162 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); | ||
163 | return semanticsUtils.getElement(problem, scope, qualifiedName, Relation.class); | ||
164 | } | ||
165 | |||
166 | private Relation findPartialRelation(PartialRelation partialRelation) { | ||
167 | return findRelation(trace.getRelation(partialRelation)); | ||
168 | } | ||
169 | |||
170 | private Node findNode(Node originalNode) { | ||
171 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalNode); | ||
172 | return findNode(qualifiedName); | ||
173 | } | ||
174 | |||
175 | private Node findNode(String name) { | ||
176 | var qualifiedName = qualifiedNameConverter.toQualifiedName(name); | ||
177 | return findNode(qualifiedName); | ||
178 | } | ||
179 | |||
180 | private Node findNode(QualifiedName qualifiedName) { | ||
181 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
182 | return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class); | ||
183 | } | ||
184 | |||
185 | private void addAssertion(Relation relation, LogicValue value, Node... arguments) { | ||
186 | var assertion = ProblemFactory.eINSTANCE.createAssertion(); | ||
187 | assertion.setRelation(relation); | ||
188 | for (var node : arguments) { | ||
189 | var argument = ProblemFactory.eINSTANCE.createNodeAssertionArgument(); | ||
190 | argument.setNode(node); | ||
191 | assertion.getArguments().add(argument); | ||
192 | } | ||
193 | var logicConstant = ProblemFactory.eINSTANCE.createLogicConstant(); | ||
194 | logicConstant.setLogicValue(value); | ||
195 | assertion.setValue(logicConstant); | ||
196 | problem.getStatements().add(assertion); | ||
197 | } | ||
198 | |||
199 | private void addExistsAssertions() { | ||
200 | var builtinSymbols = desugarer.getBuiltinSymbols(problem) | ||
201 | .orElseThrow(() -> new IllegalStateException("No builtin library in copied problem")); | ||
202 | // Make sure to output exists assertions in a deterministic order. | ||
203 | var sortedNewNodes = new TreeMap<Integer, Node>(); | ||
204 | for (var pair : trace.getNodeTrace().keyValuesView()) { | ||
205 | var originalNode = pair.getOne(); | ||
206 | int nodeId = pair.getTwo(); | ||
207 | var newNode = findNode(originalNode); | ||
208 | // Since all implicit nodes that do not exist has already been remove in serializeSolution, | ||
209 | // we only need to add !exists assertions to ::new nodes (nodes marked as an individual must always exist). | ||
210 | if (ProblemUtil.isNewNode(originalNode)) { | ||
211 | sortedNewNodes.put(nodeId, newNode); | ||
212 | } else { | ||
213 | nodes.put(nodeId, newNode); | ||
214 | } | ||
215 | } | ||
216 | for (var newNode : sortedNewNodes.values()) { | ||
217 | // If a node is a new node of the class, we should replace it with a normal node. | ||
218 | addAssertion(builtinSymbols.exists(), LogicValue.FALSE, newNode); | ||
219 | } | ||
220 | } | ||
221 | |||
222 | private void addClassAssertions() { | ||
223 | var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream() | ||
224 | .collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); | ||
225 | var indexMap = ObjectIntMaps.mutable.empty(); | ||
226 | var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); | ||
227 | while (cursor.move()) { | ||
228 | var key = cursor.getKey(); | ||
229 | var nodeId = key.get(0); | ||
230 | if (isExistingNode(nodeId)) { | ||
231 | createNodeAndAssertType(nodeId, cursor.getValue(), types, indexMap); | ||
232 | } | ||
233 | } | ||
234 | } | ||
235 | |||
236 | private void createNodeAndAssertType(int nodeId, InferredType inferredType, Map<PartialRelation, Relation> types, | ||
237 | MutableObjectIntMap<Object> indexMap) { | ||
238 | var candidateTypeSymbol = inferredType.candidateType(); | ||
239 | var candidateRelation = types.get(candidateTypeSymbol); | ||
240 | if (candidateRelation instanceof EnumDeclaration) { | ||
241 | // Type assertions for enum literals are added implicitly. | ||
242 | return; | ||
243 | } | ||
244 | Node node = nodes.get(nodeId); | ||
245 | if (node == null) { | ||
246 | String typeName = candidateRelation.getName(); | ||
247 | if (typeName == null || typeName.isEmpty()) { | ||
248 | typeName = "node"; | ||
249 | } else { | ||
250 | typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1); | ||
251 | } | ||
252 | int index = indexMap.getIfAbsent(typeName, 0); | ||
253 | String nodeName; | ||
254 | do { | ||
255 | index++; | ||
256 | nodeName = typeName + index; | ||
257 | } while (findNode(nodeName) != null); | ||
258 | node = ProblemFactory.eINSTANCE.createNode(); | ||
259 | node.setName(nodeName); | ||
260 | problem.getNodes().add(node); | ||
261 | nodes.put(nodeId, node); | ||
262 | } | ||
263 | addAssertion(candidateRelation, LogicValue.TRUE, node); | ||
264 | var typeAnalysisResult = trace.getMetamodel().typeHierarchy().getPreservedTypes().get(candidateTypeSymbol); | ||
265 | for (var subtype : typeAnalysisResult.getDirectSubtypes()) { | ||
266 | var subtypeRelation = types.get(subtype); | ||
267 | addAssertion(subtypeRelation, LogicValue.FALSE, node); | ||
268 | } | ||
269 | } | ||
270 | |||
271 | private void addReferenceAssertions() { | ||
272 | var metamodel = trace.getMetamodel(); | ||
273 | for (var partialRelation : metamodel.containmentHierarchy().keySet()) { | ||
274 | // No need to add a default value, because in a concrete model, each contained node has only a single | ||
275 | // container. | ||
276 | addAssertions(partialRelation); | ||
277 | } | ||
278 | for (var partialRelation : metamodel.directedCrossReferences().keySet()) { | ||
279 | addDefaultAssertion(partialRelation); | ||
280 | addAssertions(partialRelation); | ||
281 | } | ||
282 | // No need to add directed opposite references, because their default value is {@code unknown} and their | ||
283 | // actual value will always be computed from the value of the directed forward reference. | ||
284 | // However, undirected cross-references have to be serialized in both directions due to the default value of | ||
285 | // {@code false}. | ||
286 | for (var partialRelation : metamodel.undirectedCrossReferences().keySet()) { | ||
287 | addDefaultAssertion(partialRelation); | ||
288 | addAssertions(partialRelation); | ||
289 | } | ||
290 | } | ||
291 | |||
292 | private void addAssertions(PartialRelation partialRelation) { | ||
293 | var relation = findPartialRelation(partialRelation); | ||
294 | var cursor = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, partialRelation).getAll(); | ||
295 | // Make sure to output assertions in a deterministic order. | ||
296 | var sortedTuples = new TreeSet<Tuple>(); | ||
297 | while (cursor.move()) { | ||
298 | var tuple = cursor.getKey(); | ||
299 | var from = nodes.get(tuple.get(0)); | ||
300 | var to = nodes.get(tuple.get(1)); | ||
301 | if (from == null || to == null) { | ||
302 | // One of the endpoints does not exist in the candidate model. | ||
303 | continue; | ||
304 | } | ||
305 | var value = cursor.getValue(); | ||
306 | if (!value.isConcrete()) { | ||
307 | throw new IllegalStateException("Invalid %s %s for tuple %s".formatted(partialRelation, value, tuple)); | ||
308 | } | ||
309 | if (value.may()) { | ||
310 | sortedTuples.add(tuple); | ||
311 | } | ||
312 | } | ||
313 | for (var tuple : sortedTuples) { | ||
314 | var from = nodes.get(tuple.get(0)); | ||
315 | var to = nodes.get(tuple.get(1)); | ||
316 | addAssertion(relation, LogicValue.TRUE, from, to); | ||
317 | } | ||
318 | } | ||
319 | |||
320 | private void addDefaultAssertion(PartialRelation partialRelation) { | ||
321 | var relation = findPartialRelation(partialRelation); | ||
322 | var assertion = ProblemFactory.eINSTANCE.createAssertion(); | ||
323 | assertion.setDefault(true); | ||
324 | assertion.setRelation(relation); | ||
325 | int arity = ProblemUtil.getArity(relation); | ||
326 | for (int i = 0; i < arity; i++) { | ||
327 | var argument = ProblemFactory.eINSTANCE.createWildcardAssertionArgument(); | ||
328 | assertion.getArguments().add(argument); | ||
329 | } | ||
330 | var logicConstant = ProblemFactory.eINSTANCE.createLogicConstant(); | ||
331 | logicConstant.setLogicValue(LogicValue.FALSE); | ||
332 | assertion.setValue(logicConstant); | ||
333 | problem.getStatements().add(assertion); | ||
334 | } | ||
335 | } | ||
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java index c732f784..5d25f148 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTree.java | |||
@@ -40,8 +40,8 @@ class DecisionTree implements MutableSeed<TruthValue> { | |||
40 | } | 40 | } |
41 | 41 | ||
42 | @Override | 42 | @Override |
43 | public TruthValue reducedValue() { | 43 | public TruthValue majorityValue() { |
44 | return root.getOtherwiseReducedValue().getTruthValue(); | 44 | return root.getMajorityValue().getTruthValueOrElse(TruthValue.FALSE); |
45 | } | 45 | } |
46 | 46 | ||
47 | @Override | 47 | @Override |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java index ebca2634..31d6fc78 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/DecisionTreeNode.java | |||
@@ -100,6 +100,8 @@ abstract class DecisionTreeNode { | |||
100 | 100 | ||
101 | protected abstract LazyIntIterable getChildKeys(); | 101 | protected abstract LazyIntIterable getChildKeys(); |
102 | 102 | ||
103 | public abstract DecisionTreeValue getMajorityValue(); | ||
104 | |||
103 | protected abstract boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, | 105 | protected abstract boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, |
104 | int[] sortedChildren); | 106 | int[] sortedChildren); |
105 | 107 | ||
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java index 0e2f5d18..2ad216ce 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/IntermediateNode.java | |||
@@ -130,6 +130,11 @@ final class IntermediateNode extends DecisionTreeNode { | |||
130 | return children.keysView(); | 130 | return children.keysView(); |
131 | } | 131 | } |
132 | 132 | ||
133 | @Override | ||
134 | public DecisionTreeValue getMajorityValue() { | ||
135 | return otherwise.getMajorityValue(); | ||
136 | } | ||
137 | |||
133 | protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) { | 138 | protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) { |
134 | for (int i = startIndex; i < sortedChildren.length; i++) { | 139 | for (int i = startIndex; i < sortedChildren.length; i++) { |
135 | var key = sortedChildren[i]; | 140 | var key = sortedChildren[i]; |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java index 7a72c656..f100c5a6 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/NullaryMutableSeed.java | |||
@@ -29,14 +29,14 @@ class NullaryMutableSeed implements MutableSeed<TruthValue> { | |||
29 | } | 29 | } |
30 | 30 | ||
31 | @Override | 31 | @Override |
32 | public TruthValue reducedValue() { | 32 | public TruthValue majorityValue() { |
33 | return value.getTruthValue(); | 33 | return value.getTruthValue(); |
34 | } | 34 | } |
35 | 35 | ||
36 | @Override | 36 | @Override |
37 | public TruthValue get(Tuple key) { | 37 | public TruthValue get(Tuple key) { |
38 | validateKey(key); | 38 | validateKey(key); |
39 | return reducedValue(); | 39 | return majorityValue(); |
40 | } | 40 | } |
41 | 41 | ||
42 | private static void validateKey(Tuple key) { | 42 | private static void validateKey(Tuple key) { |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java index d3dd757c..dc501479 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/internal/TerminalNode.java | |||
@@ -115,7 +115,7 @@ class TerminalNode extends DecisionTreeNode { | |||
115 | 115 | ||
116 | @Override | 116 | @Override |
117 | protected DecisionTreeValue getOtherwiseReducedValue() { | 117 | protected DecisionTreeValue getOtherwiseReducedValue() { |
118 | return otherwise; | 118 | return getMajorityValue(); |
119 | } | 119 | } |
120 | 120 | ||
121 | @Override | 121 | @Override |
@@ -124,6 +124,11 @@ class TerminalNode extends DecisionTreeNode { | |||
124 | } | 124 | } |
125 | 125 | ||
126 | @Override | 126 | @Override |
127 | public DecisionTreeValue getMajorityValue() { | ||
128 | return otherwise; | ||
129 | } | ||
130 | |||
131 | @Override | ||
127 | protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) { | 132 | protected boolean moveNextSparse(int level, DecisionTreeCursor cursor, int startIndex, int[] sortedChildren) { |
128 | if (startIndex >= sortedChildren.length) { | 133 | if (startIndex >= sortedChildren.length) { |
129 | return false; | 134 | return false; |
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.java new file mode 100644 index 00000000..2d759c86 --- /dev/null +++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/SolutionSerializerTest.java | |||
@@ -0,0 +1,164 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.semantics; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import org.eclipse.xtext.testing.InjectWith; | ||
10 | import org.eclipse.xtext.testing.extensions.InjectionExtension; | ||
11 | import org.junit.jupiter.api.extension.ExtendWith; | ||
12 | import org.junit.jupiter.params.ParameterizedTest; | ||
13 | import org.junit.jupiter.params.provider.Arguments; | ||
14 | import org.junit.jupiter.params.provider.MethodSource; | ||
15 | import tools.refinery.generator.ModelGeneratorFactory; | ||
16 | import tools.refinery.generator.ProblemLoader; | ||
17 | import tools.refinery.language.tests.ProblemInjectorProvider; | ||
18 | |||
19 | import java.io.ByteArrayOutputStream; | ||
20 | import java.io.IOException; | ||
21 | import java.util.Map; | ||
22 | import java.util.stream.Stream; | ||
23 | |||
24 | import static org.hamcrest.MatcherAssert.assertThat; | ||
25 | import static org.hamcrest.Matchers.is; | ||
26 | |||
27 | @ExtendWith(InjectionExtension.class) | ||
28 | @InjectWith(ProblemInjectorProvider.class) | ||
29 | class SolutionSerializerTest { | ||
30 | @Inject | ||
31 | ProblemLoader loader; | ||
32 | |||
33 | @Inject | ||
34 | ModelGeneratorFactory generatorFactory; | ||
35 | |||
36 | @Inject | ||
37 | SolutionSerializer serializer; | ||
38 | |||
39 | @ParameterizedTest | ||
40 | @MethodSource | ||
41 | void solutionSerializerTest(String prefix, String input, String expectedOutput) throws IOException { | ||
42 | var problem = loader.loadString(prefix + "\n" + input); | ||
43 | var generator = generatorFactory.createGenerator(problem); | ||
44 | generator.generate(); | ||
45 | var solution = serializer.serializeSolution(generator.getProblemTrace(), generator.getModel()); | ||
46 | String actualOutput; | ||
47 | try (var outputStream = new ByteArrayOutputStream()) { | ||
48 | solution.eResource().save(outputStream, Map.of()); | ||
49 | actualOutput = outputStream.toString(); | ||
50 | } | ||
51 | assertThat(actualOutput, is(prefix + "\n" + expectedOutput)); | ||
52 | } | ||
53 | |||
54 | static Stream<Arguments> solutionSerializerTest() { | ||
55 | return Stream.of(Arguments.of(""" | ||
56 | class Foo. | ||
57 | """, """ | ||
58 | scope Foo = 3. | ||
59 | """, """ | ||
60 | !exists(Foo::new). | ||
61 | Foo(foo1). | ||
62 | Foo(foo2). | ||
63 | Foo(foo3). | ||
64 | """), Arguments.of(""" | ||
65 | class Foo { | ||
66 | contains Bar[2] bars | ||
67 | } | ||
68 | |||
69 | class Bar. | ||
70 | """, """ | ||
71 | scope Foo = 1. | ||
72 | """, """ | ||
73 | !exists(Foo::new). | ||
74 | !exists(Bar::new). | ||
75 | Foo(foo1). | ||
76 | Bar(bar1). | ||
77 | Bar(bar2). | ||
78 | bars(foo1, bar1). | ||
79 | bars(foo1, bar2). | ||
80 | """), Arguments.of(""" | ||
81 | class Foo { | ||
82 | Bar[2] bars opposite foo | ||
83 | } | ||
84 | |||
85 | class Bar { | ||
86 | Foo[1] foo opposite bars | ||
87 | } | ||
88 | """, """ | ||
89 | scope Foo = 1, Bar = 2. | ||
90 | """, """ | ||
91 | !exists(Foo::new). | ||
92 | !exists(Bar::new). | ||
93 | Foo(foo1). | ||
94 | Bar(bar1). | ||
95 | Bar(bar2). | ||
96 | default !bars(*, *). | ||
97 | bars(foo1, bar1). | ||
98 | bars(foo1, bar2). | ||
99 | """), Arguments.of(""" | ||
100 | class Person { | ||
101 | Person[2] friend opposite friend | ||
102 | } | ||
103 | """, """ | ||
104 | friend(a, b). | ||
105 | friend(a, c). | ||
106 | friend(b, c). | ||
107 | |||
108 | scope Person += 0. | ||
109 | """, """ | ||
110 | !exists(Person::new). | ||
111 | Person(a). | ||
112 | Person(b). | ||
113 | Person(c). | ||
114 | default !friend(*, *). | ||
115 | friend(a, b). | ||
116 | friend(a, c). | ||
117 | friend(b, a). | ||
118 | friend(b, c). | ||
119 | friend(c, a). | ||
120 | friend(c, b). | ||
121 | """), Arguments.of(""" | ||
122 | class Foo { | ||
123 | Bar bar | ||
124 | } | ||
125 | |||
126 | enum Bar { | ||
127 | BAR_A, | ||
128 | BAR_B | ||
129 | } | ||
130 | """, """ | ||
131 | bar(foo, BAR_A). | ||
132 | |||
133 | scope Foo += 0. | ||
134 | """, """ | ||
135 | !exists(Foo::new). | ||
136 | Foo(foo). | ||
137 | default !bar(*, *). | ||
138 | bar(foo, BAR_A). | ||
139 | """), Arguments.of(""" | ||
140 | class Foo. | ||
141 | class Bar extends Foo. | ||
142 | """, """ | ||
143 | scope Foo = 1, Bar = 0. | ||
144 | """, """ | ||
145 | !exists(Foo::new). | ||
146 | !exists(Bar::new). | ||
147 | Foo(foo1). | ||
148 | !Bar(foo1). | ||
149 | """), Arguments.of(""" | ||
150 | class Foo { | ||
151 | Foo[] ref | ||
152 | } | ||
153 | """, """ | ||
154 | ref(a, b). | ||
155 | !exists(b). | ||
156 | |||
157 | scope Foo += 0. | ||
158 | """, """ | ||
159 | !exists(Foo::new). | ||
160 | Foo(a). | ||
161 | default !ref(*, *). | ||
162 | """)); | ||
163 | } | ||
164 | } | ||
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java index 2320de2c..61ce850f 100644 --- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java +++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/internal/DecisionTreeTests.java | |||
@@ -276,6 +276,16 @@ class DecisionTreeTests { | |||
276 | assertThat(sut.getReducedValue(), is(TruthValue.TRUE)); | 276 | assertThat(sut.getReducedValue(), is(TruthValue.TRUE)); |
277 | } | 277 | } |
278 | 278 | ||
279 | @Test | ||
280 | void overwriteWildcardAllTest() { | ||
281 | var first = new DecisionTree(2, TruthValue.UNKNOWN); | ||
282 | first.mergeValue(Tuple.of(-1, -1), TruthValue.FALSE); | ||
283 | var second = new DecisionTree(2, null); | ||
284 | second.mergeValue(Tuple.of(1, -1), TruthValue.TRUE); | ||
285 | first.overwriteValues(second); | ||
286 | assertThat(first.majorityValue(), is(TruthValue.FALSE)); | ||
287 | } | ||
288 | |||
279 | private Map<Tuple, TruthValue> iterateAll(DecisionTree sut, TruthValue defaultValue, int nodeCount) { | 289 | private Map<Tuple, TruthValue> iterateAll(DecisionTree sut, TruthValue defaultValue, int nodeCount) { |
280 | var cursor = sut.getCursor(defaultValue, nodeCount); | 290 | var cursor = sut.getCursor(defaultValue, nodeCount); |
281 | var map = new LinkedHashMap<Tuple, TruthValue>(); | 291 | var map = new LinkedHashMap<Tuple, TruthValue>(); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java index 0a5cb3c2..00dd3de3 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java | |||
@@ -20,6 +20,7 @@ import org.eclipse.xtext.scoping.IGlobalScopeProvider; | |||
20 | import org.eclipse.xtext.scoping.IScopeProvider; | 20 | import org.eclipse.xtext.scoping.IScopeProvider; |
21 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | 21 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; |
22 | import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer; | 22 | import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer; |
23 | import org.eclipse.xtext.serializer.tokens.ICrossReferenceSerializer; | ||
23 | import org.eclipse.xtext.validation.IDiagnosticConverter; | 24 | import org.eclipse.xtext.validation.IDiagnosticConverter; |
24 | import org.eclipse.xtext.validation.IResourceValidator; | 25 | import org.eclipse.xtext.validation.IResourceValidator; |
25 | import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; | 26 | import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; |
@@ -34,12 +35,15 @@ import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | |||
34 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; | 35 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; |
35 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; | 36 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; |
36 | import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; | 37 | import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; |
38 | import tools.refinery.language.serializer.ProblemCrossReferenceSerializer; | ||
37 | import tools.refinery.language.validation.ProblemDiagnosticConverter; | 39 | import tools.refinery.language.validation.ProblemDiagnosticConverter; |
38 | 40 | ||
39 | /** | 41 | /** |
40 | * Use this class to register components to be used at runtime / without the | 42 | * Use this class to register components to be used at runtime / without the |
41 | * Equinox extension registry. | 43 | * Equinox extension registry. |
42 | */ | 44 | */ |
45 | // Unused methods in this class are called by reflection to configure the Xtext Injector. | ||
46 | @SuppressWarnings("unused") | ||
43 | public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { | 47 | public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { |
44 | @Override | 48 | @Override |
45 | public Class<? extends IParser> bindIParser() { | 49 | public Class<? extends IParser> bindIParser() { |
@@ -104,6 +108,10 @@ public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { | |||
104 | return PreferShortAssertionsProblemSemanticSequencer.class; | 108 | return PreferShortAssertionsProblemSemanticSequencer.class; |
105 | } | 109 | } |
106 | 110 | ||
111 | public Class<? extends ICrossReferenceSerializer> bindICrossReferenceSerializer() { | ||
112 | return ProblemCrossReferenceSerializer.class; | ||
113 | } | ||
114 | |||
107 | public Class<? extends IDiagnosticConverter> bindIDiagnosticConverter() { | 115 | public Class<? extends IDiagnosticConverter> bindIDiagnosticConverter() { |
108 | return ProblemDiagnosticConverter.class; | 116 | return ProblemDiagnosticConverter.class; |
109 | } | 117 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java index 55a5ac20..0f3bd3ee 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java | |||
@@ -69,12 +69,24 @@ public class ProblemFormatter extends AbstractJavaFormatter { | |||
69 | doc.prepend(region.keyword("{"), this::oneSpace); | 69 | doc.prepend(region.keyword("{"), this::oneSpace); |
70 | doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2)); | 70 | doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2)); |
71 | doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2)); | 71 | doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2)); |
72 | doc.interior(region.keyword("{"), region.keyword("}"), IHiddenRegionFormatter::indent); | ||
72 | doc.prepend(region.keyword("."), this::noSpace); | 73 | doc.prepend(region.keyword("."), this::noSpace); |
73 | for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) { | 74 | for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) { |
74 | doc.format(featureDeclaration); | 75 | doc.format(featureDeclaration); |
75 | } | 76 | } |
76 | } | 77 | } |
77 | 78 | ||
79 | protected void format(EnumDeclaration enumDeclaration, IFormattableDocument doc) { | ||
80 | surroundNewLines(doc, enumDeclaration, this::twoNewLines); | ||
81 | var region = regionFor(enumDeclaration); | ||
82 | doc.append(region.keyword("enum"), this::oneSpace); | ||
83 | doc.prepend(region.keyword("{"), this::oneSpace); | ||
84 | doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2)); | ||
85 | doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2)); | ||
86 | doc.interior(region.keyword("{"), region.keyword("}"), IHiddenRegionFormatter::indent); | ||
87 | doc.prepend(region.keyword("."), this::noSpace); | ||
88 | } | ||
89 | |||
78 | protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) { | 90 | protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) { |
79 | surroundNewLines(doc, predicateDefinition, this::twoNewLines); | 91 | surroundNewLines(doc, predicateDefinition, this::twoNewLines); |
80 | var region = regionFor(predicateDefinition); | 92 | var region = regionFor(predicateDefinition); |
@@ -151,14 +163,14 @@ public class ProblemFormatter extends AbstractJavaFormatter { | |||
151 | } | 163 | } |
152 | 164 | ||
153 | protected void surroundNewLines(IFormattableDocument doc, EObject eObject, | 165 | protected void surroundNewLines(IFormattableDocument doc, EObject eObject, |
154 | Procedure1<? super IHiddenRegionFormatter> init) { | 166 | Procedure1<? super IHiddenRegionFormatter> init) { |
155 | var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject); | 167 | var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject); |
156 | preprendNewLines(doc, region, init); | 168 | preprendNewLines(doc, region, init); |
157 | appendNewLines(doc, region, init); | 169 | appendNewLines(doc, region, init); |
158 | } | 170 | } |
159 | 171 | ||
160 | protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region, | 172 | protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region, |
161 | Procedure1<? super IHiddenRegionFormatter> init) { | 173 | Procedure1<? super IHiddenRegionFormatter> init) { |
162 | if (region == null) { | 174 | if (region == null) { |
163 | return; | 175 | return; |
164 | } | 176 | } |
@@ -174,7 +186,7 @@ public class ProblemFormatter extends AbstractJavaFormatter { | |||
174 | } | 186 | } |
175 | 187 | ||
176 | protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region, | 188 | protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region, |
177 | Procedure1<? super IHiddenRegionFormatter> init) { | 189 | Procedure1<? super IHiddenRegionFormatter> init) { |
178 | if (region == null) { | 190 | if (region == null) { |
179 | return; | 191 | return; |
180 | } | 192 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java index b9cafbc2..3432b2d8 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java | |||
@@ -21,8 +21,7 @@ public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemant | |||
21 | 21 | ||
22 | @Override | 22 | @Override |
23 | protected void sequence_Assertion(ISerializationContext context, Assertion semanticObject) { | 23 | protected void sequence_Assertion(ISerializationContext context, Assertion semanticObject) { |
24 | if (semanticObject.isDefault() || | 24 | if (!(semanticObject.getValue() instanceof LogicConstant logicConstant) || |
25 | !(semanticObject.getValue() instanceof LogicConstant logicConstant) || | ||
26 | logicConstant.getLogicValue() == LogicValue.ERROR) { | 25 | logicConstant.getLogicValue() == LogicValue.ERROR) { |
27 | super.sequence_Assertion(context, semanticObject); | 26 | super.sequence_Assertion(context, semanticObject); |
28 | return; | 27 | return; |
@@ -39,6 +38,9 @@ public class PreferShortAssertionsProblemSemanticSequencer extends ProblemSemant | |||
39 | } | 38 | } |
40 | var feeder = createSequencerFeeder(context, semanticObject); | 39 | var feeder = createSequencerFeeder(context, semanticObject); |
41 | var access = grammarAccess.getAssertionAccess(); | 40 | var access = grammarAccess.getAssertionAccess(); |
41 | if (semanticObject.isDefault()) { | ||
42 | feeder.accept(access.getDefaultDefaultKeyword_0_0()); | ||
43 | } | ||
42 | feeder.accept(access.getValueShortLogicConstantParserRuleCall_1_1_0_0(), logicConstant); | 44 | feeder.accept(access.getValueShortLogicConstantParserRuleCall_1_1_0_0(), logicConstant); |
43 | feeder.accept(access.getRelationRelationQualifiedNameParserRuleCall_1_1_1_0_1(), semanticObject.getRelation()); | 45 | feeder.accept(access.getRelationRelationQualifiedNameParserRuleCall_1_1_1_0_1(), semanticObject.getRelation()); |
44 | var iterator = semanticObject.getArguments().iterator(); | 46 | var iterator = semanticObject.getArguments().iterator(); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java new file mode 100644 index 00000000..42950e61 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/ProblemCrossReferenceSerializer.java | |||
@@ -0,0 +1,167 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2008, 2018 itemis AG (http://www.itemis.eu) and others. | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-2.0. | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.language.serializer; | ||
10 | |||
11 | import com.google.common.collect.Lists; | ||
12 | import com.google.inject.Inject; | ||
13 | import org.eclipse.emf.common.util.URI; | ||
14 | import org.eclipse.emf.ecore.EObject; | ||
15 | import org.eclipse.emf.ecore.EReference; | ||
16 | import org.eclipse.xtext.CrossReference; | ||
17 | import org.eclipse.xtext.EcoreUtil2; | ||
18 | import org.eclipse.xtext.GrammarUtil; | ||
19 | import org.eclipse.xtext.IGrammarAccess; | ||
20 | import org.eclipse.xtext.conversion.IValueConverterService; | ||
21 | import org.eclipse.xtext.conversion.ValueConverterException; | ||
22 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
23 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
24 | import org.eclipse.xtext.naming.QualifiedName; | ||
25 | import org.eclipse.xtext.nodemodel.INode; | ||
26 | import org.eclipse.xtext.scoping.IScope; | ||
27 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
28 | import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic; | ||
29 | import org.eclipse.xtext.serializer.diagnostic.SerializationDiagnostic; | ||
30 | import org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer; | ||
31 | import org.eclipse.xtext.serializer.tokens.SerializerScopeProviderBinding; | ||
32 | import org.eclipse.xtext.util.EmfFormatter; | ||
33 | |||
34 | import java.util.List; | ||
35 | |||
36 | public class ProblemCrossReferenceSerializer extends CrossReferenceSerializer { | ||
37 | public static final String AMBIGUOUS_REFERENCE_DIAGNOSTIC = "tools.refinery.language.serializer" + | ||
38 | ".ProblemCrossReferenceSerializer.AMBIGUOUS_REFERENCE"; | ||
39 | |||
40 | @Inject | ||
41 | private LinkingHelper linkingHelper; | ||
42 | |||
43 | @Inject | ||
44 | private IQualifiedNameConverter qualifiedNameConverter; | ||
45 | |||
46 | @Inject | ||
47 | @SerializerScopeProviderBinding | ||
48 | private IScopeProvider scopeProvider; | ||
49 | |||
50 | @Inject | ||
51 | private IValueConverterService valueConverter; | ||
52 | |||
53 | @Inject | ||
54 | private IGrammarAccess grammarAccess; | ||
55 | |||
56 | @Override | ||
57 | public String serializeCrossRef(EObject semanticObject, CrossReference crossref, EObject target, INode node, | ||
58 | ISerializationDiagnostic.Acceptor errors) { | ||
59 | if ((target == null || target.eIsProxy()) && node != null) { | ||
60 | return tokenUtil.serializeNode(node); | ||
61 | } | ||
62 | |||
63 | final EReference ref = GrammarUtil.getReference(crossref, semanticObject.eClass()); | ||
64 | final IScope scope = scopeProvider.getScope(semanticObject, ref); | ||
65 | if (scope == null) { | ||
66 | if (errors != null) { | ||
67 | errors.accept(diagnostics.getNoScopeFoundDiagnostic(semanticObject, crossref, target)); | ||
68 | } | ||
69 | return null; | ||
70 | } | ||
71 | |||
72 | if (target != null && target.eIsProxy()) { | ||
73 | target = handleProxy(target, semanticObject, ref); | ||
74 | } | ||
75 | |||
76 | if (target != null && node != null) { | ||
77 | String text = linkingHelper.getCrossRefNodeAsString(node, true); | ||
78 | QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(text); | ||
79 | URI targetUri = EcoreUtil2.getPlatformResourceOrNormalizedURI(target); | ||
80 | if (isUniqueInScope(scope, qualifiedName, targetUri)) { | ||
81 | return tokenUtil.serializeNode(node); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | return getCrossReferenceNameFromScope(semanticObject, crossref, target, scope, errors); | ||
86 | } | ||
87 | |||
88 | private boolean isUniqueInScope(IScope scope, QualifiedName qualifiedName, URI targetUri) { | ||
89 | var iterator = scope.getElements(qualifiedName).iterator(); | ||
90 | if (!iterator.hasNext()) { | ||
91 | return false; | ||
92 | } | ||
93 | var description = iterator.next(); | ||
94 | return targetUri.equals(description.getEObjectURI()) && !iterator.hasNext(); | ||
95 | } | ||
96 | |||
97 | @Override | ||
98 | protected String getCrossReferenceNameFromScope(EObject semanticObject, CrossReference crossref, EObject target, | ||
99 | IScope scope, ISerializationDiagnostic.Acceptor errors) { | ||
100 | var ruleName = linkingHelper.getRuleNameFrom(crossref); | ||
101 | var targetUri = EcoreUtil2.getPlatformResourceOrNormalizedURI(target); | ||
102 | FoundName foundName = FoundName.NONE; | ||
103 | int shortestNameLength = Integer.MAX_VALUE; | ||
104 | String shortestName = null; | ||
105 | List<ISerializationDiagnostic> recordedErrors = null; | ||
106 | for (var description : scope.getElements(target)) { | ||
107 | var qualifiedName = description.getName(); | ||
108 | var segmentCount = qualifiedName.getSegmentCount(); | ||
109 | if (shortestName != null && segmentCount >= shortestNameLength) { | ||
110 | continue; | ||
111 | } | ||
112 | if (isUniqueInScope(scope, qualifiedName, targetUri)) { | ||
113 | var unconverted = qualifiedNameConverter.toString(qualifiedName); | ||
114 | try { | ||
115 | shortestName = valueConverter.toString(unconverted, ruleName); | ||
116 | shortestNameLength = segmentCount; | ||
117 | foundName = FoundName.VALID; | ||
118 | } catch (ValueConverterException e) { | ||
119 | if (recordedErrors == null) { | ||
120 | recordedErrors = Lists.newArrayList(); | ||
121 | } | ||
122 | recordedErrors.add(diagnostics.getValueConversionExceptionDiagnostic(semanticObject, crossref, | ||
123 | unconverted, e)); | ||
124 | } | ||
125 | } else if (foundName == FoundName.NONE) { | ||
126 | foundName = FoundName.AMBIGUOUS; | ||
127 | } | ||
128 | } | ||
129 | handleErrors(semanticObject, crossref, target, scope, errors, recordedErrors, foundName); | ||
130 | return shortestName; | ||
131 | } | ||
132 | |||
133 | private void handleErrors( | ||
134 | EObject semanticObject, CrossReference crossref, EObject target, IScope scope, | ||
135 | ISerializationDiagnostic.Acceptor errors, List<ISerializationDiagnostic> recordedErrors, | ||
136 | FoundName foundName) { | ||
137 | if (errors == null) { | ||
138 | return; | ||
139 | } | ||
140 | if (recordedErrors != null) { | ||
141 | recordedErrors.forEach(errors::accept); | ||
142 | } | ||
143 | if (foundName == FoundName.NONE) { | ||
144 | errors.accept(diagnostics.getNoEObjectDescriptionFoundDiagnostic(semanticObject, crossref, target, | ||
145 | scope)); | ||
146 | } else if (foundName == FoundName.AMBIGUOUS) { | ||
147 | // Computation of reference name copied from | ||
148 | // {@link org.eclipse.xtext.serializer.diagnostic.TokenDiagnosticProvider#getFullReferenceName}. | ||
149 | var ref = GrammarUtil.getReference(crossref); | ||
150 | var clazz = semanticObject.eClass().getName(); | ||
151 | if (ref.getEContainingClass() != semanticObject.eClass()) { | ||
152 | clazz = ref.getEContainingClass().getName() + "(" + clazz + ")"; | ||
153 | } | ||
154 | var message = "No unambiguous name could be found in scope %s.%s for %s" | ||
155 | .formatted(clazz, ref.getName(), EmfFormatter.objPath(target)); | ||
156 | var diagnostic = new SerializationDiagnostic(AMBIGUOUS_REFERENCE_DIAGNOSTIC, semanticObject, crossref, | ||
157 | grammarAccess.getGrammar(), message); | ||
158 | errors.accept(diagnostic); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | private enum FoundName { | ||
163 | NONE, | ||
164 | AMBIGUOUS, | ||
165 | VALID | ||
166 | } | ||
167 | } | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java index 9f4f2bbf..65675b6b 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java | |||
@@ -74,8 +74,8 @@ class ProblemSerializerTest { | |||
74 | } | 74 | } |
75 | 75 | ||
76 | @ParameterizedTest | 76 | @ParameterizedTest |
77 | @MethodSource | 77 | @MethodSource("assertionTest") |
78 | void defaultAssertionTest(LogicValue value, String valueAsString) { | 78 | void defaultAssertionTest(LogicValue value, String serializedAssertion) { |
79 | var pred = createPred(); | 79 | var pred = createPred(); |
80 | var node = ProblemFactory.eINSTANCE.createNode(); | 80 | var node = ProblemFactory.eINSTANCE.createNode(); |
81 | node.setName("a"); | 81 | node.setName("a"); |
@@ -88,12 +88,7 @@ class ProblemSerializerTest { | |||
88 | pred foo(node p). | 88 | pred foo(node p). |
89 | 89 | ||
90 | indiv a. | 90 | indiv a. |
91 | default foo(a):\040""" + valueAsString + ".\n"); | 91 | default\040""" + serializedAssertion + "\n"); |
92 | } | ||
93 | |||
94 | static Stream<Arguments> defaultAssertionTest() { | ||
95 | return Stream.of(Arguments.of(LogicValue.TRUE, "true"), Arguments.of(LogicValue.FALSE, "false"), | ||
96 | Arguments.of(LogicValue.UNKNOWN, "unknown"), Arguments.of(LogicValue.ERROR, "error")); | ||
97 | } | 92 | } |
98 | 93 | ||
99 | @Test | 94 | @Test |
@@ -161,6 +156,7 @@ class ProblemSerializerTest { | |||
161 | problem.getStatements().add(assertion); | 156 | problem.getStatements().add(assertion); |
162 | } | 157 | } |
163 | 158 | ||
159 | |||
164 | @Test | 160 | @Test |
165 | void implicitVariableTest() { | 161 | void implicitVariableTest() { |
166 | var pred = ProblemFactory.eINSTANCE.createPredicateDefinition(); | 162 | var pred = ProblemFactory.eINSTANCE.createPredicateDefinition(); |
@@ -232,20 +228,77 @@ class ProblemSerializerTest { | |||
232 | """); | 228 | """); |
233 | } | 229 | } |
234 | 230 | ||
231 | @Test | ||
232 | void unambiguousNameTest() { | ||
233 | createClassAndAssertion("Foo", "foo"); | ||
234 | |||
235 | assertSerializedResult(""" | ||
236 | class Foo { | ||
237 | Foo ref | ||
238 | } | ||
239 | |||
240 | ref(foo, foo). | ||
241 | """); | ||
242 | } | ||
243 | |||
244 | @Test | ||
245 | void ambiguousNameTest() { | ||
246 | createClassAndAssertion("Foo", "foo"); | ||
247 | createClassAndAssertion("Bar", "bar"); | ||
248 | |||
249 | assertSerializedResult(""" | ||
250 | class Foo { | ||
251 | Foo ref | ||
252 | } | ||
253 | |||
254 | Foo::ref(foo, foo). | ||
255 | |||
256 | class Bar { | ||
257 | Bar ref | ||
258 | } | ||
259 | |||
260 | Bar::ref(bar, bar). | ||
261 | """); | ||
262 | } | ||
263 | |||
264 | private void createClassAndAssertion(String className, String nodeName) { | ||
265 | var classDeclaration = ProblemFactory.eINSTANCE.createClassDeclaration(); | ||
266 | classDeclaration.setName(className); | ||
267 | var referenceDeclaration = ProblemFactory.eINSTANCE.createReferenceDeclaration(); | ||
268 | referenceDeclaration.setReferenceType(classDeclaration); | ||
269 | referenceDeclaration.setName("ref"); | ||
270 | classDeclaration.getFeatureDeclarations().add(referenceDeclaration); | ||
271 | problem.getStatements().add(classDeclaration); | ||
272 | var node = ProblemFactory.eINSTANCE.createNode(); | ||
273 | node.setName(nodeName); | ||
274 | problem.getNodes().add(node); | ||
275 | createBinaryAssertion(referenceDeclaration, node, node); | ||
276 | } | ||
277 | |||
278 | private void createBinaryAssertion(Relation relation, Node from, Node to) { | ||
279 | var assertion = ProblemFactory.eINSTANCE.createAssertion(); | ||
280 | assertion.setRelation(relation); | ||
281 | var fromArgument = ProblemFactory.eINSTANCE.createNodeAssertionArgument(); | ||
282 | fromArgument.setNode(from); | ||
283 | assertion.getArguments().add(fromArgument); | ||
284 | var toArgument = ProblemFactory.eINSTANCE.createNodeAssertionArgument(); | ||
285 | toArgument.setNode(to); | ||
286 | assertion.getArguments().add(toArgument); | ||
287 | var value = ProblemFactory.eINSTANCE.createLogicConstant(); | ||
288 | value.setLogicValue(LogicValue.TRUE); | ||
289 | assertion.setValue(value); | ||
290 | problem.getStatements().add(assertion); | ||
291 | } | ||
292 | |||
235 | private void assertSerializedResult(String expected) { | 293 | private void assertSerializedResult(String expected) { |
236 | var outputStream = new ByteArrayOutputStream(); | 294 | String problemString; |
237 | try { | 295 | try (var outputStream = new ByteArrayOutputStream()) { |
238 | resource.save(outputStream, Map.of()); | 296 | resource.save(outputStream, Map.of()); |
297 | problemString = outputStream.toString(); | ||
239 | } catch (IOException e) { | 298 | } catch (IOException e) { |
240 | throw new AssertionError("Failed to serialize problem", e); | 299 | throw new AssertionError("Failed to serialize problem", e); |
241 | } finally { | ||
242 | try { | ||
243 | outputStream.close(); | ||
244 | } catch (IOException e) { | ||
245 | // Nothing to handle in a test. | ||
246 | } | ||
247 | } | 300 | } |
248 | var problemString = outputStream.toString(); | 301 | // Nothing to handle in a test. |
249 | 302 | ||
250 | assertThat(problemString.replace("\r\n", "\n"), equalTo(expected.replace("\r\n", "\n"))); | 303 | assertThat(problemString.replace("\r\n", "\n"), equalTo(expected.replace("\r\n", "\n"))); |
251 | } | 304 | } |
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java index ce3efb21..22f38ca9 100644 --- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java +++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstExplorer.java | |||
@@ -10,10 +10,10 @@ import tools.refinery.store.model.Model; | |||
10 | import java.util.Random; | 10 | import java.util.Random; |
11 | 11 | ||
12 | public class BestFirstExplorer extends BestFirstWorker { | 12 | public class BestFirstExplorer extends BestFirstWorker { |
13 | final int id; | 13 | final long id; |
14 | Random random; | 14 | Random random; |
15 | 15 | ||
16 | public BestFirstExplorer(BestFirstStoreManager storeManager, Model model, int id) { | 16 | public BestFirstExplorer(BestFirstStoreManager storeManager, Model model, long id) { |
17 | super(storeManager, model); | 17 | super(storeManager, model); |
18 | this.id = id; | 18 | this.id = id; |
19 | // The use of a non-cryptographic random generator is safe here, because we only use it to direct the state | 19 | // The use of a non-cryptographic random generator is safe here, because we only use it to direct the state |
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java index 3d32f84c..c20ab9a0 100644 --- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java +++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java | |||
@@ -79,7 +79,7 @@ public class BestFirstStoreManager { | |||
79 | startExploration(initial, 1); | 79 | startExploration(initial, 1); |
80 | } | 80 | } |
81 | 81 | ||
82 | public void startExploration(Version initial, int randomSeed) { | 82 | public void startExploration(Version initial, long randomSeed) { |
83 | BestFirstExplorer bestFirstExplorer = new BestFirstExplorer(this, modelStore.createModelForState(initial), | 83 | BestFirstExplorer bestFirstExplorer = new BestFirstExplorer(this, modelStore.createModelForState(initial), |
84 | randomSeed); | 84 | randomSeed); |
85 | bestFirstExplorer.explore(); | 85 | bestFirstExplorer.explore(); |
diff --git a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java index a2c56a6b..5ee97ce1 100644 --- a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java +++ b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/PredicateScopeTest.java | |||
@@ -27,7 +27,6 @@ import tools.refinery.store.reasoning.translator.containment.ContainmentHierarch | |||
27 | import tools.refinery.store.reasoning.translator.metamodel.Metamodel; | 27 | import tools.refinery.store.reasoning.translator.metamodel.Metamodel; |
28 | import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator; | 28 | import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator; |
29 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | 29 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; |
30 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
31 | import tools.refinery.store.representation.TruthValue; | 30 | import tools.refinery.store.representation.TruthValue; |
32 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | 31 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; |
33 | import tools.refinery.store.statecoding.StateCoderAdapter; | 32 | import tools.refinery.store.statecoding.StateCoderAdapter; |
@@ -109,10 +108,16 @@ class PredicateScopeTest { | |||
109 | private ModelStore createStore() { | 108 | private ModelStore createStore() { |
110 | var metamodel = Metamodel.builder() | 109 | var metamodel = Metamodel.builder() |
111 | .type(index) | 110 | .type(index) |
112 | .reference(next, index, false, | 111 | .reference(next, builder -> builder |
113 | ConstrainedMultiplicity.of(CardinalityIntervals.LONE, nextInvalidMultiplicity), index, prev) | 112 | .source(index) |
114 | .reference(prev, index, false, | 113 | .target(index) |
115 | ConstrainedMultiplicity.of(CardinalityIntervals.LONE, prevInvalidMultiplicity), index, next) | 114 | .multiplicity(CardinalityIntervals.LONE, nextInvalidMultiplicity) |
115 | .opposite(prev)) | ||
116 | .reference(prev, builder -> builder | ||
117 | .source(index) | ||
118 | .target(index) | ||
119 | .multiplicity(CardinalityIntervals.LONE, prevInvalidMultiplicity) | ||
120 | .opposite(next)) | ||
116 | .build(); | 121 | .build(); |
117 | return ModelStore.builder() | 122 | return ModelStore.builder() |
118 | .with(QueryInterpreterAdapter.builder()) | 123 | .with(QueryInterpreterAdapter.builder()) |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java index 8b1c3bb3..3b78db02 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java | |||
@@ -12,19 +12,19 @@ import tools.refinery.store.tuple.Tuple; | |||
12 | import java.util.Map; | 12 | import java.util.Map; |
13 | import java.util.Objects; | 13 | import java.util.Objects; |
14 | 14 | ||
15 | record MapBasedSeed<T>(int arity, Class<T> valueType, T reducedValue, Map<Tuple, T> map) implements Seed<T> { | 15 | record MapBasedSeed<T>(int arity, Class<T> valueType, T majorityValue, Map<Tuple, T> map) implements Seed<T> { |
16 | @Override | 16 | @Override |
17 | public T get(Tuple key) { | 17 | public T get(Tuple key) { |
18 | var value = map.get(key); | 18 | var value = map.get(key); |
19 | return value == null ? reducedValue : value; | 19 | return value == null ? majorityValue : value; |
20 | } | 20 | } |
21 | 21 | ||
22 | @Override | 22 | @Override |
23 | public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) { | 23 | public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) { |
24 | if (Objects.equals(defaultValue, reducedValue)) { | 24 | if (Objects.equals(defaultValue, majorityValue)) { |
25 | return Cursors.of(map); | 25 | return Cursors.of(map); |
26 | } | 26 | } |
27 | return new CartesianProductCursor<>(arity, nodeCount, reducedValue, defaultValue, map); | 27 | return new CartesianProductCursor<>(arity, nodeCount, majorityValue, defaultValue, map); |
28 | } | 28 | } |
29 | 29 | ||
30 | private static class CartesianProductCursor<T> implements Cursor<Tuple, T> { | 30 | private static class CartesianProductCursor<T> implements Cursor<Tuple, T> { |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java index 732efbcc..d9bad866 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java | |||
@@ -19,7 +19,7 @@ public interface Seed<T> { | |||
19 | 19 | ||
20 | Class<T> valueType(); | 20 | Class<T> valueType(); |
21 | 21 | ||
22 | T reducedValue(); | 22 | T majorityValue(); |
23 | 23 | ||
24 | T get(Tuple key); | 24 | T get(Tuple key); |
25 | 25 | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java index d8c6a5ea..7241b032 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java | |||
@@ -7,9 +7,11 @@ package tools.refinery.store.reasoning.translator.crossreference; | |||
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
10 | import tools.refinery.store.representation.TruthValue; | ||
10 | 11 | ||
11 | public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity, | 12 | public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity, |
12 | PartialRelation targetType, Multiplicity targetMultiplicity) { | 13 | PartialRelation targetType, Multiplicity targetMultiplicity, |
14 | TruthValue defaultValue) { | ||
13 | public boolean isConstrained() { | 15 | public boolean isConstrained() { |
14 | return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained(); | 16 | return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained(); |
15 | } | 17 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java new file mode 100644 index 00000000..9347e91e --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInitializer.java | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
10 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
11 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
12 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
13 | import tools.refinery.store.representation.Symbol; | ||
14 | import tools.refinery.store.representation.TruthValue; | ||
15 | import tools.refinery.store.tuple.Tuple; | ||
16 | |||
17 | class DirectedCrossReferenceInitializer implements PartialModelInitializer { | ||
18 | private final PartialRelation linkType; | ||
19 | private final PartialRelation sourceType; | ||
20 | private final PartialRelation targetType; | ||
21 | private final Symbol<TruthValue> symbol; | ||
22 | |||
23 | public DirectedCrossReferenceInitializer(PartialRelation linkType, PartialRelation sourceType, | ||
24 | PartialRelation targetType, Symbol<TruthValue> symbol) { | ||
25 | this.linkType = linkType; | ||
26 | this.sourceType = sourceType; | ||
27 | this.targetType = targetType; | ||
28 | this.symbol = symbol; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public void initialize(Model model, ModelSeed modelSeed) { | ||
33 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
34 | var sourceRefiner = reasoningAdapter.getRefiner(sourceType); | ||
35 | var targetRefiner = reasoningAdapter.getRefiner(targetType); | ||
36 | var interpretation = model.getInterpretation(symbol); | ||
37 | var cursor = modelSeed.getCursor(linkType, symbol.defaultValue()); | ||
38 | while (cursor.move()) { | ||
39 | var key = cursor.getKey(); | ||
40 | var value = cursor.getValue(); | ||
41 | interpretation.put(key, value); | ||
42 | if (value.must()) { | ||
43 | boolean merged = sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) && | ||
44 | targetRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE); | ||
45 | if (!merged) { | ||
46 | throw new IllegalArgumentException("Failed to merge end types of reference %s for key %s" | ||
47 | .formatted(linkType, key)); | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java index 9028337c..fc7477f1 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.crossreference; | 6 | package tools.refinery.store.reasoning.translator.crossreference; |
7 | 7 | ||
8 | import tools.refinery.store.dse.propagation.PropagationBuilder; | ||
8 | import tools.refinery.store.dse.transition.Rule; | 9 | import tools.refinery.store.dse.transition.Rule; |
9 | import tools.refinery.store.model.ModelStoreBuilder; | 10 | import tools.refinery.store.model.ModelStoreBuilder; |
10 | import tools.refinery.store.model.ModelStoreConfiguration; | 11 | import tools.refinery.store.model.ModelStoreConfiguration; |
@@ -14,9 +15,9 @@ import tools.refinery.store.query.view.ForbiddenView; | |||
14 | import tools.refinery.store.reasoning.lifting.DnfLifter; | 15 | import tools.refinery.store.reasoning.lifting.DnfLifter; |
15 | import tools.refinery.store.reasoning.literal.Concreteness; | 16 | import tools.refinery.store.reasoning.literal.Concreteness; |
16 | import tools.refinery.store.reasoning.literal.Modality; | 17 | import tools.refinery.store.reasoning.literal.Modality; |
17 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | ||
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | 18 | import tools.refinery.store.reasoning.representation.PartialRelation; |
19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
20 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
20 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | 21 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; |
21 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 22 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
22 | import tools.refinery.store.representation.Symbol; | 23 | import tools.refinery.store.representation.Symbol; |
@@ -24,6 +25,7 @@ import tools.refinery.store.representation.TruthValue; | |||
24 | 25 | ||
25 | import static tools.refinery.store.query.literal.Literals.not; | 26 | import static tools.refinery.store.query.literal.Literals.not; |
26 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; | 27 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; |
28 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.merge; | ||
27 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; | 29 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; |
28 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; | 30 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; |
29 | 31 | ||
@@ -35,7 +37,7 @@ public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration | |||
35 | public DirectedCrossReferenceTranslator(PartialRelation linkType, DirectedCrossReferenceInfo info) { | 37 | public DirectedCrossReferenceTranslator(PartialRelation linkType, DirectedCrossReferenceInfo info) { |
36 | this.linkType = linkType; | 38 | this.linkType = linkType; |
37 | this.info = info; | 39 | this.info = info; |
38 | symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN); | 40 | symbol = Symbol.of(linkType.name(), 2, TruthValue.class, info.defaultValue()); |
39 | } | 41 | } |
40 | 42 | ||
41 | @Override | 43 | @Override |
@@ -45,41 +47,77 @@ public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration | |||
45 | var targetType = info.targetType(); | 47 | var targetType = info.targetType(); |
46 | var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); | 48 | var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); |
47 | var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); | 49 | var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); |
48 | var forbiddenView = new ForbiddenView(symbol); | ||
49 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | 50 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); |
50 | 51 | ||
51 | storeBuilder.with(PartialRelationTranslator.of(linkType) | 52 | var defaultValue = info.defaultValue(); |
52 | .symbol(symbol) | 53 | if (defaultValue.must()) { |
53 | .may(Query.of(mayName, (builder, source, target) -> { | 54 | throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s" |
55 | .formatted(defaultValue, linkType)); | ||
56 | } | ||
57 | |||
58 | var translator = PartialRelationTranslator.of(linkType); | ||
59 | translator.symbol(symbol); | ||
60 | if (defaultValue.may()) { | ||
61 | var forbiddenView = new ForbiddenView(symbol); | ||
62 | translator.may(Query.of(mayName, (builder, source, target) -> { | ||
63 | builder.clause( | ||
64 | mayNewSource.call(source), | ||
65 | mayNewTarget.call(target), | ||
66 | not(forbiddenView.call(source, target)) | ||
67 | ); | ||
68 | if (info.isConstrained()) { | ||
69 | // Violation of monotonicity: | ||
70 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
71 | // corresponding error pattern will already mark the node as invalid. | ||
72 | builder.clause( | ||
73 | must(linkType.call(source, target)), | ||
74 | not(forbiddenView.call(source, target)), | ||
75 | may(sourceType.call(source)), | ||
76 | may(targetType.call(target)) | ||
77 | ); | ||
78 | } | ||
79 | })); | ||
80 | } else { | ||
81 | var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class); | ||
82 | propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> { | ||
83 | builder.clause( | ||
84 | may(linkType.call(p1, p2)), | ||
85 | not(may(sourceType.call(p1))) | ||
86 | ); | ||
87 | builder.clause( | ||
88 | may(linkType.call(p1, p2)), | ||
89 | not(may(targetType.call(p2))) | ||
90 | ); | ||
91 | if (info.isConstrained()) { | ||
92 | builder.clause( | ||
93 | may(linkType.call(p1, p2)), | ||
94 | not(must(linkType.call(p1, p2))), | ||
95 | not(mayNewSource.call(p1)) | ||
96 | ); | ||
54 | builder.clause( | 97 | builder.clause( |
55 | mayNewSource.call(source), | 98 | may(linkType.call(p1, p2)), |
56 | mayNewTarget.call(target), | 99 | not(must(linkType.call(p1, p2))), |
57 | not(forbiddenView.call(source, target)) | 100 | not(mayNewTarget.call(p2)) |
58 | ); | 101 | ); |
59 | if (info.isConstrained()) { | 102 | } |
60 | // Violation of monotonicity: | 103 | builder.action( |
61 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | 104 | merge(linkType, TruthValue.FALSE, p1, p2) |
62 | // corresponding error pattern will already mark the node as invalid. | 105 | ); |
63 | builder.clause( | 106 | })); |
64 | must(linkType.call(source, target)), | 107 | } |
65 | not(forbiddenView.call(source, target)), | 108 | translator.refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType)); |
66 | may(sourceType.call(source)), | 109 | translator.initializer(new DirectedCrossReferenceInitializer(linkType, sourceType, targetType, symbol)); |
67 | may(targetType.call(target)) | 110 | translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder |
68 | ); | 111 | .clause( |
69 | } | 112 | may(linkType.call(source, target)), |
70 | })) | 113 | not(candidateMust(linkType.call(source, target))), |
71 | .refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType)) | 114 | not(MULTI_VIEW.call(source)), |
72 | .initializer(new RefinementBasedInitializer<>(linkType)) | 115 | not(MULTI_VIEW.call(target)) |
73 | .decision(Rule.of(linkType.name(), (builder, source, target) -> builder | 116 | ) |
74 | .clause( | 117 | .action( |
75 | may(linkType.call(source, target)), | 118 | add(linkType, source, target) |
76 | not(candidateMust(linkType.call(source, target))), | 119 | ))); |
77 | not(MULTI_VIEW.call(source)), | 120 | storeBuilder.with(translator); |
78 | not(MULTI_VIEW.call(target)) | ||
79 | ) | ||
80 | .action( | ||
81 | add(linkType, source, target) | ||
82 | )))); | ||
83 | 121 | ||
84 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false, | 122 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false, |
85 | info.sourceMultiplicity())); | 123 | info.sourceMultiplicity())); |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java index fe99bc54..34e9032a 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java | |||
@@ -7,8 +7,9 @@ package tools.refinery.store.reasoning.translator.crossreference; | |||
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
10 | import tools.refinery.store.representation.TruthValue; | ||
10 | 11 | ||
11 | public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity) { | 12 | public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity, TruthValue defaultValue) { |
12 | public boolean isConstrained() { | 13 | public boolean isConstrained() { |
13 | return multiplicity.isConstrained(); | 14 | return multiplicity.isConstrained(); |
14 | } | 15 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java new file mode 100644 index 00000000..77339aa0 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInitializer.java | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
11 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
12 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
13 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
14 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
15 | import tools.refinery.store.representation.Symbol; | ||
16 | import tools.refinery.store.representation.TruthValue; | ||
17 | import tools.refinery.store.tuple.Tuple; | ||
18 | |||
19 | import java.util.LinkedHashMap; | ||
20 | |||
21 | class UndirectedCrossReferenceInitializer implements PartialModelInitializer { | ||
22 | private final PartialRelation linkType; | ||
23 | private final PartialRelation sourceType; | ||
24 | private final Symbol<TruthValue> symbol; | ||
25 | |||
26 | UndirectedCrossReferenceInitializer(PartialRelation linkType, PartialRelation sourceType, | ||
27 | Symbol<TruthValue> symbol) { | ||
28 | this.linkType = linkType; | ||
29 | this.sourceType = sourceType; | ||
30 | this.symbol = symbol; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public void initialize(Model model, ModelSeed modelSeed) { | ||
35 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
36 | var mergedMap = getMergedMap(modelSeed); | ||
37 | var sourceRefiner = reasoningAdapter.getRefiner(sourceType); | ||
38 | var interpretation = model.getInterpretation(symbol); | ||
39 | for (var entry : mergedMap.entrySet()) { | ||
40 | var key = entry.getKey(); | ||
41 | var value = entry.getValue(); | ||
42 | interpretation.put(key, value); | ||
43 | if (value.must()) { | ||
44 | boolean merged = sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) && | ||
45 | sourceRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE); | ||
46 | if (!merged) { | ||
47 | throw new IllegalArgumentException("Failed to merge end types of reference %s for key %s" | ||
48 | .formatted(linkType, key)); | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
54 | @NotNull | ||
55 | private LinkedHashMap<Tuple, TruthValue> getMergedMap(ModelSeed modelSeed) { | ||
56 | var defaultValue = symbol.defaultValue(); | ||
57 | var originalMap = new LinkedHashMap<Tuple, TruthValue>(); | ||
58 | var cursor = modelSeed.getCursor(linkType, defaultValue); | ||
59 | while (cursor.move()) { | ||
60 | if (originalMap.put(cursor.getKey(), cursor.getValue()) != null) { | ||
61 | throw new TranslationException(linkType, "Duplicate value for key " + cursor.getKey()); | ||
62 | } | ||
63 | } | ||
64 | var mergedMap = LinkedHashMap.<Tuple, TruthValue>newLinkedHashMap(originalMap.size()); | ||
65 | for (var entry : originalMap.entrySet()) { | ||
66 | var key = entry.getKey(); | ||
67 | var value = entry.getValue(); | ||
68 | int first = key.get(0); | ||
69 | int second = key.get(1); | ||
70 | var oppositeKey = Tuple.of(second, first); | ||
71 | var oppositeValue = originalMap.get(oppositeKey); | ||
72 | if (oppositeValue != null && second < first) { | ||
73 | // Already processed entry. | ||
74 | continue; | ||
75 | } | ||
76 | var mergedValue = value.merge(oppositeValue == null ? defaultValue : oppositeValue); | ||
77 | mergedMap.put(key, mergedValue); | ||
78 | if (first != second) { | ||
79 | mergedMap.put(oppositeKey, mergedValue); | ||
80 | } | ||
81 | } | ||
82 | return mergedMap; | ||
83 | } | ||
84 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java index c554e2a4..b76838b3 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.crossreference; | 6 | package tools.refinery.store.reasoning.translator.crossreference; |
7 | 7 | ||
8 | import tools.refinery.store.dse.propagation.PropagationBuilder; | ||
8 | import tools.refinery.store.dse.transition.Rule; | 9 | import tools.refinery.store.dse.transition.Rule; |
9 | import tools.refinery.store.model.ModelStoreBuilder; | 10 | import tools.refinery.store.model.ModelStoreBuilder; |
10 | import tools.refinery.store.model.ModelStoreConfiguration; | 11 | import tools.refinery.store.model.ModelStoreConfiguration; |
@@ -13,15 +14,16 @@ import tools.refinery.store.query.view.ForbiddenView; | |||
13 | import tools.refinery.store.reasoning.lifting.DnfLifter; | 14 | import tools.refinery.store.reasoning.lifting.DnfLifter; |
14 | import tools.refinery.store.reasoning.literal.Concreteness; | 15 | import tools.refinery.store.reasoning.literal.Concreteness; |
15 | import tools.refinery.store.reasoning.literal.Modality; | 16 | import tools.refinery.store.reasoning.literal.Modality; |
16 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | ||
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | 17 | import tools.refinery.store.reasoning.representation.PartialRelation; |
18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
19 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
19 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | 20 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; |
20 | import tools.refinery.store.representation.Symbol; | 21 | import tools.refinery.store.representation.Symbol; |
21 | import tools.refinery.store.representation.TruthValue; | 22 | import tools.refinery.store.representation.TruthValue; |
22 | 23 | ||
23 | import static tools.refinery.store.query.literal.Literals.not; | 24 | import static tools.refinery.store.query.literal.Literals.not; |
24 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; | 25 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; |
26 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.merge; | ||
25 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; | 27 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; |
26 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; | 28 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; |
27 | 29 | ||
@@ -33,50 +35,77 @@ public class UndirectedCrossReferenceTranslator implements ModelStoreConfigurati | |||
33 | public UndirectedCrossReferenceTranslator(PartialRelation linkType, UndirectedCrossReferenceInfo info) { | 35 | public UndirectedCrossReferenceTranslator(PartialRelation linkType, UndirectedCrossReferenceInfo info) { |
34 | this.linkType = linkType; | 36 | this.linkType = linkType; |
35 | this.info = info; | 37 | this.info = info; |
36 | symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN); | 38 | symbol = Symbol.of(linkType.name(), 2, TruthValue.class, info.defaultValue()); |
37 | } | 39 | } |
38 | 40 | ||
39 | @Override | 41 | @Override |
40 | public void apply(ModelStoreBuilder storeBuilder) { | 42 | public void apply(ModelStoreBuilder storeBuilder) { |
41 | var name = linkType.name(); | 43 | var name = linkType.name(); |
42 | var type = info.type(); | 44 | var type = info.type(); |
43 | var forbiddenView = new ForbiddenView(symbol); | ||
44 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | 45 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); |
45 | 46 | ||
47 | var defaultValue = info.defaultValue(); | ||
48 | if (defaultValue.must()) { | ||
49 | throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s" | ||
50 | .formatted(defaultValue, linkType)); | ||
51 | } | ||
52 | |||
46 | var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false); | 53 | var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false); |
47 | 54 | ||
48 | storeBuilder.with(PartialRelationTranslator.of(linkType) | 55 | var translator = PartialRelationTranslator.of(linkType); |
49 | .symbol(symbol) | 56 | translator.symbol(symbol); |
50 | .may(Query.of(mayName, (builder, source, target) -> { | 57 | if (defaultValue.may()) { |
58 | var forbiddenView = new ForbiddenView(symbol); | ||
59 | translator.may(Query.of(mayName, (builder, source, target) -> { | ||
60 | builder.clause( | ||
61 | mayNewSource.call(source), | ||
62 | mayNewSource.call(target), | ||
63 | not(forbiddenView.call(source, target)) | ||
64 | ); | ||
65 | if (info.isConstrained()) { | ||
66 | // Violation of monotonicity: | ||
67 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
68 | // corresponding error pattern will already mark the node as invalid. | ||
69 | builder.clause( | ||
70 | must(linkType.call(source, target)), | ||
71 | not(forbiddenView.call(source, target)), | ||
72 | may(type.call(source)), | ||
73 | may(type.call(target)) | ||
74 | ); | ||
75 | } | ||
76 | })); | ||
77 | } else { | ||
78 | var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class); | ||
79 | propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> { | ||
80 | builder.clause( | ||
81 | may(linkType.call(p1, p2)), | ||
82 | not(may(type.call(p1))) | ||
83 | ); | ||
84 | if (info.isConstrained()) { | ||
51 | builder.clause( | 85 | builder.clause( |
52 | mayNewSource.call(source), | 86 | may(linkType.call(p1, p2)), |
53 | mayNewSource.call(target), | 87 | not(must(linkType.call(p1, p2))), |
54 | not(forbiddenView.call(source, target)) | 88 | not(mayNewSource.call(p1)) |
55 | ); | 89 | ); |
56 | if (info.isConstrained()) { | 90 | } |
57 | // Violation of monotonicity: | 91 | builder.action( |
58 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | 92 | merge(linkType, TruthValue.FALSE, p1, p2) |
59 | // corresponding error pattern will already mark the node as invalid. | 93 | ); |
60 | builder.clause( | 94 | })); |
61 | must(linkType.call(source, target)), | 95 | } |
62 | not(forbiddenView.call(source, target)), | 96 | translator.refiner(UndirectedCrossReferenceRefiner.of(symbol, type)); |
63 | may(type.call(source)), | 97 | translator.initializer(new UndirectedCrossReferenceInitializer(linkType, type, symbol)); |
64 | may(type.call(target)) | 98 | translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder |
65 | ); | 99 | .clause( |
66 | } | 100 | may(linkType.call(source, target)), |
67 | })) | 101 | not(candidateMust(linkType.call(source, target))), |
68 | .refiner(UndirectedCrossReferenceRefiner.of(symbol, type)) | 102 | not(MULTI_VIEW.call(source)), |
69 | .initializer(new RefinementBasedInitializer<>(linkType)) | 103 | not(MULTI_VIEW.call(target)) |
70 | .decision(Rule.of(linkType.name(), (builder, source, target) -> builder | 104 | ) |
71 | .clause( | 105 | .action( |
72 | may(linkType.call(source, target)), | 106 | add(linkType, source, target) |
73 | not(candidateMust(linkType.call(source, target))), | 107 | ))); |
74 | not(MULTI_VIEW.call(source)), | 108 | storeBuilder.with(translator); |
75 | not(MULTI_VIEW.call(target)) | ||
76 | ) | ||
77 | .action( | ||
78 | add(linkType, source, target) | ||
79 | )))); | ||
80 | 109 | ||
81 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity())); | 110 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity())); |
82 | } | 111 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java index 74022fc6..a5047768 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java | |||
@@ -16,6 +16,7 @@ import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMulti | |||
16 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; | 16 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; |
17 | 17 | ||
18 | import java.util.*; | 18 | import java.util.*; |
19 | import java.util.function.Consumer; | ||
19 | 20 | ||
20 | public class MetamodelBuilder { | 21 | public class MetamodelBuilder { |
21 | private final ContainedTypeHierarchyBuilder typeHierarchyBuilder = new ContainedTypeHierarchyBuilder(); | 22 | private final ContainedTypeHierarchyBuilder typeHierarchyBuilder = new ContainedTypeHierarchyBuilder(); |
@@ -68,6 +69,12 @@ public class MetamodelBuilder { | |||
68 | return this; | 69 | return this; |
69 | } | 70 | } |
70 | 71 | ||
72 | public MetamodelBuilder reference(PartialRelation linkType, Consumer<ReferenceInfoBuilder> callback) { | ||
73 | var builder = ReferenceInfo.builder(); | ||
74 | callback.accept(builder); | ||
75 | return reference(linkType, builder.build()); | ||
76 | } | ||
77 | |||
71 | public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) { | 78 | public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) { |
72 | if (linkType.arity() != 2) { | 79 | if (linkType.arity() != 2) { |
73 | throw new TranslationException(linkType, | 80 | throw new TranslationException(linkType, |
@@ -81,47 +88,6 @@ public class MetamodelBuilder { | |||
81 | return this; | 88 | return this; |
82 | } | 89 | } |
83 | 90 | ||
84 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment, | ||
85 | Multiplicity multiplicity, PartialRelation targetType, | ||
86 | PartialRelation opposite) { | ||
87 | return reference(linkType, new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite)); | ||
88 | } | ||
89 | |||
90 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity, | ||
91 | PartialRelation targetType, PartialRelation opposite) { | ||
92 | return reference(linkType, sourceType, false, multiplicity, targetType, opposite); | ||
93 | } | ||
94 | |||
95 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, | ||
96 | boolean containment, PartialRelation targetType, PartialRelation opposite) { | ||
97 | return reference(linkType, sourceType, containment, UnconstrainedMultiplicity.INSTANCE, targetType, opposite); | ||
98 | } | ||
99 | |||
100 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, PartialRelation targetType, | ||
101 | PartialRelation opposite) { | ||
102 | return reference(linkType, sourceType, UnconstrainedMultiplicity.INSTANCE, targetType, opposite); | ||
103 | } | ||
104 | |||
105 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment, | ||
106 | Multiplicity multiplicity, PartialRelation targetType) { | ||
107 | return reference(linkType, sourceType, containment, multiplicity, targetType, null); | ||
108 | } | ||
109 | |||
110 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity, | ||
111 | PartialRelation targetType) { | ||
112 | return reference(linkType, sourceType, multiplicity, targetType, null); | ||
113 | } | ||
114 | |||
115 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment, | ||
116 | PartialRelation targetType) { | ||
117 | return reference(linkType, sourceType, containment, targetType, null); | ||
118 | } | ||
119 | |||
120 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, | ||
121 | PartialRelation targetType) { | ||
122 | return reference(linkType, sourceType, targetType, null); | ||
123 | } | ||
124 | |||
125 | public MetamodelBuilder references(Collection<Map.Entry<PartialRelation, ReferenceInfo>> entries) { | 91 | public MetamodelBuilder references(Collection<Map.Entry<PartialRelation, ReferenceInfo>> entries) { |
126 | for (var entry : entries) { | 92 | for (var entry : entries) { |
127 | reference(entry.getKey(), entry.getValue()); | 93 | reference(entry.getKey(), entry.getValue()); |
@@ -160,10 +126,12 @@ public class MetamodelBuilder { | |||
160 | var targetType = info.targetType(); | 126 | var targetType = info.targetType(); |
161 | var opposite = info.opposite(); | 127 | var opposite = info.opposite(); |
162 | Multiplicity targetMultiplicity = UnconstrainedMultiplicity.INSTANCE; | 128 | Multiplicity targetMultiplicity = UnconstrainedMultiplicity.INSTANCE; |
129 | var defaultValue = info.defaultValue(); | ||
163 | if (opposite != null) { | 130 | if (opposite != null) { |
164 | var oppositeInfo = referenceInfoMap.get(opposite); | 131 | var oppositeInfo = referenceInfoMap.get(opposite); |
165 | validateOpposite(linkType, info, opposite, oppositeInfo); | 132 | validateOpposite(linkType, info, opposite, oppositeInfo); |
166 | targetMultiplicity = oppositeInfo.multiplicity(); | 133 | targetMultiplicity = oppositeInfo.multiplicity(); |
134 | defaultValue = defaultValue.merge(oppositeInfo.defaultValue()); | ||
167 | if (oppositeInfo.containment()) { | 135 | if (oppositeInfo.containment()) { |
168 | // Skip processing this reference and process it once we encounter its containment opposite. | 136 | // Skip processing this reference and process it once we encounter its containment opposite. |
169 | return; | 137 | return; |
@@ -175,7 +143,7 @@ public class MetamodelBuilder { | |||
175 | targetType, linkType, sourceType)); | 143 | targetType, linkType, sourceType)); |
176 | } | 144 | } |
177 | undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, | 145 | undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, |
178 | info.multiplicity())); | 146 | info.multiplicity(), defaultValue)); |
179 | return; | 147 | return; |
180 | } | 148 | } |
181 | oppositeReferences.put(opposite, linkType); | 149 | oppositeReferences.put(opposite, linkType); |
@@ -185,7 +153,7 @@ public class MetamodelBuilder { | |||
185 | return; | 153 | return; |
186 | } | 154 | } |
187 | directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(), | 155 | directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(), |
188 | targetType, targetMultiplicity)); | 156 | targetType, targetMultiplicity, defaultValue)); |
189 | } | 157 | } |
190 | 158 | ||
191 | private void processContainmentInfo(PartialRelation linkType, ReferenceInfo info, | 159 | private void processContainmentInfo(PartialRelation linkType, ReferenceInfo info, |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java index 9a6b4012..9d7fc9c3 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java | |||
@@ -7,7 +7,11 @@ package tools.refinery.store.reasoning.translator.metamodel; | |||
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
10 | import tools.refinery.store.representation.TruthValue; | ||
10 | 11 | ||
11 | public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity, | 12 | public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity, |
12 | PartialRelation targetType, PartialRelation opposite) { | 13 | PartialRelation targetType, PartialRelation opposite, TruthValue defaultValue) { |
14 | public static ReferenceInfoBuilder builder() { | ||
15 | return new ReferenceInfoBuilder(); | ||
16 | } | ||
13 | } | 17 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java new file mode 100644 index 00000000..43d01503 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | import org.jetbrains.annotations.Nullable; | ||
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
11 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
12 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
13 | import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity; | ||
14 | import tools.refinery.store.representation.TruthValue; | ||
15 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
16 | |||
17 | public final class ReferenceInfoBuilder { | ||
18 | private boolean containment; | ||
19 | private PartialRelation sourceType; | ||
20 | private Multiplicity multiplicity = UnconstrainedMultiplicity.INSTANCE; | ||
21 | private PartialRelation targetType; | ||
22 | private PartialRelation opposite; | ||
23 | private TruthValue defaultValue = TruthValue.UNKNOWN; | ||
24 | |||
25 | ReferenceInfoBuilder() { | ||
26 | } | ||
27 | |||
28 | public ReferenceInfoBuilder source(@NotNull PartialRelation sourceType) { | ||
29 | if (sourceType.arity() != 1) { | ||
30 | throw new IllegalArgumentException("Source type %s must be of arity 1".formatted(sourceType)); | ||
31 | } | ||
32 | this.sourceType = sourceType; | ||
33 | return this; | ||
34 | } | ||
35 | |||
36 | public ReferenceInfoBuilder target(@NotNull PartialRelation targetType) { | ||
37 | if (targetType.arity() != 1) { | ||
38 | throw new IllegalArgumentException("Target type %s must be of arity 1".formatted(targetType)); | ||
39 | } | ||
40 | this.targetType = targetType; | ||
41 | return this; | ||
42 | } | ||
43 | |||
44 | public ReferenceInfoBuilder containment(boolean containment) { | ||
45 | this.containment = containment; | ||
46 | return this; | ||
47 | } | ||
48 | |||
49 | public ReferenceInfoBuilder multiplicity(@NotNull Multiplicity multiplicity) { | ||
50 | this.multiplicity = multiplicity; | ||
51 | return this; | ||
52 | } | ||
53 | |||
54 | public ReferenceInfoBuilder multiplicity(@NotNull CardinalityInterval multiplicityInterval, | ||
55 | @NotNull PartialRelation errorSymbol) { | ||
56 | return multiplicity(ConstrainedMultiplicity.of(multiplicityInterval, errorSymbol)); | ||
57 | } | ||
58 | |||
59 | public ReferenceInfoBuilder opposite(@Nullable PartialRelation opposite) { | ||
60 | if (opposite != null && opposite.arity() != 2) { | ||
61 | throw new IllegalArgumentException("Opposite %s must be of arity 2".formatted(targetType)); | ||
62 | } | ||
63 | this.opposite = opposite; | ||
64 | return this; | ||
65 | } | ||
66 | |||
67 | public ReferenceInfoBuilder defaultValue(@NotNull TruthValue defaultValue) { | ||
68 | if (defaultValue.must()) { | ||
69 | throw new IllegalArgumentException("Unsupported default value"); | ||
70 | } | ||
71 | this.defaultValue = defaultValue; | ||
72 | return this; | ||
73 | } | ||
74 | |||
75 | public ReferenceInfo build() { | ||
76 | if (sourceType == null) { | ||
77 | throw new IllegalStateException("Source type is required"); | ||
78 | } | ||
79 | if (targetType == null) { | ||
80 | throw new IllegalStateException("Target type is required"); | ||
81 | } | ||
82 | return new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite, defaultValue); | ||
83 | } | ||
84 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java index 0f1a1006..ba6ba6da 100644 --- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java | |||
@@ -8,7 +8,6 @@ package tools.refinery.store.reasoning.translator.metamodel; | |||
8 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | 9 | import tools.refinery.store.reasoning.representation.PartialRelation; |
10 | import tools.refinery.store.reasoning.translator.TranslationException; | 10 | import tools.refinery.store.reasoning.translator.TranslationException; |
11 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
12 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | 11 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; |
13 | 12 | ||
14 | import static org.junit.jupiter.api.Assertions.assertThrows; | 13 | import static org.junit.jupiter.api.Assertions.assertThrows; |
@@ -24,8 +23,13 @@ class MetamodelBuilderTest { | |||
24 | var builder = Metamodel.builder() | 23 | var builder = Metamodel.builder() |
25 | .type(university) | 24 | .type(university) |
26 | .type(course) | 25 | .type(course) |
27 | .reference(courses, university, course, location) | 26 | .reference(courses, referenceBuilder -> referenceBuilder |
28 | .reference(location, course, university); | 27 | .source(university) |
28 | .target(course) | ||
29 | .opposite(location)) | ||
30 | .reference(location, referenceBuilder -> referenceBuilder | ||
31 | .source(course) | ||
32 | .target(university)); | ||
29 | 33 | ||
30 | assertThrows(TranslationException.class, builder::build); | 34 | assertThrows(TranslationException.class, builder::build); |
31 | } | 35 | } |
@@ -35,8 +39,14 @@ class MetamodelBuilderTest { | |||
35 | var builder = Metamodel.builder() | 39 | var builder = Metamodel.builder() |
36 | .type(university) | 40 | .type(university) |
37 | .type(course) | 41 | .type(course) |
38 | .reference(courses, university, course, location) | 42 | .reference(courses, referenceBuilder -> referenceBuilder |
39 | .reference(location, course, course, courses); | 43 | .source(university) |
44 | .target(course) | ||
45 | .opposite(location)) | ||
46 | .reference(location, referenceBuilder -> referenceBuilder | ||
47 | .source(course) | ||
48 | .target(course) | ||
49 | .opposite(courses)); | ||
40 | 50 | ||
41 | assertThrows(TranslationException.class, builder::build); | 51 | assertThrows(TranslationException.class, builder::build); |
42 | } | 52 | } |
@@ -48,10 +58,16 @@ class MetamodelBuilderTest { | |||
48 | var builder = Metamodel.builder() | 58 | var builder = Metamodel.builder() |
49 | .type(university) | 59 | .type(university) |
50 | .type(course) | 60 | .type(course) |
51 | .reference(courses, university, true, course, location) | 61 | .reference(courses, referenceBuilder -> referenceBuilder |
52 | .reference(location, course, | 62 | .containment(true) |
53 | ConstrainedMultiplicity.of(CardinalityIntervals.atLeast(2), invalidMultiplicity), | 63 | .source(university) |
54 | university, courses); | 64 | .target(course) |
65 | .opposite(location)) | ||
66 | .reference(location, referenceBuilder -> referenceBuilder | ||
67 | .source(course) | ||
68 | .multiplicity(CardinalityIntervals.atLeast(2), invalidMultiplicity) | ||
69 | .target(university) | ||
70 | .opposite(courses)); | ||
55 | 71 | ||
56 | assertThrows(TranslationException.class, builder::build); | 72 | assertThrows(TranslationException.class, builder::build); |
57 | } | 73 | } |
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java index 6c2f55af..88e6a4d7 100644 --- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java | |||
@@ -16,7 +16,6 @@ import tools.refinery.store.reasoning.representation.PartialRelation; | |||
16 | import tools.refinery.store.reasoning.seed.ModelSeed; | 16 | import tools.refinery.store.reasoning.seed.ModelSeed; |
17 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | 17 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; |
18 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | 18 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; |
19 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
20 | import tools.refinery.store.representation.TruthValue; | 19 | import tools.refinery.store.representation.TruthValue; |
21 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | 20 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; |
22 | import tools.refinery.store.tuple.Tuple; | 21 | import tools.refinery.store.tuple.Tuple; |
@@ -45,12 +44,23 @@ class MetamodelTest { | |||
45 | .type(teacher, person) | 44 | .type(teacher, person) |
46 | .type(university) | 45 | .type(university) |
47 | .type(course) | 46 | .type(course) |
48 | .reference(courses, university, true, course, location) | 47 | .reference(courses, builder -> builder |
49 | .reference(location, course, university, courses) | 48 | .containment(true) |
50 | .reference(lecturer, course, | 49 | .source(university) |
51 | ConstrainedMultiplicity.of(CardinalityIntervals.ONE, invalidLecturerCount), teacher) | 50 | .target(course) |
52 | .reference(enrolledStudents, course, | 51 | .opposite(location)) |
53 | ConstrainedMultiplicity.of(CardinalityIntervals.SOME, invalidStudentCount), student) | 52 | .reference(location, builder -> builder |
53 | .source(course) | ||
54 | .target(university) | ||
55 | .opposite(courses)) | ||
56 | .reference(lecturer, builder -> builder | ||
57 | .source(course) | ||
58 | .multiplicity(CardinalityIntervals.ONE, invalidLecturerCount) | ||
59 | .target(teacher)) | ||
60 | .reference(enrolledStudents, builder -> builder | ||
61 | .source(course) | ||
62 | .multiplicity(CardinalityIntervals.SOME, invalidStudentCount) | ||
63 | .target(student)) | ||
54 | .build(); | 64 | .build(); |
55 | 65 | ||
56 | var seed = ModelSeed.builder(5) | 66 | var seed = ModelSeed.builder(5) |
@@ -105,10 +115,12 @@ class MetamodelTest { | |||
105 | var metamodel = Metamodel.builder() | 115 | var metamodel = Metamodel.builder() |
106 | .type(university) | 116 | .type(university) |
107 | .type(course) | 117 | .type(course) |
108 | .reference(courses, university, true, course) | 118 | .reference(courses, builder -> builder |
119 | .containment(true) | ||
120 | .source(university) | ||
121 | .target(course)) | ||
109 | .build(); | 122 | .build(); |
110 | 123 | ||
111 | |||
112 | var seed = ModelSeed.builder(4) | 124 | var seed = ModelSeed.builder(4) |
113 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | 125 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder |
114 | .reducedValue(CardinalityIntervals.ONE) | 126 | .reducedValue(CardinalityIntervals.ONE) |