@hzhangxyz/ddss 0.0.11 → 0.0.30-alpha1
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 +30 -15
- package/dist/index.js +220 -272
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Distributed Deductive System Sorts (DDSS)
|
|
2
2
|
|
|
3
|
-
DDSS is a distributed deductive system with a scalable architecture
|
|
3
|
+
DDSS is a distributed deductive system with a scalable architecture, providing identical implementations in both **Python** and **TypeScript**. It currently supports distributed engines including forward-chaining, E-graph, and more.
|
|
4
4
|
|
|
5
5
|
## Design Philosophy
|
|
6
6
|
|
|
7
7
|
DDSS adopts a modular architecture that decomposes the deductive system into independent but collaborative sub-systems:
|
|
8
8
|
|
|
9
|
-
1. **Separation of Concerns**: Each module focuses on a specific reasoning task
|
|
10
|
-
2. **Concurrent Execution**: All modules collaborate asynchronously through a shared database, fully utilizing system resources
|
|
11
|
-
3. **Persistent Storage**: Uses a database to store facts and ideas, ensuring data consistency
|
|
9
|
+
1. **Separation of Concerns**: Each module focuses on a specific reasoning task.
|
|
10
|
+
2. **Concurrent Execution**: All modules collaborate asynchronously through a shared database, fully utilizing system resources.
|
|
11
|
+
3. **Persistent Storage**: Uses a database to store facts and ideas, ensuring data consistency.
|
|
12
12
|
|
|
13
13
|
The system uses a database as the central hub, with two tables (`facts` and `ideas`) for interaction between sub-systems:
|
|
14
14
|
|
|
@@ -18,34 +18,49 @@ The system uses a database as the central hub, with two tables (`facts` and `ide
|
|
|
18
18
|
|
|
19
19
|
## Modules
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
21
|
+
The system consists of the following modules, implemented symmetrically in `ddss/*.py` and `ddss/*.ts`:
|
|
22
|
+
|
|
23
|
+
- **Input** (`ddss/input.py`, `ddss/input.ts`): Interactive input interface with BNF syntax parsing
|
|
24
|
+
- **Output** (`ddss/output.py`, `ddss/output.ts`): Real-time display of facts and ideas from the database
|
|
25
|
+
- **Load** (`ddss/load.py`, `ddss/load.ts`): Batch import of facts from standard input
|
|
26
|
+
- **Dump** (`ddss/dump.py`, `ddss/dump.ts`): Export all facts and ideas to output
|
|
27
|
+
- **DS** (`ddss/ds.py`, `ddss/ds.ts`): Forward-chaining deductive search engine
|
|
28
|
+
- **Egg** (`ddss/egg.py`, `ddss/egg.ts`): E-graph based equality reasoning engine
|
|
27
29
|
|
|
28
30
|
## Installation
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
You can choose either the Python or TypeScript version. Both provide the same `ddss` command-line interface.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
### Python Version
|
|
33
35
|
|
|
36
|
+
#### Using uvx (Recommended)
|
|
34
37
|
```bash
|
|
35
38
|
uvx ddss
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
#### Using pip
|
|
42
|
+
```bash
|
|
43
|
+
pip install ddss
|
|
44
|
+
ddss
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### TypeScript Version
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
#### Using npx (Recommended)
|
|
50
|
+
```bash
|
|
51
|
+
npx @hzhangxyz/ddss
|
|
52
|
+
```
|
|
41
53
|
|
|
54
|
+
#### Using npm
|
|
42
55
|
```bash
|
|
43
|
-
|
|
56
|
+
npm install -g @hzhangxyz/ddss
|
|
44
57
|
ddss
|
|
45
58
|
```
|
|
46
59
|
|
|
47
60
|
## Usage
|
|
48
61
|
|
|
62
|
+
The usage, command-line options, and interactive syntax are identical regardless of the implementation language used.
|
|
63
|
+
|
|
49
64
|
### Basic Usage
|
|
50
65
|
|
|
51
66
|
Run DDSS with a temporary SQLite database:
|
package/dist/index.js
CHANGED
|
@@ -1,101 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
4
3
|
import * as os from 'node:os';
|
|
5
|
-
import * as
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
6
|
import { Sequelize, DataTypes, Model, Op } from 'sequelize';
|
|
7
7
|
import { Search as Search$1, Term, List, Rule } from 'atsds';
|
|
8
|
+
import { stdout, stdin } from 'node:process';
|
|
9
|
+
import { unparse, parse } from 'atsds-bnf';
|
|
8
10
|
import { EGraph } from 'atsds-egg';
|
|
9
11
|
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
|
-
import { fileURLToPath } from 'node:url';
|
|
14
12
|
|
|
15
|
-
// 定义基础模型类
|
|
16
13
|
class Fact extends Model {
|
|
17
14
|
}
|
|
18
15
|
class Idea extends Model {
|
|
19
16
|
}
|
|
20
17
|
async function initializeDatabase(addr) {
|
|
21
|
-
|
|
22
|
-
let sequelizeAddr = addr;
|
|
23
|
-
if (addr.startsWith("sqlite:///")) {
|
|
24
|
-
sequelizeAddr = `sqlite:${addr.replace("sqlite:///", "")}`;
|
|
25
|
-
}
|
|
26
|
-
const sequelize = new Sequelize(sequelizeAddr, {
|
|
27
|
-
logging: false,
|
|
28
|
-
define: {
|
|
29
|
-
timestamps: false, // 去掉 createdAt 和 updatedAt
|
|
30
|
-
},
|
|
31
|
-
});
|
|
18
|
+
const sequelize = new Sequelize(addr, { logging: false });
|
|
32
19
|
Fact.init({
|
|
33
20
|
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
34
21
|
data: { type: DataTypes.TEXT, unique: true, allowNull: false },
|
|
35
|
-
}, { sequelize, tableName: "facts" });
|
|
22
|
+
}, { sequelize, tableName: "facts", timestamps: false });
|
|
36
23
|
Idea.init({
|
|
37
24
|
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
38
25
|
data: { type: DataTypes.TEXT, unique: true, allowNull: false },
|
|
39
|
-
}, { sequelize, tableName: "ideas" });
|
|
26
|
+
}, { sequelize, tableName: "ideas", timestamps: false });
|
|
40
27
|
await sequelize.sync();
|
|
41
28
|
return sequelize;
|
|
42
29
|
}
|
|
43
|
-
/**
|
|
44
|
-
* 对应 Python 中的 insert_or_ignore。
|
|
45
|
-
* Sequelize 的 bulkCreate 配合 ignoreDuplicates 可以实现跨方言的“重复则跳过”。
|
|
46
|
-
*/
|
|
47
30
|
async function insertOrIgnore(model, data) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
// 忽略重复键错误
|
|
55
|
-
}
|
|
31
|
+
await model.bulkCreate([{ data }], {
|
|
32
|
+
ignoreDuplicates: true,
|
|
33
|
+
});
|
|
56
34
|
}
|
|
57
35
|
|
|
58
36
|
function strRuleGetStrIdea(data) {
|
|
59
37
|
if (!data.startsWith("--")) {
|
|
60
38
|
const lines = data.split("\n");
|
|
61
|
-
|
|
62
|
-
return `----\n${lines[0]}\n`;
|
|
63
|
-
}
|
|
39
|
+
return `----\n${lines[0]}\n`;
|
|
64
40
|
}
|
|
65
41
|
return null;
|
|
66
42
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Patches process.stdout.write to prevent asynchronous logs from messing up the readline prompt.
|
|
69
|
-
*/
|
|
70
43
|
function patchStdout(rl) {
|
|
71
|
-
const originalWrite =
|
|
44
|
+
const originalWrite = stdout.write;
|
|
72
45
|
let isReprompting = false;
|
|
73
46
|
// @ts-ignore
|
|
74
|
-
|
|
47
|
+
stdout.write = (chunk, encoding, callback) => {
|
|
48
|
+
// (1) Use original write when reprompting to avoid infinite recursion
|
|
49
|
+
// since rl.prompt() internally calls stdout.write.
|
|
75
50
|
if (isReprompting) {
|
|
76
|
-
return originalWrite.call(
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
originalWrite.call(
|
|
87
|
-
const result = originalWrite.call(process.stdout, chunk, encoding, callback);
|
|
51
|
+
return originalWrite.call(stdout, chunk, encoding, callback);
|
|
52
|
+
}
|
|
53
|
+
// (2) Let normal newlines (e.g., from user pressing Enter) pass through
|
|
54
|
+
// without triggering the reprompting logic.
|
|
55
|
+
if (chunk === "\n" || chunk === "\r\n") {
|
|
56
|
+
return originalWrite.call(stdout, chunk, encoding, callback);
|
|
57
|
+
}
|
|
58
|
+
if (chunk.includes("\n")) {
|
|
59
|
+
// Move cursor to the start of the line.
|
|
60
|
+
originalWrite.call(stdout, "\r");
|
|
61
|
+
const result = originalWrite.call(stdout, chunk, encoding, callback);
|
|
88
62
|
isReprompting = true;
|
|
89
|
-
|
|
90
|
-
rl.prompt(true);
|
|
63
|
+
rl.prompt();
|
|
91
64
|
isReprompting = false;
|
|
92
65
|
return result;
|
|
93
66
|
}
|
|
94
|
-
return originalWrite.call(
|
|
67
|
+
return originalWrite.call(stdout, chunk, encoding, callback);
|
|
95
68
|
};
|
|
96
69
|
return () => {
|
|
97
70
|
// @ts-ignore
|
|
98
|
-
|
|
71
|
+
stdout.write = originalWrite;
|
|
99
72
|
};
|
|
100
73
|
}
|
|
101
74
|
|
|
@@ -103,42 +76,50 @@ async function main$5(addr, sequelize) {
|
|
|
103
76
|
if (!sequelize) {
|
|
104
77
|
sequelize = await initializeDatabase(addr);
|
|
105
78
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (idea) {
|
|
126
|
-
tasks.push(insertOrIgnore(Idea, idea));
|
|
127
|
-
}
|
|
128
|
-
return false;
|
|
129
|
-
};
|
|
130
|
-
count = search.execute(handler);
|
|
131
|
-
await Promise.all(tasks);
|
|
132
|
-
const end = Date.now();
|
|
133
|
-
const duration = (end - begin) / 1000;
|
|
134
|
-
if (count === 0) {
|
|
135
|
-
const delay = Math.max(0, 0.1 - duration);
|
|
136
|
-
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
79
|
+
const search = new Search$1();
|
|
80
|
+
let maxFact = -1;
|
|
81
|
+
while (true) {
|
|
82
|
+
const begin = Date.now();
|
|
83
|
+
let count = 0;
|
|
84
|
+
const newFacts = await Fact.findAll({
|
|
85
|
+
where: { id: { [Op.gt]: maxFact } },
|
|
86
|
+
});
|
|
87
|
+
for (const fact of newFacts) {
|
|
88
|
+
maxFact = Math.max(maxFact, fact.id);
|
|
89
|
+
search.add(fact.data);
|
|
90
|
+
}
|
|
91
|
+
const tasks = [];
|
|
92
|
+
const handler = (rule) => {
|
|
93
|
+
const dsStr = rule.toString();
|
|
94
|
+
tasks.push(insertOrIgnore(Fact, dsStr));
|
|
95
|
+
const idea = strRuleGetStrIdea(dsStr);
|
|
96
|
+
if (idea) {
|
|
97
|
+
tasks.push(insertOrIgnore(Idea, idea));
|
|
137
98
|
}
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
count = search.execute(handler);
|
|
102
|
+
await Promise.all(tasks);
|
|
103
|
+
const end = Date.now();
|
|
104
|
+
const duration = (end - begin) / 1000;
|
|
105
|
+
if (count === 0) {
|
|
106
|
+
const delay = Math.max(0, 0.1 - duration);
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
138
108
|
}
|
|
139
109
|
}
|
|
140
|
-
|
|
141
|
-
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main$4(addr, sequelize) {
|
|
113
|
+
if (!sequelize) {
|
|
114
|
+
sequelize = await initializeDatabase(addr);
|
|
115
|
+
}
|
|
116
|
+
const ideas = await Idea.findAll();
|
|
117
|
+
for (const idea of ideas) {
|
|
118
|
+
console.log("idea:", unparse(idea.data));
|
|
119
|
+
}
|
|
120
|
+
const facts = await Fact.findAll();
|
|
121
|
+
for (const fact of facts) {
|
|
122
|
+
console.log("fact:", unparse(fact.data));
|
|
142
123
|
}
|
|
143
124
|
}
|
|
144
125
|
|
|
@@ -154,7 +135,6 @@ function extractLhsRhsFromRule(data) {
|
|
|
154
135
|
if (!(inner instanceof List)) {
|
|
155
136
|
return null;
|
|
156
137
|
}
|
|
157
|
-
// (binary == lhs rhs) -> list of 4 elements: "binary", "==", lhs, rhs
|
|
158
138
|
if (!(inner.length() === 4 && inner.getitem(0).toString() === "binary" && inner.getitem(1).toString() === "==")) {
|
|
159
139
|
return null;
|
|
160
140
|
}
|
|
@@ -178,8 +158,7 @@ class InternalEGraph {
|
|
|
178
158
|
return this.mapping.get(key);
|
|
179
159
|
}
|
|
180
160
|
find(data) {
|
|
181
|
-
|
|
182
|
-
return this.core.find(dataId);
|
|
161
|
+
return this.core.find(this.getOrAdd(data));
|
|
183
162
|
}
|
|
184
163
|
setEquality(lhs, rhs) {
|
|
185
164
|
const lhsId = this.getOrAdd(lhs);
|
|
@@ -212,22 +191,24 @@ class Search {
|
|
|
212
191
|
}
|
|
213
192
|
rebuild() {
|
|
214
193
|
this.egraph.rebuild();
|
|
194
|
+
const newlyAddedTermsList = Array.from(this.newlyAddedTerms).map((s) => new Term(s));
|
|
215
195
|
for (const factStr of this.facts) {
|
|
216
196
|
const fact = new Term(factStr);
|
|
217
197
|
if (!this.factMatchingCache.has(factStr)) {
|
|
218
198
|
this.factMatchingCache.set(factStr, new Set());
|
|
219
199
|
}
|
|
220
|
-
const candidates = this.collectMatchingCandidates(fact,
|
|
200
|
+
const candidates = this.collectMatchingCandidates(fact, newlyAddedTermsList);
|
|
221
201
|
for (const c of candidates) {
|
|
222
202
|
this.factMatchingCache.get(factStr).add(c.toString());
|
|
223
203
|
}
|
|
224
204
|
}
|
|
205
|
+
const termsList = Array.from(this.terms).map((s) => new Term(s));
|
|
225
206
|
for (const factStr of this.newlyAddedFacts) {
|
|
226
207
|
const fact = new Term(factStr);
|
|
227
208
|
if (!this.factMatchingCache.has(factStr)) {
|
|
228
209
|
this.factMatchingCache.set(factStr, new Set());
|
|
229
210
|
}
|
|
230
|
-
const candidates = this.collectMatchingCandidates(fact,
|
|
211
|
+
const candidates = this.collectMatchingCandidates(fact, termsList);
|
|
231
212
|
for (const c of candidates) {
|
|
232
213
|
this.factMatchingCache.get(factStr).add(c.toString());
|
|
233
214
|
}
|
|
@@ -271,8 +252,9 @@ class Search {
|
|
|
271
252
|
if (this.egraph.getEquality(lhs, rhs)) {
|
|
272
253
|
yield data;
|
|
273
254
|
}
|
|
274
|
-
const
|
|
275
|
-
const
|
|
255
|
+
const termsList = Array.from(this.terms).map((s) => new Term(s));
|
|
256
|
+
const lhsPool = this.collectMatchingCandidates(lhs, termsList);
|
|
257
|
+
const rhsPool = this.collectMatchingCandidates(rhs, termsList);
|
|
276
258
|
if (lhsPool.length === 0 || rhsPool.length === 0)
|
|
277
259
|
return;
|
|
278
260
|
const lhsGroups = this.groupByEquivalenceClass(lhsPool);
|
|
@@ -312,7 +294,8 @@ class Search {
|
|
|
312
294
|
yield data;
|
|
313
295
|
}
|
|
314
296
|
}
|
|
315
|
-
const
|
|
297
|
+
const termsList = Array.from(this.terms).map((s) => new Term(s));
|
|
298
|
+
const ideaPool = this.collectMatchingCandidates(idea, termsList);
|
|
316
299
|
if (ideaPool.length === 0)
|
|
317
300
|
return;
|
|
318
301
|
const ideaGroups = this.groupByEquivalenceClass(ideaPool);
|
|
@@ -339,7 +322,7 @@ class Search {
|
|
|
339
322
|
const result = target.ground(unification, "1");
|
|
340
323
|
if (result) {
|
|
341
324
|
const inner = result.term();
|
|
342
|
-
if (inner instanceof List
|
|
325
|
+
if (inner instanceof List) {
|
|
343
326
|
yield buildTermToRule(inner.getitem(2));
|
|
344
327
|
}
|
|
345
328
|
}
|
|
@@ -360,7 +343,7 @@ class Search {
|
|
|
360
343
|
return [];
|
|
361
344
|
const eidToTerms = new Map();
|
|
362
345
|
for (const term of terms) {
|
|
363
|
-
const eid = this.egraph.find(term);
|
|
346
|
+
const eid = this.egraph.find(term).toString();
|
|
364
347
|
if (!eidToTerms.has(eid)) {
|
|
365
348
|
eidToTerms.set(eid, new Set());
|
|
366
349
|
}
|
|
@@ -370,227 +353,176 @@ class Search {
|
|
|
370
353
|
}
|
|
371
354
|
}
|
|
372
355
|
|
|
373
|
-
async function main$
|
|
356
|
+
async function main$3(addr, sequelize) {
|
|
374
357
|
if (!sequelize) {
|
|
375
358
|
sequelize = await initializeDatabase(addr);
|
|
376
359
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (i.toString() === o.toString()) {
|
|
410
|
-
found = true;
|
|
411
|
-
break;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
if (!found) {
|
|
415
|
-
nextPool.push(i);
|
|
360
|
+
const search = new Search();
|
|
361
|
+
let pool = [];
|
|
362
|
+
let maxFact = -1;
|
|
363
|
+
let maxIdea = -1;
|
|
364
|
+
while (true) {
|
|
365
|
+
const begin = Date.now();
|
|
366
|
+
let count = 0;
|
|
367
|
+
const newIdeas = await Idea.findAll({
|
|
368
|
+
where: { id: { [Op.gt]: maxIdea } },
|
|
369
|
+
});
|
|
370
|
+
for (const idea of newIdeas) {
|
|
371
|
+
maxIdea = Math.max(maxIdea, idea.id);
|
|
372
|
+
pool.push(new Rule(idea.data));
|
|
373
|
+
}
|
|
374
|
+
const newFacts = await Fact.findAll({
|
|
375
|
+
where: { id: { [Op.gt]: maxFact } },
|
|
376
|
+
});
|
|
377
|
+
for (const fact of newFacts) {
|
|
378
|
+
maxFact = Math.max(maxFact, fact.id);
|
|
379
|
+
search.add(new Rule(fact.data));
|
|
380
|
+
}
|
|
381
|
+
search.rebuild();
|
|
382
|
+
const tasks = [];
|
|
383
|
+
const nextPool = [];
|
|
384
|
+
for (const i of pool) {
|
|
385
|
+
let found = false;
|
|
386
|
+
for (const o of search.execute(i)) {
|
|
387
|
+
tasks.push(insertOrIgnore(Fact, o.toString()));
|
|
388
|
+
count++;
|
|
389
|
+
if (i.toString() === o.toString()) {
|
|
390
|
+
found = true;
|
|
391
|
+
break;
|
|
416
392
|
}
|
|
417
393
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const end = Date.now();
|
|
421
|
-
const duration = (end - begin) / 1000;
|
|
422
|
-
if (count === 0) {
|
|
423
|
-
const delay = Math.max(0, 0.1 - duration);
|
|
424
|
-
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
394
|
+
if (!found) {
|
|
395
|
+
nextPool.push(i);
|
|
425
396
|
}
|
|
426
397
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
398
|
+
pool = nextPool;
|
|
399
|
+
await Promise.all(tasks);
|
|
400
|
+
const end = Date.now();
|
|
401
|
+
const duration = (end - begin) / 1000;
|
|
402
|
+
if (count === 0) {
|
|
403
|
+
const delay = Math.max(0, 0.1 - duration);
|
|
404
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
405
|
+
}
|
|
430
406
|
}
|
|
431
407
|
}
|
|
432
408
|
|
|
433
|
-
async function main$
|
|
409
|
+
async function main$2(addr, sequelize) {
|
|
434
410
|
if (!sequelize) {
|
|
435
411
|
sequelize = await initializeDatabase(addr);
|
|
436
412
|
}
|
|
437
413
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
438
414
|
rl.setPrompt("input: ");
|
|
439
415
|
const unpatch = patchStdout(rl);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (data === "" || data.startsWith("//")) {
|
|
445
|
-
rl.prompt();
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
try {
|
|
449
|
-
const ds = parse(data);
|
|
450
|
-
const dsStr = ds.toString();
|
|
451
|
-
await insertOrIgnore(Fact, dsStr);
|
|
452
|
-
const idea = strRuleGetStrIdea(dsStr);
|
|
453
|
-
if (idea) {
|
|
454
|
-
await insertOrIgnore(Idea, idea);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
catch (e) {
|
|
458
|
-
console.error(`error: ${e}`);
|
|
459
|
-
}
|
|
416
|
+
rl.prompt();
|
|
417
|
+
for await (const line of rl) {
|
|
418
|
+
const data = line.trim();
|
|
419
|
+
if (data === "" || data.startsWith("//")) {
|
|
460
420
|
rl.prompt();
|
|
421
|
+
continue;
|
|
461
422
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async function main$2(addr, sequelize) {
|
|
473
|
-
if (!sequelize) {
|
|
474
|
-
sequelize = await initializeDatabase(addr);
|
|
475
|
-
}
|
|
476
|
-
try {
|
|
477
|
-
let maxFact = -1;
|
|
478
|
-
let maxIdea = -1;
|
|
479
|
-
while (true) {
|
|
480
|
-
const begin = Date.now();
|
|
481
|
-
let count = 0;
|
|
482
|
-
const newIdeas = await Idea.findAll({
|
|
483
|
-
where: { id: { [Op.gt]: maxIdea } },
|
|
484
|
-
order: [["id", "ASC"]],
|
|
485
|
-
});
|
|
486
|
-
for (const idea of newIdeas) {
|
|
487
|
-
maxIdea = Math.max(maxIdea, idea.id);
|
|
488
|
-
console.log("idea:", unparse(idea.data));
|
|
489
|
-
count++;
|
|
490
|
-
}
|
|
491
|
-
const newFacts = await Fact.findAll({
|
|
492
|
-
where: { id: { [Op.gt]: maxFact } },
|
|
493
|
-
order: [["id", "ASC"]],
|
|
494
|
-
});
|
|
495
|
-
for (const fact of newFacts) {
|
|
496
|
-
maxFact = Math.max(maxFact, fact.id);
|
|
497
|
-
console.log("fact:", unparse(fact.data));
|
|
498
|
-
count++;
|
|
499
|
-
}
|
|
500
|
-
const end = Date.now();
|
|
501
|
-
const duration = (end - begin) / 1000;
|
|
502
|
-
if (count === 0) {
|
|
503
|
-
const delay = Math.max(0, 0.1 - duration);
|
|
504
|
-
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
423
|
+
try {
|
|
424
|
+
const ds = parse(data);
|
|
425
|
+
const dsStr = ds.toString();
|
|
426
|
+
await insertOrIgnore(Fact, dsStr);
|
|
427
|
+
const idea = strRuleGetStrIdea(dsStr);
|
|
428
|
+
if (idea) {
|
|
429
|
+
await insertOrIgnore(Idea, idea);
|
|
505
430
|
}
|
|
506
431
|
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
console.error(`error: ${e}`);
|
|
434
|
+
}
|
|
435
|
+
rl.prompt();
|
|
507
436
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
437
|
+
unpatch();
|
|
438
|
+
rl.close();
|
|
511
439
|
}
|
|
512
440
|
|
|
513
441
|
async function main$1(addr, sequelize) {
|
|
514
442
|
if (!sequelize) {
|
|
515
443
|
sequelize = await initializeDatabase(addr);
|
|
516
444
|
}
|
|
517
|
-
const rl = readline
|
|
518
|
-
input:
|
|
445
|
+
const rl = readline.createInterface({
|
|
446
|
+
input: stdin,
|
|
519
447
|
terminal: false,
|
|
520
448
|
});
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
await insertOrIgnore(Idea, idea);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
catch (e) {
|
|
537
|
-
console.error(`error: ${e}`);
|
|
449
|
+
for await (const line of rl) {
|
|
450
|
+
const data = line.trim();
|
|
451
|
+
if (data === "" || data.startsWith("//")) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const ds = parse(data);
|
|
456
|
+
const dsStr = ds.toString();
|
|
457
|
+
await insertOrIgnore(Fact, dsStr);
|
|
458
|
+
const idea = strRuleGetStrIdea(dsStr);
|
|
459
|
+
if (idea) {
|
|
460
|
+
await insertOrIgnore(Idea, idea);
|
|
538
461
|
}
|
|
539
462
|
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
console.error(`error: ${e}`);
|
|
465
|
+
}
|
|
540
466
|
}
|
|
541
|
-
|
|
542
|
-
rl.close();
|
|
543
|
-
}
|
|
467
|
+
rl.close();
|
|
544
468
|
}
|
|
545
469
|
|
|
546
470
|
async function main(addr, sequelize) {
|
|
547
471
|
if (!sequelize) {
|
|
548
472
|
sequelize = await initializeDatabase(addr);
|
|
549
473
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
474
|
+
let maxFact = -1;
|
|
475
|
+
let maxIdea = -1;
|
|
476
|
+
while (true) {
|
|
477
|
+
const begin = Date.now();
|
|
478
|
+
let count = 0;
|
|
479
|
+
const newIdeas = await Idea.findAll({
|
|
480
|
+
where: { id: { [Op.gt]: maxIdea } },
|
|
481
|
+
});
|
|
482
|
+
for (const idea of newIdeas) {
|
|
483
|
+
maxIdea = Math.max(maxIdea, idea.id);
|
|
553
484
|
console.log("idea:", unparse(idea.data));
|
|
485
|
+
count++;
|
|
554
486
|
}
|
|
555
|
-
const
|
|
556
|
-
|
|
487
|
+
const newFacts = await Fact.findAll({
|
|
488
|
+
where: { id: { [Op.gt]: maxFact } },
|
|
489
|
+
});
|
|
490
|
+
for (const fact of newFacts) {
|
|
491
|
+
maxFact = Math.max(maxFact, fact.id);
|
|
557
492
|
console.log("fact:", unparse(fact.data));
|
|
493
|
+
count++;
|
|
494
|
+
}
|
|
495
|
+
const end = Date.now();
|
|
496
|
+
const duration = (end - begin) / 1000;
|
|
497
|
+
if (count === 0) {
|
|
498
|
+
const delay = Math.max(0, 0.1 - duration);
|
|
499
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1000));
|
|
558
500
|
}
|
|
559
|
-
}
|
|
560
|
-
finally {
|
|
561
|
-
// Session is handled by main or caller
|
|
562
501
|
}
|
|
563
502
|
}
|
|
564
503
|
|
|
565
504
|
const componentMap = {
|
|
566
505
|
ds: main$5,
|
|
567
|
-
egg: main$
|
|
568
|
-
input: main$
|
|
569
|
-
output: main
|
|
506
|
+
egg: main$3,
|
|
507
|
+
input: main$2,
|
|
508
|
+
output: main,
|
|
570
509
|
load: main$1,
|
|
571
|
-
dump: main,
|
|
510
|
+
dump: main$4,
|
|
572
511
|
};
|
|
573
512
|
async function run(addr, components) {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
console.error(`error: unsupported component: ${name}`);
|
|
580
|
-
process.exit(1);
|
|
581
|
-
}
|
|
582
|
-
return component(addr, sequelize);
|
|
583
|
-
});
|
|
584
|
-
// Run all components in parallel.
|
|
585
|
-
// Note: Some components like input/output run forever.
|
|
586
|
-
await Promise.all(promises);
|
|
587
|
-
}
|
|
588
|
-
catch (err) {
|
|
589
|
-
// Handle error
|
|
590
|
-
}
|
|
591
|
-
finally {
|
|
592
|
-
await sequelize.close();
|
|
513
|
+
for (const name of components) {
|
|
514
|
+
if (!(name in componentMap)) {
|
|
515
|
+
console.error(`error: unsupported component: ${name}`);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
593
518
|
}
|
|
519
|
+
const sequelize = await initializeDatabase(addr);
|
|
520
|
+
const promises = components.map((name) => {
|
|
521
|
+
const component = componentMap[name];
|
|
522
|
+
return component(addr, sequelize);
|
|
523
|
+
});
|
|
524
|
+
await Promise.race(promises);
|
|
525
|
+
await sequelize.close();
|
|
594
526
|
}
|
|
595
527
|
function cli() {
|
|
596
528
|
const program = new Command();
|
|
@@ -601,17 +533,33 @@ function cli() {
|
|
|
601
533
|
.option("-c, --component <names...>", "Components to run.", ["input", "output", "ds", "egg"])
|
|
602
534
|
.action(async (options) => {
|
|
603
535
|
let addr = options.addr;
|
|
536
|
+
let tmpDir;
|
|
604
537
|
if (!addr) {
|
|
605
|
-
|
|
538
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "ddss-"));
|
|
606
539
|
const dbPath = path.join(tmpDir, "ddss.db");
|
|
607
540
|
addr = `sqlite:///${dbPath}`;
|
|
608
541
|
}
|
|
542
|
+
if (addr.startsWith("sqlite://")) {
|
|
543
|
+
addr = addr.replace("sqlite:///", "sqlite:");
|
|
544
|
+
}
|
|
545
|
+
else if (addr.startsWith("mysql:///")) ;
|
|
546
|
+
else if (addr.startsWith("mariadb://")) ;
|
|
547
|
+
else if (addr.startsWith("postgresql://")) {
|
|
548
|
+
addr = addr.replace("postgresql://", "postgres://");
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
console.error(`error: unsupported database: ${addr}`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
609
554
|
console.log(`addr: ${addr}`);
|
|
610
555
|
await run(addr, options.component);
|
|
556
|
+
if (tmpDir) {
|
|
557
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
558
|
+
}
|
|
611
559
|
});
|
|
612
560
|
program.parse();
|
|
613
561
|
}
|
|
614
|
-
if (
|
|
562
|
+
if (import.meta.main) {
|
|
615
563
|
cli();
|
|
616
564
|
}
|
|
617
565
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzhangxyz/ddss",
|
|
3
|
-
"version": "0.0.11",
|
|
4
3
|
"description": "Distributed Deductive System Sorts",
|
|
5
4
|
"author": "Hao Zhang <hzhangxyz@outlook.com>",
|
|
6
5
|
"license": "AGPL-3.0-or-later",
|
|
@@ -23,6 +22,9 @@
|
|
|
23
22
|
"atsds-bnf": "^0.0.12",
|
|
24
23
|
"atsds-egg": "^0.0.12",
|
|
25
24
|
"commander": "^14.0.2",
|
|
25
|
+
"mariadb": "^3.4.5",
|
|
26
|
+
"mysql2": "^3.16.0",
|
|
27
|
+
"pg": "^8.16.3",
|
|
26
28
|
"sequelize": "^6.37.7",
|
|
27
29
|
"sqlite3": "^5.1.7"
|
|
28
30
|
},
|
|
@@ -32,5 +34,6 @@
|
|
|
32
34
|
"rollup": "^4.54.0",
|
|
33
35
|
"tslib": "^2.8.1",
|
|
34
36
|
"typescript": "^5.9.3"
|
|
35
|
-
}
|
|
37
|
+
},
|
|
38
|
+
"version": "0.0.30-alpha1"
|
|
36
39
|
}
|