eyeprolog 1.2.44 → 1.3.1
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 +34 -2
- package/examples/book/chapter-06/03-square.pl +0 -1
- package/examples/fast-fourier-transform.pl +2 -0
- package/examples/output/fast-fourier-transform.pl +1 -1
- package/examples/sudoku.pl +4 -4
- package/index.d.ts +8 -0
- package/package.json +2 -1
- package/src/cli.js +26 -1
- package/src/index.js +9 -1
- package/src/iso.js +0 -9
- package/src/lib/lists.pl +132 -15
- package/src/program.js +233 -1
- package/src/standard-library.js +60 -5
- package/test/run-interop.mjs +77 -0
- package/test/run-regression.mjs +212 -104
- package/the-art-of-eyeprolog.md +48 -11
package/test/run-regression.mjs
CHANGED
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
eyePrologLibraryIndicators,
|
|
24
24
|
eyePrologNativeLibraryIndicators,
|
|
25
25
|
eyePrologPortableLibraryIndicators,
|
|
26
|
+
eyePrologInteropAutoload,
|
|
27
|
+
eyePrologInteropLibraryIndicators,
|
|
28
|
+
eyePrologInteropLibraryModules,
|
|
26
29
|
atom,
|
|
27
30
|
compound,
|
|
28
31
|
listFromItems,
|
|
@@ -692,108 +695,6 @@ c4 ?- call((!;1)).
|
|
|
692
695
|
}
|
|
693
696
|
},
|
|
694
697
|
},
|
|
695
|
-
{
|
|
696
|
-
name: 'read/2 and number_chars/2 agree on bounded numeric syntax (issue #29)',
|
|
697
|
-
run: () => {
|
|
698
|
-
const registry = createDefaultRegistry();
|
|
699
|
-
const numberChars = registry.get('number_chars', 2).handler;
|
|
700
|
-
const read = registry.get('read', 2).handler;
|
|
701
|
-
const solver = new Solver(Program.parse(''), { registry, ioOptions: { input: '' } });
|
|
702
|
-
const stream = solver.io.resolve(0);
|
|
703
|
-
const streamTerm = compound('$stream', [numberTerm('0')]);
|
|
704
|
-
const alphabet = ['0', '1', '2', '7', '8', '9', 'a', 'f', 'x', 'e', 'E', '+', '-', '.', "'", '\\', ' '];
|
|
705
|
-
let checked = 0;
|
|
706
|
-
let accepted = 0;
|
|
707
|
-
|
|
708
|
-
const visit = (prefix, remaining) => {
|
|
709
|
-
if (remaining === 0) {
|
|
710
|
-
checked++;
|
|
711
|
-
const converted = variable('Converted');
|
|
712
|
-
const conversionGoal = compound('number_chars', [
|
|
713
|
-
converted,
|
|
714
|
-
listFromItems(Array.from(prefix, atom)),
|
|
715
|
-
]);
|
|
716
|
-
let conversion;
|
|
717
|
-
try {
|
|
718
|
-
conversion = numberChars({ goal: conversionGoal, env: new Env() }).next();
|
|
719
|
-
} catch (_) {
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
if (conversion.done) return;
|
|
723
|
-
accepted++;
|
|
724
|
-
const expected = copyResolved(converted, conversion.value);
|
|
725
|
-
|
|
726
|
-
stream.content = `${prefix}. `;
|
|
727
|
-
stream.position = 0;
|
|
728
|
-
stream.pastEnd = false;
|
|
729
|
-
const readValue = variable('ReadValue');
|
|
730
|
-
const readGoal = compound('read', [streamTerm, readValue]);
|
|
731
|
-
const answer = read({ solver, goal: readGoal, env: new Env() }).next();
|
|
732
|
-
if (answer.done) throw new Error(`read/2 rejected number_chars/2 spelling ${JSON.stringify(prefix)}`);
|
|
733
|
-
const actual = copyResolved(readValue, answer.value);
|
|
734
|
-
assertEqual(actual.type, 'number', `read/2 type for ${JSON.stringify(prefix)}`);
|
|
735
|
-
assertEqual(actual.name, expected.name, `read/2 value for ${JSON.stringify(prefix)}`);
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
for (const character of alphabet) visit(prefix + character, remaining - 1);
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
for (let length = 1; length <= 4; length++) visit('', length);
|
|
742
|
-
assertEqual(checked, 88740, 'bounded numeric spellings checked');
|
|
743
|
-
if (accepted < 1000) throw new Error(`unexpectedly small accepted numeric corpus: ${accepted}`);
|
|
744
|
-
},
|
|
745
|
-
},
|
|
746
|
-
{
|
|
747
|
-
name: 'number_chars/2 to read/2 cross-check stays bounded under a small heap (issue #29)',
|
|
748
|
-
run: () => {
|
|
749
|
-
const engineUrl = new URL('../src/index.js', import.meta.url).href;
|
|
750
|
-
const script = `
|
|
751
|
-
import {
|
|
752
|
-
Program, Solver, Env, atom, compound, variable, listFromItems,
|
|
753
|
-
numberTerm, copyResolved, createDefaultRegistry,
|
|
754
|
-
} from ${JSON.stringify(engineUrl)};
|
|
755
|
-
const registry = createDefaultRegistry();
|
|
756
|
-
const numberChars = registry.get('number_chars', 2).handler;
|
|
757
|
-
const read = registry.get('read', 2).handler;
|
|
758
|
-
const solver = new Solver(Program.parse(''), { registry, ioOptions: { input: '' } });
|
|
759
|
-
const stream = solver.io.resolve(0);
|
|
760
|
-
const streamTerm = compound('$stream', [numberTerm('0')]);
|
|
761
|
-
for (let i = 0; i < 250000; i++) {
|
|
762
|
-
const text = String(i);
|
|
763
|
-
const converted = variable('Converted');
|
|
764
|
-
const conversionGoal = compound('number_chars', [
|
|
765
|
-
converted,
|
|
766
|
-
listFromItems(Array.from(text, atom)),
|
|
767
|
-
]);
|
|
768
|
-
const conversion = numberChars({ goal: conversionGoal, env: new Env() }).next();
|
|
769
|
-
if (conversion.done) throw new Error('number_chars/2 failed at ' + i);
|
|
770
|
-
const expected = copyResolved(converted, conversion.value);
|
|
771
|
-
|
|
772
|
-
stream.content = text + '. ';
|
|
773
|
-
stream.position = 0;
|
|
774
|
-
stream.pastEnd = false;
|
|
775
|
-
const readValue = variable('ReadValue');
|
|
776
|
-
const readGoal = compound('read', [streamTerm, readValue]);
|
|
777
|
-
const answer = read({ solver, goal: readGoal, env: new Env() }).next();
|
|
778
|
-
if (answer.done) throw new Error('read/2 failed at ' + i);
|
|
779
|
-
const actual = copyResolved(readValue, answer.value);
|
|
780
|
-
if (actual.type !== 'number' || actual.name !== expected.name) {
|
|
781
|
-
throw new Error('number mismatch at ' + i + ': ' + actual.name + ' != ' + expected.name);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
process.stdout.write('250000');
|
|
785
|
-
`;
|
|
786
|
-
const result = spawnSync(process.execPath, [
|
|
787
|
-
'--max-old-space-size=32',
|
|
788
|
-
'--input-type=module',
|
|
789
|
-
'--eval',
|
|
790
|
-
script,
|
|
791
|
-
], { cwd: packageRoot, encoding: 'utf8', timeout: 30000 });
|
|
792
|
-
if (result.error) throw result.error;
|
|
793
|
-
assertEqual(result.status, 0, `bounded-heap child status; stderr=${result.stderr}`);
|
|
794
|
-
assertEqual(result.stdout, '250000', 'number_chars/read cross-check count');
|
|
795
|
-
},
|
|
796
|
-
},
|
|
797
698
|
{
|
|
798
699
|
name: 'number syntax and number_chars normalize floating-point negative zero',
|
|
799
700
|
run: () => {
|
|
@@ -1270,6 +1171,8 @@ c4 ?- call((!;1)).
|
|
|
1270
1171
|
assertIncludes(result.stdout, '-p, --proof', 'stdout');
|
|
1271
1172
|
assertIncludes(result.stdout, '-q, --quads', 'stdout');
|
|
1272
1173
|
assertIncludes(result.stdout, '-s, --stats', 'stdout');
|
|
1174
|
+
assertIncludes(result.stdout, '--portable', 'stdout');
|
|
1175
|
+
assertIncludes(result.stdout, '--no-autoload', 'stdout');
|
|
1273
1176
|
assertIncludes(result.stdout, '-v, --version', 'stdout');
|
|
1274
1177
|
assertIncludes(result.stdout, '-w, --warnings', 'stdout');
|
|
1275
1178
|
assertIncludes(result.stdout, '-v, --version Show the package version and exit.\n -w, --warnings Print non-fatal portability warnings to stderr.', 'stdout');
|
|
@@ -2107,6 +2010,140 @@ c4 ?- call((!;1)).
|
|
|
2107
2010
|
assertIncludes(result.stderr, 'existence_error(procedure)', 'stderr');
|
|
2108
2011
|
},
|
|
2109
2012
|
},
|
|
2013
|
+
{
|
|
2014
|
+
name: 'portable library predicates autoload without use_module directives',
|
|
2015
|
+
run: () => {
|
|
2016
|
+
const input = '%% goal: answer(X)\nanswer(X) :- member(X, [a,b]).\n';
|
|
2017
|
+
const result = runCli(['-'], { input });
|
|
2018
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2019
|
+
assertEqual(result.stdout, 'answer(a).\nanswer(b).\n', 'stdout');
|
|
2020
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2021
|
+
},
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
name: 'between/3 autoload removes the EyeProlog-specific prologue dependency',
|
|
2025
|
+
run: () => {
|
|
2026
|
+
const input = '%% goal: answer(X)\nanswer(X) :- between(1, 3, X).\n';
|
|
2027
|
+
const result = runCli(['-'], { input });
|
|
2028
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2029
|
+
assertEqual(result.stdout, 'answer(1).\nanswer(2).\nanswer(3).\n', 'stdout');
|
|
2030
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2031
|
+
},
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
name: 'library(lists) length/2 stays relational and call_nth/2 autoloads (issue #28)',
|
|
2035
|
+
run: () => {
|
|
2036
|
+
const input = [
|
|
2037
|
+
':- use_module(library(lists)).',
|
|
2038
|
+
'%% goal: fourth_length(N)',
|
|
2039
|
+
'fourth_length(N) :- call_nth(length(_Xs, N), 4).',
|
|
2040
|
+
'',
|
|
2041
|
+
].join('\n');
|
|
2042
|
+
const result = runCli(['-'], { input });
|
|
2043
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2044
|
+
assertEqual(result.stdout, 'fourth_length(3).\n', 'stdout');
|
|
2045
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2046
|
+
},
|
|
2047
|
+
},
|
|
2048
|
+
{
|
|
2049
|
+
name: 'strict ISO mode disables interop autoloading',
|
|
2050
|
+
run: () => {
|
|
2051
|
+
const input = '%% goal: answer(X)\nanswer(X) :- member(X, [a,b]).\n';
|
|
2052
|
+
const result = runCli(['--iso-strict', '-'], { input });
|
|
2053
|
+
assertEqual(result.status, 1, 'exit status');
|
|
2054
|
+
assertIncludes(result.stderr, 'existence_error(procedure)', 'stderr');
|
|
2055
|
+
assertIncludes(result.stderr, '/(member, 2)', 'stderr');
|
|
2056
|
+
},
|
|
2057
|
+
},
|
|
2058
|
+
{
|
|
2059
|
+
name: 'CLI top-level goals participate in interop autoloading',
|
|
2060
|
+
run: () => {
|
|
2061
|
+
const result = runCli(['-g', 'member(X,[a,b])', '-'], { input: '' });
|
|
2062
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2063
|
+
assertEqual(result.stdout, 'member(a, "ab").\nmember(b, "ab").\n', 'stdout');
|
|
2064
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2065
|
+
},
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
name: '--no-autoload also applies to CLI top-level goals',
|
|
2069
|
+
run: () => {
|
|
2070
|
+
const result = runCli(['--no-autoload', '-g', 'member(X,[a])', '-'], { input: '' });
|
|
2071
|
+
assertEqual(result.status, 1, 'exit status');
|
|
2072
|
+
assertIncludes(result.stderr, 'existence_error(procedure)', 'stderr');
|
|
2073
|
+
assertIncludes(result.stderr, '/(member, 2)', 'stderr');
|
|
2074
|
+
},
|
|
2075
|
+
},
|
|
2076
|
+
{
|
|
2077
|
+
name: '--portable checks non-portable predicates used only by top-level goals',
|
|
2078
|
+
run: () => {
|
|
2079
|
+
const input = ':- use_module(library(lists)).\n';
|
|
2080
|
+
const result = runCli(['--portable', '-g', 'set_nth0(0,[a],b,X)', '-'], { input });
|
|
2081
|
+
assertEqual(result.status, 1, 'exit status');
|
|
2082
|
+
assertEqual(result.stdout, '', 'stdout');
|
|
2083
|
+
assertIncludes(result.stderr, 'non-portable library predicate', 'stderr');
|
|
2084
|
+
assertIncludes(result.stderr, 'set_nth0/4', 'stderr');
|
|
2085
|
+
},
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
name: '--portable accepts the common interop profile',
|
|
2089
|
+
run: () => {
|
|
2090
|
+
const input = ':- use_module(library(lists)).\n%% goal: answer(X)\nanswer(X) :- member(X, [a]).\n';
|
|
2091
|
+
const result = runCli(['--portable', '-'], { input });
|
|
2092
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2093
|
+
assertEqual(result.stdout, 'answer(a).\n', 'stdout');
|
|
2094
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2095
|
+
},
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
name: '--portable rejects implementation-specific library dependencies',
|
|
2099
|
+
run: () => {
|
|
2100
|
+
const input = ':- use_module(library(prologue), [between/3]).\n%% goal: answer\nanswer :- between(1, 1, _).\n';
|
|
2101
|
+
const result = runCli(['--portable', '-'], { input });
|
|
2102
|
+
assertEqual(result.status, 1, 'exit status');
|
|
2103
|
+
assertEqual(result.stdout, '', 'stdout');
|
|
2104
|
+
assertIncludes(result.stderr, 'non-portable library dependency', 'stderr');
|
|
2105
|
+
},
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
name: '--no-autoload exposes unresolved portable dependencies',
|
|
2109
|
+
run: () => {
|
|
2110
|
+
const input = '%% goal: answer(X)\nanswer(X) :- member(X, [a]).\n';
|
|
2111
|
+
const result = runCli(['--no-autoload', '-'], { input });
|
|
2112
|
+
assertEqual(result.status, 1, 'exit status');
|
|
2113
|
+
assertIncludes(result.stderr, 'existence_error(procedure)', 'stderr');
|
|
2114
|
+
assertIncludes(result.stderr, '/(member, 2)', 'stderr');
|
|
2115
|
+
},
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
name: '--warnings flags explicit library(prologue) dependencies',
|
|
2119
|
+
run: () => {
|
|
2120
|
+
const input = ':- use_module(library(prologue), [between/3]).\n%% goal: answer\nanswer :- between(1, 1, _).\n';
|
|
2121
|
+
const result = runCli(['--warnings', '-'], { input });
|
|
2122
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2123
|
+
assertIncludes(result.stderr, 'eyeprolog warning: non-portable library dependency\n', 'stderr');
|
|
2124
|
+
assertIncludes(result.stderr, 'library(prologue) is outside the EyeProlog/Trealla/Scryer interop profile', 'stderr');
|
|
2125
|
+
},
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
name: '--warnings flags EyeProlog-only predicates from library(lists)',
|
|
2129
|
+
run: () => {
|
|
2130
|
+
const input = ':- use_module(library(lists)).\n%% goal: answer(X)\nanswer(X) :- set_nth0(0, [a], b, X).\n';
|
|
2131
|
+
const result = runCli(['--warnings', '-'], { input });
|
|
2132
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2133
|
+
assertIncludes(result.stderr, 'eyeprolog warning: non-portable library predicate\n', 'stderr');
|
|
2134
|
+
assertIncludes(result.stderr, 'set_nth0/4 from library(lists) is outside the interop profile', 'stderr');
|
|
2135
|
+
},
|
|
2136
|
+
},
|
|
2137
|
+
{
|
|
2138
|
+
name: '--warnings stays quiet for the common library(lists) profile',
|
|
2139
|
+
run: () => {
|
|
2140
|
+
const input = ':- use_module(library(lists)).\n%% goal: answer(X)\nanswer(X) :- member(X, [a]).\n';
|
|
2141
|
+
const result = runCli(['--warnings', '-'], { input });
|
|
2142
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2143
|
+
assertEqual(result.stdout, 'answer(a).\n', 'stdout');
|
|
2144
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2145
|
+
},
|
|
2146
|
+
},
|
|
2110
2147
|
{
|
|
2111
2148
|
name: '--warnings prints unstratified negation diagnostics without failing',
|
|
2112
2149
|
run: () => {
|
|
@@ -3055,6 +3092,71 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
3055
3092
|
assertEqual(answers.join('\n'), 'answer("ab")', 'answers');
|
|
3056
3093
|
},
|
|
3057
3094
|
},
|
|
3095
|
+
{
|
|
3096
|
+
name: 'library(lists) exposes the Trealla/Scryer common surface',
|
|
3097
|
+
run: () => {
|
|
3098
|
+
const program = Program.parse(`:- use_module(library(lists)).
|
|
3099
|
+
identity(X, X).
|
|
3100
|
+
pair(A, B, A-B).
|
|
3101
|
+
step(X, A0, A) :- A is A0 + X.
|
|
3102
|
+
check(A, B, C, D, E, F) :-
|
|
3103
|
+
append([[a],[b]], A),
|
|
3104
|
+
memberchk(a, A),
|
|
3105
|
+
same_length(A, [_,_]),
|
|
3106
|
+
nth0(1, A, b, B),
|
|
3107
|
+
maplist(identity, A, C),
|
|
3108
|
+
maplist(pair, A, [1,2], D),
|
|
3109
|
+
foldl(step, [1,2,3], 0, E),
|
|
3110
|
+
nth1(2, A, b, F).
|
|
3111
|
+
`);
|
|
3112
|
+
assertEqual(program.findGroup('append', 2)?.module, 'lists', 'append/2 import');
|
|
3113
|
+
assertEqual(program.findGroup('memberchk', 2)?.module, 'lists', 'memberchk/2 import');
|
|
3114
|
+
assertEqual(program.findGroup('same_length', 2)?.module, 'lists', 'same_length/2 import');
|
|
3115
|
+
assertEqual(program.findGroup('nth0', 4)?.module, 'lists', 'nth0/4 import');
|
|
3116
|
+
assertEqual(program.findGroup('maplist', 2)?.module, 'lists', 'maplist/2 import');
|
|
3117
|
+
assertEqual(program.findGroup('maplist', 3)?.module, 'lists', 'maplist/3 import');
|
|
3118
|
+
assertEqual(program.findGroup('foldl', 4)?.module, 'lists', 'foldl/4 import');
|
|
3119
|
+
const result = run(program, { goal: 'check(A,B,C,D,E,F)' });
|
|
3120
|
+
assertIncludes(result.stdout, 'check("ab", "a", "ab", [a - 1, b - 2], 6, "a").\n', 'stdout');
|
|
3121
|
+
},
|
|
3122
|
+
},
|
|
3123
|
+
{
|
|
3124
|
+
name: 'autoload metadata records canonical interop imports',
|
|
3125
|
+
run: () => {
|
|
3126
|
+
const program = Program.parse('answer(X) :- member(X, [a]), between(1, 1, _).\n');
|
|
3127
|
+
assertEqual(program.autoloadedPredicates.length, 2, 'autoloaded predicate count');
|
|
3128
|
+
assertEqual(program.autoloadedPredicates.map((entry) => `${entry.indicator}:${entry.library}`).sort().join(','),
|
|
3129
|
+
'between/3:prologue,member/2:lists', 'autoload mapping');
|
|
3130
|
+
assertEqual(program.interopPortabilityWarnings.length, 0, 'autoloaded portable calls are warning-free');
|
|
3131
|
+
},
|
|
3132
|
+
},
|
|
3133
|
+
{
|
|
3134
|
+
name: 'JavaScript run top-level goals participate in interop autoloading',
|
|
3135
|
+
run: () => {
|
|
3136
|
+
const result = runEyeProlog('', { goal: 'member(X,[a,b])' });
|
|
3137
|
+
assertEqual(result.stdout, 'member(a, "ab").\nmember(b, "ab").\n', 'stdout');
|
|
3138
|
+
},
|
|
3139
|
+
},
|
|
3140
|
+
{
|
|
3141
|
+
name: 'JavaScript run can disable top-level goal autoloading',
|
|
3142
|
+
run: () => {
|
|
3143
|
+
let error = null;
|
|
3144
|
+
try {
|
|
3145
|
+
runEyeProlog('', { goal: 'member(X,[a])', autoload: false });
|
|
3146
|
+
} catch (caught) {
|
|
3147
|
+
error = caught;
|
|
3148
|
+
}
|
|
3149
|
+
assertIncludes(error?.message ?? '', 'existence_error(procedure)', 'error');
|
|
3150
|
+
},
|
|
3151
|
+
},
|
|
3152
|
+
{
|
|
3153
|
+
name: 'autoload can be disabled explicitly in the JavaScript API',
|
|
3154
|
+
run: () => {
|
|
3155
|
+
const program = Program.parse('answer(X) :- member(X, [a]).\n', { autoload: false });
|
|
3156
|
+
assertEqual(program.findGroup('member', 2), null, 'member/2 remains unresolved');
|
|
3157
|
+
assertEqual(program.autoloadedPredicates.length, 0, 'no autoloads');
|
|
3158
|
+
},
|
|
3159
|
+
},
|
|
3058
3160
|
{
|
|
3059
3161
|
name: 'program and solver public classes',
|
|
3060
3162
|
run: () => {
|
|
@@ -3345,10 +3447,16 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
3345
3447
|
assertEqual(Boolean(library.get('statistics', 0)), true, 'statistics/0 is an EyeProlog observability extension');
|
|
3346
3448
|
assertEqual(Boolean(library.get('statistics', 2)), true, 'statistics/2 is an EyeProlog observability extension');
|
|
3347
3449
|
assertEqual(registeredNativeEyePrologLibraryNames().length, 40, 'public native EyeProlog builtin count');
|
|
3348
|
-
assertEqual(eyePrologPortableLibraryIndicators.length,
|
|
3450
|
+
assertEqual(eyePrologPortableLibraryIndicators.length, 62, 'portable Prolog library count');
|
|
3451
|
+
assertEqual(eyePrologInteropLibraryIndicators.length, 27, 'cross-implementation interop profile count');
|
|
3452
|
+
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists', 'common explicit library module profile');
|
|
3453
|
+
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
3454
|
+
assertEqual(eyePrologInteropAutoload['between/3'], 'prologue', 'between/3 canonical internal autoload');
|
|
3455
|
+
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'prologue', 'call_nth/2 canonical internal autoload');
|
|
3456
|
+
assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'EyeProlog-only set_nth0/4 is not autoloadable');
|
|
3349
3457
|
assertEqual(eyePrologNativeLibraryIndicators.length, 40, 'native host library count');
|
|
3350
3458
|
assertEqual(eyePrologNativeLibraryIndicators.slice(0, 2).join(','), 'call_nth/2,freeze/2', 'control predicates requiring host support');
|
|
3351
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
3459
|
+
assertEqual(eyePrologLibraryIndicators.length, 102, 'complete EyeProlog library surface');
|
|
3352
3460
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
3353
3461
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
3354
3462
|
assertEqual(library.get('eyeprolog__call_nth', 2)?.eyePrologLibrary, true, 'private adapter is marked as library support');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1065,7 +1065,6 @@ safe_reading(Sensor, Value) :-
|
|
|
1065
1065
|
already-bound value:
|
|
1066
1066
|
|
|
1067
1067
|
```eyeprolog
|
|
1068
|
-
:- use_module(library(prologue), [between/3]).
|
|
1069
1068
|
:- use_module(library(lists)).
|
|
1070
1069
|
|
|
1071
1070
|
square(N, Square) :-
|
|
@@ -5975,8 +5974,8 @@ so side effects occur in Prolog execution order.
|
|
|
5975
5974
|
|
|
5976
5975
|
### The EyeProlog library
|
|
5977
5976
|
|
|
5978
|
-
EyeProlog exposes **
|
|
5979
|
-
indicators in its isolated ISO profile. **
|
|
5977
|
+
EyeProlog exposes **102 library predicate indicators** in addition to the 129
|
|
5978
|
+
indicators in its isolated ISO profile. **63 are defined as ordinary Prolog
|
|
5980
5979
|
clauses** in focused modules under `src/lib/`. The remaining 39
|
|
5981
5980
|
are public wrappers around backtrackable host support: Prologue `call_nth/2` and
|
|
5982
5981
|
`freeze/2`, plus the 37-predicate finite-domain `library(clpz)` kernel. The
|
|
@@ -5994,9 +5993,10 @@ only the modules it needs, and
|
|
|
5994
5993
|
module implements p.p.1 through p.p.11 of the
|
|
5995
5994
|
[working-draft Prologue](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue),
|
|
5996
5995
|
with the published Prologue, `call_nth/2`, and `length/2` quads retained as
|
|
5997
|
-
offline regressions. `src/standard-library.js` registers the module sources and
|
|
5998
|
-
|
|
5999
|
-
|
|
5996
|
+
offline regressions. `src/standard-library.js` registers the module sources and private control and
|
|
5997
|
+
constraint adapters for Node and browser resolution. Explicit `use_module/1-2`
|
|
5998
|
+
loads remain supported; outside strict ISO mode, the conservative interop
|
|
5999
|
+
autoloader may also load a module for one uniquely mapped common predicate.
|
|
6000
6000
|
The core registry remains available through `createDefaultRegistry()` and
|
|
6001
6001
|
`getDefaultRegistry()` for low-level embedding. The stricter Part 1 +
|
|
6002
6002
|
Corrigenda registry is exposed as `createStrictIsoRegistry()` and
|
|
@@ -6018,7 +6018,7 @@ between solution branches.
|
|
|
6018
6018
|
| `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` |
|
|
6019
6019
|
| `library(dates)` | `difference/3` |
|
|
6020
6020
|
| `library(iso_ext)` | `countall/2`, `forall/2`, `succ/2`, `cfor/3`, `findall/4`, `variant/2` |
|
|
6021
|
-
| `library(lists)` | `
|
|
6021
|
+
| `library(lists)` | `member/2`, `memberchk/2`, `select/3`, `append/2`, `append/3`, `last/2`, `same_length/2`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `reverse/2`, `length/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `countall/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` |
|
|
6022
6022
|
| `library(primes)` | `smallest_divisor_from/3` |
|
|
6023
6023
|
| `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `freeze/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` |
|
|
6024
6024
|
| `library(random)` | `random/3` |
|
|
@@ -6027,6 +6027,39 @@ between solution branches.
|
|
|
6027
6027
|
|
|
6028
6028
|
<!-- eyeprolog-library-catalog:end -->
|
|
6029
6029
|
|
|
6030
|
+
#### Interoperability profile and conservative autoloading
|
|
6031
|
+
|
|
6032
|
+
The full EyeProlog library is larger than the deliberately conservative
|
|
6033
|
+
EyeProlog/Trealla/Scryer source-interoperability profile. The two concepts are
|
|
6034
|
+
kept separate: a predicate can be implemented as ordinary portable Prolog and
|
|
6035
|
+
still have an API that is not shared by the other systems.
|
|
6036
|
+
|
|
6037
|
+
`library(lists)` is the first aligned common module. Its interop surface includes
|
|
6038
|
+
`member/2`, `memberchk/2`, `select/3`, `append/2-3`, `last/2`,
|
|
6039
|
+
`same_length/2`, `nth0/3-4`, `nth1/3-4`, `reverse/2`, `length/2`,
|
|
6040
|
+
`maplist/2-8`, `foldl/4-6`, `sum_list/2`, and `list_to_set/2`. Its `length/2`
|
|
6041
|
+
is fully relational: when both arguments are variables, `length(Xs, N)`
|
|
6042
|
+
enumerates `Xs = [], N = 0`, then one-element lists with `N = 1`, and so on.
|
|
6043
|
+
This generator mode is important for portable stress and enumeration programs.
|
|
6044
|
+
EyeProlog-only helpers such as `set_nth0/4`, `take/3`, `drop/3`, and `slice/4`
|
|
6045
|
+
remain available for compatibility but are outside this profile.
|
|
6046
|
+
|
|
6047
|
+
Normal EyeProlog execution can autoload an otherwise undefined unqualified call
|
|
6048
|
+
only when the interop table assigns it one canonical provider. Thus `member/2`
|
|
6049
|
+
autoloads from `library(lists)`. `between/3` and `call_nth/2` are also in the
|
|
6050
|
+
interop profile; EyeProlog can satisfy them from its internal
|
|
6051
|
+
`library(prologue)` implementation without forcing portable source to mention
|
|
6052
|
+
that EyeProlog-specific library name. Autoloading is disabled by
|
|
6053
|
+
`--no-autoload`, by the JavaScript option `autoload: false`, and always by
|
|
6054
|
+
`--iso-strict`.
|
|
6055
|
+
|
|
6056
|
+
`-w` / `--warnings` reports explicit non-profile library dependencies and calls
|
|
6057
|
+
to non-profile predicates from common libraries. `--portable` turns those
|
|
6058
|
+
portability diagnostics into a failing command, making the profile suitable for
|
|
6059
|
+
continuous integration. `npm run test:interop` executes the same Sudoku source
|
|
6060
|
+
under EyeProlog, Trealla, and Scryer when those commands are installed; the
|
|
6061
|
+
repository's interoperability workflow installs them and runs that check.
|
|
6062
|
+
|
|
6030
6063
|
`library(clpz)` follows the Trealla and Scryer convention for constraint logic
|
|
6031
6064
|
programming over integers. Its first implementation step provides finite
|
|
6032
6065
|
interval and union domains, arithmetic and reified constraints, backtrackable
|
|
@@ -6420,7 +6453,9 @@ make the observed question explicit.
|
|
|
6420
6453
|
| `-h`, `--help` | Show usage |
|
|
6421
6454
|
| `-p`, `--proof` | Print `why/2` explanations |
|
|
6422
6455
|
| `-q`, `--quads` | Run embedded quad tests and fail if any do not hold |
|
|
6423
|
-
| `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions and disable automatic tabling |
|
|
6456
|
+
| `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions, disable interop autoloading, and disable automatic tabling |
|
|
6457
|
+
| `--portable` | Enforce the conservative EyeProlog/Trealla/Scryer interoperability profile |
|
|
6458
|
+
| `--no-autoload` | Disable conservative interop predicate autoloading |
|
|
6424
6459
|
| `-s`, `--stats` | Print final solver and memory statistics to stderr after execution |
|
|
6425
6460
|
| `-v`, `--version` | Print the package version |
|
|
6426
6461
|
| `-w`, `--warnings` | Print non-fatal portability warnings |
|
|
@@ -6434,8 +6469,9 @@ retains the Part 1 prefix `(?-)/1` operator and treats `-->/2` as ordinary Part
|
|
|
6434
6469
|
1 operator syntax; it does not perform Part 3 grammar-rule expansion or expose
|
|
6435
6470
|
`phrase/2-3`. Part 2 module directives and EyeProlog libraries are rejected,
|
|
6436
6471
|
the implementation-specific `occurs_check` flag is absent, and automatic
|
|
6437
|
-
tabling/recursion guards are disabled. Normal mode
|
|
6438
|
-
|
|
6472
|
+
tabling/recursion guards are disabled. Normal mode continues to support Parts
|
|
6473
|
+
2–3 and EyeProlog extensions, plus conservative interop autoloading for the
|
|
6474
|
+
profile documented above.
|
|
6439
6475
|
|
|
6440
6476
|
Inputs may be local files, HTTP(S) URLs, or one `-` for stdin. The bare command
|
|
6441
6477
|
`eyeprolog` starts the normal REPL; `eyeprolog --iso-strict` starts the strict
|
|
@@ -6462,7 +6498,7 @@ Work in a fixed sequence:
|
|
|
6462
6498
|
2. run without observation flags and compare stdout with that prediction;
|
|
6463
6499
|
3. add `--proof` when the support for an answer is the question;
|
|
6464
6500
|
4. add `--warnings` when portability or negative dependencies are the
|
|
6465
|
-
question;
|
|
6501
|
+
question; use `--portable` when non-profile dependencies must fail CI;
|
|
6466
6502
|
5. add `--stats` only when comparing two executions of the same semantic case.
|
|
6467
6503
|
|
|
6468
6504
|
For example:
|
|
@@ -6471,6 +6507,7 @@ For example:
|
|
|
6471
6507
|
eyeprolog --goal 'ancestor(X, Y)' examples/ancestor.pl
|
|
6472
6508
|
eyeprolog --proof --goal 'type(X, Y)' examples/socrates.pl
|
|
6473
6509
|
eyeprolog --warnings --goal 'answer(X)' test/conformance/warnings/negation/unstratified_mutual.pl
|
|
6510
|
+
eyeprolog --portable --goal 'sudoku_solution(S)' examples/sudoku.pl
|
|
6474
6511
|
eyeprolog --stats --goal 'path(a, X)' examples/path-discovery.pl > answers.pl 2> run.stats
|
|
6475
6512
|
```
|
|
6476
6513
|
|