diff options
21 files changed, 495 insertions, 107 deletions
diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx index 905fa2ec..aafaad40 100644 --- a/subprojects/frontend/src/editor/EditorArea.tsx +++ b/subprojects/frontend/src/editor/EditorArea.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -38,6 +38,7 @@ export default observer(function EditorArea({ | |||
38 | <EditorTheme | 38 | <EditorTheme |
39 | showLineNumbers={editorStore.showLineNumbers} | 39 | showLineNumbers={editorStore.showLineNumbers} |
40 | showActiveLine={!editorStore.hasSelection} | 40 | showActiveLine={!editorStore.hasSelection} |
41 | colorIdentifiers={editorStore.colorIdentifiers} | ||
41 | ref={editorParentRef} | 42 | ref={editorParentRef} |
42 | /> | 43 | /> |
43 | </Box> | 44 | </Box> |
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx index ca51f975..f4513909 100644 --- a/subprojects/frontend/src/editor/EditorButtons.tsx +++ b/subprojects/frontend/src/editor/EditorButtons.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -8,8 +8,9 @@ import type { Diagnostic } from '@codemirror/lint'; | |||
8 | import CancelIcon from '@mui/icons-material/Cancel'; | 8 | import CancelIcon from '@mui/icons-material/Cancel'; |
9 | import CheckIcon from '@mui/icons-material/Check'; | 9 | import CheckIcon from '@mui/icons-material/Check'; |
10 | import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; | 10 | import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; |
11 | import FormatPaint from '@mui/icons-material/FormatPaint'; | 11 | import FormatPaintIcon from '@mui/icons-material/FormatPaint'; |
12 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; | 12 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; |
13 | import LooksIcon from '@mui/icons-material/Looks'; | ||
13 | import RedoIcon from '@mui/icons-material/Redo'; | 14 | import RedoIcon from '@mui/icons-material/Redo'; |
14 | import SearchIcon from '@mui/icons-material/Search'; | 15 | import SearchIcon from '@mui/icons-material/Search'; |
15 | import UndoIcon from '@mui/icons-material/Undo'; | 16 | import UndoIcon from '@mui/icons-material/Undo'; |
@@ -72,6 +73,15 @@ export default observer(function EditorButtons({ | |||
72 | <FormatListNumberedIcon fontSize="small" /> | 73 | <FormatListNumberedIcon fontSize="small" /> |
73 | </ToggleButton> | 74 | </ToggleButton> |
74 | <ToggleButton | 75 | <ToggleButton |
76 | selected={editorStore?.colorIdentifiers ?? false} | ||
77 | disabled={editorStore === undefined} | ||
78 | onClick={() => editorStore?.toggleColorIdentifiers()} | ||
79 | aria-label="Color identifiers" | ||
80 | value="color-identifiers" | ||
81 | > | ||
82 | <LooksIcon fontSize="small" /> | ||
83 | </ToggleButton> | ||
84 | <ToggleButton | ||
75 | selected={editorStore?.searchPanel?.state ?? false} | 85 | selected={editorStore?.searchPanel?.state ?? false} |
76 | disabled={editorStore === undefined} | 86 | disabled={editorStore === undefined} |
77 | onClick={() => editorStore?.searchPanel?.toggle()} | 87 | onClick={() => editorStore?.searchPanel?.toggle()} |
@@ -104,7 +114,7 @@ export default observer(function EditorButtons({ | |||
104 | aria-label="Automatic format" | 114 | aria-label="Automatic format" |
105 | color="inherit" | 115 | color="inherit" |
106 | > | 116 | > |
107 | <FormatPaint fontSize="small" /> | 117 | <FormatPaintIcon fontSize="small" /> |
108 | </IconButton> | 118 | </IconButton> |
109 | <ConnectButton editorStore={editorStore} /> | 119 | <ConnectButton editorStore={editorStore} /> |
110 | </Stack> | 120 | </Stack> |
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts index 87c4040e..5e7d05e1 100644 --- a/subprojects/frontend/src/editor/EditorStore.ts +++ b/subprojects/frontend/src/editor/EditorStore.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -62,6 +62,8 @@ export default class EditorStore { | |||
62 | 62 | ||
63 | showLineNumbers = false; | 63 | showLineNumbers = false; |
64 | 64 | ||
65 | colorIdentifiers = true; | ||
66 | |||
65 | disposed = false; | 67 | disposed = false; |
66 | 68 | ||
67 | analyzing = false; | 69 | analyzing = false; |
@@ -96,7 +98,7 @@ export default class EditorStore { | |||
96 | })().catch((error) => { | 98 | })().catch((error) => { |
97 | log.error('Failed to load XtextClient', error); | 99 | log.error('Failed to load XtextClient', error); |
98 | }); | 100 | }); |
99 | this.graph = new GraphStore(); | 101 | this.graph = new GraphStore(this); |
100 | makeAutoObservable<EditorStore, 'client'>(this, { | 102 | makeAutoObservable<EditorStore, 'client'>(this, { |
101 | id: false, | 103 | id: false, |
102 | state: observable.ref, | 104 | state: observable.ref, |
@@ -279,6 +281,11 @@ export default class EditorStore { | |||
279 | log.debug('Show line numbers', this.showLineNumbers); | 281 | log.debug('Show line numbers', this.showLineNumbers); |
280 | } | 282 | } |
281 | 283 | ||
284 | toggleColorIdentifiers(): void { | ||
285 | this.colorIdentifiers = !this.colorIdentifiers; | ||
286 | log.debug('Color identifiers', this.colorIdentifiers); | ||
287 | } | ||
288 | |||
282 | get hasSelection(): boolean { | 289 | get hasSelection(): boolean { |
283 | return this.state.selection.ranges.some(({ from, to }) => from !== to); | 290 | return this.state.selection.ranges.some(({ from, to }) => from !== to); |
284 | } | 291 | } |
@@ -324,7 +331,7 @@ export default class EditorStore { | |||
324 | } | 331 | } |
325 | 332 | ||
326 | addGeneratedModel(uuid: string, randomSeed: number): void { | 333 | addGeneratedModel(uuid: string, randomSeed: number): void { |
327 | this.generatedModels.set(uuid, new GeneratedModelStore(randomSeed)); | 334 | this.generatedModels.set(uuid, new GeneratedModelStore(randomSeed, this)); |
328 | this.selectGeneratedModel(uuid); | 335 | this.selectGeneratedModel(uuid); |
329 | } | 336 | } |
330 | 337 | ||
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts index 9f560dfb..1cad4a36 100644 --- a/subprojects/frontend/src/editor/EditorTheme.ts +++ b/subprojects/frontend/src/editor/EditorTheme.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -8,10 +8,35 @@ import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; | |||
8 | import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; | 8 | import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; |
9 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; | 9 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; |
10 | import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; | 10 | import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; |
11 | import { alpha, styled, type CSSObject } from '@mui/material/styles'; | 11 | import { |
12 | alpha, | ||
13 | styled, | ||
14 | type CSSObject, | ||
15 | type Theme, | ||
16 | } from '@mui/material/styles'; | ||
17 | import { range } from 'lodash-es'; | ||
12 | 18 | ||
13 | import svgURL from '../utils/svgURL'; | 19 | import svgURL from '../utils/svgURL'; |
14 | 20 | ||
21 | function createTypeHashStyles( | ||
22 | theme: Theme, | ||
23 | colorIdentifiers: boolean, | ||
24 | ): CSSObject { | ||
25 | if (!colorIdentifiers) { | ||
26 | return {}; | ||
27 | } | ||
28 | const result: CSSObject = {}; | ||
29 | range(theme.palette.highlight.typeHash.length).forEach((i) => { | ||
30 | result[`.tok-problem-typeHash-${i}`] = { | ||
31 | '&, .tok-typeName': { | ||
32 | color: theme.palette.highlight.typeHash[i]?.text, | ||
33 | fontWeight: theme.typography.fontWeightEditorTypeHash, | ||
34 | }, | ||
35 | }; | ||
36 | }); | ||
37 | return result; | ||
38 | } | ||
39 | |||
15 | export default styled('div', { | 40 | export default styled('div', { |
16 | name: 'EditorTheme', | 41 | name: 'EditorTheme', |
17 | shouldForwardProp: (propName) => | 42 | shouldForwardProp: (propName) => |
@@ -19,7 +44,8 @@ export default styled('div', { | |||
19 | })<{ | 44 | })<{ |
20 | showLineNumbers: boolean; | 45 | showLineNumbers: boolean; |
21 | showActiveLine: boolean; | 46 | showActiveLine: boolean; |
22 | }>(({ theme, showLineNumbers, showActiveLine }) => { | 47 | colorIdentifiers: boolean; |
48 | }>(({ theme, showLineNumbers, showActiveLine, colorIdentifiers }) => { | ||
23 | const editorFontStyle: CSSObject = { | 49 | const editorFontStyle: CSSObject = { |
24 | ...theme.typography.editor, | 50 | ...theme.typography.editor, |
25 | fontWeight: theme.typography.fontWeightEditorNormal, | 51 | fontWeight: theme.typography.fontWeightEditorNormal, |
@@ -114,6 +140,11 @@ export default styled('div', { | |||
114 | }, | 140 | }, |
115 | '.tok-problem-error': { | 141 | '.tok-problem-error': { |
116 | '&, & .tok-typeName': { | 142 | '&, & .tok-typeName': { |
143 | color: theme.palette.highlight.comment, | ||
144 | }, | ||
145 | }, | ||
146 | '.tok-invalid': { | ||
147 | '&, & .tok-typeName': { | ||
117 | color: theme.palette.error.main, | 148 | color: theme.palette.error.main, |
118 | }, | 149 | }, |
119 | }, | 150 | }, |
@@ -124,6 +155,7 @@ export default styled('div', { | |||
124 | fontStyle: 'normal', | 155 | fontStyle: 'normal', |
125 | }, | 156 | }, |
126 | }, | 157 | }, |
158 | ...createTypeHashStyles(theme, colorIdentifiers), | ||
127 | }; | 159 | }; |
128 | 160 | ||
129 | const matchingStyle: CSSObject = { | 161 | const matchingStyle: CSSObject = { |
diff --git a/subprojects/frontend/src/editor/GeneratedModelStore.ts b/subprojects/frontend/src/editor/GeneratedModelStore.ts index 5088d603..f2695d9a 100644 --- a/subprojects/frontend/src/editor/GeneratedModelStore.ts +++ b/subprojects/frontend/src/editor/GeneratedModelStore.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -9,6 +9,8 @@ import { makeAutoObservable } from 'mobx'; | |||
9 | import GraphStore from '../graph/GraphStore'; | 9 | import GraphStore from '../graph/GraphStore'; |
10 | import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; | 10 | import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; |
11 | 11 | ||
12 | import type EditorStore from './EditorStore'; | ||
13 | |||
12 | export default class GeneratedModelStore { | 14 | export default class GeneratedModelStore { |
13 | title: string; | 15 | title: string; |
14 | 16 | ||
@@ -18,10 +20,15 @@ export default class GeneratedModelStore { | |||
18 | 20 | ||
19 | graph: GraphStore | undefined; | 21 | graph: GraphStore | undefined; |
20 | 22 | ||
21 | constructor(randomSeed: number) { | 23 | constructor( |
24 | randomSeed: number, | ||
25 | private readonly editorStore: EditorStore, | ||
26 | ) { | ||
22 | const time = new Date().toLocaleTimeString(undefined, { hour12: false }); | 27 | const time = new Date().toLocaleTimeString(undefined, { hour12: false }); |
23 | this.title = `Generated at ${time} (${randomSeed})`; | 28 | this.title = `Generated at ${time} (${randomSeed})`; |
24 | makeAutoObservable(this); | 29 | makeAutoObservable<GeneratedModelStore, 'editorStore'>(this, { |
30 | editorStore: false, | ||
31 | }); | ||
25 | } | 32 | } |
26 | 33 | ||
27 | get running(): boolean { | 34 | get running(): boolean { |
@@ -43,7 +50,7 @@ export default class GeneratedModelStore { | |||
43 | 50 | ||
44 | setSemantics(semantics: SemanticsSuccessResult): void { | 51 | setSemantics(semantics: SemanticsSuccessResult): void { |
45 | if (this.running) { | 52 | if (this.running) { |
46 | this.graph = new GraphStore(); | 53 | this.graph = new GraphStore(this.editorStore); |
47 | this.graph.setSemantics(semantics); | 54 | this.graph.setSemantics(semantics); |
48 | } | 55 | } |
49 | } | 56 | } |
diff --git a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx index eec72a7d..72ac58fa 100644 --- a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx +++ b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -150,7 +150,7 @@ function DotGraphVisualizer({ | |||
150 | ], | 150 | ], |
151 | ); | 151 | ); |
152 | 152 | ||
153 | return <GraphTheme ref={setElement} />; | 153 | return <GraphTheme ref={setElement} colorNodes={graph.colorNodes} />; |
154 | } | 154 | } |
155 | 155 | ||
156 | DotGraphVisualizer.defaultProps = { | 156 | DotGraphVisualizer.defaultProps = { |
diff --git a/subprojects/frontend/src/graph/GraphStore.ts b/subprojects/frontend/src/graph/GraphStore.ts index ecb016b5..58c4422d 100644 --- a/subprojects/frontend/src/graph/GraphStore.ts +++ b/subprojects/frontend/src/graph/GraphStore.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | 6 | ||
7 | import { makeAutoObservable, observable } from 'mobx'; | 7 | import { makeAutoObservable, observable } from 'mobx'; |
8 | 8 | ||
9 | import type EditorStore from '../editor/EditorStore'; | ||
9 | import type { | 10 | import type { |
10 | RelationMetadata, | 11 | RelationMetadata, |
11 | SemanticsSuccessResult, | 12 | SemanticsSuccessResult, |
@@ -65,8 +66,9 @@ export default class GraphStore { | |||
65 | 66 | ||
66 | selectedSymbol: RelationMetadata | undefined; | 67 | selectedSymbol: RelationMetadata | undefined; |
67 | 68 | ||
68 | constructor() { | 69 | constructor(private readonly editorStore: EditorStore) { |
69 | makeAutoObservable(this, { | 70 | makeAutoObservable<GraphStore, 'editorStore'>(this, { |
71 | editorStore: false, | ||
70 | semantics: observable.ref, | 72 | semantics: observable.ref, |
71 | }); | 73 | }); |
72 | } | 74 | } |
@@ -184,4 +186,8 @@ export default class GraphStore { | |||
184 | }); | 186 | }); |
185 | this.setSelectedSymbol(this.selectedSymbol); | 187 | this.setSelectedSymbol(this.selectedSymbol); |
186 | } | 188 | } |
189 | |||
190 | get colorNodes(): boolean { | ||
191 | return this.editorStore.colorIdentifiers; | ||
192 | } | ||
187 | } | 193 | } |
diff --git a/subprojects/frontend/src/graph/GraphTheme.tsx b/subprojects/frontend/src/graph/GraphTheme.tsx index 14d58b96..7334f559 100644 --- a/subprojects/frontend/src/graph/GraphTheme.tsx +++ b/subprojects/frontend/src/graph/GraphTheme.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -7,7 +7,13 @@ | |||
7 | import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; | 7 | import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; |
8 | import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw'; | 8 | import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw'; |
9 | import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; | 9 | import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; |
10 | import { alpha, styled, type CSSObject } from '@mui/material/styles'; | 10 | import { |
11 | alpha, | ||
12 | styled, | ||
13 | type CSSObject, | ||
14 | type Theme, | ||
15 | } from '@mui/material/styles'; | ||
16 | import { range } from 'lodash-es'; | ||
11 | 17 | ||
12 | import svgURL from '../utils/svgURL'; | 18 | import svgURL from '../utils/svgURL'; |
13 | 19 | ||
@@ -31,9 +37,24 @@ function createEdgeColor( | |||
31 | }; | 37 | }; |
32 | } | 38 | } |
33 | 39 | ||
40 | function createTypeHashStyles(theme: Theme, colorNodes: boolean): CSSObject { | ||
41 | if (!colorNodes) { | ||
42 | return {}; | ||
43 | } | ||
44 | const result: CSSObject = {}; | ||
45 | range(theme.palette.highlight.typeHash.length).forEach((i) => { | ||
46 | result[`.node-typeHash-${i}`] = { | ||
47 | '& [fill="green"]': { | ||
48 | fill: theme.palette.highlight.typeHash[i]?.box, | ||
49 | }, | ||
50 | }; | ||
51 | }); | ||
52 | return result; | ||
53 | } | ||
54 | |||
34 | export default styled('div', { | 55 | export default styled('div', { |
35 | name: 'GraphTheme', | 56 | name: 'GraphTheme', |
36 | })(({ theme }) => ({ | 57 | })<{ colorNodes: boolean }>(({ theme, colorNodes }) => ({ |
37 | '& svg': { | 58 | '& svg': { |
38 | userSelect: 'none', | 59 | userSelect: 'none', |
39 | '.node': { | 60 | '.node': { |
@@ -68,6 +89,7 @@ export default styled('div', { | |||
68 | '.node-exists-UNKNOWN [stroke="black"]': { | 89 | '.node-exists-UNKNOWN [stroke="black"]': { |
69 | strokeDasharray: '5 2', | 90 | strokeDasharray: '5 2', |
70 | }, | 91 | }, |
92 | ...createTypeHashStyles(theme, colorNodes), | ||
71 | '.edge': { | 93 | '.edge': { |
72 | '& text': { | 94 | '& text': { |
73 | fontFamily: theme.typography.fontFamily, | 95 | fontFamily: theme.typography.fontFamily, |
diff --git a/subprojects/frontend/src/graph/dotSource.ts b/subprojects/frontend/src/graph/dotSource.ts index bd358dfa..3ac5eb1c 100644 --- a/subprojects/frontend/src/graph/dotSource.ts +++ b/subprojects/frontend/src/graph/dotSource.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -142,6 +142,9 @@ function createNodes( | |||
142 | if (data.unaryPredicates.size === 0) { | 142 | if (data.unaryPredicates.size === 0) { |
143 | classList.push('node-empty'); | 143 | classList.push('node-empty'); |
144 | } | 144 | } |
145 | if (node.typeHash !== undefined) { | ||
146 | classList.push(`node-typeHash-${node.typeHash}`); | ||
147 | } | ||
145 | const classes = classList.join(' '); | 148 | const classes = classList.join(' '); |
146 | const name = nodeName(graph, node); | 149 | const name = nodeName(graph, node); |
147 | const border = node.kind === 'INDIVIDUAL' ? 2 : 1; | 150 | const border = node.kind === 'INDIVIDUAL' ? 2 : 1; |
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 18310147..a996cde8 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -24,6 +24,11 @@ interface OuterPalette { | |||
24 | border: string; | 24 | border: string; |
25 | } | 25 | } |
26 | 26 | ||
27 | interface TypeHashPalette { | ||
28 | text: string; | ||
29 | box: string; | ||
30 | } | ||
31 | |||
27 | interface HighlightPalette { | 32 | interface HighlightPalette { |
28 | number: string; | 33 | number: string; |
29 | parameter: string; | 34 | parameter: string; |
@@ -41,17 +46,20 @@ interface HighlightPalette { | |||
41 | selected: string; | 46 | selected: string; |
42 | contrastText: string; | 47 | contrastText: string; |
43 | }; | 48 | }; |
49 | typeHash: TypeHashPalette[]; | ||
44 | } | 50 | } |
45 | 51 | ||
46 | declare module '@mui/material/styles' { | 52 | declare module '@mui/material/styles' { |
47 | interface TypographyVariants { | 53 | interface TypographyVariants { |
48 | fontWeightEditorNormal: number; | 54 | fontWeightEditorNormal: number; |
55 | fontWeightEditorTypeHash: number; | ||
49 | fontWeightEditorBold: number; | 56 | fontWeightEditorBold: number; |
50 | editor: TypographyStyle; | 57 | editor: TypographyStyle; |
51 | } | 58 | } |
52 | 59 | ||
53 | interface TypographyVariantsOptions { | 60 | interface TypographyVariantsOptions { |
54 | fontWeightEditorNormal?: number; | 61 | fontWeightEditorNormal?: number; |
62 | fontWeightEditorTypeHash?: number; | ||
55 | fontWeightEditorBold?: number; | 63 | fontWeightEditorBold?: number; |
56 | editor?: TypographyStyle; | 64 | editor?: TypographyStyle; |
57 | } | 65 | } |
@@ -78,6 +86,7 @@ function createResponsiveTheme( | |||
78 | '"Open Sans Variable", "Open Sans", "Roboto", "Helvetica", "Arial", sans-serif', | 86 | '"Open Sans Variable", "Open Sans", "Roboto", "Helvetica", "Arial", sans-serif', |
79 | fontWeightMedium: 500, | 87 | fontWeightMedium: 500, |
80 | fontWeightEditorNormal: 400, | 88 | fontWeightEditorNormal: 400, |
89 | fontWeightEditorTypeHash: 500, | ||
81 | fontWeightEditorBold: 700, | 90 | fontWeightEditorBold: 700, |
82 | button: { | 91 | button: { |
83 | fontWeight: 600, | 92 | fontWeight: 600, |
@@ -220,7 +229,7 @@ const lightTheme = (() => { | |||
220 | palette: { | 229 | palette: { |
221 | mode: 'light', | 230 | mode: 'light', |
222 | primary: { main: '#038a99' }, | 231 | primary: { main: '#038a99' }, |
223 | secondary: { main: '#e45649' }, | 232 | secondary: { main: '#61afef' }, |
224 | error: { main: '#ca1243' }, | 233 | error: { main: '#ca1243' }, |
225 | warning: { main: '#c18401' }, | 234 | warning: { main: '#c18401' }, |
226 | success: { main: '#50a14f' }, | 235 | success: { main: '#50a14f' }, |
@@ -256,6 +265,18 @@ const lightTheme = (() => { | |||
256 | selected: '#d500f9', | 265 | selected: '#d500f9', |
257 | contrastText: '#fff', | 266 | contrastText: '#fff', |
258 | }, | 267 | }, |
268 | typeHash: [ | ||
269 | { text: '#986801', box: '#e5c07b' }, | ||
270 | { text: '#d6493e', box: '#e06c75' }, | ||
271 | { text: '#50a14f', box: '#98c379' }, | ||
272 | { text: '#a626a4', box: '#c678dd' }, | ||
273 | { text: '#4078f2', box: '#80a7f4' }, | ||
274 | { text: '#827662', box: '#e3d1b2' }, | ||
275 | { text: '#904f53', box: '#e78b8f' }, | ||
276 | { text: '#637855', box: '#abcc94' }, | ||
277 | { text: '#805f89', box: '#dbb2e8' }, | ||
278 | { text: '#5987ae', box: '#92c0e9' }, | ||
279 | ], | ||
259 | }, | 280 | }, |
260 | }, | 281 | }, |
261 | }); | 282 | }); |
@@ -270,6 +291,7 @@ const darkTheme = (() => { | |||
270 | { | 291 | { |
271 | typography: { | 292 | typography: { |
272 | fontWeightEditorNormal: 350, | 293 | fontWeightEditorNormal: 350, |
294 | fontWeightEditorTypeHash: 350, | ||
273 | fontWeightEditorBold: 650, | 295 | fontWeightEditorBold: 650, |
274 | }, | 296 | }, |
275 | palette: { | 297 | palette: { |
@@ -277,7 +299,7 @@ const darkTheme = (() => { | |||
277 | primary: { main: '#56b6c2' }, | 299 | primary: { main: '#56b6c2' }, |
278 | secondary: { main: '#be5046' }, | 300 | secondary: { main: '#be5046' }, |
279 | error: { main: '#e06c75' }, | 301 | error: { main: '#e06c75' }, |
280 | warning: { main: '#e5c07b' }, | 302 | warning: { main: '#d19a66' }, |
281 | success: { main: '#98c379' }, | 303 | success: { main: '#98c379' }, |
282 | info: { main: '#61afef' }, | 304 | info: { main: '#61afef' }, |
283 | background: { | 305 | background: { |
@@ -311,6 +333,18 @@ const darkTheme = (() => { | |||
311 | selected: '#dd33fa', | 333 | selected: '#dd33fa', |
312 | contrastText: darkBackground, | 334 | contrastText: darkBackground, |
313 | }, | 335 | }, |
336 | typeHash: [ | ||
337 | { text: '#e5c07b', box: '#ae8003' }, | ||
338 | { text: '#e06c75', box: '#a23b47' }, | ||
339 | { text: '#98c379', box: '#428141' }, | ||
340 | { text: '#c678dd', box: '#854797' }, | ||
341 | { text: '#61afef', box: '#3982bb' }, | ||
342 | { text: '#e3d1b2', box: '#827662' }, | ||
343 | { text: '#e78b8f', box: '#904f53' }, | ||
344 | { text: '#abcc94', box: '#647e63' }, | ||
345 | { text: '#dbb2e8', box: '#805f89' }, | ||
346 | { text: '#92c0e9', box: '#4f7799' }, | ||
347 | ], | ||
314 | }, | 348 | }, |
315 | }, | 349 | }, |
316 | }, | 350 | }, |
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts index e473bd48..792c7de3 100644 --- a/subprojects/frontend/src/xtext/xtextServiceResults.ts +++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -137,6 +137,7 @@ export type ModelGenerationStartedResult = z.infer< | |||
137 | export const NodeMetadata = z.object({ | 137 | export const NodeMetadata = z.object({ |
138 | name: z.string(), | 138 | name: z.string(), |
139 | simpleName: z.string(), | 139 | simpleName: z.string(), |
140 | typeHash: z.string().optional(), | ||
140 | kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']), | 141 | kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']), |
141 | }); | 142 | }); |
142 | 143 | ||
@@ -182,15 +183,15 @@ export type SemanticsResult = z.infer<typeof SemanticsResult>; | |||
182 | 183 | ||
183 | export const ModelGenerationResult = z.union([ | 184 | export const ModelGenerationResult = z.union([ |
184 | z.object({ | 185 | z.object({ |
185 | uuid: z.string().nonempty(), | 186 | uuid: z.string().min(1), |
186 | status: z.string(), | 187 | status: z.string(), |
187 | }), | 188 | }), |
188 | z.object({ | 189 | z.object({ |
189 | uuid: z.string().nonempty(), | 190 | uuid: z.string().min(1), |
190 | error: z.string(), | 191 | error: z.string(), |
191 | }), | 192 | }), |
192 | SemanticsSuccessResult.extend({ | 193 | SemanticsSuccessResult.extend({ |
193 | uuid: z.string().nonempty(), | 194 | uuid: z.string().min(1), |
194 | }), | 195 | }), |
195 | ]); | 196 | ]); |
196 | 197 | ||
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java index ae8c70e0..f64d4066 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -38,9 +38,12 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli | |||
38 | @Inject | 38 | @Inject |
39 | private ProblemDesugarer desugarer; | 39 | private ProblemDesugarer desugarer; |
40 | 40 | ||
41 | @Inject | ||
42 | private TypeHashProvider typeHashProvider; | ||
43 | |||
41 | @Override | 44 | @Override |
42 | protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor, | 45 | protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor, |
43 | CancelIndicator cancelIndicator) { | 46 | CancelIndicator cancelIndicator) { |
44 | highlightName(object, acceptor); | 47 | highlightName(object, acceptor); |
45 | highlightCrossReferences(object, acceptor, cancelIndicator); | 48 | highlightCrossReferences(object, acceptor, cancelIndicator); |
46 | return false; | 49 | return false; |
@@ -57,7 +60,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli | |||
57 | } | 60 | } |
58 | 61 | ||
59 | protected void highlightCrossReferences(EObject object, IHighlightedPositionAcceptor acceptor, | 62 | protected void highlightCrossReferences(EObject object, IHighlightedPositionAcceptor acceptor, |
60 | CancelIndicator cancelIndicator) { | 63 | CancelIndicator cancelIndicator) { |
61 | for (EReference reference : object.eClass().getEAllReferences()) { | 64 | for (EReference reference : object.eClass().getEAllReferences()) { |
62 | if (reference.isContainment()) { | 65 | if (reference.isContainment()) { |
63 | continue; | 66 | continue; |
@@ -98,7 +101,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli | |||
98 | boolean isError = ProblemUtil.isError(eObject); | 101 | boolean isError = ProblemUtil.isError(eObject); |
99 | if (ProblemUtil.isBuiltIn(eObject)) { | 102 | if (ProblemUtil.isBuiltIn(eObject)) { |
100 | var className = isError ? ERROR_CLASS : BUILTIN_CLASS; | 103 | var className = isError ? ERROR_CLASS : BUILTIN_CLASS; |
101 | return new String[] { className }; | 104 | return new String[]{className}; |
102 | } | 105 | } |
103 | return getUserDefinedElementHighlightClass(eObject, reference, isError); | 106 | return getUserDefinedElementHighlightClass(eObject, reference, isError); |
104 | } | 107 | } |
@@ -113,21 +116,32 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli | |||
113 | && desugarer.isContainmentReference(referenceDeclaration)) { | 116 | && desugarer.isContainmentReference(referenceDeclaration)) { |
114 | classesBuilder.add(CONTAINMENT_CLASS); | 117 | classesBuilder.add(CONTAINMENT_CLASS); |
115 | } | 118 | } |
116 | if (isError) { | 119 | if (isError && reference != null) { |
120 | // References to error patterns should be highlighted as errors, but error pattern definitions shouldn't. | ||
117 | classesBuilder.add(ERROR_CLASS); | 121 | classesBuilder.add(ERROR_CLASS); |
118 | } | 122 | } |
119 | if (eObject instanceof Node node) { | 123 | if (eObject instanceof Node node) { |
120 | if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE) { | 124 | highlightNode(node, reference, classesBuilder); |
121 | classesBuilder.add(NODE_CLASS); | 125 | } |
122 | } | 126 | if (eObject instanceof Relation relation) { |
123 | if (ProblemUtil.isIndividualNode(node)) { | 127 | var typeHash = typeHashProvider.getTypeHash(relation); |
124 | classesBuilder.add(INDIVIDUAL_NODE_CLASS); | 128 | if (typeHash != null) { |
125 | } | 129 | classesBuilder.add("typeHash-" + typeHash); |
126 | if (ProblemUtil.isNewNode(node)) { | ||
127 | classesBuilder.add(NEW_NODE_CLASS); | ||
128 | } | 130 | } |
129 | } | 131 | } |
130 | List<String> classes = classesBuilder.build(); | 132 | List<String> classes = classesBuilder.build(); |
131 | return classes.toArray(new String[0]); | 133 | return classes.toArray(new String[0]); |
132 | } | 134 | } |
135 | |||
136 | private static void highlightNode(Node node, EReference reference, ImmutableList.Builder<String> classesBuilder) { | ||
137 | if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE) { | ||
138 | classesBuilder.add(NODE_CLASS); | ||
139 | } | ||
140 | if (ProblemUtil.isIndividualNode(node)) { | ||
141 | classesBuilder.add(INDIVIDUAL_NODE_CLASS); | ||
142 | } | ||
143 | if (ProblemUtil.isNewNode(node)) { | ||
144 | classesBuilder.add(NEW_NODE_CLASS); | ||
145 | } | ||
146 | } | ||
133 | } | 147 | } |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java new file mode 100644 index 00000000..f75ecdb2 --- /dev/null +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java | |||
@@ -0,0 +1,93 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.ide.syntaxcoloring; | ||
7 | |||
8 | import com.google.common.collect.ImmutableMap; | ||
9 | import com.google.inject.Inject; | ||
10 | import com.google.inject.Singleton; | ||
11 | import org.eclipse.xtext.EcoreUtil2; | ||
12 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
13 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | ||
14 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
15 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
16 | import tools.refinery.language.model.problem.*; | ||
17 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
18 | import tools.refinery.language.utils.ProblemUtil; | ||
19 | |||
20 | import java.util.*; | ||
21 | |||
22 | @Singleton | ||
23 | public class TypeHashProvider { | ||
24 | private static final String CACHE_KEY = "tools.refinery.language.ide.syntaxcoloring.TypeHashProvider"; | ||
25 | private static final int COLOR_COUNT = 10; | ||
26 | |||
27 | @Inject | ||
28 | private IResourceScopeCache resourceScopeCache; | ||
29 | |||
30 | @Inject | ||
31 | private IScopeProvider scopeProvider; | ||
32 | |||
33 | @Inject | ||
34 | private IQualifiedNameProvider qualifiedNameProvider; | ||
35 | |||
36 | @Inject | ||
37 | private IQualifiedNameConverter qualifiedNameConverter; | ||
38 | |||
39 | public String getTypeHash(Relation relation) { | ||
40 | if (!(relation instanceof ClassDeclaration || relation instanceof EnumDeclaration) || | ||
41 | ProblemUtil.isBuiltIn(relation)) { | ||
42 | return null; | ||
43 | } | ||
44 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(relation); | ||
45 | if (qualifiedName == null) { | ||
46 | return null; | ||
47 | } | ||
48 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | ||
49 | var problem = EcoreUtil2.getContainerOfType(relation, Problem.class); | ||
50 | if (problem == null) { | ||
51 | return null; | ||
52 | } | ||
53 | var cache = resourceScopeCache.get(CACHE_KEY, problem.eResource(), () -> computeHashes(problem)); | ||
54 | return cache.get(qualifiedNameString); | ||
55 | } | ||
56 | |||
57 | private Map<String, String> computeHashes(Problem problem) { | ||
58 | var qualifiedNameStrings = new TreeSet<String>(); | ||
59 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); | ||
60 | for (var description : scope.getAllElements()) { | ||
61 | if (ProblemResourceDescriptionStrategy.COLOR_RELATION_TRUE.equals( | ||
62 | description.getUserData(ProblemResourceDescriptionStrategy.COLOR_RELATION))) { | ||
63 | var qualifiedNameString = qualifiedNameConverter.toString(description.getQualifiedName()); | ||
64 | qualifiedNameStrings.add(qualifiedNameString); | ||
65 | } | ||
66 | } | ||
67 | var stringList = new ArrayList<>(qualifiedNameStrings); | ||
68 | int size = stringList.size(); | ||
69 | if (size == 0) { | ||
70 | return Map.of(); | ||
71 | } | ||
72 | // The use of a non-cryptographic random generator is safe here, because we only use it to shuffle the color | ||
73 | // IDs in a pseudo-random way. The shuffle depends on the size of the list of identifiers before padding to | ||
74 | // make sure that adding a new class randomizes all color IDs. | ||
75 | @SuppressWarnings("squid:S2245") | ||
76 | var random = new Random(size); | ||
77 | int padding = COLOR_COUNT - (size % COLOR_COUNT); | ||
78 | for (int i = 0; i < padding; i++) { | ||
79 | stringList.add(null); | ||
80 | } | ||
81 | size += padding; | ||
82 | Collections.shuffle(stringList, random); | ||
83 | var mapBuilder = ImmutableMap.<String, String>builder(); | ||
84 | for (int i = 0; i < size; i++) { | ||
85 | var key = stringList.get(i); | ||
86 | if (key != null) { | ||
87 | int colorId = i % COLOR_COUNT; | ||
88 | mapBuilder.put(key, Integer.toString(colorId)); | ||
89 | } | ||
90 | } | ||
91 | return mapBuilder.build(); | ||
92 | } | ||
93 | } | ||
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java new file mode 100644 index 00000000..517b8604 --- /dev/null +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 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.collections.api.factory.primitive.ObjectIntMaps; | ||
10 | import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; | ||
11 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
12 | import org.eclipse.xtext.naming.QualifiedName; | ||
13 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
14 | import tools.refinery.language.model.problem.Node; | ||
15 | import tools.refinery.language.model.problem.Problem; | ||
16 | import tools.refinery.language.model.problem.ProblemPackage; | ||
17 | |||
18 | import java.util.Locale; | ||
19 | |||
20 | public class NodeNameProvider { | ||
21 | @Inject | ||
22 | private IQualifiedNameConverter qualifiedNameConverter; | ||
23 | |||
24 | @Inject | ||
25 | private SemanticsUtils semanticsUtils; | ||
26 | |||
27 | @Inject | ||
28 | private IScopeProvider scopeProvider; | ||
29 | |||
30 | private Problem problem; | ||
31 | private final MutableObjectIntMap<String> indexMap = ObjectIntMaps.mutable.empty(); | ||
32 | |||
33 | public void setProblem(Problem problem) { | ||
34 | if (this.problem != null) { | ||
35 | throw new IllegalStateException("Problem was already set"); | ||
36 | } | ||
37 | this.problem = problem; | ||
38 | } | ||
39 | |||
40 | public String getNextName(String typeName) { | ||
41 | if (problem == null) { | ||
42 | throw new IllegalStateException("Problem was not set"); | ||
43 | } | ||
44 | String namePrefix; | ||
45 | if (typeName == null || typeName.isEmpty()) { | ||
46 | namePrefix = "node"; | ||
47 | } else { | ||
48 | namePrefix = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1); | ||
49 | } | ||
50 | int index = indexMap.getIfAbsent(namePrefix, 0); | ||
51 | String nodeName; | ||
52 | QualifiedName qualifiedName; | ||
53 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
54 | do { | ||
55 | index++; | ||
56 | nodeName = namePrefix + index; | ||
57 | qualifiedName = qualifiedNameConverter.toQualifiedName(nodeName); | ||
58 | } while (semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class) != null); | ||
59 | indexMap.put(namePrefix, index); | ||
60 | return nodeName; | ||
61 | } | ||
62 | } | ||
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 index 57af599e..09ba34fc 100644 --- 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -8,12 +8,9 @@ package tools.refinery.language.semantics; | |||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import com.google.inject.Provider; | 9 | import com.google.inject.Provider; |
10 | import org.eclipse.collections.api.factory.primitive.IntObjectMaps; | 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; | 11 | 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; | 12 | import org.eclipse.emf.common.util.URI; |
15 | import org.eclipse.emf.ecore.util.EcoreUtil; | 13 | import org.eclipse.emf.ecore.util.EcoreUtil; |
16 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
17 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 14 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
18 | import org.eclipse.xtext.naming.QualifiedName; | 15 | import org.eclipse.xtext.naming.QualifiedName; |
19 | import org.eclipse.xtext.resource.FileExtensionProvider; | 16 | import org.eclipse.xtext.resource.FileExtensionProvider; |
@@ -37,7 +34,6 @@ import tools.refinery.store.tuple.Tuple; | |||
37 | import java.io.ByteArrayInputStream; | 34 | import java.io.ByteArrayInputStream; |
38 | import java.io.ByteArrayOutputStream; | 35 | import java.io.ByteArrayOutputStream; |
39 | import java.io.IOException; | 36 | import java.io.IOException; |
40 | import java.util.Locale; | ||
41 | import java.util.Map; | 37 | import java.util.Map; |
42 | import java.util.TreeMap; | 38 | import java.util.TreeMap; |
43 | import java.util.TreeSet; | 39 | import java.util.TreeSet; |
@@ -57,9 +53,6 @@ public class SolutionSerializer { | |||
57 | private IQualifiedNameProvider qualifiedNameProvider; | 53 | private IQualifiedNameProvider qualifiedNameProvider; |
58 | 54 | ||
59 | @Inject | 55 | @Inject |
60 | private IQualifiedNameConverter qualifiedNameConverter; | ||
61 | |||
62 | @Inject | ||
63 | private SemanticsUtils semanticsUtils; | 56 | private SemanticsUtils semanticsUtils; |
64 | 57 | ||
65 | @Inject | 58 | @Inject |
@@ -68,6 +61,9 @@ public class SolutionSerializer { | |||
68 | @Inject | 61 | @Inject |
69 | private ProblemDesugarer desugarer; | 62 | private ProblemDesugarer desugarer; |
70 | 63 | ||
64 | @Inject | ||
65 | private NodeNameProvider nameProvider; | ||
66 | |||
71 | private ProblemTrace trace; | 67 | private ProblemTrace trace; |
72 | private Model model; | 68 | private Model model; |
73 | private ReasoningAdapter reasoningAdapter; | 69 | private ReasoningAdapter reasoningAdapter; |
@@ -95,6 +91,7 @@ public class SolutionSerializer { | |||
95 | problem = copyProblem(originalProblem, uri); | 91 | problem = copyProblem(originalProblem, uri); |
96 | problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); | 92 | problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); |
97 | problem.getNodes().removeIf(this::shouldRemoveNode); | 93 | problem.getNodes().removeIf(this::shouldRemoveNode); |
94 | nameProvider.setProblem(problem); | ||
98 | addExistsAssertions(); | 95 | addExistsAssertions(); |
99 | addClassAssertions(); | 96 | addClassAssertions(); |
100 | addReferenceAssertions(); | 97 | addReferenceAssertions(); |
@@ -172,11 +169,6 @@ public class SolutionSerializer { | |||
172 | return findNode(qualifiedName); | 169 | return findNode(qualifiedName); |
173 | } | 170 | } |
174 | 171 | ||
175 | private Node findNode(String name) { | ||
176 | var qualifiedName = qualifiedNameConverter.toQualifiedName(name); | ||
177 | return findNode(qualifiedName); | ||
178 | } | ||
179 | |||
180 | private Node findNode(QualifiedName qualifiedName) { | 172 | private Node findNode(QualifiedName qualifiedName) { |
181 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | 173 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); |
182 | return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class); | 174 | return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class); |
@@ -222,19 +214,17 @@ public class SolutionSerializer { | |||
222 | private void addClassAssertions() { | 214 | private void addClassAssertions() { |
223 | var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream() | 215 | var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream() |
224 | .collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); | 216 | .collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); |
225 | var indexMap = ObjectIntMaps.mutable.empty(); | ||
226 | var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); | 217 | var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); |
227 | while (cursor.move()) { | 218 | while (cursor.move()) { |
228 | var key = cursor.getKey(); | 219 | var key = cursor.getKey(); |
229 | var nodeId = key.get(0); | 220 | var nodeId = key.get(0); |
230 | if (isExistingNode(nodeId)) { | 221 | if (isExistingNode(nodeId)) { |
231 | createNodeAndAssertType(nodeId, cursor.getValue(), types, indexMap); | 222 | createNodeAndAssertType(nodeId, cursor.getValue(), types); |
232 | } | 223 | } |
233 | } | 224 | } |
234 | } | 225 | } |
235 | 226 | ||
236 | private void createNodeAndAssertType(int nodeId, InferredType inferredType, Map<PartialRelation, Relation> types, | 227 | private void createNodeAndAssertType(int nodeId, InferredType inferredType, Map<PartialRelation, Relation> types) { |
237 | MutableObjectIntMap<Object> indexMap) { | ||
238 | var candidateTypeSymbol = inferredType.candidateType(); | 228 | var candidateTypeSymbol = inferredType.candidateType(); |
239 | var candidateRelation = types.get(candidateTypeSymbol); | 229 | var candidateRelation = types.get(candidateTypeSymbol); |
240 | if (candidateRelation instanceof EnumDeclaration) { | 230 | if (candidateRelation instanceof EnumDeclaration) { |
@@ -243,18 +233,8 @@ public class SolutionSerializer { | |||
243 | } | 233 | } |
244 | Node node = nodes.get(nodeId); | 234 | Node node = nodes.get(nodeId); |
245 | if (node == null) { | 235 | if (node == null) { |
246 | String typeName = candidateRelation.getName(); | 236 | var typeName = candidateRelation.getName(); |
247 | if (typeName == null || typeName.isEmpty()) { | 237 | var nodeName = nameProvider.getNextName(typeName); |
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(); | 238 | node = ProblemFactory.eINSTANCE.createNode(); |
259 | node.setName(nodeName); | 239 | node.setName(nodeName); |
260 | problem.getNodes().add(node); | 240 | problem.getNodes().add(node); |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java index a3b6ca82..7febce7d 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -17,6 +17,7 @@ import tools.refinery.generator.ValidationErrorsException; | |||
17 | import tools.refinery.language.web.semantics.PartialInterpretation2Json; | 17 | import tools.refinery.language.web.semantics.PartialInterpretation2Json; |
18 | import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider; | 18 | import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider; |
19 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; | 19 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; |
20 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
20 | import tools.refinery.store.util.CancellationToken; | 21 | import tools.refinery.store.util.CancellationToken; |
21 | 22 | ||
22 | import java.io.IOException; | 23 | import java.io.IOException; |
@@ -142,7 +143,7 @@ public class ModelGenerationWorker implements Runnable { | |||
142 | } catch (ValidationErrorsException e) { | 143 | } catch (ValidationErrorsException e) { |
143 | var errors = e.getErrors(); | 144 | var errors = e.getErrors(); |
144 | if (errors != null && !errors.isEmpty()) { | 145 | if (errors != null && !errors.isEmpty()) { |
145 | return new ModelGenerationErrorResult(uuid, "Validation error: " + errors.get(0).getMessage()); | 146 | return new ModelGenerationErrorResult(uuid, "Validation error: " + errors.getFirst().getMessage()); |
146 | } | 147 | } |
147 | throw e; | 148 | throw e; |
148 | } | 149 | } |
@@ -154,7 +155,7 @@ public class ModelGenerationWorker implements Runnable { | |||
154 | notifyResult(new ModelGenerationStatusResult(uuid, "Saving generated model")); | 155 | notifyResult(new ModelGenerationStatusResult(uuid, "Saving generated model")); |
155 | cancellationToken.checkCancelled(); | 156 | cancellationToken.checkCancelled(); |
156 | metadataCreator.setProblemTrace(generator.getProblemTrace()); | 157 | metadataCreator.setProblemTrace(generator.getProblemTrace()); |
157 | var nodesMetadata = metadataCreator.getNodesMetadata(generator.getModel(), false); | 158 | var nodesMetadata = metadataCreator.getNodesMetadata(generator.getModel(), Concreteness.CANDIDATE); |
158 | cancellationToken.checkCancelled(); | 159 | cancellationToken.checkCancelled(); |
159 | var relationsMetadata = metadataCreator.getRelationsMetadata(); | 160 | var relationsMetadata = metadataCreator.getRelationsMetadata(); |
160 | cancellationToken.checkCancelled(); | 161 | cancellationToken.checkCancelled(); |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java index 44974869..fed3c8a3 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -20,6 +20,7 @@ import tools.refinery.generator.ModelSemanticsFactory; | |||
20 | import tools.refinery.language.model.problem.Problem; | 20 | import tools.refinery.language.model.problem.Problem; |
21 | import tools.refinery.language.web.semantics.metadata.MetadataCreator; | 21 | import tools.refinery.language.web.semantics.metadata.MetadataCreator; |
22 | import tools.refinery.language.semantics.TracedException; | 22 | import tools.refinery.language.semantics.TracedException; |
23 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
23 | import tools.refinery.store.reasoning.translator.TranslationException; | 24 | import tools.refinery.store.reasoning.translator.TranslationException; |
24 | import tools.refinery.store.util.CancellationToken; | 25 | import tools.refinery.store.util.CancellationToken; |
25 | 26 | ||
@@ -73,7 +74,7 @@ class SemanticsWorker implements Callable<SemanticsResult> { | |||
73 | } | 74 | } |
74 | cancellationToken.checkCancelled(); | 75 | cancellationToken.checkCancelled(); |
75 | metadataCreator.setProblemTrace(semantics.getProblemTrace()); | 76 | metadataCreator.setProblemTrace(semantics.getProblemTrace()); |
76 | var nodesMetadata = metadataCreator.getNodesMetadata(semantics.getModel(), true); | 77 | var nodesMetadata = metadataCreator.getNodesMetadata(semantics.getModel(), Concreteness.PARTIAL); |
77 | cancellationToken.checkCancelled(); | 78 | cancellationToken.checkCancelled(); |
78 | var relationsMetadata = metadataCreator.getRelationsMetadata(); | 79 | var relationsMetadata = metadataCreator.getRelationsMetadata(); |
79 | cancellationToken.checkCancelled(); | 80 | cancellationToken.checkCancelled(); |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java index 3fbc5d2d..f05abc45 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java | |||
@@ -1,11 +1,12 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.language.web.semantics.metadata; | 6 | package tools.refinery.language.web.semantics.metadata; |
7 | 7 | ||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import com.google.inject.Provider; | ||
9 | import org.eclipse.emf.ecore.EObject; | 10 | import org.eclipse.emf.ecore.EObject; |
10 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | 11 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
11 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 12 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
@@ -18,6 +19,7 @@ import tools.refinery.language.semantics.TracedException; | |||
18 | import tools.refinery.language.utils.ProblemUtil; | 19 | import tools.refinery.language.utils.ProblemUtil; |
19 | import tools.refinery.store.model.Model; | 20 | import tools.refinery.store.model.Model; |
20 | import tools.refinery.store.reasoning.ReasoningAdapter; | 21 | import tools.refinery.store.reasoning.ReasoningAdapter; |
22 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
21 | import tools.refinery.store.reasoning.representation.PartialRelation; | 23 | import tools.refinery.store.reasoning.representation.PartialRelation; |
22 | 24 | ||
23 | import java.util.ArrayList; | 25 | import java.util.ArrayList; |
@@ -35,10 +37,11 @@ public class MetadataCreator { | |||
35 | @Inject | 37 | @Inject |
36 | private IQualifiedNameConverter qualifiedNameConverter; | 38 | private IQualifiedNameConverter qualifiedNameConverter; |
37 | 39 | ||
38 | private ProblemTrace problemTrace; | 40 | @Inject |
41 | private Provider<NodeMetadataFactory> nodeMetadataFactoryProvider; | ||
39 | 42 | ||
43 | private ProblemTrace problemTrace; | ||
40 | private IScope nodeScope; | 44 | private IScope nodeScope; |
41 | |||
42 | private IScope relationScope; | 45 | private IScope relationScope; |
43 | 46 | ||
44 | public void setProblemTrace(ProblemTrace problemTrace) { | 47 | public void setProblemTrace(ProblemTrace problemTrace) { |
@@ -51,37 +54,35 @@ public class MetadataCreator { | |||
51 | relationScope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); | 54 | relationScope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); |
52 | } | 55 | } |
53 | 56 | ||
54 | public static String unnamedNode(int nodeId) { | 57 | public List<NodeMetadata> getNodesMetadata(Model model, Concreteness concreteness) { |
55 | return "::" + nodeId; | ||
56 | } | ||
57 | |||
58 | public List<NodeMetadata> getNodesMetadata(Model model, boolean preserveNewNodes) { | ||
59 | int nodeCount = model.getAdapter(ReasoningAdapter.class).getNodeCount(); | 58 | int nodeCount = model.getAdapter(ReasoningAdapter.class).getNodeCount(); |
60 | var nodeTrace = problemTrace.getNodeTrace(); | 59 | var nodeTrace = problemTrace.getNodeTrace(); |
61 | var nodes = new NodeMetadata[Math.max(nodeTrace.size(), nodeCount)]; | 60 | var nodes = new NodeMetadata[Math.max(nodeTrace.size(), nodeCount)]; |
61 | var nodeMetadataFactory = nodeMetadataFactoryProvider.get(); | ||
62 | nodeMetadataFactory.initialize(problemTrace, concreteness, model); | ||
63 | boolean preserveNewNodes = concreteness == Concreteness.PARTIAL; | ||
62 | for (var entry : nodeTrace.keyValuesView()) { | 64 | for (var entry : nodeTrace.keyValuesView()) { |
63 | var node = entry.getOne(); | 65 | var node = entry.getOne(); |
64 | var id = entry.getTwo(); | 66 | var id = entry.getTwo(); |
65 | nodes[id] = getNodeMetadata(id, node, preserveNewNodes); | 67 | nodes[id] = getNodeMetadata(id, node, preserveNewNodes, nodeMetadataFactory); |
66 | } | 68 | } |
67 | for (int i = 0; i < nodes.length; i++) { | 69 | for (int i = 0; i < nodes.length; i++) { |
68 | if (nodes[i] == null) { | 70 | if (nodes[i] == null) { |
69 | var nodeName = unnamedNode(i); | 71 | nodes[i] = nodeMetadataFactory.createFreshlyNamedMetadata(i); |
70 | nodes[i] = new NodeMetadata(nodeName, nodeName, NodeKind.IMPLICIT); | ||
71 | } | 72 | } |
72 | } | 73 | } |
73 | return List.of(nodes); | 74 | return List.of(nodes); |
74 | } | 75 | } |
75 | 76 | ||
76 | private NodeMetadata getNodeMetadata(int nodeId, Node node, boolean preserveNewNodes) { | 77 | private NodeMetadata getNodeMetadata(int nodeId, Node node, boolean preserveNewNodes, |
78 | NodeMetadataFactory nodeMetadataFactory) { | ||
77 | var kind = getNodeKind(node); | 79 | var kind = getNodeKind(node); |
78 | if (!preserveNewNodes && kind == NodeKind.NEW) { | 80 | if (!preserveNewNodes && kind == NodeKind.NEW) { |
79 | var nodeName = unnamedNode(nodeId); | 81 | return nodeMetadataFactory.createFreshlyNamedMetadata(nodeId); |
80 | return new NodeMetadata(nodeName, nodeName, NodeKind.IMPLICIT); | ||
81 | } | 82 | } |
82 | var qualifiedName = getQualifiedName(node); | 83 | var qualifiedName = getQualifiedName(node); |
83 | var simpleName = getSimpleName(node, qualifiedName, nodeScope); | 84 | var simpleName = getSimpleName(node, qualifiedName, nodeScope); |
84 | return new NodeMetadata(qualifiedNameConverter.toString(qualifiedName), | 85 | return nodeMetadataFactory.doCreateMetadata(nodeId, qualifiedNameConverter.toString(qualifiedName), |
85 | qualifiedNameConverter.toString(simpleName), getNodeKind(node)); | 86 | qualifiedNameConverter.toString(simpleName), getNodeKind(node)); |
86 | } | 87 | } |
87 | 88 | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java index 5da28acf..f3347eac 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java | |||
@@ -1,9 +1,9 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2023 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.language.web.semantics.metadata; | 6 | package tools.refinery.language.web.semantics.metadata; |
7 | 7 | ||
8 | public record NodeMetadata(String name, String simpleName, NodeKind kind) implements Metadata { | 8 | public record NodeMetadata(String name, String simpleName, String typeHash, NodeKind kind) implements Metadata { |
9 | } | 9 | } |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java new file mode 100644 index 00000000..ce0e50c1 --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java | |||
@@ -0,0 +1,89 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.web.semantics.metadata; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import tools.refinery.language.ide.syntaxcoloring.TypeHashProvider; | ||
10 | import tools.refinery.language.semantics.NodeNameProvider; | ||
11 | import tools.refinery.language.semantics.ProblemTrace; | ||
12 | import tools.refinery.store.model.Interpretation; | ||
13 | import tools.refinery.store.model.Model; | ||
14 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
15 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
16 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
18 | import tools.refinery.store.reasoning.translator.typehierarchy.InferredType; | ||
19 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator; | ||
20 | import tools.refinery.store.representation.TruthValue; | ||
21 | import tools.refinery.store.tuple.Tuple; | ||
22 | |||
23 | public class NodeMetadataFactory { | ||
24 | @Inject | ||
25 | private NodeNameProvider nodeNameProvider; | ||
26 | |||
27 | @Inject | ||
28 | private TypeHashProvider typeHashProvider; | ||
29 | |||
30 | private ProblemTrace problemTrace; | ||
31 | private Concreteness concreteness; | ||
32 | private Interpretation<InferredType> typeInterpretation; | ||
33 | private PartialInterpretation<TruthValue, Boolean> existsInterpretation; | ||
34 | |||
35 | public void initialize(ProblemTrace problemTrace, Concreteness concreteness, Model model) { | ||
36 | this.problemTrace = problemTrace; | ||
37 | this.concreteness = concreteness; | ||
38 | typeInterpretation = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL); | ||
39 | existsInterpretation = model.getAdapter(ReasoningAdapter.class).getPartialInterpretation(concreteness, | ||
40 | ReasoningAdapter.EXISTS_SYMBOL); | ||
41 | nodeNameProvider.setProblem(problemTrace.getProblem()); | ||
42 | } | ||
43 | |||
44 | public NodeMetadata doCreateMetadata(int nodeId, String name, String simpleName, NodeKind kind) { | ||
45 | var type = getType(nodeId); | ||
46 | return doCreateMetadata(name, simpleName, type, kind); | ||
47 | } | ||
48 | |||
49 | public NodeMetadata createFreshlyNamedMetadata(int nodeId) { | ||
50 | var type = getType(nodeId); | ||
51 | var name = getName(type, nodeId); | ||
52 | return doCreateMetadata(name, name, type, NodeKind.IMPLICIT); | ||
53 | } | ||
54 | |||
55 | private PartialRelation getType(int nodeId) { | ||
56 | var inferredType = typeInterpretation.get(Tuple.of(nodeId)); | ||
57 | if (inferredType == null) { | ||
58 | return null; | ||
59 | } | ||
60 | return inferredType.candidateType(); | ||
61 | } | ||
62 | |||
63 | private String getName(PartialRelation type, int nodeId) { | ||
64 | if (concreteness == Concreteness.CANDIDATE && !existsInterpretation.get(Tuple.of(nodeId)).may()) { | ||
65 | // Do not increment the node name counter for non-existent nodes in the candidate interpretation. | ||
66 | // While non-existent nodes may appear in the partial interpretation, they are never displayed in the | ||
67 | // candidate interpretation. | ||
68 | return "::" + nodeId; | ||
69 | } | ||
70 | if (type == null) { | ||
71 | return nodeNameProvider.getNextName(null); | ||
72 | } | ||
73 | var relation = problemTrace.getRelation(type); | ||
74 | return nodeNameProvider.getNextName(relation.getName()); | ||
75 | } | ||
76 | |||
77 | private NodeMetadata doCreateMetadata(String name, String simpleName, PartialRelation type, NodeKind kind) { | ||
78 | var typeHash = getTypeHash(type); | ||
79 | return new NodeMetadata(name, simpleName, typeHash, kind); | ||
80 | } | ||
81 | |||
82 | private String getTypeHash(PartialRelation type) { | ||
83 | if (type == null) { | ||
84 | return null; | ||
85 | } | ||
86 | var relation = problemTrace.getRelation(type); | ||
87 | return typeHashProvider.getTypeHash(relation); | ||
88 | } | ||
89 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java index cac1f265..c04c7d09 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -28,6 +28,8 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
28 | public static final String ARITY = DATA_PREFIX + "ARITY"; | 28 | public static final String ARITY = DATA_PREFIX + "ARITY"; |
29 | public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE"; | 29 | public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE"; |
30 | public static final String ERROR_PREDICATE_TRUE = "true"; | 30 | public static final String ERROR_PREDICATE_TRUE = "true"; |
31 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; | ||
32 | public static final String COLOR_RELATION_TRUE = "true"; | ||
31 | 33 | ||
32 | @Inject | 34 | @Inject |
33 | private IQualifiedNameConverter qualifiedNameConverter; | 35 | private IQualifiedNameConverter qualifiedNameConverter; |
@@ -44,12 +46,9 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
44 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); | 46 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); |
45 | var problemQualifiedName = getNameAsQualifiedName(problem); | 47 | var problemQualifiedName = getNameAsQualifiedName(problem); |
46 | var userData = getUserData(eObject); | 48 | var userData = getUserData(eObject); |
47 | boolean nameExported; | 49 | QualifiedName lastQualifiedNameToExport = null; |
48 | if (shouldExportSimpleName(eObject)) { | 50 | if (shouldExportSimpleName(eObject)) { |
49 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor); | 51 | lastQualifiedNameToExport = qualifiedName; |
50 | nameExported = true; | ||
51 | } else { | ||
52 | nameExported = false; | ||
53 | } | 52 | } |
54 | var parent = eObject.eContainer(); | 53 | var parent = eObject.eContainer(); |
55 | while (parent != null && parent != problem) { | 54 | while (parent != null && parent != problem) { |
@@ -60,16 +59,18 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
60 | } | 59 | } |
61 | qualifiedName = parentQualifiedName.append(qualifiedName); | 60 | qualifiedName = parentQualifiedName.append(qualifiedName); |
62 | if (shouldExportSimpleName(parent)) { | 61 | if (shouldExportSimpleName(parent)) { |
63 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor); | 62 | if (lastQualifiedNameToExport != null) { |
64 | nameExported = true; | 63 | acceptEObjectDescription(eObject, problemQualifiedName, lastQualifiedNameToExport, userData, |
65 | } else { | 64 | acceptor); |
66 | nameExported = false; | 65 | } |
66 | lastQualifiedNameToExport = qualifiedName; | ||
67 | } | 67 | } |
68 | parent = parent.eContainer(); | 68 | parent = parent.eContainer(); |
69 | } | 69 | } |
70 | if (!nameExported) { | 70 | if (lastQualifiedNameToExport == null) { |
71 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor); | 71 | lastQualifiedNameToExport = qualifiedName; |
72 | } | 72 | } |
73 | acceptEObjectDescription(eObject, problemQualifiedName, lastQualifiedNameToExport, userData, true, acceptor); | ||
73 | return true; | 74 | return true; |
74 | } | 75 | } |
75 | 76 | ||
@@ -120,8 +121,31 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
120 | 121 | ||
121 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, | 122 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, |
122 | Map<String, String> userData, IAcceptor<IEObjectDescription> acceptor) { | 123 | Map<String, String> userData, IAcceptor<IEObjectDescription> acceptor) { |
124 | acceptEObjectDescription(eObject, prefix, qualifiedName, userData, false, acceptor); | ||
125 | } | ||
126 | |||
127 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, | ||
128 | Map<String, String> userData, boolean fullyQualified, | ||
129 | IAcceptor<IEObjectDescription> acceptor) { | ||
123 | var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); | 130 | var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); |
124 | var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userData); | 131 | Map<String, String> userDataWithFullyQualified; |
132 | if (fullyQualified && shouldColorRelation(eObject)) { | ||
133 | userDataWithFullyQualified = ImmutableMap.<String, String>builder() | ||
134 | .putAll(userData) | ||
135 | .put(COLOR_RELATION, COLOR_RELATION_TRUE) | ||
136 | .build(); | ||
137 | } else { | ||
138 | userDataWithFullyQualified = userData; | ||
139 | } | ||
140 | var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userDataWithFullyQualified); | ||
125 | acceptor.accept(description); | 141 | acceptor.accept(description); |
126 | } | 142 | } |
143 | |||
144 | private boolean shouldColorRelation(EObject eObject) { | ||
145 | if (ProblemUtil.isBuiltIn(eObject)) { | ||
146 | return false; | ||
147 | } | ||
148 | return eObject instanceof ClassDeclaration || eObject instanceof EnumDeclaration; | ||
149 | |||
150 | } | ||
127 | } | 151 | } |