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.
@@ -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, 59, 'portable Prolog library count');
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, 99, 'complete EyeProlog library surface');
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');
@@ -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 **99 library predicate indicators** in addition to the 129
5979
- indicators in its isolated ISO profile. **60 are defined as ordinary Prolog
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
- private control and constraint adapters for Node and browser resolution, and never adds clauses
5999
- implicitly.
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)` | `maplist/3`, `append/3`, `member/2`, `select/3`, `last/2`, `nth0/3`, `nth1/3`, `reverse/2`, `length/2`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `countall/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` |
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 is unchanged and continues
6438
- to support Parts 2–3 and EyeProlog extensions.
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