eyeprolog 1.3.28 → 1.3.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 +10 -3
- package/package.json +2 -1
- package/src/index.js +2 -1
- package/src/program.js +42 -0
- package/test/run-all.mjs +2 -0
- package/test/run-openrulebench.mjs +58 -0
- package/test/run-regression.mjs +32 -0
- package/the-art-of-eyeprolog.md +9 -2
package/README.md
CHANGED
|
@@ -158,7 +158,7 @@ avoiding benchmark sizes that require multi-gigabyte collectors on some
|
|
|
158
158
|
engines. Run EyeProlog's complete profile with:
|
|
159
159
|
|
|
160
160
|
```sh
|
|
161
|
-
./openrulebench/run-eyeprolog.
|
|
161
|
+
./openrulebench/run-eyeprolog.mjs
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
The benchmark README records the expected answer counts. Timing values are
|
|
@@ -291,8 +291,11 @@ and Scryer (`scryer-prolog`) installed, run:
|
|
|
291
291
|
npm run test:interop
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
This optional check runs the same portable Towers of Hanoi source under
|
|
295
|
+
EyeProlog, Trealla, and Scryer. The default `npm test` does not require external
|
|
296
|
+
Prolog implementations; it does validate all four generated OpenRuleBench
|
|
297
|
+
source trees and their engine-specific tabling and WFS adaptations. Run those
|
|
298
|
+
fast structural checks separately with `npm run test:openrulebench`.
|
|
296
299
|
|
|
297
300
|
## Development
|
|
298
301
|
|
|
@@ -303,4 +306,8 @@ npm install
|
|
|
303
306
|
npm test
|
|
304
307
|
```
|
|
305
308
|
|
|
309
|
+
The GitHub test workflow runs the complete suite and an npm package dry-run on
|
|
310
|
+
both the minimum supported Node.js 18 release line and Node.js 24. Publishing
|
|
311
|
+
repeats those release checks before uploading the package.
|
|
312
|
+
|
|
306
313
|
EyeProlog is released under the [MIT License](LICENSE.md).
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.3.
|
|
6
|
+
"version": "1.3.30",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"test:conformance": "node test/run-conformance.mjs",
|
|
52
52
|
"test:iso-strict": "node test/run-iso-strict.mjs",
|
|
53
53
|
"test:interop": "node test/run-interop.mjs",
|
|
54
|
+
"test:openrulebench": "node test/run-openrulebench.mjs",
|
|
54
55
|
"test:wg17": "node test/run-wg17.mjs",
|
|
55
56
|
"test:examples": "node test/run-examples.mjs",
|
|
56
57
|
"test:regression": "node test/run-regression.mjs",
|
package/src/index.js
CHANGED
|
@@ -28,7 +28,7 @@ export { StreamManager } from './io.js';
|
|
|
28
28
|
export { runQuads } from './quads.js';
|
|
29
29
|
|
|
30
30
|
import { ATOM, COMPOUND, VAR, Env, copyResolved, termIsGround } from './term.js';
|
|
31
|
-
import { Program } from './program.js';
|
|
31
|
+
import { Program, autoloadProgramGoals } from './program.js';
|
|
32
32
|
import { Solver } from './solver.js';
|
|
33
33
|
import { whyNoProof, whyProof } from './explain.js';
|
|
34
34
|
import { HaltSignal, PrologError, getStrictIsoRegistry } from './iso.js';
|
|
@@ -49,6 +49,7 @@ export function run(source, options = {}) {
|
|
|
49
49
|
autoloadGoals: requestedGoals,
|
|
50
50
|
};
|
|
51
51
|
let program = source instanceof Program ? source : Program.parse(source, parseOptions);
|
|
52
|
+
if (source instanceof Program) autoloadProgramGoals(program, requestedGoals, options);
|
|
52
53
|
const strictIso = requestedStrictIso || program.strictIso === true;
|
|
53
54
|
const runOptions = strictIso
|
|
54
55
|
? { ...options, isoStrict: true, registry: getStrictIsoRegistry() }
|
package/src/program.js
CHANGED
|
@@ -706,6 +706,48 @@ function buildProgramFromSources(sources, options) {
|
|
|
706
706
|
return builder.finish();
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
+
// Program.parse() can see host-supplied goals through autoloadGoals while it
|
|
710
|
+
// builds the program. A Program passed directly to run() has already completed
|
|
711
|
+
// that phase, so extend the existing instance with the same canonical interop
|
|
712
|
+
// imports before solving those goals. Keeping this here lets both paths share
|
|
713
|
+
// the dependency analysis and module-loading rules.
|
|
714
|
+
export function autoloadProgramGoals(program, inputs, options = {}) {
|
|
715
|
+
if (inputs == null || (Array.isArray(inputs) && inputs.length === 0)) return program;
|
|
716
|
+
const operatorState = createParserOperatorState(
|
|
717
|
+
[...program.operators.values()],
|
|
718
|
+
false,
|
|
719
|
+
{ isoStrict: program.strictIso },
|
|
720
|
+
);
|
|
721
|
+
const parserFlagState = { doubleQuotes: program.doubleQuotes ?? options.doubleQuotes ?? 'chars' };
|
|
722
|
+
const goals = parseInteropGoalInputs(inputs, {
|
|
723
|
+
...options,
|
|
724
|
+
isoStrict: program.strictIso,
|
|
725
|
+
operatorState,
|
|
726
|
+
parserFlagState,
|
|
727
|
+
}, program);
|
|
728
|
+
|
|
729
|
+
if (!program.strictIso && options.autoload !== false) {
|
|
730
|
+
const builder = new ProgramBuilder({ ...options, isoStrict: false }, program);
|
|
731
|
+
const loadedModules = new Set([...program.modules.keys()].filter((name) => name !== 'user'));
|
|
732
|
+
const autoloadCount = program.autoloadedPredicates.length;
|
|
733
|
+
autoloadInteropDependencies(builder, {
|
|
734
|
+
...options,
|
|
735
|
+
isoStrict: false,
|
|
736
|
+
operatorState,
|
|
737
|
+
parserFlagState,
|
|
738
|
+
}, new Set(), loadedModules, goals);
|
|
739
|
+
if (program.autoloadedPredicates.length !== autoloadCount) {
|
|
740
|
+
// Existing Solver instances key their tables by Program revision, and a
|
|
741
|
+
// previously requested negation analysis is no longer complete once new
|
|
742
|
+
// library groups become reachable.
|
|
743
|
+
program.noteMutation(false);
|
|
744
|
+
builder.finish();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
analyzeInteropPortability(program, goals);
|
|
748
|
+
return program;
|
|
749
|
+
}
|
|
750
|
+
|
|
709
751
|
function loadSourcesIntoBuilder(builder, sources, options, fast) {
|
|
710
752
|
const ensured = new Set();
|
|
711
753
|
const loadedModules = new Set();
|
package/test/run-all.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { runPlayground } from './run-playground.mjs';
|
|
|
10
10
|
import { runExamples } from './run-examples.mjs';
|
|
11
11
|
import { runBookExamples } from './run-book-examples.mjs';
|
|
12
12
|
import { runWg17 } from './run-wg17.mjs';
|
|
13
|
+
import { runOpenRuleBenchChecks } from './run-openrulebench.mjs';
|
|
13
14
|
|
|
14
15
|
const reporter = new TestReporter();
|
|
15
16
|
|
|
@@ -17,6 +18,7 @@ try {
|
|
|
17
18
|
runConformance(reporter);
|
|
18
19
|
runIsoStrict(reporter);
|
|
19
20
|
runWg17(reporter);
|
|
21
|
+
runOpenRuleBenchChecks(reporter);
|
|
20
22
|
runRegression(reporter);
|
|
21
23
|
await runPlayground(reporter);
|
|
22
24
|
runExamples(reporter);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fast structural checks for the generated multi-engine OpenRuleBench corpus.
|
|
3
|
+
// Full benchmark execution remains separate because it requires external
|
|
4
|
+
// Prolog implementations and is intentionally performance-oriented.
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
10
|
+
|
|
11
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
+
|
|
13
|
+
export function runOpenRuleBenchChecks(reporter = new TestReporter()) {
|
|
14
|
+
reporter.section('OpenRuleBench source integrity');
|
|
15
|
+
runChecker(
|
|
16
|
+
reporter,
|
|
17
|
+
'generated sources pass lexical checks',
|
|
18
|
+
'openrulebench/tools/check_sources.mjs',
|
|
19
|
+
'eyeprolog: 14 sources; lexical checks ok',
|
|
20
|
+
);
|
|
21
|
+
runChecker(
|
|
22
|
+
reporter,
|
|
23
|
+
'engine variants preserve table and WFS adaptations',
|
|
24
|
+
'openrulebench/tools/check_multiengine.mjs',
|
|
25
|
+
'OK: 14 benchmarks x 4 engines; table/WFS adaptations verified.',
|
|
26
|
+
);
|
|
27
|
+
reporter.sectionTotal('OpenRuleBench source-integrity');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function runChecker(reporter, name, relativeScript, expectedOutput) {
|
|
31
|
+
reporter.test(name, () => {
|
|
32
|
+
const result = spawnSync(process.execPath, [relativeScript], {
|
|
33
|
+
cwd: packageRoot,
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
timeout: 30000,
|
|
36
|
+
});
|
|
37
|
+
if (result.error) throw result.error;
|
|
38
|
+
if (result.status !== 0) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`${relativeScript} exited with ${result.status}\n` +
|
|
41
|
+
`${result.stdout ?? ''}${result.stderr ?? ''}`.trimEnd(),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!String(result.stdout).includes(expectedOutput)) {
|
|
45
|
+
throw new Error(`${relativeScript} did not report its expected summary\n${result.stdout ?? ''}`.trimEnd());
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isMainModule(import.meta.url)) {
|
|
51
|
+
const reporter = new TestReporter();
|
|
52
|
+
try {
|
|
53
|
+
runOpenRuleBenchChecks(reporter);
|
|
54
|
+
reporter.totalLine();
|
|
55
|
+
} catch (_) {
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -3407,6 +3407,23 @@ function documentationSyncCases() {
|
|
|
3407
3407
|
name: 'documented npm scripts exist in package.json',
|
|
3408
3408
|
run: () => assertArrayEqual(missingDocumentedPackageScripts(), [], 'missing documented npm scripts'),
|
|
3409
3409
|
},
|
|
3410
|
+
{
|
|
3411
|
+
name: 'CI verifies the supported Node floor and gates npm publishing',
|
|
3412
|
+
run: () => {
|
|
3413
|
+
const testWorkflow = fs.readFileSync(path.join(packageRoot, '.github', 'workflows', 'test.yml'), 'utf8');
|
|
3414
|
+
assertIncludes(testWorkflow, "node-version: ['18', '24']", 'test workflow Node matrix');
|
|
3415
|
+
assertIncludes(testWorkflow, 'run: npm test', 'test workflow suite');
|
|
3416
|
+
assertIncludes(testWorkflow, 'run: npm pack --dry-run', 'test workflow package check');
|
|
3417
|
+
|
|
3418
|
+
const publishWorkflow = fs.readFileSync(path.join(packageRoot, '.github', 'workflows', 'publish-npm.yml'), 'utf8');
|
|
3419
|
+
const testIndex = publishWorkflow.indexOf('run: npm test');
|
|
3420
|
+
const packIndex = publishWorkflow.indexOf('run: npm pack --dry-run');
|
|
3421
|
+
const publishIndex = publishWorkflow.indexOf('run: npm publish');
|
|
3422
|
+
assertEqual(testIndex >= 0 && testIndex < publishIndex, true, 'publish workflow test gate');
|
|
3423
|
+
assertEqual(packIndex >= 0 && packIndex < publishIndex, true, 'publish workflow package gate');
|
|
3424
|
+
assertEqual(pkg.scripts?.['test:openrulebench'], 'node test/run-openrulebench.mjs', 'OpenRuleBench test script');
|
|
3425
|
+
},
|
|
3426
|
+
},
|
|
3410
3427
|
{
|
|
3411
3428
|
name: 'documented conformance totals match the generated report',
|
|
3412
3429
|
run: () => assertArrayEqual(documentedConformanceMetricIssues(), [], 'documented conformance totals'),
|
|
@@ -3856,6 +3873,21 @@ answer(ok) :-
|
|
|
3856
3873
|
assertEqual(result.stdout, 'member(a, "ab").\nmember(b, "ab").\n', 'stdout');
|
|
3857
3874
|
},
|
|
3858
3875
|
},
|
|
3876
|
+
{
|
|
3877
|
+
name: 'JavaScript run autoloads top-level goals for parsed Program instances',
|
|
3878
|
+
run: () => {
|
|
3879
|
+
const program = Program.parse('');
|
|
3880
|
+
const revision = program.revision;
|
|
3881
|
+
program.stratifiedNegation;
|
|
3882
|
+
const result = runEyeProlog(program, { goal: 'member(X,[a,b])' });
|
|
3883
|
+
assertEqual(result.stdout, 'member(a, "ab").\nmember(b, "ab").\n', 'stdout');
|
|
3884
|
+
assertEqual(program.findGroup('member', 2)?.module, 'lists', 'autoloaded Program predicate');
|
|
3885
|
+
assertEqual(program.autoloadedPredicates.map((entry) => `${entry.indicator}:${entry.library}`).join(','),
|
|
3886
|
+
'member/2:lists', 'autoload metadata');
|
|
3887
|
+
assertEqual(program.revision, revision + 1, 'Program revision after autoload');
|
|
3888
|
+
assertEqual(program.stratifiedNegation, true, 'negation metadata recomputed after autoload');
|
|
3889
|
+
},
|
|
3890
|
+
},
|
|
3859
3891
|
{
|
|
3860
3892
|
name: 'JavaScript run can disable top-level goal autoloading',
|
|
3861
3893
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1819,6 +1819,11 @@ additional logical answer.
|
|
|
1819
1819
|
`analyzeNegation`. It returns `stdout`, the solver's numeric `stats`, and a
|
|
1820
1820
|
nullable `haltCode`; it does not write to the process streams.
|
|
1821
1821
|
|
|
1822
|
+
When `run` receives an already parsed `Program`, canonical interop imports
|
|
1823
|
+
needed only by its host-supplied goals are added to that Program before solving,
|
|
1824
|
+
just as they are while source text is parsed. Pass `autoload: false` when the
|
|
1825
|
+
Program must retain only explicitly imported predicates.
|
|
1826
|
+
|
|
1822
1827
|
For applications that inspect or prepare a theory before running it, use
|
|
1823
1828
|
`Program` directly:
|
|
1824
1829
|
|
|
@@ -6302,8 +6307,10 @@ calls to non-profile predicates from otherwise common modules. `--portable`
|
|
|
6302
6307
|
turns those diagnostics into a failing run, making the conservative profile
|
|
6303
6308
|
suitable for continuous integration. `npm run test:interop` executes the same
|
|
6304
6309
|
portable Towers of Hanoi source under EyeProlog, Trealla, and Scryer when those commands
|
|
6305
|
-
are installed
|
|
6306
|
-
|
|
6310
|
+
are installed. Because those are external runtimes, the default `npm test`
|
|
6311
|
+
instead validates all four generated OpenRuleBench source trees and their
|
|
6312
|
+
engine-specific tabling and WFS adaptations without requiring them. The fast
|
|
6313
|
+
structural check is also available directly as `npm run test:openrulebench`.
|
|
6307
6314
|
|
|
6308
6315
|
#### Library notes beyond the interoperability profile
|
|
6309
6316
|
|