@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.
Files changed (3) hide show
  1. package/README.md +30 -15
  2. package/dist/index.js +220 -272
  3. 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. It currently supports distributed engines including forward-chaining, E-graph, and more.
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
- - **Input** (`ddss/input.py`): Interactive input interface with BNF syntax parsing
22
- - **Output** (`ddss/output.py`): Real-time display of facts and ideas from the database
23
- - **Load** (`ddss/load.py`): Batch import of facts from standard input
24
- - **Dump** (`ddss/dump.py`): Export all facts and ideas to output
25
- - **DS** (`ddss/ds.py`): Forward-chaining deductive search engine
26
- - **Egg** (`ddss/egg.py`): E-graph based equality reasoning engine
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
- ### Using uvx (Recommended)
32
+ You can choose either the Python or TypeScript version. Both provide the same `ddss` command-line interface.
31
33
 
32
- The simplest way is to run with `uvx`:
34
+ ### Python Version
33
35
 
36
+ #### Using uvx (Recommended)
34
37
  ```bash
35
38
  uvx ddss
36
39
  ```
37
40
 
38
- This automatically installs all dependencies and starts the DDSS system.
41
+ #### Using pip
42
+ ```bash
43
+ pip install ddss
44
+ ddss
45
+ ```
46
+
47
+ ### TypeScript Version
39
48
 
40
- ### Using pip
49
+ #### Using npx (Recommended)
50
+ ```bash
51
+ npx @hzhangxyz/ddss
52
+ ```
41
53
 
54
+ #### Using npm
42
55
  ```bash
43
- pip install ddss
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 { Command } from 'commander';
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 fs from 'node:fs';
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
- // 转换地址:Python sqlite:///path 转为 Sequelize sqlite:path
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
- try {
49
- await model.bulkCreate([{ data }], {
50
- ignoreDuplicates: true,
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
- if (lines.length > 0) {
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 = process.stdout.write;
44
+ const originalWrite = stdout.write;
72
45
  let isReprompting = false;
73
46
  // @ts-ignore
74
- process.stdout.write = (chunk, encoding, callback) => {
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(process.stdout, chunk, encoding, callback);
77
- }
78
- const str = chunk.toString();
79
- // If it's just a newline (like when the user hits Enter), let it pass through.
80
- // This allows the input line to stay in the terminal history.
81
- if (str === "\n" || str === "\r\n") {
82
- return originalWrite.call(process.stdout, chunk, encoding, callback);
83
- }
84
- if (str.includes("\n")) {
85
- // Move to start of line and clear it (wiping the current prompt display)
86
- originalWrite.call(process.stdout, "\r\x1b[K");
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
- // Redraw the prompt and current input buffer on the NEW line
90
- rl.prompt(true);
63
+ rl.prompt();
91
64
  isReprompting = false;
92
65
  return result;
93
66
  }
94
- return originalWrite.call(process.stdout, chunk, encoding, callback);
67
+ return originalWrite.call(stdout, chunk, encoding, callback);
95
68
  };
96
69
  return () => {
97
70
  // @ts-ignore
98
- process.stdout.write = originalWrite;
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
- try {
107
- const search = new Search$1();
108
- let maxFact = -1;
109
- while (true) {
110
- const begin = Date.now();
111
- let count = 0;
112
- const newFacts = await Fact.findAll({
113
- where: { id: { [Op.gt]: maxFact } },
114
- order: [["id", "ASC"]],
115
- });
116
- for (const fact of newFacts) {
117
- maxFact = Math.max(maxFact, fact.id);
118
- search.add(fact.data);
119
- }
120
- const tasks = [];
121
- const handler = (rule) => {
122
- const dsStr = rule.toString();
123
- tasks.push(insertOrIgnore(Fact, dsStr));
124
- const idea = strRuleGetStrIdea(dsStr);
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
- catch (err) {
141
- // Handle error
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
- const dataId = this.getOrAdd(data);
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, Array.from(this.newlyAddedTerms).map((s) => new Term(s)));
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, Array.from(this.terms).map((s) => new Term(s)));
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 lhsPool = this.collectMatchingCandidates(lhs, Array.from(this.terms).map((s) => new Term(s)));
275
- const rhsPool = this.collectMatchingCandidates(rhs, Array.from(this.terms).map((s) => new Term(s)));
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 ideaPool = this.collectMatchingCandidates(idea, Array.from(this.terms).map((s) => new Term(s)));
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 && inner.length() >= 3) {
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$4(addr, sequelize) {
356
+ async function main$3(addr, sequelize) {
374
357
  if (!sequelize) {
375
358
  sequelize = await initializeDatabase(addr);
376
359
  }
377
- try {
378
- const search = new Search();
379
- let pool = [];
380
- let maxFact = -1;
381
- let maxIdea = -1;
382
- while (true) {
383
- const begin = Date.now();
384
- let count = 0;
385
- const newIdeas = await Idea.findAll({
386
- where: { id: { [Op.gt]: maxIdea } },
387
- order: [["id", "ASC"]],
388
- });
389
- for (const idea of newIdeas) {
390
- maxIdea = Math.max(maxIdea, idea.id);
391
- pool.push(new Rule(idea.data));
392
- }
393
- const newFacts = await Fact.findAll({
394
- where: { id: { [Op.gt]: maxFact } },
395
- order: [["id", "ASC"]],
396
- });
397
- for (const fact of newFacts) {
398
- maxFact = Math.max(maxFact, fact.id);
399
- search.add(new Rule(fact.data));
400
- }
401
- search.rebuild();
402
- const tasks = [];
403
- const nextPool = [];
404
- for (const i of pool) {
405
- let found = false;
406
- for (const o of search.execute(i)) {
407
- tasks.push(insertOrIgnore(Fact, o.toString()));
408
- count++;
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
- pool = nextPool;
419
- await Promise.all(tasks);
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
- catch (err) {
429
- // Handle error
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$3(addr, sequelize) {
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
- try {
441
- rl.prompt(); // Initial prompt
442
- for await (const line of rl) {
443
- const data = line.trim();
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
- catch (err) {
464
- // Silent catch for unexpected termination
465
- }
466
- finally {
467
- unpatch();
468
- rl.close();
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
- catch (err) {
509
- // Handle error
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$1.createInterface({
518
- input: process.stdin,
445
+ const rl = readline.createInterface({
446
+ input: stdin,
519
447
  terminal: false,
520
448
  });
521
- try {
522
- for await (const line of rl) {
523
- const data = line.trim();
524
- if (data === "" || data.startsWith("//")) {
525
- continue;
526
- }
527
- try {
528
- const ds = parse(data);
529
- const dsStr = ds.toString();
530
- await insertOrIgnore(Fact, dsStr);
531
- const idea = strRuleGetStrIdea(dsStr);
532
- if (idea) {
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
- finally {
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
- try {
551
- const ideas = await Idea.findAll();
552
- for (const idea of ideas) {
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 facts = await Fact.findAll();
556
- for (const fact of facts) {
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$4,
568
- input: main$3,
569
- output: main$2,
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 sequelize = await initializeDatabase(addr);
575
- try {
576
- const promises = components.map((name) => {
577
- const component = componentMap[name];
578
- if (!component) {
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
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ddss-"));
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 (process.argv[1] === fileURLToPath(import.meta.url)) {
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
  }