@hzhangxyz/ddss 0.0.29 → 0.0.30
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 +219 -270
- package/package.json +17 -5
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,100 +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
12
|
|
|
14
|
-
// 定义基础模型类
|
|
15
13
|
class Fact extends Model {
|
|
16
14
|
}
|
|
17
15
|
class Idea extends Model {
|
|
18
16
|
}
|
|
19
17
|
async function initializeDatabase(addr) {
|
|
20
|
-
|
|
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
|
-
});
|
|
18
|
+
const sequelize = new Sequelize(addr, { logging: false });
|
|
31
19
|
Fact.init({
|
|
32
20
|
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
33
21
|
data: { type: DataTypes.TEXT, unique: true, allowNull: false },
|
|
34
|
-
}, { sequelize, tableName: "facts" });
|
|
22
|
+
}, { sequelize, tableName: "facts", timestamps: false });
|
|
35
23
|
Idea.init({
|
|
36
24
|
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
37
25
|
data: { type: DataTypes.TEXT, unique: true, allowNull: false },
|
|
38
|
-
}, { sequelize, tableName: "ideas" });
|
|
26
|
+
}, { sequelize, tableName: "ideas", timestamps: false });
|
|
39
27
|
await sequelize.sync();
|
|
40
28
|
return sequelize;
|
|
41
29
|
}
|
|
42
|
-
/**
|
|
43
|
-
* 对应 Python 中的 insert_or_ignore。
|
|
44
|
-
* Sequelize 的 bulkCreate 配合 ignoreDuplicates 可以实现跨方言的“重复则跳过”。
|
|
45
|
-
*/
|
|
46
30
|
async function insertOrIgnore(model, data) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
// 忽略重复键错误
|
|
54
|
-
}
|
|
31
|
+
await model.bulkCreate([{ data }], {
|
|
32
|
+
ignoreDuplicates: true,
|
|
33
|
+
});
|
|
55
34
|
}
|
|
56
35
|
|
|
57
36
|
function strRuleGetStrIdea(data) {
|
|
58
37
|
if (!data.startsWith("--")) {
|
|
59
38
|
const lines = data.split("\n");
|
|
60
|
-
|
|
61
|
-
return `----\n${lines[0]}\n`;
|
|
62
|
-
}
|
|
39
|
+
return `----\n${lines[0]}\n`;
|
|
63
40
|
}
|
|
64
41
|
return null;
|
|
65
42
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Patches process.stdout.write to prevent asynchronous logs from messing up the readline prompt.
|
|
68
|
-
*/
|
|
69
43
|
function patchStdout(rl) {
|
|
70
|
-
const originalWrite =
|
|
44
|
+
const originalWrite = stdout.write;
|
|
71
45
|
let isReprompting = false;
|
|
72
46
|
// @ts-ignore
|
|
73
|
-
|
|
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.
|
|
74
50
|
if (isReprompting) {
|
|
75
|
-
return originalWrite.call(
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
originalWrite.call(
|
|
86
|
-
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);
|
|
87
62
|
isReprompting = true;
|
|
88
|
-
|
|
89
|
-
rl.prompt(true);
|
|
63
|
+
rl.prompt();
|
|
90
64
|
isReprompting = false;
|
|
91
65
|
return result;
|
|
92
66
|
}
|
|
93
|
-
return originalWrite.call(
|
|
67
|
+
return originalWrite.call(stdout, chunk, encoding, callback);
|
|
94
68
|
};
|
|
95
69
|
return () => {
|
|
96
70
|
// @ts-ignore
|
|
97
|
-
|
|
71
|
+
stdout.write = originalWrite;
|
|
98
72
|
};
|
|
99
73
|
}
|
|
100
74
|
|
|
@@ -102,42 +76,50 @@ async function main$5(addr, sequelize) {
|
|
|
102
76
|
if (!sequelize) {
|
|
103
77
|
sequelize = await initializeDatabase(addr);
|
|
104
78
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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));
|
|
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));
|
|
136
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));
|
|
137
108
|
}
|
|
138
109
|
}
|
|
139
|
-
|
|
140
|
-
|
|
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));
|
|
141
123
|
}
|
|
142
124
|
}
|
|
143
125
|
|
|
@@ -153,7 +135,6 @@ function extractLhsRhsFromRule(data) {
|
|
|
153
135
|
if (!(inner instanceof List)) {
|
|
154
136
|
return null;
|
|
155
137
|
}
|
|
156
|
-
// (binary == lhs rhs) -> list of 4 elements: "binary", "==", lhs, rhs
|
|
157
138
|
if (!(inner.length() === 4 && inner.getitem(0).toString() === "binary" && inner.getitem(1).toString() === "==")) {
|
|
158
139
|
return null;
|
|
159
140
|
}
|
|
@@ -177,8 +158,7 @@ class InternalEGraph {
|
|
|
177
158
|
return this.mapping.get(key);
|
|
178
159
|
}
|
|
179
160
|
find(data) {
|
|
180
|
-
|
|
181
|
-
return this.core.find(dataId);
|
|
161
|
+
return this.core.find(this.getOrAdd(data));
|
|
182
162
|
}
|
|
183
163
|
setEquality(lhs, rhs) {
|
|
184
164
|
const lhsId = this.getOrAdd(lhs);
|
|
@@ -211,22 +191,24 @@ class Search {
|
|
|
211
191
|
}
|
|
212
192
|
rebuild() {
|
|
213
193
|
this.egraph.rebuild();
|
|
194
|
+
const newlyAddedTermsList = Array.from(this.newlyAddedTerms).map((s) => new Term(s));
|
|
214
195
|
for (const factStr of this.facts) {
|
|
215
196
|
const fact = new Term(factStr);
|
|
216
197
|
if (!this.factMatchingCache.has(factStr)) {
|
|
217
198
|
this.factMatchingCache.set(factStr, new Set());
|
|
218
199
|
}
|
|
219
|
-
const candidates = this.collectMatchingCandidates(fact,
|
|
200
|
+
const candidates = this.collectMatchingCandidates(fact, newlyAddedTermsList);
|
|
220
201
|
for (const c of candidates) {
|
|
221
202
|
this.factMatchingCache.get(factStr).add(c.toString());
|
|
222
203
|
}
|
|
223
204
|
}
|
|
205
|
+
const termsList = Array.from(this.terms).map((s) => new Term(s));
|
|
224
206
|
for (const factStr of this.newlyAddedFacts) {
|
|
225
207
|
const fact = new Term(factStr);
|
|
226
208
|
if (!this.factMatchingCache.has(factStr)) {
|
|
227
209
|
this.factMatchingCache.set(factStr, new Set());
|
|
228
210
|
}
|
|
229
|
-
const candidates = this.collectMatchingCandidates(fact,
|
|
211
|
+
const candidates = this.collectMatchingCandidates(fact, termsList);
|
|
230
212
|
for (const c of candidates) {
|
|
231
213
|
this.factMatchingCache.get(factStr).add(c.toString());
|
|
232
214
|
}
|
|
@@ -270,8 +252,9 @@ class Search {
|
|
|
270
252
|
if (this.egraph.getEquality(lhs, rhs)) {
|
|
271
253
|
yield data;
|
|
272
254
|
}
|
|
273
|
-
const
|
|
274
|
-
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);
|
|
275
258
|
if (lhsPool.length === 0 || rhsPool.length === 0)
|
|
276
259
|
return;
|
|
277
260
|
const lhsGroups = this.groupByEquivalenceClass(lhsPool);
|
|
@@ -311,7 +294,8 @@ class Search {
|
|
|
311
294
|
yield data;
|
|
312
295
|
}
|
|
313
296
|
}
|
|
314
|
-
const
|
|
297
|
+
const termsList = Array.from(this.terms).map((s) => new Term(s));
|
|
298
|
+
const ideaPool = this.collectMatchingCandidates(idea, termsList);
|
|
315
299
|
if (ideaPool.length === 0)
|
|
316
300
|
return;
|
|
317
301
|
const ideaGroups = this.groupByEquivalenceClass(ideaPool);
|
|
@@ -338,7 +322,7 @@ class Search {
|
|
|
338
322
|
const result = target.ground(unification, "1");
|
|
339
323
|
if (result) {
|
|
340
324
|
const inner = result.term();
|
|
341
|
-
if (inner instanceof List
|
|
325
|
+
if (inner instanceof List) {
|
|
342
326
|
yield buildTermToRule(inner.getitem(2));
|
|
343
327
|
}
|
|
344
328
|
}
|
|
@@ -359,7 +343,7 @@ class Search {
|
|
|
359
343
|
return [];
|
|
360
344
|
const eidToTerms = new Map();
|
|
361
345
|
for (const term of terms) {
|
|
362
|
-
const eid = this.egraph.find(term);
|
|
346
|
+
const eid = this.egraph.find(term).toString();
|
|
363
347
|
if (!eidToTerms.has(eid)) {
|
|
364
348
|
eidToTerms.set(eid, new Set());
|
|
365
349
|
}
|
|
@@ -369,227 +353,176 @@ class Search {
|
|
|
369
353
|
}
|
|
370
354
|
}
|
|
371
355
|
|
|
372
|
-
async function main$
|
|
356
|
+
async function main$3(addr, sequelize) {
|
|
373
357
|
if (!sequelize) {
|
|
374
358
|
sequelize = await initializeDatabase(addr);
|
|
375
359
|
}
|
|
376
|
-
|
|
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
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (i.toString() === o.toString()) {
|
|
409
|
-
found = true;
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (!found) {
|
|
414
|
-
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;
|
|
415
392
|
}
|
|
416
393
|
}
|
|
417
|
-
|
|
418
|
-
|
|
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));
|
|
394
|
+
if (!found) {
|
|
395
|
+
nextPool.push(i);
|
|
424
396
|
}
|
|
425
397
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
+
}
|
|
429
406
|
}
|
|
430
407
|
}
|
|
431
408
|
|
|
432
|
-
async function main$
|
|
409
|
+
async function main$2(addr, sequelize) {
|
|
433
410
|
if (!sequelize) {
|
|
434
411
|
sequelize = await initializeDatabase(addr);
|
|
435
412
|
}
|
|
436
413
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
437
414
|
rl.setPrompt("input: ");
|
|
438
415
|
const unpatch = patchStdout(rl);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
}
|
|
416
|
+
rl.prompt();
|
|
417
|
+
for await (const line of rl) {
|
|
418
|
+
const data = line.trim();
|
|
419
|
+
if (data === "" || data.startsWith("//")) {
|
|
459
420
|
rl.prompt();
|
|
421
|
+
continue;
|
|
460
422
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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));
|
|
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);
|
|
504
430
|
}
|
|
505
431
|
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
console.error(`error: ${e}`);
|
|
434
|
+
}
|
|
435
|
+
rl.prompt();
|
|
506
436
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
437
|
+
unpatch();
|
|
438
|
+
rl.close();
|
|
510
439
|
}
|
|
511
440
|
|
|
512
441
|
async function main$1(addr, sequelize) {
|
|
513
442
|
if (!sequelize) {
|
|
514
443
|
sequelize = await initializeDatabase(addr);
|
|
515
444
|
}
|
|
516
|
-
const rl = readline
|
|
517
|
-
input:
|
|
445
|
+
const rl = readline.createInterface({
|
|
446
|
+
input: stdin,
|
|
518
447
|
terminal: false,
|
|
519
448
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
await insertOrIgnore(Idea, idea);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
catch (e) {
|
|
536
|
-
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);
|
|
537
461
|
}
|
|
538
462
|
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
console.error(`error: ${e}`);
|
|
465
|
+
}
|
|
539
466
|
}
|
|
540
|
-
|
|
541
|
-
rl.close();
|
|
542
|
-
}
|
|
467
|
+
rl.close();
|
|
543
468
|
}
|
|
544
469
|
|
|
545
470
|
async function main(addr, sequelize) {
|
|
546
471
|
if (!sequelize) {
|
|
547
472
|
sequelize = await initializeDatabase(addr);
|
|
548
473
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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);
|
|
552
484
|
console.log("idea:", unparse(idea.data));
|
|
485
|
+
count++;
|
|
553
486
|
}
|
|
554
|
-
const
|
|
555
|
-
|
|
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);
|
|
556
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));
|
|
557
500
|
}
|
|
558
|
-
}
|
|
559
|
-
finally {
|
|
560
|
-
// Session is handled by main or caller
|
|
561
501
|
}
|
|
562
502
|
}
|
|
563
503
|
|
|
564
504
|
const componentMap = {
|
|
565
505
|
ds: main$5,
|
|
566
|
-
egg: main$
|
|
567
|
-
input: main$
|
|
568
|
-
output: main
|
|
506
|
+
egg: main$3,
|
|
507
|
+
input: main$2,
|
|
508
|
+
output: main,
|
|
569
509
|
load: main$1,
|
|
570
|
-
dump: main,
|
|
510
|
+
dump: main$4,
|
|
571
511
|
};
|
|
572
512
|
async function run(addr, components) {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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();
|
|
513
|
+
for (const name of components) {
|
|
514
|
+
if (!(name in componentMap)) {
|
|
515
|
+
console.error(`error: unsupported component: ${name}`);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
592
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();
|
|
593
526
|
}
|
|
594
527
|
function cli() {
|
|
595
528
|
const program = new Command();
|
|
@@ -600,13 +533,29 @@ function cli() {
|
|
|
600
533
|
.option("-c, --component <names...>", "Components to run.", ["input", "output", "ds", "egg"])
|
|
601
534
|
.action(async (options) => {
|
|
602
535
|
let addr = options.addr;
|
|
536
|
+
let tmpDir;
|
|
603
537
|
if (!addr) {
|
|
604
|
-
|
|
538
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "ddss-"));
|
|
605
539
|
const dbPath = path.join(tmpDir, "ddss.db");
|
|
606
540
|
addr = `sqlite:///${dbPath}`;
|
|
607
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
|
+
}
|
|
608
554
|
console.log(`addr: ${addr}`);
|
|
609
555
|
await run(addr, options.component);
|
|
556
|
+
if (tmpDir) {
|
|
557
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
558
|
+
}
|
|
610
559
|
});
|
|
611
560
|
program.parse();
|
|
612
561
|
}
|
package/package.json
CHANGED
|
@@ -15,22 +15,34 @@
|
|
|
15
15
|
"dist/index.js"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"
|
|
18
|
+
"rollup": "rollup --config rollup.config.js",
|
|
19
|
+
"build": "run-s rollup",
|
|
20
|
+
"test": "cross-env NODE_OPTIONS='$NODE_OPTIONS --experimental-vm-modules' jest --config=jest.config.js",
|
|
21
|
+
"all": "run-s build test"
|
|
19
22
|
},
|
|
20
23
|
"dependencies": {
|
|
21
|
-
"atsds": "^0.0.
|
|
22
|
-
"atsds-bnf": "^0.0.
|
|
23
|
-
"atsds-egg": "^0.0.
|
|
24
|
+
"atsds": "^0.0.13",
|
|
25
|
+
"atsds-bnf": "^0.0.13",
|
|
26
|
+
"atsds-egg": "^0.0.13",
|
|
24
27
|
"commander": "^14.0.2",
|
|
28
|
+
"mariadb": "^3.4.5",
|
|
29
|
+
"mysql2": "^3.16.0",
|
|
30
|
+
"pg": "^8.16.3",
|
|
25
31
|
"sequelize": "^6.37.7",
|
|
26
32
|
"sqlite3": "^5.1.7"
|
|
27
33
|
},
|
|
28
34
|
"devDependencies": {
|
|
29
35
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
36
|
+
"@types/jest": "^30.0.0",
|
|
30
37
|
"@types/node": "^25.0.3",
|
|
38
|
+
"cross-env": "^10.1.0",
|
|
39
|
+
"jest": "^30.2.0",
|
|
40
|
+
"npm-run-all": "^4.1.5",
|
|
31
41
|
"rollup": "^4.54.0",
|
|
42
|
+
"ts-jest": "^29.4.6",
|
|
43
|
+
"ts-node": "^10.9.2",
|
|
32
44
|
"tslib": "^2.8.1",
|
|
33
45
|
"typescript": "^5.9.3"
|
|
34
46
|
},
|
|
35
|
-
"version": "0.0.
|
|
47
|
+
"version": "0.0.30"
|
|
36
48
|
}
|