-
Notifications
You must be signed in to change notification settings - Fork 1
/
NemoRunner.ts
221 lines (185 loc) · 6.39 KB
/
NemoRunner.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import {
getNemoVersion,
NemoEngine,
NemoProgram,
NemoResults,
setAllocErrorHook,
setPanicHook,
} from "./nemoWASM";
import { Timer } from "../Timer";
setPanicHook();
setAllocErrorHook();
class NemoResultsIterable {
public constructor(private iterator: NemoResults) {}
public [Symbol.iterator]() {
return this.iterator;
}
}
function* take<T>(iterable: Iterable<T>, count: number) {
if (count === 0) {
return;
}
for (const value of iterable) {
yield value;
count--;
if (count === 0) {
break;
}
}
}
export type FactCounts = Awaited<ReturnType<NemoRunner["getCounts"]>>;
/**
* Allows for interaction with the Nemo WASM API
*
* Everything that interacts with a {@link NemoProgram} or {@link NemoEngine} should be added here as new asynchronous functions.
*
* All code in this class should be executable inside or outside of web worker environments.
* This allows to use the Nemo API with or without a web worker, depending on the web browser support.
* Detecting this is handled by NemoWorker.
*
* Because interaction with a web worker is always asynchronous, all functions in the NemoRunner class must only return promises (e.g. by being async functions).
* Otherwise the interface would break between the web worker and non web worker environments.
*/
export class NemoRunner {
private program = new NemoProgram("");
private engine = new NemoEngine(this.program, {});
private nextResultIterableID = 1;
private resultsIterables: { [id: number]: NemoResultsIterable } = {};
public async parseProgram(programText: string) {
console.info("[NemoRunner] Parsing program");
const timer = new Timer("parse");
this.program = new NemoProgram(programText);
return {
edbPredicates: this.program.getEDBPredicates(),
outputPredicates: this.program.getOutputPredicates(),
parsingDuration: timer.getDuration(),
};
}
public async start(initialResourceBlobs: { [resource: string]: Blob }) {
console.info("[NemoRunner] Preparing resource blobs");
const resourceBlobs = { ...initialResourceBlobs };
// resources in programs are just Nemo Terms and transforming them to strings wraps the actual strings in quotes, that is why we remove quotes here
for (const resource_with_quotes of this.program.getResourcesUsedInImports()) {
const resource = resource_with_quotes.slice(1, -1);
if (resource in resourceBlobs) {
continue;
}
let url: URL;
try {
url = new URL(resource);
} catch (error) {
throw new Error(`Could not parse resource \`${resource}\` as URL`);
}
if (!["http:", "https:"].includes(url.protocol)) {
throw new Error(`Invalid protocol in resource \`${resource}\``);
}
try {
const response = await fetch(resource, { mode: "cors" });
if (!response.ok) {
throw new Error(
`Request for resource \`${resource}\` failed with status code \`${response.status}\``,
);
}
resourceBlobs[resource] = await response.blob();
} catch (error) {
console.warn("[NemoRunner] Error while fetching resource", {
resource,
error,
});
throw new Error(
`Request for resource \`${resource}\` failed due to a network error (e.g. a DNS/TLS error or missing CORS headers).`,
);
}
}
console.info("[NemoRunner] Reasoning", resourceBlobs);
const initializationTimer = new Timer("initializationTimer");
this.engine = new NemoEngine(this.program, resourceBlobs);
const initializationDuration = initializationTimer.getDuration();
const reasoningTimer = new Timer("reason");
this.engine.reason();
return {
initializationDuration,
reasoningDuration: reasoningTimer.getDuration(),
};
}
public async getCounts() {
const outputPredicateCounts = Object.fromEntries(
await Promise.all(
this.program
.getOutputPredicates()
.map((predicate) => [
predicate,
this.engine.countFactsOfPredicate(predicate),
]),
),
);
return {
factsOfDerivedPredicates: this.engine.countFactsOfDerivedPredicates(),
outputPredicates: outputPredicateCounts,
};
}
public async initializeResultsIterable(predicate: string): Promise<number> {
const id = this.nextResultIterableID;
this.nextResultIterableID++;
this.resultsIterables[id] = new NemoResultsIterable(
this.engine.getResult(predicate),
);
return id;
}
public async loadNextRowsOfResultsIterable(id: number, maxCount: number) {
if (!(id in this.resultsIterables)) {
throw new Error();
}
// Fetch next rows from web worker
return Array.from(take(this.resultsIterables[id], maxCount)) as any[][];
}
public async deleteResultsIterable(id: number) {
if (!(id in this.resultsIterables)) {
throw new Error();
}
delete this.resultsIterables[id];
}
public async writeResultsToFileHandle(
predicate: string,
fileHandle: FileSystemFileHandle,
) {
const syncAccessHandle = await (fileHandle as any).createSyncAccessHandle();
this.engine.savePredicate(predicate, syncAccessHandle);
}
public async traceFactAtIndexAscii(predicate: string, rowIndex: number) {
return this.engine.traceFactAtIndexAscii(predicate, rowIndex);
}
public async traceFactAtIndexGraphMlTree(
predicate: string,
rowIndex: number,
) {
return this.engine.traceFactAtIndexGraphMlTree(predicate, rowIndex);
}
public async traceFactAtIndexGraphMlDag(predicate: string, rowIndex: number) {
return this.engine.traceFactAtIndexGraphMlDag(predicate, rowIndex);
}
public async parseAndTraceFactAscii(fact: string) {
return this.engine.parseAndTraceFactAscii(fact);
}
public async parseAndTraceFactGraphMlTree(fact: string) {
return this.engine.parseAndTraceFactGraphMlTree(fact);
}
public async parseAndTraceFactGraphMlDag(fact: string) {
return this.engine.parseAndTraceFactGraphMlDag(fact);
}
public async getNemoVersion() {
return getNemoVersion();
}
public async markDefaultOutputs() {
return this.program.markDefaultOutputs();
}
public async getOutputPredicates() {
return this.program.getOutputPredicates();
}
/*
* This additionally gets handled by `NemoWorker` to terminate the web worker
*/
public async stop() {
console.info("[NemoRunner] Stopping runner");
}
}