@gsl-lang/gql 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -2
- package/dist/src/expressions.d.ts +88 -0
- package/dist/src/expressions.d.ts.map +1 -0
- package/dist/src/expressions.js +658 -0
- package/dist/src/expressions.js.map +1 -0
- package/dist/src/graph.d.ts +31 -0
- package/dist/src/graph.d.ts.map +1 -0
- package/dist/src/graph.js +68 -0
- package/dist/src/graph.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +22 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lexer.d.ts +10 -0
- package/dist/src/lexer.d.ts.map +1 -0
- package/dist/src/lexer.js +107 -0
- package/dist/src/lexer.js.map +1 -0
- package/dist/src/parser.d.ts +5 -0
- package/dist/src/parser.d.ts.map +1 -0
- package/dist/src/parser.js +336 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/predicates.d.ts +117 -0
- package/dist/src/predicates.d.ts.map +1 -0
- package/dist/src/predicates.js +313 -0
- package/dist/src/predicates.js.map +1 -0
- package/package.json +44 -4
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 Pascal Dennerly.
|
|
3
|
+
import { buildGlobalLabelIndex, ParentPredicate, setGlobalLabelIndex, } from "./predicates.js";
|
|
4
|
+
import { GraphBuilder, toWorkGraph } from "./graph.js";
|
|
5
|
+
function cloneNode(node) {
|
|
6
|
+
return {
|
|
7
|
+
id: node.id,
|
|
8
|
+
attributes: { ...node.attributes },
|
|
9
|
+
sets: new Set(node.sets),
|
|
10
|
+
parent: node.parent,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function cloneEdge(edge) {
|
|
14
|
+
return {
|
|
15
|
+
from: edge.from,
|
|
16
|
+
to: edge.to,
|
|
17
|
+
label: edge.label,
|
|
18
|
+
parent: edge.parent,
|
|
19
|
+
attributes: { ...edge.attributes },
|
|
20
|
+
sets: new Set(edge.sets),
|
|
21
|
+
scopeDepth: edge.scopeDepth,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function validateTargetType(pred) {
|
|
25
|
+
const t = pred.targetType();
|
|
26
|
+
if (t === "error")
|
|
27
|
+
throw new Error("predicate mixes node and edge targets");
|
|
28
|
+
return t;
|
|
29
|
+
}
|
|
30
|
+
export class Query {
|
|
31
|
+
expressions;
|
|
32
|
+
constructor(expressions) {
|
|
33
|
+
this.expressions = expressions;
|
|
34
|
+
}
|
|
35
|
+
execute(ctx) {
|
|
36
|
+
let value = { graph: ctx.inputGraph };
|
|
37
|
+
for (const expr of this.expressions) {
|
|
38
|
+
value = expr.apply(ctx, value);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class IdentityExpr {
|
|
44
|
+
apply(_ctx, input) {
|
|
45
|
+
return input;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class FromExpr {
|
|
49
|
+
isWildcard;
|
|
50
|
+
name;
|
|
51
|
+
constructor(isWildcard, name) {
|
|
52
|
+
this.isWildcard = isWildcard;
|
|
53
|
+
this.name = name;
|
|
54
|
+
}
|
|
55
|
+
apply(ctx) {
|
|
56
|
+
if (this.isWildcard)
|
|
57
|
+
return { graph: ctx.inputGraph };
|
|
58
|
+
const graph = ctx.namedGraphs.get(this.name);
|
|
59
|
+
if (!graph)
|
|
60
|
+
throw new Error(`named graph not found: ${this.name}`);
|
|
61
|
+
return { graph };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class BindExpr {
|
|
65
|
+
pipeline;
|
|
66
|
+
name;
|
|
67
|
+
constructor(pipeline, name) {
|
|
68
|
+
this.pipeline = pipeline;
|
|
69
|
+
this.name = name;
|
|
70
|
+
}
|
|
71
|
+
apply(ctx, input) {
|
|
72
|
+
if (ctx.namedGraphs.has(this.name)) {
|
|
73
|
+
throw new Error(`named graph already bound: ${this.name}`);
|
|
74
|
+
}
|
|
75
|
+
const result = this.pipeline.execute(ctx);
|
|
76
|
+
ctx.namedGraphs.set(this.name, result.graph);
|
|
77
|
+
return input;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export class SubgraphExpr {
|
|
81
|
+
pred;
|
|
82
|
+
traversal;
|
|
83
|
+
constructor(pred, traversal) {
|
|
84
|
+
this.pred = pred;
|
|
85
|
+
this.traversal = traversal;
|
|
86
|
+
}
|
|
87
|
+
apply(_ctx, input) {
|
|
88
|
+
const graph = input.graph;
|
|
89
|
+
const targetType = validateTargetType(this.pred);
|
|
90
|
+
const work = toWorkGraph(graph);
|
|
91
|
+
const nodes = work.nodes;
|
|
92
|
+
const edges = work.edges;
|
|
93
|
+
buildGlobalLabelIndex(edges);
|
|
94
|
+
const { nodeSet, edgeSet } = this.buildSubgraph(nodes, edges, targetType);
|
|
95
|
+
let baseNodes = nodeSet;
|
|
96
|
+
let baseEdges = edgeSet;
|
|
97
|
+
if (this.traversal && this.traversal.depth > 0) {
|
|
98
|
+
baseNodes = this.traverse(edges, baseNodes, this.traversal);
|
|
99
|
+
const rebuilt = new Set();
|
|
100
|
+
for (let i = 0; i < edges.length; i++) {
|
|
101
|
+
const edge = edges[i];
|
|
102
|
+
if (baseNodes.has(edge.from) && baseNodes.has(edge.to))
|
|
103
|
+
rebuilt.add(i);
|
|
104
|
+
}
|
|
105
|
+
baseEdges = rebuilt;
|
|
106
|
+
}
|
|
107
|
+
setGlobalLabelIndex(null);
|
|
108
|
+
const result = new GraphBuilder();
|
|
109
|
+
for (const set of work.sets.values()) {
|
|
110
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
111
|
+
}
|
|
112
|
+
for (const id of baseNodes) {
|
|
113
|
+
const node = nodes.get(id);
|
|
114
|
+
if (node)
|
|
115
|
+
result.addNode(cloneNode(node));
|
|
116
|
+
}
|
|
117
|
+
if (targetType === "edge") {
|
|
118
|
+
const indices = Array.from(baseEdges).sort((a, b) => a - b);
|
|
119
|
+
for (const idx of indices) {
|
|
120
|
+
const edge = edges[idx];
|
|
121
|
+
if (edge)
|
|
122
|
+
result.addEdge(cloneEdge(edge));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (const edge of edges) {
|
|
127
|
+
if (baseNodes.has(edge.from) && baseNodes.has(edge.to)) {
|
|
128
|
+
result.addEdge(cloneEdge(edge));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { graph: result.finalize() };
|
|
133
|
+
}
|
|
134
|
+
buildSubgraph(nodes, edges, targetType) {
|
|
135
|
+
const nodeSet = new Set();
|
|
136
|
+
const edgeSet = new Set();
|
|
137
|
+
if (targetType === "node") {
|
|
138
|
+
for (const [id, node] of nodes) {
|
|
139
|
+
if (this.pred.evaluateNode(node))
|
|
140
|
+
nodeSet.add(id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (targetType === "edge") {
|
|
144
|
+
this.injectEdges(edges);
|
|
145
|
+
for (let i = 0; i < edges.length; i++) {
|
|
146
|
+
const edge = edges[i];
|
|
147
|
+
if (this.pred.evaluateEdge(edge)) {
|
|
148
|
+
nodeSet.add(edge.from);
|
|
149
|
+
nodeSet.add(edge.to);
|
|
150
|
+
edgeSet.add(i);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
for (const [id, node] of nodes) {
|
|
156
|
+
if (this.pred.evaluateNode(node))
|
|
157
|
+
nodeSet.add(id);
|
|
158
|
+
}
|
|
159
|
+
if (nodeSet.size === 0) {
|
|
160
|
+
this.injectEdges(edges);
|
|
161
|
+
for (let i = 0; i < edges.length; i++) {
|
|
162
|
+
const edge = edges[i];
|
|
163
|
+
if (this.pred.evaluateEdge(edge)) {
|
|
164
|
+
nodeSet.add(edge.from);
|
|
165
|
+
nodeSet.add(edge.to);
|
|
166
|
+
edgeSet.add(i);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (nodeSet.size === 0) {
|
|
171
|
+
for (const id of nodes.keys())
|
|
172
|
+
nodeSet.add(id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { nodeSet, edgeSet };
|
|
176
|
+
}
|
|
177
|
+
injectEdges(edges) {
|
|
178
|
+
if (this.pred instanceof ParentPredicate) {
|
|
179
|
+
this.pred.allEdges = edges;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
traverse(edges, startNodes, cfg) {
|
|
183
|
+
const result = new Set(startNodes);
|
|
184
|
+
const visited = new Set(startNodes);
|
|
185
|
+
let frontier = Array.from(startNodes);
|
|
186
|
+
for (let depth = 0; depth < cfg.depth && frontier.length > 0; depth++) {
|
|
187
|
+
const nextFrontier = [];
|
|
188
|
+
for (const nodeId of frontier) {
|
|
189
|
+
for (const neighbor of getNeighbors(edges, nodeId, cfg.directions)) {
|
|
190
|
+
if (!visited.has(neighbor)) {
|
|
191
|
+
visited.add(neighbor);
|
|
192
|
+
result.add(neighbor);
|
|
193
|
+
nextFrontier.push(neighbor);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
frontier = nextFrontier;
|
|
198
|
+
}
|
|
199
|
+
if (hasDependencyDirection(cfg.directions)) {
|
|
200
|
+
traverseDependencies(edges, result, cfg);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function getNeighbors(edges, nodeId, directions) {
|
|
206
|
+
const neighbors = new Set();
|
|
207
|
+
for (const direction of directions) {
|
|
208
|
+
if (direction === "out") {
|
|
209
|
+
for (const edge of edges)
|
|
210
|
+
if (edge.from === nodeId)
|
|
211
|
+
neighbors.add(edge.to);
|
|
212
|
+
}
|
|
213
|
+
else if (direction === "in") {
|
|
214
|
+
for (const edge of edges)
|
|
215
|
+
if (edge.to === nodeId)
|
|
216
|
+
neighbors.add(edge.from);
|
|
217
|
+
}
|
|
218
|
+
else if (direction === "both") {
|
|
219
|
+
for (const edge of edges) {
|
|
220
|
+
if (edge.from === nodeId)
|
|
221
|
+
neighbors.add(edge.to);
|
|
222
|
+
if (edge.to === nodeId)
|
|
223
|
+
neighbors.add(edge.from);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return Array.from(neighbors);
|
|
228
|
+
}
|
|
229
|
+
function hasDependencyDirection(dirs) {
|
|
230
|
+
return dirs.some((d) => d === "up" || d === "down");
|
|
231
|
+
}
|
|
232
|
+
function traverseDependencies(edges, result, cfg) {
|
|
233
|
+
const labelIndex = new Map();
|
|
234
|
+
const childIndex = new Map();
|
|
235
|
+
for (const edge of edges) {
|
|
236
|
+
if (edge.label) {
|
|
237
|
+
if (!labelIndex.has(edge.label))
|
|
238
|
+
labelIndex.set(edge.label, edge);
|
|
239
|
+
}
|
|
240
|
+
if (edge.parent) {
|
|
241
|
+
if (!childIndex.has(edge.parent))
|
|
242
|
+
childIndex.set(edge.parent, []);
|
|
243
|
+
childIndex.get(edge.parent).push(edge);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
setGlobalLabelIndex(labelIndex);
|
|
247
|
+
let edgeQueue = [];
|
|
248
|
+
const edgeVisited = new Set();
|
|
249
|
+
for (const edge of edges) {
|
|
250
|
+
if (result.has(edge.from) || result.has(edge.to)) {
|
|
251
|
+
if (!edgeVisited.has(edge)) {
|
|
252
|
+
edgeVisited.add(edge);
|
|
253
|
+
edgeQueue.push(edge);
|
|
254
|
+
}
|
|
255
|
+
result.add(edge.from);
|
|
256
|
+
result.add(edge.to);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const dir of cfg.directions) {
|
|
260
|
+
if (dir === "up") {
|
|
261
|
+
for (let depth = 0; depth < cfg.depth && edgeQueue.length > 0; depth++) {
|
|
262
|
+
const nextQueue = [];
|
|
263
|
+
for (const edge of edgeQueue) {
|
|
264
|
+
if (edge.parent) {
|
|
265
|
+
const parent = labelIndex.get(edge.parent);
|
|
266
|
+
if (parent) {
|
|
267
|
+
result.add(parent.from);
|
|
268
|
+
result.add(parent.to);
|
|
269
|
+
if (!edgeVisited.has(parent)) {
|
|
270
|
+
edgeVisited.add(parent);
|
|
271
|
+
nextQueue.push(parent);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
edgeQueue = nextQueue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else if (dir === "down") {
|
|
280
|
+
for (let depth = 0; depth < cfg.depth && edgeQueue.length > 0; depth++) {
|
|
281
|
+
const nextQueue = [];
|
|
282
|
+
for (const edge of edgeQueue) {
|
|
283
|
+
for (const child of childIndex.get(edge.label ?? "") ?? []) {
|
|
284
|
+
result.add(child.from);
|
|
285
|
+
result.add(child.to);
|
|
286
|
+
if (!edgeVisited.has(child)) {
|
|
287
|
+
edgeVisited.add(child);
|
|
288
|
+
nextQueue.push(child);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
edgeQueue = nextQueue;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export class RemoveEdgeExpr {
|
|
298
|
+
pred;
|
|
299
|
+
constructor(pred) {
|
|
300
|
+
this.pred = pred;
|
|
301
|
+
}
|
|
302
|
+
apply(_ctx, input) {
|
|
303
|
+
const graph = input.graph;
|
|
304
|
+
const targetType = validateTargetType(this.pred);
|
|
305
|
+
const work = toWorkGraph(graph);
|
|
306
|
+
const result = new GraphBuilder();
|
|
307
|
+
for (const node of work.nodes.values())
|
|
308
|
+
result.addNode(cloneNode(node));
|
|
309
|
+
for (const set of work.sets.values()) {
|
|
310
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
311
|
+
}
|
|
312
|
+
for (const edge of work.edges) {
|
|
313
|
+
if (targetType === "edge" || targetType === "") {
|
|
314
|
+
if (!this.pred.evaluateEdge(edge))
|
|
315
|
+
result.addEdge(cloneEdge(edge));
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
result.addEdge(cloneEdge(edge));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { graph: result.finalize() };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export class RemoveAttributeExpr {
|
|
325
|
+
target;
|
|
326
|
+
attr;
|
|
327
|
+
pred;
|
|
328
|
+
constructor(target, attr, pred) {
|
|
329
|
+
this.target = target;
|
|
330
|
+
this.attr = attr;
|
|
331
|
+
this.pred = pred;
|
|
332
|
+
}
|
|
333
|
+
apply(_ctx, input) {
|
|
334
|
+
const graph = input.graph;
|
|
335
|
+
const targetType = validateTargetType(this.pred);
|
|
336
|
+
const work = toWorkGraph(graph);
|
|
337
|
+
const result = new GraphBuilder();
|
|
338
|
+
for (const set of work.sets.values()) {
|
|
339
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
340
|
+
}
|
|
341
|
+
for (const node of work.nodes.values()) {
|
|
342
|
+
const copy = cloneNode(node);
|
|
343
|
+
if (this.target === "node" && (targetType === "" || targetType === "node")) {
|
|
344
|
+
if (this.pred.evaluateNode(node))
|
|
345
|
+
delete copy.attributes[this.attr];
|
|
346
|
+
}
|
|
347
|
+
result.addNode(copy);
|
|
348
|
+
}
|
|
349
|
+
for (const edge of work.edges) {
|
|
350
|
+
const copy = cloneEdge(edge);
|
|
351
|
+
if (this.target === "edge" && (targetType === "" || targetType === "edge")) {
|
|
352
|
+
if (this.pred.evaluateEdge(edge))
|
|
353
|
+
delete copy.attributes[this.attr];
|
|
354
|
+
}
|
|
355
|
+
result.addEdge(copy);
|
|
356
|
+
}
|
|
357
|
+
return { graph: result.finalize() };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
export class RemoveOrphansExpr {
|
|
361
|
+
apply(_ctx, input) {
|
|
362
|
+
const graph = input.graph;
|
|
363
|
+
const work = toWorkGraph(graph);
|
|
364
|
+
const result = new GraphBuilder();
|
|
365
|
+
for (const set of work.sets.values()) {
|
|
366
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
367
|
+
}
|
|
368
|
+
const incident = new Set();
|
|
369
|
+
for (const edge of work.edges) {
|
|
370
|
+
incident.add(edge.from);
|
|
371
|
+
incident.add(edge.to);
|
|
372
|
+
}
|
|
373
|
+
for (const node of work.nodes.values()) {
|
|
374
|
+
if (incident.has(node.id))
|
|
375
|
+
result.addNode(cloneNode(node));
|
|
376
|
+
}
|
|
377
|
+
for (const edge of work.edges)
|
|
378
|
+
result.addEdge(cloneEdge(edge));
|
|
379
|
+
return { graph: result.finalize() };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
export class CollapseExpr {
|
|
383
|
+
nodeId;
|
|
384
|
+
pred;
|
|
385
|
+
constructor(nodeId, pred) {
|
|
386
|
+
this.nodeId = nodeId;
|
|
387
|
+
this.pred = pred;
|
|
388
|
+
}
|
|
389
|
+
apply(_ctx, input) {
|
|
390
|
+
const graph = input.graph;
|
|
391
|
+
const targetType = validateTargetType(this.pred);
|
|
392
|
+
if (targetType === "edge") {
|
|
393
|
+
throw new Error("collapse only works with node predicates");
|
|
394
|
+
}
|
|
395
|
+
const work = toWorkGraph(graph);
|
|
396
|
+
const collapsed = new Set();
|
|
397
|
+
for (const [id, node] of work.nodes) {
|
|
398
|
+
if (this.pred.evaluateNode(node))
|
|
399
|
+
collapsed.add(id);
|
|
400
|
+
}
|
|
401
|
+
if (collapsed.size === 0)
|
|
402
|
+
return { graph };
|
|
403
|
+
const result = new GraphBuilder();
|
|
404
|
+
for (const set of work.sets.values()) {
|
|
405
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
406
|
+
}
|
|
407
|
+
for (const node of work.nodes.values()) {
|
|
408
|
+
if (!collapsed.has(node.id))
|
|
409
|
+
result.addNode(cloneNode(node));
|
|
410
|
+
}
|
|
411
|
+
const merged = {};
|
|
412
|
+
for (const id of Array.from(collapsed).sort()) {
|
|
413
|
+
const node = work.nodes.get(id);
|
|
414
|
+
if (node) {
|
|
415
|
+
for (const k of Object.keys(node.attributes))
|
|
416
|
+
merged[k] = node.attributes[k];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
result.addNode({ id: this.nodeId, attributes: merged, sets: new Set(), parent: null });
|
|
420
|
+
const seen = new Set();
|
|
421
|
+
for (const edge of work.edges) {
|
|
422
|
+
const fromCollapsed = collapsed.has(edge.from);
|
|
423
|
+
const toCollapsed = collapsed.has(edge.to);
|
|
424
|
+
if (fromCollapsed && toCollapsed)
|
|
425
|
+
continue;
|
|
426
|
+
const newEdge = {
|
|
427
|
+
from: fromCollapsed ? this.nodeId : edge.from,
|
|
428
|
+
to: toCollapsed ? this.nodeId : edge.to,
|
|
429
|
+
label: null,
|
|
430
|
+
parent: null,
|
|
431
|
+
attributes: { ...edge.attributes },
|
|
432
|
+
sets: new Set(edge.sets),
|
|
433
|
+
scopeDepth: 0,
|
|
434
|
+
};
|
|
435
|
+
const key = edgeKey(newEdge);
|
|
436
|
+
if (!seen.has(key)) {
|
|
437
|
+
seen.add(key);
|
|
438
|
+
result.addEdge(newEdge);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return { graph: result.finalize() };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function edgeKey(edge) {
|
|
445
|
+
const keys = Object.keys(edge.attributes).sort();
|
|
446
|
+
let attrs = "";
|
|
447
|
+
for (const k of keys) {
|
|
448
|
+
if (attrs !== "")
|
|
449
|
+
attrs += ",";
|
|
450
|
+
attrs += `${k}:${String(edge.attributes[k])}`;
|
|
451
|
+
}
|
|
452
|
+
return `${edge.from}|${edge.to}|${attrs}`;
|
|
453
|
+
}
|
|
454
|
+
export class MakeExpr {
|
|
455
|
+
target;
|
|
456
|
+
attr;
|
|
457
|
+
value;
|
|
458
|
+
pred;
|
|
459
|
+
constructor(target, attr, value, pred) {
|
|
460
|
+
this.target = target;
|
|
461
|
+
this.attr = attr;
|
|
462
|
+
this.value = value;
|
|
463
|
+
this.pred = pred;
|
|
464
|
+
}
|
|
465
|
+
apply(_ctx, input) {
|
|
466
|
+
const graph = input.graph;
|
|
467
|
+
const targetType = validateTargetType(this.pred);
|
|
468
|
+
const work = toWorkGraph(graph);
|
|
469
|
+
const result = new GraphBuilder();
|
|
470
|
+
for (const set of work.sets.values()) {
|
|
471
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
472
|
+
}
|
|
473
|
+
for (const node of work.nodes.values()) {
|
|
474
|
+
const copy = cloneNode(node);
|
|
475
|
+
if (this.target === "node" && (targetType === "" || targetType === "node")) {
|
|
476
|
+
if (this.pred.evaluateNode(node))
|
|
477
|
+
copy.attributes[this.attr] = this.value;
|
|
478
|
+
}
|
|
479
|
+
result.addNode(copy);
|
|
480
|
+
}
|
|
481
|
+
for (const edge of work.edges) {
|
|
482
|
+
const copy = cloneEdge(edge);
|
|
483
|
+
if (this.target === "edge" && (targetType === "" || targetType === "edge")) {
|
|
484
|
+
if (this.pred.evaluateEdge(edge))
|
|
485
|
+
copy.attributes[this.attr] = this.value;
|
|
486
|
+
}
|
|
487
|
+
result.addEdge(copy);
|
|
488
|
+
}
|
|
489
|
+
return { graph: result.finalize() };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
export class GraphAlgebraExpr {
|
|
493
|
+
leftRef;
|
|
494
|
+
operator;
|
|
495
|
+
rightRef;
|
|
496
|
+
constructor(leftRef, operator, rightRef) {
|
|
497
|
+
this.leftRef = leftRef;
|
|
498
|
+
this.operator = operator;
|
|
499
|
+
this.rightRef = rightRef;
|
|
500
|
+
}
|
|
501
|
+
apply(ctx) {
|
|
502
|
+
const left = this.resolveGraph(ctx, this.leftRef);
|
|
503
|
+
const right = this.resolveGraph(ctx, this.rightRef);
|
|
504
|
+
switch (this.operator) {
|
|
505
|
+
case "+":
|
|
506
|
+
return { graph: this.union(left, right) };
|
|
507
|
+
case "&":
|
|
508
|
+
return { graph: this.intersection(left, right) };
|
|
509
|
+
case "-":
|
|
510
|
+
return { graph: this.difference(left, right) };
|
|
511
|
+
case "^":
|
|
512
|
+
return { graph: this.symmetricDifference(left, right) };
|
|
513
|
+
default:
|
|
514
|
+
throw new Error(`unknown algebra operator: ${this.operator}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
resolveGraph(ctx, ref) {
|
|
518
|
+
if (ref === "*")
|
|
519
|
+
return ctx.inputGraph;
|
|
520
|
+
const graph = ctx.namedGraphs.get(ref);
|
|
521
|
+
if (!graph)
|
|
522
|
+
throw new Error(`named graph not found: ${ref}`);
|
|
523
|
+
return graph;
|
|
524
|
+
}
|
|
525
|
+
union(left, right) {
|
|
526
|
+
const lb = toWorkGraph(left);
|
|
527
|
+
const rb = toWorkGraph(right);
|
|
528
|
+
const result = new GraphBuilder();
|
|
529
|
+
for (const node of lb.nodes.values())
|
|
530
|
+
result.addNode(cloneNode(node));
|
|
531
|
+
for (const edge of lb.edges)
|
|
532
|
+
result.addEdge(cloneEdge(edge));
|
|
533
|
+
for (const set of lb.sets.values()) {
|
|
534
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
535
|
+
}
|
|
536
|
+
for (const node of rb.nodes.values()) {
|
|
537
|
+
const existing = result.nodes.get(node.id);
|
|
538
|
+
if (existing) {
|
|
539
|
+
for (const k of Object.keys(node.attributes))
|
|
540
|
+
existing.attributes[k] = node.attributes[k];
|
|
541
|
+
for (const s of node.sets)
|
|
542
|
+
existing.sets.add(s);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
result.addNode(cloneNode(node));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
for (const edge of rb.edges)
|
|
549
|
+
result.addEdge(cloneEdge(edge));
|
|
550
|
+
for (const set of rb.sets.values()) {
|
|
551
|
+
const existing = result.sets.get(set.id);
|
|
552
|
+
if (existing) {
|
|
553
|
+
for (const k of Object.keys(set.attributes))
|
|
554
|
+
existing.attributes[k] = set.attributes[k];
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return result.finalize();
|
|
561
|
+
}
|
|
562
|
+
intersection(left, right) {
|
|
563
|
+
const lb = toWorkGraph(left);
|
|
564
|
+
const rb = toWorkGraph(right);
|
|
565
|
+
const result = new GraphBuilder();
|
|
566
|
+
const resultNodes = new Set();
|
|
567
|
+
for (const [id, node] of lb.nodes) {
|
|
568
|
+
if (rb.nodes.has(id)) {
|
|
569
|
+
result.addNode(cloneNode(node));
|
|
570
|
+
resultNodes.add(id);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const rightEdgeSet = new Set();
|
|
574
|
+
for (const edge of rb.edges)
|
|
575
|
+
rightEdgeSet.add(`${edge.from}|${edge.to}`);
|
|
576
|
+
for (const edge of lb.edges) {
|
|
577
|
+
if (rightEdgeSet.has(`${edge.from}|${edge.to}`) &&
|
|
578
|
+
resultNodes.has(edge.from) &&
|
|
579
|
+
resultNodes.has(edge.to)) {
|
|
580
|
+
result.addEdge(cloneEdge(edge));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for (const [id, set] of lb.sets) {
|
|
584
|
+
if (rb.sets.has(id))
|
|
585
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
586
|
+
}
|
|
587
|
+
return result.finalize();
|
|
588
|
+
}
|
|
589
|
+
difference(left, right) {
|
|
590
|
+
const lb = toWorkGraph(left);
|
|
591
|
+
const rb = toWorkGraph(right);
|
|
592
|
+
const result = new GraphBuilder();
|
|
593
|
+
const resultNodes = new Set();
|
|
594
|
+
for (const [id, node] of lb.nodes) {
|
|
595
|
+
if (!rb.nodes.has(id)) {
|
|
596
|
+
result.addNode(cloneNode(node));
|
|
597
|
+
resultNodes.add(id);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const rightEdgeSet = new Set();
|
|
601
|
+
for (const edge of rb.edges)
|
|
602
|
+
rightEdgeSet.add(`${edge.from}|${edge.to}`);
|
|
603
|
+
for (const edge of lb.edges) {
|
|
604
|
+
if (!rightEdgeSet.has(`${edge.from}|${edge.to}`) && resultNodes.has(edge.from) && resultNodes.has(edge.to)) {
|
|
605
|
+
result.addEdge(cloneEdge(edge));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
for (const [id, set] of lb.sets) {
|
|
609
|
+
if (!rb.sets.has(id))
|
|
610
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
611
|
+
}
|
|
612
|
+
return result.finalize();
|
|
613
|
+
}
|
|
614
|
+
symmetricDifference(left, right) {
|
|
615
|
+
const lb = toWorkGraph(left);
|
|
616
|
+
const rb = toWorkGraph(right);
|
|
617
|
+
const result = new GraphBuilder();
|
|
618
|
+
const resultNodes = new Set();
|
|
619
|
+
for (const [id, node] of lb.nodes) {
|
|
620
|
+
if (!rb.nodes.has(id)) {
|
|
621
|
+
result.addNode(cloneNode(node));
|
|
622
|
+
resultNodes.add(id);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
for (const [id, node] of rb.nodes) {
|
|
626
|
+
if (!lb.nodes.has(id)) {
|
|
627
|
+
result.addNode(cloneNode(node));
|
|
628
|
+
resultNodes.add(id);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const leftEdgeSet = new Set();
|
|
632
|
+
const rightEdgeSet = new Set();
|
|
633
|
+
for (const edge of lb.edges)
|
|
634
|
+
leftEdgeSet.add(`${edge.from}|${edge.to}`);
|
|
635
|
+
for (const edge of rb.edges)
|
|
636
|
+
rightEdgeSet.add(`${edge.from}|${edge.to}`);
|
|
637
|
+
for (const edge of lb.edges) {
|
|
638
|
+
if (!rightEdgeSet.has(`${edge.from}|${edge.to}`) && resultNodes.has(edge.from) && resultNodes.has(edge.to)) {
|
|
639
|
+
result.addEdge(cloneEdge(edge));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
for (const edge of rb.edges) {
|
|
643
|
+
if (!leftEdgeSet.has(`${edge.from}|${edge.to}`) && resultNodes.has(edge.from) && resultNodes.has(edge.to)) {
|
|
644
|
+
result.addEdge(cloneEdge(edge));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
for (const [id, set] of lb.sets) {
|
|
648
|
+
if (!rb.sets.has(id))
|
|
649
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
650
|
+
}
|
|
651
|
+
for (const [id, set] of rb.sets) {
|
|
652
|
+
if (!lb.sets.has(id))
|
|
653
|
+
result.addSet({ id: set.id, attributes: { ...set.attributes } });
|
|
654
|
+
}
|
|
655
|
+
return result.finalize();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
//# sourceMappingURL=expressions.js.map
|