eyeprolog 1.3.9 → 1.3.11
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/package.json +1 -1
- package/src/program.js +51 -15
- package/src/term.js +7 -1
- package/test/run-regression.mjs +47 -0
package/package.json
CHANGED
package/src/program.js
CHANGED
|
@@ -314,15 +314,10 @@ export class Program {
|
|
|
314
314
|
const wfsNegativeEdges = [];
|
|
315
315
|
for (const group of groups) {
|
|
316
316
|
const groupIndex = indexByGroup.get(group);
|
|
317
|
+
let compactDependencies = null;
|
|
317
318
|
for (const clause of group.clauses) {
|
|
318
319
|
if (isCompactBinaryClause(clause)) {
|
|
319
|
-
if (clause.bodyName != null)
|
|
320
|
-
const dep = this.findGroup(clause.bodyName, 2, group.module);
|
|
321
|
-
if (dep) {
|
|
322
|
-
deps[groupIndex].add(indexByGroup.get(dep));
|
|
323
|
-
cutDeps[groupIndex].add(indexByGroup.get(dep));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
320
|
+
if (clause.bodyName != null) (compactDependencies ??= new Set()).add(clause.bodyName);
|
|
326
321
|
continue;
|
|
327
322
|
}
|
|
328
323
|
for (const goal of clause.body) {
|
|
@@ -349,6 +344,13 @@ export class Program {
|
|
|
349
344
|
}
|
|
350
345
|
}
|
|
351
346
|
}
|
|
347
|
+
for (const name of compactDependencies ?? []) {
|
|
348
|
+
const dep = this.findGroup(name, 2, group.module);
|
|
349
|
+
if (!dep) continue;
|
|
350
|
+
const dependencyIndex = indexByGroup.get(dep);
|
|
351
|
+
deps[groupIndex].add(dependencyIndex);
|
|
352
|
+
cutDeps[groupIndex].add(dependencyIndex);
|
|
353
|
+
}
|
|
352
354
|
}
|
|
353
355
|
const finiteDatalogCache = new Map();
|
|
354
356
|
const rangeRestrictedDatalogCache = new Map();
|
|
@@ -1301,12 +1303,10 @@ function datalogDependencyClauseCount(program, group, seen = new Set()) {
|
|
|
1301
1303
|
if (seen.has(group)) return 0;
|
|
1302
1304
|
seen.add(group);
|
|
1303
1305
|
let count = group.clauses.length;
|
|
1306
|
+
let compactDependencies = null;
|
|
1304
1307
|
for (const clause of group.clauses) {
|
|
1305
1308
|
if (isCompactBinaryClause(clause)) {
|
|
1306
|
-
if (clause.bodyName != null)
|
|
1307
|
-
const target = program.findGroup(clause.bodyName, 2, group.module);
|
|
1308
|
-
if (target) count += datalogDependencyClauseCount(program, target, seen);
|
|
1309
|
-
}
|
|
1309
|
+
if (clause.bodyName != null) (compactDependencies ??= new Set()).add(clause.bodyName);
|
|
1310
1310
|
continue;
|
|
1311
1311
|
}
|
|
1312
1312
|
for (const goal of clause.body) {
|
|
@@ -1315,6 +1315,10 @@ function datalogDependencyClauseCount(program, group, seen = new Set()) {
|
|
|
1315
1315
|
if (target) count += datalogDependencyClauseCount(program, target, seen);
|
|
1316
1316
|
}
|
|
1317
1317
|
}
|
|
1318
|
+
for (const name of compactDependencies ?? []) {
|
|
1319
|
+
const target = program.findGroup(name, 2, group.module);
|
|
1320
|
+
if (target) count += datalogDependencyClauseCount(program, target, seen);
|
|
1321
|
+
}
|
|
1318
1322
|
return count;
|
|
1319
1323
|
}
|
|
1320
1324
|
|
|
@@ -1324,12 +1328,10 @@ function isFiniteDatalogGroup(program, group, cache = new Map(), visiting = new
|
|
|
1324
1328
|
if (visiting.has(group)) return true;
|
|
1325
1329
|
visiting.add(group);
|
|
1326
1330
|
let finite = true;
|
|
1331
|
+
let compactDependencies = null;
|
|
1327
1332
|
for (const clause of group.clauses) {
|
|
1328
1333
|
if (isCompactBinaryClause(clause)) {
|
|
1329
|
-
if (clause.bodyName != null)
|
|
1330
|
-
const target = program.findGroup(clause.bodyName, 2, group.module);
|
|
1331
|
-
if (!target || !isFiniteDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1332
|
-
}
|
|
1334
|
+
if (clause.bodyName != null) (compactDependencies ??= new Set()).add(clause.bodyName);
|
|
1333
1335
|
continue;
|
|
1334
1336
|
}
|
|
1335
1337
|
if (clause.head.type === COMPOUND && !clause.head.args.every(isFiniteDatalogArgument)) { finite = false; break; }
|
|
@@ -1345,6 +1347,12 @@ function isFiniteDatalogGroup(program, group, cache = new Map(), visiting = new
|
|
|
1345
1347
|
}
|
|
1346
1348
|
if (!finite) break;
|
|
1347
1349
|
}
|
|
1350
|
+
if (finite) {
|
|
1351
|
+
for (const name of compactDependencies ?? []) {
|
|
1352
|
+
const target = program.findGroup(name, 2, group.module);
|
|
1353
|
+
if (!target || !isFiniteDatalogGroup(program, target, cache, visiting)) { finite = false; break; }
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1348
1356
|
visiting.delete(group);
|
|
1349
1357
|
cache.set(group, finite);
|
|
1350
1358
|
return finite;
|
|
@@ -1358,7 +1366,35 @@ function isRangeRestrictedFiniteDatalogGroup(program, group, cache = new Map(),
|
|
|
1358
1366
|
visiting.add(group);
|
|
1359
1367
|
let finite = true;
|
|
1360
1368
|
|
|
1369
|
+
let compactDependencyResults = null;
|
|
1361
1370
|
for (const clause of group.clauses) {
|
|
1371
|
+
if (isCompactBinaryClause(clause)) {
|
|
1372
|
+
const head0Variable = clause.head0Type === VAR;
|
|
1373
|
+
const head1Variable = clause.head1Type === VAR;
|
|
1374
|
+
if (clause.bodyName == null) {
|
|
1375
|
+
if (head0Variable || head1Variable) { finite = false; break; }
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
const head0RangeRestricted = !head0Variable ||
|
|
1379
|
+
(clause.body0Type === VAR && clause.body0Name === clause.head0Name) ||
|
|
1380
|
+
(clause.body1Type === VAR && clause.body1Name === clause.head0Name);
|
|
1381
|
+
const head1RangeRestricted = !head1Variable ||
|
|
1382
|
+
(clause.body0Type === VAR && clause.body0Name === clause.head1Name) ||
|
|
1383
|
+
(clause.body1Type === VAR && clause.body1Name === clause.head1Name);
|
|
1384
|
+
if (!head0RangeRestricted || !head1RangeRestricted) {
|
|
1385
|
+
finite = false;
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
let targetFinite = compactDependencyResults?.get(clause.bodyName);
|
|
1389
|
+
if (targetFinite == null) {
|
|
1390
|
+
const target = program.findGroup(clause.bodyName, 2, group.module);
|
|
1391
|
+
targetFinite = target != null && isRangeRestrictedFiniteDatalogGroup(program, target, cache, visiting);
|
|
1392
|
+
(compactDependencyResults ??= new Map()).set(clause.bodyName, targetFinite);
|
|
1393
|
+
}
|
|
1394
|
+
if (!targetFinite) { finite = false; break; }
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1362
1398
|
const head = clause.head;
|
|
1363
1399
|
if ((head.type !== COMPOUND && head.type !== ATOM) ||
|
|
1364
1400
|
(head.type === COMPOUND && !head.args.every(isFiniteDatalogArgument))) {
|
package/src/term.js
CHANGED
|
@@ -683,7 +683,13 @@ export function numberTextFromDouble(value) {
|
|
|
683
683
|
if (Object.is(value, -0)) value = 0;
|
|
684
684
|
let text = Number(value).toPrecision(17);
|
|
685
685
|
if (text.includes('e') || text.includes('E')) {
|
|
686
|
-
text = text
|
|
686
|
+
text = text
|
|
687
|
+
.replace(/(\.\d*?[1-9])0+(e[+-]?\d+)$/i, '$1$2')
|
|
688
|
+
// ISO floating-point syntax requires a fractional part before the
|
|
689
|
+
// exponent. Keep one zero when the fraction is otherwise all zeros so
|
|
690
|
+
// generated text remains readable by EyeProlog itself (for example
|
|
691
|
+
// 1.0e-8 rather than JavaScript's 1e-8).
|
|
692
|
+
.replace(/\.0+(e[+-]?\d+)$/i, '.0$1');
|
|
687
693
|
} else if (text.includes('.')) {
|
|
688
694
|
text = text.replace(/0+$/, '').replace(/\.$/, '');
|
|
689
695
|
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
compound,
|
|
32
32
|
listFromItems,
|
|
33
33
|
numberTerm,
|
|
34
|
+
numberTextFromDouble,
|
|
34
35
|
stringTerm,
|
|
35
36
|
variable,
|
|
36
37
|
copyResolved,
|
|
@@ -697,6 +698,35 @@ c4 ?- call((!;1)).
|
|
|
697
698
|
}
|
|
698
699
|
},
|
|
699
700
|
},
|
|
701
|
+
{
|
|
702
|
+
name: 'number_chars and number_codes keep exponent floats syntactically readable (issue #50)',
|
|
703
|
+
run: () => {
|
|
704
|
+
const source = `
|
|
705
|
+
?- number_chars(1.0e-8,Cs).
|
|
706
|
+
Cs = "1.0e-8".
|
|
707
|
+
?- number_chars(N,"1.0e-8"), number_chars(N,Cs).
|
|
708
|
+
N = 1.0e-8, Cs = "1.0e-8".
|
|
709
|
+
?- number_codes(N,[49,46,48,101,45,56]), number_codes(N,Codes).
|
|
710
|
+
N = 1.0e-8, Codes = [49,46,48,101,45,56].
|
|
711
|
+
`;
|
|
712
|
+
const result = publicApi.runQuads(source);
|
|
713
|
+
assertEqual(result.total, 3, 'quad total');
|
|
714
|
+
assertEqual(result.passed, 3, 'quad passed');
|
|
715
|
+
assertEqual(result.failed, 0, 'quad failed');
|
|
716
|
+
|
|
717
|
+
const generated = numberTerm(numberTextFromDouble(1e-8));
|
|
718
|
+
assertEqual(generated.name, '1.0e-8', 'generated float spelling');
|
|
719
|
+
assertEqual(parseNumberTokenText(generated.name).name, '1.0e-8', 'generated spelling parses as a float');
|
|
720
|
+
|
|
721
|
+
for (const value of [1e-8, -1e-8, 1e20, 1e21, Number.MIN_VALUE, Number.MAX_VALUE]) {
|
|
722
|
+
const text = numberTextFromDouble(value);
|
|
723
|
+
if (/[eE]/.test(text)) {
|
|
724
|
+
assertEqual(/^-?\d+\.\d+[eE][+-]?\d+$/.test(text), true, `exponent float syntax ${text}`);
|
|
725
|
+
}
|
|
726
|
+
assertEqual(Number(parseNumberTokenText(text).name), value, `generated float round-trip ${text}`);
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
},
|
|
700
730
|
{
|
|
701
731
|
name: 'number syntax and number_chars normalize floating-point negative zero',
|
|
702
732
|
run: () => {
|
|
@@ -4484,6 +4514,23 @@ function whiteBoxCases() {
|
|
|
4484
4514
|
assertEqual(ground.stats.datalog_evaluations, 0, 'ground query keeps the ordinary indexed chain path');
|
|
4485
4515
|
},
|
|
4486
4516
|
},
|
|
4517
|
+
{
|
|
4518
|
+
name: 'compact finite Datalog planning preserves lazy clause terms',
|
|
4519
|
+
run: () => {
|
|
4520
|
+
const facts = Array.from({ length: 130 }, (_, i) => `compact_edge(n${i}, n${i + 1}).`).join('\n');
|
|
4521
|
+
const source = `${facts}\ncompact_edge(X,Y) :- compact_edge(X,Y).\n`;
|
|
4522
|
+
const program = Program.parseSources([{ text: source, filename: 'compact-datalog.pl' }], {
|
|
4523
|
+
sourceMetadata: false,
|
|
4524
|
+
});
|
|
4525
|
+
const group = program.findGroup('compact_edge', 2);
|
|
4526
|
+
assertEqual(group.datalogLeastModel, true, 'compact recursive Datalog remains eligible');
|
|
4527
|
+
assertEqual(
|
|
4528
|
+
group.clauses.filter((clause) => clause._head != null || clause._body != null).length,
|
|
4529
|
+
0,
|
|
4530
|
+
'planning keeps compact clause terms lazy',
|
|
4531
|
+
);
|
|
4532
|
+
},
|
|
4533
|
+
},
|
|
4487
4534
|
{
|
|
4488
4535
|
name: 'findall length counting preserves multiplicity and observable bags',
|
|
4489
4536
|
run: () => {
|