@hzhangxyz/ddss 0.0.10 → 0.0.29
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 +144 -0
- package/dist/index.js +617 -0
- package/package.json +23 -22
- package/dist/main.js +0 -58
package/dist/index.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import { Sequelize, DataTypes, Model, Op } from 'sequelize';
|
|
7
|
+
import { Search as Search$1, Term, List, Rule } from 'atsds';
|
|
8
|
+
import { EGraph } from 'atsds-egg';
|
|
9
|
+
import * as readline from 'node:readline/promises';
|
|
10
|
+
import { stdout, stdin } from 'node:process';
|
|
11
|
+
import { parse, unparse } from 'atsds-bnf';
|
|
12
|
+
import * as readline$1 from 'node:readline';
|
|
13
|
+
|
|
14
|
+
// 定义基础模型类
|
|
15
|
+
class Fact extends Model {
|
|
16
|
+
}
|
|
17
|
+
class Idea extends Model {
|
|
18
|
+
}
|
|
19
|
+
async function initializeDatabase(addr) {
|
|
20
|
+
// 转换地址:Python 的 sqlite:///path 转为 Sequelize 的 sqlite:path
|
|
21
|
+
let sequelizeAddr = addr;
|
|
22
|
+
if (addr.startsWith("sqlite:///")) {
|
|
23
|
+
sequelizeAddr = `sqlite:${addr.replace("sqlite:///", "")}`;
|
|
24
|
+
}
|
|
25
|
+
const sequelize = new Sequelize(sequelizeAddr, {
|
|
26
|
+
logging: false,
|
|
27
|
+
define: {
|
|
28
|
+
timestamps: false, // 去掉 createdAt 和 updatedAt
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
Fact.init({
|
|
32
|
+
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
33
|
+
data: { type: DataTypes.TEXT, unique: true, allowNull: false },
|
|
34
|
+
}, { sequelize, tableName: "facts" });
|
|
35
|
+
Idea.init({
|
|
36
|
+
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
37
|
+
data: { type: DataTypes.TEXT, unique: true, allowNull: false },
|
|
38
|
+
}, { sequelize, tableName: "ideas" });
|
|
39
|
+
await sequelize.sync();
|
|
40
|
+
return sequelize;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 对应 Python 中的 insert_or_ignore。
|
|
44
|
+
* Sequelize 的 bulkCreate 配合 ignoreDuplicates 可以实现跨方言的“重复则跳过”。
|
|
45
|
+
*/
|
|
46
|
+
async function insertOrIgnore(model, data) {
|
|
47
|
+
try {
|
|
48
|
+
await model.bulkCreate([{ data }], {
|
|
49
|
+
ignoreDuplicates: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// 忽略重复键错误
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function strRuleGetStrIdea(data) {
|
|
58
|
+
if (!data.startsWith("--")) {
|
|
59
|
+
const lines = data.split("\n");
|
|
60
|
+
if (lines.length > 0) {
|
|
61
|
+
return `----\n${lines[0]}\n`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Patches process.stdout.write to prevent asynchronous logs from messing up the readline prompt.
|
|
68
|
+
*/
|
|
69
|
+
function patchStdout(rl) {
|
|
70
|
+
const originalWrite = process.stdout.write;
|
|
71
|
+
let isReprompting = false;
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
process.stdout.write = (chunk, encoding, callback) => {
|
|
74
|
+
if (isReprompting) {
|
|
75
|
+
return originalWrite.call(process.stdout, chunk, encoding, callback);
|
|
76
|
+
}
|
|
77
|
+
const str = chunk.toString();
|
|
78
|
+
// If it's just a newline (like when the user hits Enter), let it pass through.
|
|
79
|
+
// This allows the input line to stay in the terminal history.
|
|
80
|
+
if (str === "\n" || str === "\r\n") {
|
|
81
|
+
return originalWrite.call(process.stdout, chunk, encoding, callback);
|
|
82
|
+
}
|
|
83
|
+
if (str.includes("\n")) {
|
|
84
|
+
// Move to start of line and clear it (wiping the current prompt display)
|
|
85
|
+
originalWrite.call(process.stdout, "\r\x1b[K");
|
|
86
|
+
const result = originalWrite.call(process.stdout, chunk, encoding, callback);
|
|
87
|
+
isReprompting = true;
|
|
88
|
+
// Redraw the prompt and current input buffer on the NEW line
|
|
89
|
+
rl.prompt(true);
|
|
90
|
+
isReprompting = false;
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
return originalWrite.call(process.stdout, chunk, encoding, callback);
|
|
94
|
+
};
|
|
95
|
+
return () => {
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
process.stdout.write = originalWrite;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function main$5(addr, sequelize) {
|
|
102
|
+
if (!sequelize) {
|
|
103
|
+
sequelize = await initializeDatabase(addr);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const search = new Search$1();
|
|
107
|
+
let maxFact = -1;
|
|
108
|
+
while (true) {
|
|
109
|
+
const begin = Date.now();
|
|
110
|
+
let count = 0;
|
|
111
|
+
const newFacts = await Fact.findAll({
|
|
112
|
+
where: { id: { [Op.gt]: maxFact } },
|
|
113
|
+
order: [["id", "ASC"]],
|
|
114
|
+
});
|
|
115
|
+
for (const fact of newFacts) {
|
|
116
|
+
maxFact = Math.max(maxFact, fact.id);
|
|
117
|
+
search.add(fact.data);
|
|
118
|
+
}
|
|
119
|
+
const tasks = [];
|
|
120
|
+
const handler = (rule) => {
|
|
121
|
+
const dsStr = rule.toString();
|
|
122
|
+
tasks.push(insertOrIgnore(Fact, dsStr));
|
|
123
|
+
const idea = strRuleGetStrIdea(dsStr);
|
|
124
|
+
if (idea) {
|
|
125
|
+
tasks.push(insertOrIgnore(Idea, idea));
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
};
|
|
129
|
+
count = search.execute(handler);
|
|
130
|
+
await Promise.all(tasks);
|
|
131
|
+
const end = Date.now();
|
|
132
|
+
const duration = (end - begin) / 1000;
|
|
133
|
+
if (count === 0) {
|
|
134
|
+
const delay = Math.max(0, 0.1 - duration);
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
// Handle error
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildTermToRule(data) {
|
|
145
|
+
return new Rule(`----\n${data.toString()}\n`);
|
|
146
|
+
}
|
|
147
|
+
function extractLhsRhsFromRule(data) {
|
|
148
|
+
if (data.length() !== 0) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const term = data.conclusion();
|
|
152
|
+
const inner = term.term();
|
|
153
|
+
if (!(inner instanceof List)) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
// (binary == lhs rhs) -> list of 4 elements: "binary", "==", lhs, rhs
|
|
157
|
+
if (!(inner.length() === 4 && inner.getitem(0).toString() === "binary" && inner.getitem(1).toString() === "==")) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return [inner.getitem(2), inner.getitem(3)];
|
|
161
|
+
}
|
|
162
|
+
function buildLhsRhsToTerm(lhs, rhs) {
|
|
163
|
+
return new Term(`(binary == ${lhs.toString()} ${rhs.toString()})`);
|
|
164
|
+
}
|
|
165
|
+
class InternalEGraph {
|
|
166
|
+
core;
|
|
167
|
+
mapping;
|
|
168
|
+
constructor() {
|
|
169
|
+
this.core = new EGraph();
|
|
170
|
+
this.mapping = new Map();
|
|
171
|
+
}
|
|
172
|
+
getOrAdd(data) {
|
|
173
|
+
const key = data.toString();
|
|
174
|
+
if (!this.mapping.has(key)) {
|
|
175
|
+
this.mapping.set(key, this.core.add(data));
|
|
176
|
+
}
|
|
177
|
+
return this.mapping.get(key);
|
|
178
|
+
}
|
|
179
|
+
find(data) {
|
|
180
|
+
const dataId = this.getOrAdd(data);
|
|
181
|
+
return this.core.find(dataId);
|
|
182
|
+
}
|
|
183
|
+
setEquality(lhs, rhs) {
|
|
184
|
+
const lhsId = this.getOrAdd(lhs);
|
|
185
|
+
const rhsId = this.getOrAdd(rhs);
|
|
186
|
+
this.core.merge(lhsId, rhsId);
|
|
187
|
+
}
|
|
188
|
+
getEquality(lhs, rhs) {
|
|
189
|
+
const lhsId = this.getOrAdd(lhs);
|
|
190
|
+
const rhsId = this.getOrAdd(rhs);
|
|
191
|
+
return this.core.find(lhsId) === this.core.find(rhsId);
|
|
192
|
+
}
|
|
193
|
+
rebuild() {
|
|
194
|
+
this.core.rebuild();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
class Search {
|
|
198
|
+
egraph;
|
|
199
|
+
terms;
|
|
200
|
+
facts;
|
|
201
|
+
newlyAddedTerms;
|
|
202
|
+
newlyAddedFacts;
|
|
203
|
+
factMatchingCache;
|
|
204
|
+
constructor() {
|
|
205
|
+
this.egraph = new InternalEGraph();
|
|
206
|
+
this.terms = new Set();
|
|
207
|
+
this.facts = new Set();
|
|
208
|
+
this.newlyAddedTerms = new Set();
|
|
209
|
+
this.newlyAddedFacts = new Set();
|
|
210
|
+
this.factMatchingCache = new Map();
|
|
211
|
+
}
|
|
212
|
+
rebuild() {
|
|
213
|
+
this.egraph.rebuild();
|
|
214
|
+
for (const factStr of this.facts) {
|
|
215
|
+
const fact = new Term(factStr);
|
|
216
|
+
if (!this.factMatchingCache.has(factStr)) {
|
|
217
|
+
this.factMatchingCache.set(factStr, new Set());
|
|
218
|
+
}
|
|
219
|
+
const candidates = this.collectMatchingCandidates(fact, Array.from(this.newlyAddedTerms).map((s) => new Term(s)));
|
|
220
|
+
for (const c of candidates) {
|
|
221
|
+
this.factMatchingCache.get(factStr).add(c.toString());
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const factStr of this.newlyAddedFacts) {
|
|
225
|
+
const fact = new Term(factStr);
|
|
226
|
+
if (!this.factMatchingCache.has(factStr)) {
|
|
227
|
+
this.factMatchingCache.set(factStr, new Set());
|
|
228
|
+
}
|
|
229
|
+
const candidates = this.collectMatchingCandidates(fact, Array.from(this.terms).map((s) => new Term(s)));
|
|
230
|
+
for (const c of candidates) {
|
|
231
|
+
this.factMatchingCache.get(factStr).add(c.toString());
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.newlyAddedTerms.clear();
|
|
235
|
+
this.newlyAddedFacts.clear();
|
|
236
|
+
}
|
|
237
|
+
add(data) {
|
|
238
|
+
this.addExpr(data);
|
|
239
|
+
this.addFact(data);
|
|
240
|
+
}
|
|
241
|
+
addExpr(data) {
|
|
242
|
+
const lhsRhs = extractLhsRhsFromRule(data);
|
|
243
|
+
if (!lhsRhs)
|
|
244
|
+
return;
|
|
245
|
+
const [lhs, rhs] = lhsRhs;
|
|
246
|
+
this.terms.add(lhs.toString());
|
|
247
|
+
this.newlyAddedTerms.add(lhs.toString());
|
|
248
|
+
this.terms.add(rhs.toString());
|
|
249
|
+
this.newlyAddedTerms.add(rhs.toString());
|
|
250
|
+
this.egraph.setEquality(lhs, rhs);
|
|
251
|
+
}
|
|
252
|
+
addFact(data) {
|
|
253
|
+
if (data.length() !== 0)
|
|
254
|
+
return;
|
|
255
|
+
const term = data.conclusion();
|
|
256
|
+
this.terms.add(term.toString());
|
|
257
|
+
this.newlyAddedTerms.add(term.toString());
|
|
258
|
+
this.facts.add(term.toString());
|
|
259
|
+
this.newlyAddedFacts.add(term.toString());
|
|
260
|
+
}
|
|
261
|
+
*execute(data) {
|
|
262
|
+
yield* this.executeExpr(data);
|
|
263
|
+
yield* this.executeFact(data);
|
|
264
|
+
}
|
|
265
|
+
*executeExpr(data) {
|
|
266
|
+
const lhsRhs = extractLhsRhsFromRule(data);
|
|
267
|
+
if (!lhsRhs)
|
|
268
|
+
return;
|
|
269
|
+
const [lhs, rhs] = lhsRhs;
|
|
270
|
+
if (this.egraph.getEquality(lhs, rhs)) {
|
|
271
|
+
yield data;
|
|
272
|
+
}
|
|
273
|
+
const lhsPool = this.collectMatchingCandidates(lhs, Array.from(this.terms).map((s) => new Term(s)));
|
|
274
|
+
const rhsPool = this.collectMatchingCandidates(rhs, Array.from(this.terms).map((s) => new Term(s)));
|
|
275
|
+
if (lhsPool.length === 0 || rhsPool.length === 0)
|
|
276
|
+
return;
|
|
277
|
+
const lhsGroups = this.groupByEquivalenceClass(lhsPool);
|
|
278
|
+
const rhsGroups = this.groupByEquivalenceClass(rhsPool);
|
|
279
|
+
for (const lhsGroup of lhsGroups) {
|
|
280
|
+
for (const rhsGroup of rhsGroups) {
|
|
281
|
+
if (lhsGroup.size > 0 && rhsGroup.size > 0) {
|
|
282
|
+
const firstLhs = new Term(lhsGroup.values().next().value);
|
|
283
|
+
const firstRhs = new Term(rhsGroup.values().next().value);
|
|
284
|
+
if (this.egraph.getEquality(firstLhs, firstRhs)) {
|
|
285
|
+
for (const xStr of lhsGroup) {
|
|
286
|
+
for (const yStr of rhsGroup) {
|
|
287
|
+
const x = new Term(xStr);
|
|
288
|
+
const y = new Term(yStr);
|
|
289
|
+
const target = buildLhsRhsToTerm(x, y);
|
|
290
|
+
const query = data.conclusion();
|
|
291
|
+
const unification = target.match(query);
|
|
292
|
+
if (unification) {
|
|
293
|
+
const result = target.ground(unification, "1");
|
|
294
|
+
if (result) {
|
|
295
|
+
yield buildTermToRule(result);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
*executeFact(data) {
|
|
306
|
+
if (data.length() !== 0)
|
|
307
|
+
return;
|
|
308
|
+
const idea = data.conclusion();
|
|
309
|
+
for (const factStr of this.facts) {
|
|
310
|
+
if (this.egraph.getEquality(idea, new Term(factStr))) {
|
|
311
|
+
yield data;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const ideaPool = this.collectMatchingCandidates(idea, Array.from(this.terms).map((s) => new Term(s)));
|
|
315
|
+
if (ideaPool.length === 0)
|
|
316
|
+
return;
|
|
317
|
+
const ideaGroups = this.groupByEquivalenceClass(ideaPool);
|
|
318
|
+
for (const factStr of this.facts) {
|
|
319
|
+
const factPoolStr = this.factMatchingCache.get(factStr);
|
|
320
|
+
if (!factPoolStr || factPoolStr.size === 0)
|
|
321
|
+
continue;
|
|
322
|
+
const factPool = Array.from(factPoolStr).map((s) => new Term(s));
|
|
323
|
+
const factGroups = this.groupByEquivalenceClass(factPool);
|
|
324
|
+
for (const ideaGroup of ideaGroups) {
|
|
325
|
+
for (const factGroup of factGroups) {
|
|
326
|
+
if (ideaGroup.size > 0 && factGroup.size > 0) {
|
|
327
|
+
const firstIdea = new Term(ideaGroup.values().next().value);
|
|
328
|
+
const firstFact = new Term(factGroup.values().next().value);
|
|
329
|
+
if (this.egraph.getEquality(firstIdea, firstFact)) {
|
|
330
|
+
for (const xStr of ideaGroup) {
|
|
331
|
+
for (const yStr of factGroup) {
|
|
332
|
+
const x = new Term(xStr);
|
|
333
|
+
const y = new Term(yStr);
|
|
334
|
+
const target = buildLhsRhsToTerm(x, y);
|
|
335
|
+
const query = buildLhsRhsToTerm(idea, new Term(factStr));
|
|
336
|
+
const unification = target.match(query);
|
|
337
|
+
if (unification) {
|
|
338
|
+
const result = target.ground(unification, "1");
|
|
339
|
+
if (result) {
|
|
340
|
+
const inner = result.term();
|
|
341
|
+
if (inner instanceof List && inner.length() >= 3) {
|
|
342
|
+
yield buildTermToRule(inner.getitem(2));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
collectMatchingCandidates(pattern, candidates) {
|
|
355
|
+
return candidates.filter((c) => pattern.match(c) !== null);
|
|
356
|
+
}
|
|
357
|
+
groupByEquivalenceClass(terms) {
|
|
358
|
+
if (terms.length === 0)
|
|
359
|
+
return [];
|
|
360
|
+
const eidToTerms = new Map();
|
|
361
|
+
for (const term of terms) {
|
|
362
|
+
const eid = this.egraph.find(term);
|
|
363
|
+
if (!eidToTerms.has(eid)) {
|
|
364
|
+
eidToTerms.set(eid, new Set());
|
|
365
|
+
}
|
|
366
|
+
eidToTerms.get(eid).add(term.toString());
|
|
367
|
+
}
|
|
368
|
+
return Array.from(eidToTerms.values());
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function main$4(addr, sequelize) {
|
|
373
|
+
if (!sequelize) {
|
|
374
|
+
sequelize = await initializeDatabase(addr);
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
const search = new Search();
|
|
378
|
+
let pool = [];
|
|
379
|
+
let maxFact = -1;
|
|
380
|
+
let maxIdea = -1;
|
|
381
|
+
while (true) {
|
|
382
|
+
const begin = Date.now();
|
|
383
|
+
let count = 0;
|
|
384
|
+
const newIdeas = await Idea.findAll({
|
|
385
|
+
where: { id: { [Op.gt]: maxIdea } },
|
|
386
|
+
order: [["id", "ASC"]],
|
|
387
|
+
});
|
|
388
|
+
for (const idea of newIdeas) {
|
|
389
|
+
maxIdea = Math.max(maxIdea, idea.id);
|
|
390
|
+
pool.push(new Rule(idea.data));
|
|
391
|
+
}
|
|
392
|
+
const newFacts = await Fact.findAll({
|
|
393
|
+
where: { id: { [Op.gt]: maxFact } },
|
|
394
|
+
order: [["id", "ASC"]],
|
|
395
|
+
});
|
|
396
|
+
for (const fact of newFacts) {
|
|
397
|
+
maxFact = Math.max(maxFact, fact.id);
|
|
398
|
+
search.add(new Rule(fact.data));
|
|
399
|
+
}
|
|
400
|
+
search.rebuild();
|
|
401
|
+
const tasks = [];
|
|
402
|
+
const nextPool = [];
|
|
403
|
+
for (const i of pool) {
|
|
404
|
+
let found = false;
|
|
405
|
+
for (const o of search.execute(i)) {
|
|
406
|
+
tasks.push(insertOrIgnore(Fact, o.toString()));
|
|
407
|
+
count++;
|
|
408
|
+
if (i.toString() === o.toString()) {
|
|
409
|
+
found = true;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (!found) {
|
|
414
|
+
nextPool.push(i);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
pool = nextPool;
|
|
418
|
+
await Promise.all(tasks);
|
|
419
|
+
const end = Date.now();
|
|
420
|
+
const duration = (end - begin) / 1000;
|
|
421
|
+
if (count === 0) {
|
|
422
|
+
const delay = Math.max(0, 0.1 - duration);
|
|
423
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
// Handle error
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function main$3(addr, sequelize) {
|
|
433
|
+
if (!sequelize) {
|
|
434
|
+
sequelize = await initializeDatabase(addr);
|
|
435
|
+
}
|
|
436
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
437
|
+
rl.setPrompt("input: ");
|
|
438
|
+
const unpatch = patchStdout(rl);
|
|
439
|
+
try {
|
|
440
|
+
rl.prompt(); // Initial prompt
|
|
441
|
+
for await (const line of rl) {
|
|
442
|
+
const data = line.trim();
|
|
443
|
+
if (data === "" || data.startsWith("//")) {
|
|
444
|
+
rl.prompt();
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
const ds = parse(data);
|
|
449
|
+
const dsStr = ds.toString();
|
|
450
|
+
await insertOrIgnore(Fact, dsStr);
|
|
451
|
+
const idea = strRuleGetStrIdea(dsStr);
|
|
452
|
+
if (idea) {
|
|
453
|
+
await insertOrIgnore(Idea, idea);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
console.error(`error: ${e}`);
|
|
458
|
+
}
|
|
459
|
+
rl.prompt();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
// Silent catch for unexpected termination
|
|
464
|
+
}
|
|
465
|
+
finally {
|
|
466
|
+
unpatch();
|
|
467
|
+
rl.close();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function main$2(addr, sequelize) {
|
|
472
|
+
if (!sequelize) {
|
|
473
|
+
sequelize = await initializeDatabase(addr);
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
let maxFact = -1;
|
|
477
|
+
let maxIdea = -1;
|
|
478
|
+
while (true) {
|
|
479
|
+
const begin = Date.now();
|
|
480
|
+
let count = 0;
|
|
481
|
+
const newIdeas = await Idea.findAll({
|
|
482
|
+
where: { id: { [Op.gt]: maxIdea } },
|
|
483
|
+
order: [["id", "ASC"]],
|
|
484
|
+
});
|
|
485
|
+
for (const idea of newIdeas) {
|
|
486
|
+
maxIdea = Math.max(maxIdea, idea.id);
|
|
487
|
+
console.log("idea:", unparse(idea.data));
|
|
488
|
+
count++;
|
|
489
|
+
}
|
|
490
|
+
const newFacts = await Fact.findAll({
|
|
491
|
+
where: { id: { [Op.gt]: maxFact } },
|
|
492
|
+
order: [["id", "ASC"]],
|
|
493
|
+
});
|
|
494
|
+
for (const fact of newFacts) {
|
|
495
|
+
maxFact = Math.max(maxFact, fact.id);
|
|
496
|
+
console.log("fact:", unparse(fact.data));
|
|
497
|
+
count++;
|
|
498
|
+
}
|
|
499
|
+
const end = Date.now();
|
|
500
|
+
const duration = (end - begin) / 1000;
|
|
501
|
+
if (count === 0) {
|
|
502
|
+
const delay = Math.max(0, 0.1 - duration);
|
|
503
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (err) {
|
|
508
|
+
// Handle error
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function main$1(addr, sequelize) {
|
|
513
|
+
if (!sequelize) {
|
|
514
|
+
sequelize = await initializeDatabase(addr);
|
|
515
|
+
}
|
|
516
|
+
const rl = readline$1.createInterface({
|
|
517
|
+
input: process.stdin,
|
|
518
|
+
terminal: false,
|
|
519
|
+
});
|
|
520
|
+
try {
|
|
521
|
+
for await (const line of rl) {
|
|
522
|
+
const data = line.trim();
|
|
523
|
+
if (data === "" || data.startsWith("//")) {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
const ds = parse(data);
|
|
528
|
+
const dsStr = ds.toString();
|
|
529
|
+
await insertOrIgnore(Fact, dsStr);
|
|
530
|
+
const idea = strRuleGetStrIdea(dsStr);
|
|
531
|
+
if (idea) {
|
|
532
|
+
await insertOrIgnore(Idea, idea);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch (e) {
|
|
536
|
+
console.error(`error: ${e}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
rl.close();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function main(addr, sequelize) {
|
|
546
|
+
if (!sequelize) {
|
|
547
|
+
sequelize = await initializeDatabase(addr);
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
const ideas = await Idea.findAll();
|
|
551
|
+
for (const idea of ideas) {
|
|
552
|
+
console.log("idea:", unparse(idea.data));
|
|
553
|
+
}
|
|
554
|
+
const facts = await Fact.findAll();
|
|
555
|
+
for (const fact of facts) {
|
|
556
|
+
console.log("fact:", unparse(fact.data));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
finally {
|
|
560
|
+
// Session is handled by main or caller
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const componentMap = {
|
|
565
|
+
ds: main$5,
|
|
566
|
+
egg: main$4,
|
|
567
|
+
input: main$3,
|
|
568
|
+
output: main$2,
|
|
569
|
+
load: main$1,
|
|
570
|
+
dump: main,
|
|
571
|
+
};
|
|
572
|
+
async function run(addr, components) {
|
|
573
|
+
const sequelize = await initializeDatabase(addr);
|
|
574
|
+
try {
|
|
575
|
+
const promises = components.map((name) => {
|
|
576
|
+
const component = componentMap[name];
|
|
577
|
+
if (!component) {
|
|
578
|
+
console.error(`error: unsupported component: ${name}`);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
return component(addr, sequelize);
|
|
582
|
+
});
|
|
583
|
+
// Run all components in parallel.
|
|
584
|
+
// Note: Some components like input/output run forever.
|
|
585
|
+
await Promise.all(promises);
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
// Handle error
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
await sequelize.close();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function cli() {
|
|
595
|
+
const program = new Command();
|
|
596
|
+
program
|
|
597
|
+
.name("ddss")
|
|
598
|
+
.description("DDSS - Distributed Deductive System Sorts: Run DDSS with an interactive deductive environment.")
|
|
599
|
+
.option("-a, --addr <url>", "Database address URL. If not provided, uses a temporary SQLite database.")
|
|
600
|
+
.option("-c, --component <names...>", "Components to run.", ["input", "output", "ds", "egg"])
|
|
601
|
+
.action(async (options) => {
|
|
602
|
+
let addr = options.addr;
|
|
603
|
+
if (!addr) {
|
|
604
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ddss-"));
|
|
605
|
+
const dbPath = path.join(tmpDir, "ddss.db");
|
|
606
|
+
addr = `sqlite:///${dbPath}`;
|
|
607
|
+
}
|
|
608
|
+
console.log(`addr: ${addr}`);
|
|
609
|
+
await run(addr, options.component);
|
|
610
|
+
});
|
|
611
|
+
program.parse();
|
|
612
|
+
}
|
|
613
|
+
if (import.meta.main) {
|
|
614
|
+
cli();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export { cli };
|
package/package.json
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzhangxyz/ddss",
|
|
3
|
+
"description": "Distributed Deductive System Sorts",
|
|
4
|
+
"author": "Hao Zhang <hzhangxyz@outlook.com>",
|
|
3
5
|
"license": "AGPL-3.0-or-later",
|
|
4
|
-
"
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/USTC-KnowledgeComputingLab/ddss.git"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
|
-
"bin":
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
"bin": {
|
|
12
|
+
"ddss": "./dist/index.js"
|
|
13
|
+
},
|
|
9
14
|
"files": [
|
|
10
|
-
"dist/
|
|
15
|
+
"dist/index.js"
|
|
11
16
|
],
|
|
12
17
|
"scripts": {
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
"build": "rollup --config rollup.config.js"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"atsds": "^0.0.12",
|
|
22
|
+
"atsds-bnf": "^0.0.12",
|
|
23
|
+
"atsds-egg": "^0.0.12",
|
|
24
|
+
"commander": "^14.0.2",
|
|
25
|
+
"sequelize": "^6.37.7",
|
|
26
|
+
"sqlite3": "^5.1.7"
|
|
16
27
|
},
|
|
17
28
|
"devDependencies": {
|
|
18
|
-
"@biomejs/biome": "^2.3.8",
|
|
19
|
-
"@bufbuild/protobuf": "^2.10.2",
|
|
20
|
-
"@grpc/grpc-js": "^1.14.3",
|
|
21
|
-
"@rollup/plugin-commonjs": "^29.0.0",
|
|
22
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
23
|
-
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
24
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
25
29
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
26
|
-
"@types/node": "^25.0.
|
|
27
|
-
"
|
|
28
|
-
"atsds-bnf": "^0.0.7",
|
|
29
|
-
"npm-run-all": "^4.1.5",
|
|
30
|
-
"rollup": "^4.53.3",
|
|
31
|
-
"ts-proto": "^2.8.3",
|
|
30
|
+
"@types/node": "^25.0.3",
|
|
31
|
+
"rollup": "^4.54.0",
|
|
32
32
|
"tslib": "^2.8.1",
|
|
33
33
|
"typescript": "^5.9.3"
|
|
34
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"version": "0.0.29"
|
|
35
36
|
}
|