eyeprolog 1.5.1 → 1.5.2

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/index.d.ts CHANGED
@@ -289,6 +289,9 @@ export const eyePrologLibraryIndicators: readonly string[];
289
289
  export const eyePrologNativeLibraryIndicators: readonly string[];
290
290
  export const eyePrologPortableLibraryIndicators: readonly string[];
291
291
  export const eyePrologInteropAutoload: Readonly<Record<string, string>>;
292
+ export const eyePrologLibraryAutoload: Readonly<Record<string, string>>;
293
+ export const eyePrologAmbiguousLibraryAutoload: Readonly<Record<string, readonly string[]>>;
294
+ export const eyePrologLibraryAutoloadModules: readonly string[];
292
295
  export const eyePrologInteropLibraryIndicators: readonly string[];
293
296
  export const eyePrologInteropLibraryModules: readonly string[];
294
297
  export class PrologError extends Error {
@@ -374,6 +377,9 @@ declare const eyeprolog: {
374
377
  eyePrologNativeLibraryIndicators: typeof eyePrologNativeLibraryIndicators;
375
378
  eyePrologPortableLibraryIndicators: typeof eyePrologPortableLibraryIndicators;
376
379
  eyePrologInteropAutoload: typeof eyePrologInteropAutoload;
380
+ eyePrologLibraryAutoload: typeof eyePrologLibraryAutoload;
381
+ eyePrologAmbiguousLibraryAutoload: typeof eyePrologAmbiguousLibraryAutoload;
382
+ eyePrologLibraryAutoloadModules: typeof eyePrologLibraryAutoloadModules;
377
383
  eyePrologInteropLibraryIndicators: typeof eyePrologInteropLibraryIndicators;
378
384
  eyePrologInteropLibraryModules: typeof eyePrologInteropLibraryModules;
379
385
  run: typeof run;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.1",
6
+ "version": "1.5.2",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -46,7 +46,8 @@
46
46
  },
47
47
  "scripts": {
48
48
  "test": "node test/run-all.mjs",
49
- "generate": "node tools/extract-book-examples.mjs",
49
+ "generate": "node tools/generate-library-autoload-index.mjs && node tools/extract-book-examples.mjs",
50
+ "generate:autoload": "node tools/generate-library-autoload-index.mjs",
50
51
  "test:eyeprolog": "node test/run-all.mjs",
51
52
  "test:conformance": "node test/run-conformance.mjs",
52
53
  "test:iso-strict": "node test/run-iso-strict.mjs",
package/src/cli.js CHANGED
@@ -281,7 +281,7 @@ Options:
281
281
  --iso-strict Use ISO/IEC 13211-1 core + Corrigenda 1-3 only;
282
282
  reject EyeProlog language extensions.
283
283
  --portable Enforce the EyeProlog/Trealla/Scryer interop profile.
284
- --no-autoload Disable conservative interop predicate autoloading.
284
+ --no-autoload Disable bundled library predicate autoloading.
285
285
  -v, --version Show the package version and exit.
286
286
  -w, --warnings Print non-fatal portability warnings to stderr.
287
287
  -g, --goal goal Solve goal and print its ground answers; may be repeated.
package/src/index.js CHANGED
@@ -21,6 +21,9 @@ export {
21
21
  eyePrologNativeLibraryIndicators,
22
22
  eyePrologPortableLibraryIndicators,
23
23
  eyePrologInteropAutoload,
24
+ eyePrologLibraryAutoload,
25
+ eyePrologAmbiguousLibraryAutoload,
26
+ eyePrologLibraryAutoloadModules,
24
27
  eyePrologInteropLibraryIndicators,
25
28
  eyePrologInteropLibraryModules,
26
29
  } from './standard-library.js';
@@ -0,0 +1,346 @@
1
+ // Generated by tools/generate-library-autoload-index.mjs.
2
+ // Do not edit by hand; module/2 export lists under src/lib/ are the source of truth.
3
+
4
+ export const eyePrologLibraryAutoload = Object.freeze({
5
+ ",/3": "reif",
6
+ ";/3": "reif",
7
+ ".../2": "iso_ext",
8
+ "*/1": "debug",
9
+ "\\/1": "lambda",
10
+ "\\/2": "lambda",
11
+ "\\/3": "lambda",
12
+ "\\/4": "lambda",
13
+ "\\/5": "lambda",
14
+ "\\/6": "lambda",
15
+ "\\/7": "lambda",
16
+ "\\/8": "lambda",
17
+ "#/\\/2": "clpz",
18
+ "#\\//2": "clpz",
19
+ "#\\/1": "clpz",
20
+ "#\\/2": "clpz",
21
+ "#\\=/2": "clpz",
22
+ "#</2": "clpz",
23
+ "#</3": "clpz",
24
+ "#<==/2": "clpz",
25
+ "#<==>/2": "clpz",
26
+ "#=/2": "clpz",
27
+ "#=/3": "clpz",
28
+ "#=</2": "clpz",
29
+ "#==>/2": "clpz",
30
+ "#>/2": "clpz",
31
+ "#>=/2": "clpz",
32
+ "^/10": "lambda",
33
+ "^/3": "lambda",
34
+ "^/4": "lambda",
35
+ "^/5": "lambda",
36
+ "^/6": "lambda",
37
+ "^/7": "lambda",
38
+ "^/8": "lambda",
39
+ "^/9": "lambda",
40
+ "+\\/2": "lambda",
41
+ "+\\/3": "lambda",
42
+ "+\\/4": "lambda",
43
+ "+\\/5": "lambda",
44
+ "+\\/6": "lambda",
45
+ "+\\/7": "lambda",
46
+ "+\\/8": "lambda",
47
+ "+\\/9": "lambda",
48
+ "=/3": "reif",
49
+ "$-/1": "debug",
50
+ "$/1": "debug",
51
+ "abolish_all_tables/0": "tabling",
52
+ "add_edges/3": "ugraphs",
53
+ "add_vertices/3": "ugraphs",
54
+ "aggregate_max/5": "aggregate",
55
+ "aggregate_min/5": "aggregate",
56
+ "all_different/1": "clpz",
57
+ "all_distinct/1": "clpz",
58
+ "append/2": "lists",
59
+ "append/3": "lists",
60
+ "assoc_to_keys/2": "assoc",
61
+ "assoc_to_list/2": "assoc",
62
+ "assoc_to_values/2": "assoc",
63
+ "atom_si/1": "si",
64
+ "atom_string/2": "strings",
65
+ "atomic_si/1": "si",
66
+ "automaton/3": "clpz",
67
+ "automaton/8": "clpz",
68
+ "bb_b_put/2": "debug",
69
+ "bb_get/2": "debug",
70
+ "bb_global_get/2": "debug",
71
+ "bb_put/2": "debug",
72
+ "between/3": "between",
73
+ "call_nth/2": "iso_ext",
74
+ "call_residue_vars/2": "atts",
75
+ "call_with_error_context/2": "error",
76
+ "can_be/2": "error",
77
+ "cfor/3": "iso_ext",
78
+ "chain/2": "clpz",
79
+ "char_type/2": "charsio",
80
+ "character_si/1": "si",
81
+ "chars_si/1": "si",
82
+ "circuit/1": "clpz",
83
+ "clpz_t/2": "clpz",
84
+ "complement/2": "ugraphs",
85
+ "compose/3": "ugraphs",
86
+ "cond_t/3": "reif",
87
+ "connect_ugraph/3": "ugraphs",
88
+ "contains/2": "strings",
89
+ "copy_term_nat/2": "terms",
90
+ "countall/2": "iso_ext",
91
+ "cumulative/1": "clpz",
92
+ "cumulative/2": "clpz",
93
+ "current_time/1": "time",
94
+ "debug/1": "debug",
95
+ "debug/3": "debug",
96
+ "del_assoc/4": "assoc",
97
+ "del_attr/2": "atts",
98
+ "del_edges/3": "ugraphs",
99
+ "del_max_assoc/4": "assoc",
100
+ "del_min_assoc/4": "assoc",
101
+ "del_vertices/3": "ugraphs",
102
+ "dif_si/2": "si",
103
+ "dif/2": "dif",
104
+ "dif/3": "reif",
105
+ "difference/3": "dates",
106
+ "disjoint2/1": "clpz",
107
+ "domain_error/2": "error",
108
+ "domain_error/3": "error",
109
+ "drop/3": "lists",
110
+ "edges/2": "ugraphs",
111
+ "element/3": "clpz",
112
+ "empty_assoc/1": "assoc",
113
+ "fd_dom/2": "clpz",
114
+ "fd_inf/2": "clpz",
115
+ "fd_size/2": "clpz",
116
+ "fd_sup/2": "clpz",
117
+ "fd_var/1": "clpz",
118
+ "findall/4": "iso_ext",
119
+ "foldl/4": "lists",
120
+ "foldl/5": "lists",
121
+ "foldl/6": "lists",
122
+ "forall/2": "iso_ext",
123
+ "format_/4": "format",
124
+ "format_time/4": "time",
125
+ "format/2": "format",
126
+ "format/3": "format",
127
+ "freeze/2": "freeze",
128
+ "ge/2": "comparison",
129
+ "gen_assoc/3": "assoc",
130
+ "gen_int/1": "between",
131
+ "gen_nat/1": "between",
132
+ "gensym/2": "gensym",
133
+ "get_assoc/3": "assoc",
134
+ "get_assoc/5": "assoc",
135
+ "get_attr/3": "atts",
136
+ "get_atts/2": "atts",
137
+ "get_line_to_chars/3": "charsio",
138
+ "get_n_chars/3": "charsio",
139
+ "get_single_char/1": "charsio",
140
+ "global_cardinality/2": "clpz",
141
+ "global_cardinality/3": "clpz",
142
+ "group_pairs_by_key/2": "pairs",
143
+ "gt/2": "comparison",
144
+ "if_/3": "reif",
145
+ "in/2": "clpz",
146
+ "indomain/1": "clpz",
147
+ "ins/2": "clpz",
148
+ "instantiation_error/0": "error",
149
+ "instantiation_error/1": "error",
150
+ "integer_si/1": "si",
151
+ "is_assoc/1": "assoc",
152
+ "is_ordset/1": "ordsets",
153
+ "join/3": "strings",
154
+ "label/1": "clpz",
155
+ "labeling/1": "clpb",
156
+ "labeling/2": "clpz",
157
+ "last/2": "lists",
158
+ "le/2": "comparison",
159
+ "length/2": "lists",
160
+ "lex_chain/1": "clpz",
161
+ "list_max/2": "lists",
162
+ "list_min/2": "lists",
163
+ "list_si/1": "si",
164
+ "list_to_assoc/2": "assoc",
165
+ "list_to_ord_set/2": "ordsets",
166
+ "list_to_set/2": "lists",
167
+ "listing/1": "format",
168
+ "lowercase/2": "strings",
169
+ "lsb/2": "arithmetic",
170
+ "lt/2": "comparison",
171
+ "map_assoc/2": "assoc",
172
+ "map_assoc/3": "assoc",
173
+ "map_list_to_pairs/3": "pairs",
174
+ "maplist/2": "lists",
175
+ "maplist/3": "lists",
176
+ "maplist/4": "lists",
177
+ "maplist/5": "lists",
178
+ "maplist/6": "lists",
179
+ "maplist/7": "lists",
180
+ "maplist/8": "lists",
181
+ "matches/2": "strings",
182
+ "matches/3": "strings",
183
+ "max_assoc/3": "assoc",
184
+ "max_list/2": "lists",
185
+ "maybe/0": "random",
186
+ "member/2": "lists",
187
+ "memberchk/2": "lists",
188
+ "memberd_t/3": "reif",
189
+ "min_assoc/3": "assoc",
190
+ "min_list/2": "lists",
191
+ "msb/2": "arithmetic",
192
+ "must_be/2": "error",
193
+ "neighbors/3": "ugraphs",
194
+ "neighbours/3": "ugraphs",
195
+ "nodebug/1": "debug",
196
+ "not_si/1": "si",
197
+ "nth0/3": "lists",
198
+ "nth0/4": "lists",
199
+ "nth1/3": "lists",
200
+ "nth1/4": "lists",
201
+ "number_string/2": "strings",
202
+ "numbervars/3": "terms",
203
+ "numlist/2": "between",
204
+ "numlist/3": "between",
205
+ "nvalue/2": "clpz",
206
+ "ord_add_element/3": "ordsets",
207
+ "ord_del_element/3": "ordsets",
208
+ "ord_disjoint/2": "ordsets",
209
+ "ord_empty/1": "ordsets",
210
+ "ord_intersect/2": "ordsets",
211
+ "ord_intersect/3": "ordsets",
212
+ "ord_intersection/2": "ordsets",
213
+ "ord_intersection/3": "ordsets",
214
+ "ord_intersection/4": "ordsets",
215
+ "ord_list_to_assoc/2": "assoc",
216
+ "ord_memberchk/2": "ordsets",
217
+ "ord_selectchk/3": "ordsets",
218
+ "ord_seteq/2": "ordsets",
219
+ "ord_subset/2": "ordsets",
220
+ "ord_subtract/3": "ordsets",
221
+ "ord_symdiff/3": "ordsets",
222
+ "ord_union/2": "ordsets",
223
+ "ord_union/3": "ordsets",
224
+ "ord_union/4": "ordsets",
225
+ "pairs_keys_values/3": "pairs",
226
+ "pairs_keys/2": "pairs",
227
+ "pairs_values/2": "pairs",
228
+ "permutation/2": "lists",
229
+ "phrase_from_file/2": "pio",
230
+ "phrase_from_file/3": "pio",
231
+ "phrase_to_file/2": "pio",
232
+ "phrase_to_file/3": "pio",
233
+ "phrase_to_stream/2": "pio",
234
+ "phrase/2": "dcgs",
235
+ "phrase/3": "dcgs",
236
+ "popcount/2": "arithmetic",
237
+ "portray_clause_/3": "format",
238
+ "portray_clause/1": "format",
239
+ "portray_clause/2": "format",
240
+ "put_assoc/4": "assoc",
241
+ "put_attr/3": "atts",
242
+ "put_atts/2": "atts",
243
+ "random_integer/3": "random",
244
+ "random_labeling/2": "clpb",
245
+ "random/1": "random",
246
+ "random/3": "random",
247
+ "reachable/3": "ugraphs",
248
+ "repeat/1": "between",
249
+ "replace/4": "strings",
250
+ "representation_error/1": "error",
251
+ "reset_gensym/1": "gensym",
252
+ "resource_error/1": "error",
253
+ "reverse/2": "lists",
254
+ "same_length/2": "lists",
255
+ "sat_count/2": "clpb",
256
+ "sat/1": "clpb",
257
+ "scalar_product/4": "clpz",
258
+ "select/3": "lists",
259
+ "seq/3": "dcgs",
260
+ "seqq/3": "dcgs",
261
+ "serialized/2": "clpz",
262
+ "set_nth0/4": "lists",
263
+ "set_random/1": "random",
264
+ "slice/4": "lists",
265
+ "smallest_divisor_from/3": "primes",
266
+ "split/3": "strings",
267
+ "start_tabling/2": "tabling",
268
+ "string_concat/3": "strings",
269
+ "substring/4": "strings",
270
+ "succ/2": "iso_ext",
271
+ "sum_list/2": "lists",
272
+ "sum/3": "clpz",
273
+ "sumall/3": "aggregate",
274
+ "take/3": "lists",
275
+ "taut/2": "clpb",
276
+ "term_attributed_variables/2": "atts",
277
+ "term_si/1": "si",
278
+ "term_string/2": "strings",
279
+ "tfilter/3": "reif",
280
+ "time/1": "iso_ext",
281
+ "tmember_t/3": "reif",
282
+ "tmember/2": "reif",
283
+ "top_sort/2": "ugraphs",
284
+ "top_sort/3": "ugraphs",
285
+ "tpartition/4": "reif",
286
+ "transitive_closure/2": "ugraphs",
287
+ "transpose_ugraph/2": "ugraphs",
288
+ "transpose/2": "lists",
289
+ "trim/2": "strings",
290
+ "tuples_in/2": "clpz",
291
+ "type_error/2": "error",
292
+ "type_error/3": "error",
293
+ "ugraph_union/3": "ugraphs",
294
+ "uppercase/2": "strings",
295
+ "uuid_string/2": "uuid",
296
+ "uuid/3": "uuid",
297
+ "uuidv4_string/1": "uuid",
298
+ "uuidv4/1": "uuid",
299
+ "variant/2": "iso_ext",
300
+ "vertices_edges_to_ugraph/3": "ugraphs",
301
+ "vertices/2": "ugraphs",
302
+ "weighted_maximum/3": "clpb",
303
+ "when_si/2": "si",
304
+ "when/2": "when",
305
+ "zcompare/3": "clpz"
306
+ });
307
+
308
+ export const eyePrologAmbiguousLibraryAutoload = Object.freeze({});
309
+
310
+ export const eyePrologLibraryAutoloadModules = Object.freeze([
311
+ "aggregate",
312
+ "arithmetic",
313
+ "assoc",
314
+ "atts",
315
+ "between",
316
+ "charsio",
317
+ "clpb",
318
+ "clpz",
319
+ "comparison",
320
+ "dates",
321
+ "dcgs",
322
+ "debug",
323
+ "dif",
324
+ "error",
325
+ "format",
326
+ "freeze",
327
+ "gensym",
328
+ "iso_ext",
329
+ "lambda",
330
+ "lists",
331
+ "ordsets",
332
+ "pairs",
333
+ "pio",
334
+ "primes",
335
+ "prologue",
336
+ "random",
337
+ "reif",
338
+ "si",
339
+ "strings",
340
+ "tabling",
341
+ "terms",
342
+ "time",
343
+ "ugraphs",
344
+ "uuid",
345
+ "when"
346
+ ]);
package/src/program.js CHANGED
@@ -12,11 +12,12 @@ import {
12
12
  parseGoalText,
13
13
  tryParseClausesFastInto,
14
14
  } from './parser.js';
15
- import { PrologError, convertClauseBodyTerm, getStrictIsoRegistry } from './iso.js';
15
+ import { PrologError, convertClauseBodyTerm, getDefaultRegistry, getStrictIsoRegistry } from './iso.js';
16
16
  import { currentWorkingDirectory, fs, path } from './platform.js';
17
17
  import {
18
18
  standardLibrarySources,
19
- eyePrologInteropAutoload,
19
+ eyePrologAmbiguousLibraryAutoload,
20
+ eyePrologLibraryAutoload,
20
21
  eyePrologInteropLibraryIndicators,
21
22
  eyePrologInteropLibraryModules,
22
23
  } from './standard-library.js';
@@ -255,7 +256,10 @@ export class Program {
255
256
  }
256
257
  }
257
258
  this.initializations = [];
258
- this._initializationsExecuted = false;
259
+ // Track how many initialization/1 directives have run so a REPL can
260
+ // autoload a library after startup and execute only that library's newly
261
+ // appended initializations without repeating earlier side effects.
262
+ this._initializationsExecutedCount = 0;
259
263
  this.quads = [];
260
264
  this.prologFlagDirectives = [];
261
265
  this.charConversionDirectives = [];
@@ -1045,7 +1049,7 @@ export function autoloadProgramGoals(program, inputs, options = {}) {
1045
1049
  const builder = new ProgramBuilder({ ...options, isoStrict: false }, program);
1046
1050
  const loadedModules = new Set([...program.modules.keys()].filter((name) => name !== 'user'));
1047
1051
  const autoloadCount = program.autoloadedPredicates.length;
1048
- autoloadInteropDependencies(builder, {
1052
+ autoloadLibraryDependencies(builder, {
1049
1053
  ...options,
1050
1054
  isoStrict: false,
1051
1055
  operatorState,
@@ -1094,7 +1098,7 @@ function loadSourcesIntoBuilder(builder, sources, options, fast) {
1094
1098
  parserFlagState,
1095
1099
  }, builder.program);
1096
1100
  if (options.isoStrict !== true && options.autoload !== false) {
1097
- autoloadInteropDependencies(builder, {
1101
+ autoloadLibraryDependencies(builder, {
1098
1102
  ...options,
1099
1103
  operatorState,
1100
1104
  parserFlagState,
@@ -1164,14 +1168,32 @@ function extraGoalDependencies(goals) {
1164
1168
  return dependencies;
1165
1169
  }
1166
1170
 
1167
- function interopAutoloadRequests(program, extraGoals = []) {
1171
+ function procedureResolvedBeforeAutoload(program, dependency, targetModule) {
1172
+ if (program.findGroup(dependency.name, dependency.arity, targetModule)) return true;
1173
+ return program.moduleImports.get(targetModule)?.has(dependency.key) === true;
1174
+ }
1175
+
1176
+ function autoloadLibraryFor(dependency) {
1177
+ // ISO built-ins always take precedence over optional library autoloading.
1178
+ if (getDefaultRegistry().get(dependency.name, dependency.arity) != null) return null;
1179
+ const ambiguous = eyePrologAmbiguousLibraryAutoload[dependency.key];
1180
+ if (ambiguous != null) {
1181
+ throw new PrologError(
1182
+ 'permission_error(import, procedure)',
1183
+ compound('/', [atom(dependency.name), numberTerm(dependency.arity)]),
1184
+ );
1185
+ }
1186
+ return eyePrologLibraryAutoload[dependency.key] ?? null;
1187
+ }
1188
+
1189
+ function libraryAutoloadRequests(program, extraGoals = []) {
1168
1190
  const requests = new Map();
1169
1191
  for (const group of program.groups.values()) {
1170
1192
  if (bundledLibraryModule(program, group.module)) continue;
1171
1193
  for (const dependency of groupDependencies(group)) {
1172
1194
  const targetModule = dependency.module ?? group.module;
1173
- if (program.findGroup(dependency.name, dependency.arity, targetModule)) continue;
1174
- const library = eyePrologInteropAutoload[dependency.key];
1195
+ if (procedureResolvedBeforeAutoload(program, dependency, targetModule)) continue;
1196
+ const library = autoloadLibraryFor(dependency);
1175
1197
  if (library == null) continue;
1176
1198
  const requestKey = `${targetModule}\u0000${dependency.key}`;
1177
1199
  requests.set(requestKey, {
@@ -1186,8 +1208,8 @@ function interopAutoloadRequests(program, extraGoals = []) {
1186
1208
  for (const goal of program.initializations) {
1187
1209
  for (const dependency of collectGoalDependencies(goal, false, true)) {
1188
1210
  const targetModule = dependency.module ?? 'user';
1189
- if (program.findGroup(dependency.name, dependency.arity, targetModule)) continue;
1190
- const library = eyePrologInteropAutoload[dependency.key];
1211
+ if (procedureResolvedBeforeAutoload(program, dependency, targetModule)) continue;
1212
+ const library = autoloadLibraryFor(dependency);
1191
1213
  if (library == null) continue;
1192
1214
  const requestKey = `${targetModule}\u0000${dependency.key}`;
1193
1215
  requests.set(requestKey, {
@@ -1201,8 +1223,8 @@ function interopAutoloadRequests(program, extraGoals = []) {
1201
1223
  }
1202
1224
  for (const dependency of extraGoalDependencies(extraGoals)) {
1203
1225
  const targetModule = dependency.module ?? 'user';
1204
- if (program.findGroup(dependency.name, dependency.arity, targetModule)) continue;
1205
- const library = eyePrologInteropAutoload[dependency.key];
1226
+ if (procedureResolvedBeforeAutoload(program, dependency, targetModule)) continue;
1227
+ const library = autoloadLibraryFor(dependency);
1206
1228
  if (library == null) continue;
1207
1229
  const requestKey = `${targetModule}\u0000${dependency.key}`;
1208
1230
  requests.set(requestKey, {
@@ -1216,14 +1238,14 @@ function interopAutoloadRequests(program, extraGoals = []) {
1216
1238
  return [...requests.values()];
1217
1239
  }
1218
1240
 
1219
- function autoloadInteropDependencies(builder, options, ensured, loadedModules, extraGoals = []) {
1241
+ function autoloadLibraryDependencies(builder, options, ensured, loadedModules, extraGoals = []) {
1220
1242
  const program = builder.program;
1221
1243
  while (true) {
1222
- const requests = interopAutoloadRequests(program, extraGoals);
1244
+ const requests = libraryAutoloadRequests(program, extraGoals);
1223
1245
  if (requests.length === 0) return;
1224
1246
  let changed = false;
1225
1247
  for (const request of requests) {
1226
- if (program.findGroup(request.name, request.arity, request.targetModule)) continue;
1248
+ if (procedureResolvedBeforeAutoload(program, request, request.targetModule)) continue;
1227
1249
  if (!loadedModules.has(request.library)) {
1228
1250
  const designation = compound('library', [atom(request.library)]);
1229
1251
  const loaded = readModuleSource(designation, options);
package/src/repl.js CHANGED
@@ -4,6 +4,7 @@ import { readSync } from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { createInterface } from 'node:readline';
6
6
  import { formalErrorTerm } from './iso.js';
7
+ import { autoloadProgramGoals } from './program.js';
7
8
  import {
8
9
  characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
9
10
  } from './syntax-scan.js';
@@ -67,6 +68,19 @@ export async function runRepl(engine, options = {}) {
67
68
  continue;
68
69
  }
69
70
 
71
+ // Queries entered after the initial Program was prepared need the same
72
+ // bundled-library autoload resolution as file and -g execution. This
73
+ // is intentionally after parsing: predicate autoloading cannot provide
74
+ // syntax operators retroactively (libraries such as clpz must still be
75
+ // imported explicitly before their operators are used).
76
+ if (!state.strictIso && options.autoload !== false) {
77
+ autoloadProgramGoals(state.program, [goal], {
78
+ autoload: true,
79
+ doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
80
+ });
81
+ runWithTerminalSignals(reader, () => state.solver.runInitializations());
82
+ }
83
+
70
84
  const flagsBefore = snapshotFlagValues(state.solver);
71
85
  try {
72
86
  await prepareInteractiveTermInput(state, goal, reader);
@@ -284,7 +298,11 @@ function runWithTerminalSignals(reader, operation) {
284
298
 
285
299
  function makeState(engine, sources, output, options = {}, previousState = null, reader = null) {
286
300
  const strictIso = options.isoStrict === true;
287
- const program = engine.Program.parseSources(sources, { strictIso, sourceMetadata: strictIso });
301
+ const program = engine.Program.parseSources(sources, {
302
+ isoStrict: strictIso,
303
+ sourceMetadata: strictIso,
304
+ autoload: !strictIso && options.autoload !== false,
305
+ });
288
306
  const solver = new engine.Solver(program, {
289
307
  registry: strictIso ? engine.getStrictIsoRegistry() : engine.getEyePrologRegistry(),
290
308
  isoStrict: strictIso,
@@ -756,12 +774,29 @@ function formatAnswer(engine, state, variables, env) {
756
774
  collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
757
775
  bindings.push(engine.formatTermForWrite(residual, env, answerWriteOptions));
758
776
  }
777
+ // Residual attributes are part of the answer even when the attributed
778
+ // variable was created inside a called predicate and is not itself a query
779
+ // variable (issue #87). First retain visible query names for attributed
780
+ // roots, then project every remaining live attributed root with a generated
781
+ // top-level variable name. This also makes call_residue_vars/2 useful at the
782
+ // top level: variables returned through its list share the same name with the
783
+ // projected constraint instead of appearing unconstrained.
759
784
  const projectedAttributeRoots = new Set();
785
+ const attributeRoots = [];
760
786
  for (const variable of variables) {
761
787
  const root = engine.deref(variable, env);
762
- if (root.type !== 'var' || projectedAttributeRoots.has(root.name) || !env.hasPrologAttributes?.(root.name)) continue;
763
- projectedAttributeRoots.add(root.name);
788
+ if (root.type !== 'var' || !env.hasPrologAttributes?.(root.name)) continue;
764
789
  names.set(root.name, variable.name);
790
+ attributeRoots.push(root);
791
+ }
792
+ for (const name of env.attributedVariableNames?.() ?? []) {
793
+ const root = engine.deref(engine.variable(name), env);
794
+ if (root.type === 'var') attributeRoots.push(root);
795
+ }
796
+ for (const root of attributeRoots) {
797
+ if (projectedAttributeRoots.has(root.name) || !env.hasPrologAttributes?.(root.name)) continue;
798
+ projectedAttributeRoots.add(root.name);
799
+ if (!names.has(root.name)) names.set(root.name, `_${letterName(generated++)}`);
765
800
  for (const residual of state.solver.attributeResidualGoals(root, env)) {
766
801
  collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
767
802
  bindings.push(engine.formatTermForWrite(residual, env, answerWriteOptions));
package/src/solver.js CHANGED
@@ -375,18 +375,19 @@ export class Solver {
375
375
  }
376
376
 
377
377
  runInitializations() {
378
- if (this.program._initializationsExecuted) return;
379
- for (const goal of this.program.initializations ?? []) {
378
+ const goals = this.program.initializations ?? [];
379
+ let index = this.program._initializationsExecutedCount ?? 0;
380
+ for (; index < goals.length; index++) {
380
381
  let succeeded = false;
381
- for (const _ of this.solve([goal], new Env(), 0)) {
382
+ for (const _ of this.solve([goals[index]], new Env(), 0)) {
382
383
  succeeded = true;
383
384
  break;
384
385
  }
385
386
  if (!succeeded) throw new PrologError('initialization_error');
387
+ // Mark each successful initialization immediately. If a later one fails,
388
+ // retrying does not repeat already completed initialization side effects.
389
+ this.program._initializationsExecutedCount = index + 1;
386
390
  }
387
- // 7.4.2.6 attaches initialization to preparation of the Prolog text, not
388
- // to each later Solver instance created for the same prepared Program.
389
- this.program._initializationsExecuted = true;
390
391
  }
391
392
 
392
393
  *solve(goals, env = new Env(), depth = 0) {
@@ -1,7 +1,7 @@
1
1
  // Prolog library modules shipped with EyeProlog and loaded through its documented module compatibility surface.
2
2
  // The sources are registered here so library(Name) works in Node and browsers.
3
- // Modules are loaded on demand by explicit use_module/1-2 or by the conservative
4
- // source-level interop autoloader declared below.
3
+ // Modules are loaded on demand by explicit use_module/1-2 or by the generic
4
+ // source-level autoloader generated from every bundled module/2 export list.
5
5
  import { PrologError, createDefaultRegistry, eyePrologLibraryBuiltins } from './iso.js';
6
6
  import { attsBuiltins } from './atts.js';
7
7
  import { expansionBuiltins } from './expansion-builtins.js';
@@ -9,6 +9,9 @@ import { scryerCompatibilityBuiltins } from './scryer-compat.js';
9
9
  import { registerCleanupBuiltins } from './cleanup.js';
10
10
  import { fs, isNode, memoryStatistics } from './platform.js';
11
11
  import { ATOM, VAR, atom, deref, numberTerm, unify } from './term.js';
12
+ import { eyePrologAmbiguousLibraryAutoload, eyePrologLibraryAutoload, eyePrologLibraryAutoloadModules } from './library-autoload-index.js';
13
+
14
+ export { eyePrologAmbiguousLibraryAutoload, eyePrologLibraryAutoload, eyePrologLibraryAutoloadModules };
12
15
 
13
16
  const moduleFiles = Object.freeze({
14
17
  aggregate: 'aggregate.pl',
@@ -152,10 +155,12 @@ export const eyePrologLibraryIndicators = Object.freeze([
152
155
  ]);
153
156
 
154
157
 
155
- // Conservative cross-implementation profile. These predicate indicators are
156
- // intentionally limited to interfaces that EyeProlog wants to keep source-
157
- // compatible with the Trealla/Scryer ecosystem. The value is the canonical
158
- // EyeProlog library used when an otherwise undefined call is autoloaded.
158
+ // Conservative cross-implementation profile metadata. These predicate indicators
159
+ // are intentionally limited to interfaces that EyeProlog wants to keep source-
160
+ // compatible with the Trealla/Scryer ecosystem. The complete runtime autoload
161
+ // index is generated separately from every bundled module/2 export; this smaller
162
+ // map is retained as the canonical interoperability subset used by diagnostics
163
+ // and public metadata.
159
164
  //
160
165
  // This is deliberately separate from eyePrologPortableLibraryIndicators:
161
166
  // "portable" there means implemented in Prolog rather than as a host builtin;
@@ -26,6 +26,9 @@ import {
26
26
  eyePrologNativeLibraryIndicators,
27
27
  eyePrologPortableLibraryIndicators,
28
28
  eyePrologInteropAutoload,
29
+ eyePrologLibraryAutoload,
30
+ eyePrologAmbiguousLibraryAutoload,
31
+ eyePrologLibraryAutoloadModules,
29
32
  eyePrologInteropLibraryIndicators,
30
33
  eyePrologInteropLibraryModules,
31
34
  atom,
@@ -519,6 +522,24 @@ attribute_goals(X) --> { get_atts(X, required(V)) }, [required(X,V)].
519
522
  assertIncludes(repl.stdout, 'dif(Y, c), freeze:freeze(Y, a).',
520
523
  'freeze/2 and dif/2 residuals share the query variable (issue #73)');
521
524
 
525
+ const hiddenResidualFile = path.join(tmp, `atts-hidden-residual-${++tmpCounter}.pl`);
526
+ fs.writeFileSync(hiddenResidualFile, 'ffalse :- freeze(_, false).\n');
527
+ const hiddenResidual = runCli([], {
528
+ input:
529
+ `[${sourceAtom(hiddenResidualFile)}].\n` +
530
+ 'ffalse.\n' +
531
+ 'call_residue_vars(ffalse, Vs).\n' +
532
+ 'freeze(V,false).\n' +
533
+ 'halt.\n',
534
+ });
535
+ assertEqual(hiddenResidual.status, 0, 'hidden attribute residual REPL status');
536
+ assertIncludes(hiddenResidual.stdout, 'freeze:freeze(_A, false).',
537
+ 'top level projects hidden attributed variables (issue #87)');
538
+ assertIncludes(hiddenResidual.stdout, 'Vs = [_A], freeze:freeze(_A, false).',
539
+ 'call_residue_vars/2 shares its returned variable with the projected residue (issue #87)');
540
+ assertIncludes(hiddenResidual.stdout, 'freeze:freeze(V, false).',
541
+ 'direct top-level freeze/2 keeps the visible query variable name');
542
+
522
543
  const scryerShape = Program.parse(`:- module(scryer_shape, [op(700, xfx, #=), probe/0]).
523
544
  :- op(700, xfx, #=).
524
545
  :- use_module(library(atts)).
@@ -2850,7 +2871,7 @@ c4 ?- call((!;1)).
2850
2871
  },
2851
2872
  },
2852
2873
  {
2853
- name: 'REPL applies conservative autoloading to interactive time/1 and consulted ... //0',
2874
+ name: 'REPL applies bundled-library autoloading to interactive time/1 and consulted ... //0',
2854
2875
  run: () => {
2855
2876
  const filename = path.join(tmp, `issue49-handoff-${tmpCounter++}.pl`);
2856
2877
  fs.writeFileSync(filename, 'a --> ..., epsilon.\nepsilon --> [].\n');
@@ -3632,14 +3653,14 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
3632
3653
  },
3633
3654
  },
3634
3655
  {
3635
- name: 'REPL use_module imports Part 2 library predicates',
3656
+ name: 'REPL autoload and explicit use_module coexist for Part 2 library predicates',
3636
3657
  run: () => {
3637
3658
  const result = runCli([], {
3638
3659
  input: 'append(X, Y, [1, 2, 3, 4]).\nuse_module(library(lists)).\nappend(X, Y, [1, 2, 3, 4]).\n\nhalt.\n',
3639
3660
  });
3640
3661
  assertEqual(result.status, 0, 'exit status');
3641
3662
  assertEqual(result.stdout,
3642
- '?- error(existence_error(procedure, append / 3), eyeprolog).\n' +
3663
+ '?- X = [], Y = [1, 2, 3, 4].\n' +
3643
3664
  '?- true.\n' +
3644
3665
  '?- X = [], Y = [1, 2, 3, 4]\n; ... .\n?- ',
3645
3666
  'stdout');
@@ -3946,6 +3967,50 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
3946
3967
  assertEqual(result.stderr, '', 'stderr');
3947
3968
  },
3948
3969
  },
3970
+ {
3971
+ name: 'interactive top level autoloads unresolved bundled predicates',
3972
+ run: () => {
3973
+ const result = runCli([], {
3974
+ input: 'append(X,Y,[1,2]).\n;\nhalt.\n',
3975
+ });
3976
+ assertEqual(result.status, 0, 'exit status');
3977
+ assertIncludes(result.stdout, 'X = [], Y = [1, 2]', 'first append/3 answer');
3978
+ assertIncludes(result.stdout, 'X = [1], Y = [2].', 'second append/3 answer');
3979
+ assertNotIncludes(result.stdout, 'existence_error(procedure, append / 3)', 'autoload failure');
3980
+ assertEqual(result.stderr, '', 'stderr');
3981
+ },
3982
+ },
3983
+ {
3984
+ name: 'all bundled library exports participate in generic autoloading',
3985
+ run: () => {
3986
+ const input = [
3987
+ '%% goal: pair_answer(K,V)',
3988
+ '%% goal: string_answer(X)',
3989
+ '%% goal: prime_answer(X)',
3990
+ 'pair_answer(K,V) :- pairs_keys_values([a-1,b-2], K, V).',
3991
+ 'string_answer(X) :- uppercase("hello", X).',
3992
+ 'prime_answer(X) :- smallest_divisor_from(91, 2, X).',
3993
+ '',
3994
+ ].join('\n');
3995
+ const result = runCli(['-'], { input });
3996
+ assertEqual(result.status, 0, 'exit status');
3997
+ assertEqual(result.stdout,
3998
+ 'pair_answer("ab", [1, 2]).\nstring_answer(\'HELLO\').\nprime_answer(7).\n', 'stdout');
3999
+ assertEqual(result.stderr, '', 'stderr');
4000
+ },
4001
+ },
4002
+ {
4003
+ name: 'generic autoload index is generated from every bundled library module',
4004
+ run: () => {
4005
+ assertEqual(eyePrologLibraryAutoload['member/2'], 'lists', 'facade duplicate resolves to defining lists module');
4006
+ assertEqual(eyePrologLibraryAutoload['pairs_keys_values/3'], 'pairs', 'pairs export is autoloadable');
4007
+ assertEqual(eyePrologLibraryAutoload['uppercase/2'], 'strings', 'strings export is autoloadable');
4008
+ assertEqual(eyePrologLibraryAutoload['smallest_divisor_from/3'], 'primes', 'primes export is autoloadable');
4009
+ assertEqual(Object.keys(eyePrologAmbiguousLibraryAutoload).length, 0, 'current bundled surface has no unresolved export ambiguities');
4010
+ assertEqual(eyePrologLibraryAutoloadModules.length, standardLibrarySources.size, 'every registered bundled module is indexed');
4011
+ assertEqual([...standardLibrarySources.keys()].sort().join(','), [...eyePrologLibraryAutoloadModules].sort().join(','), 'autoload module index matches bundled sources');
4012
+ },
4013
+ },
3949
4014
  {
3950
4015
  name: 'between/3 generated values avoid recursive environment chains (issue #52)',
3951
4016
  run: () => {
@@ -3994,7 +4059,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
3994
4059
  },
3995
4060
  },
3996
4061
  {
3997
- name: 'strict ISO mode disables interop autoloading',
4062
+ name: 'strict ISO mode disables bundled-library autoloading',
3998
4063
  run: () => {
3999
4064
  const input = '%% goal: answer(X)\nanswer(X) :- member(X, [a,b]).\n';
4000
4065
  const result = runCli(['--iso-strict', '-'], { input });
@@ -4004,7 +4069,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
4004
4069
  },
4005
4070
  },
4006
4071
  {
4007
- name: 'CLI top-level goals participate in interop autoloading',
4072
+ name: 'CLI top-level goals participate in bundled-library autoloading',
4008
4073
  run: () => {
4009
4074
  const result = runCli(['-g', 'member(X,[a,b])', '-'], { input: '' });
4010
4075
  assertEqual(result.status, 0, 'exit status');
@@ -4707,6 +4772,18 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
4707
4772
 
4708
4773
  function documentationSyncCases() {
4709
4774
  return [
4775
+ {
4776
+ name: 'generated bundled-library autoload index matches src/lib module exports',
4777
+ run: () => {
4778
+ const result = spawnSync(process.execPath, [path.join(packageRoot, 'tools', 'generate-library-autoload-index.mjs'), '--check'], {
4779
+ cwd: packageRoot,
4780
+ encoding: 'utf8',
4781
+ });
4782
+ assertEqual(result.status, 0, 'exit status');
4783
+ assertIncludes(result.stdout, 'library-autoload-index.js is up to date', 'stdout');
4784
+ assertEqual(result.stderr, '', 'stderr');
4785
+ },
4786
+ },
4710
4787
  {
4711
4788
  name: 'WG17 syntax status matches its executable-coverage manifest',
4712
4789
  run: () => {
@@ -5678,7 +5755,7 @@ answer(ok) :-
5678
5755
  },
5679
5756
  },
5680
5757
  {
5681
- name: 'autoload metadata records canonical interop imports',
5758
+ name: 'autoload metadata records canonical bundled-library imports',
5682
5759
  run: () => {
5683
5760
  const program = Program.parse('answer(X) :- member(X, [a]), between(1, 1, _).\n');
5684
5761
  assertEqual(program.autoloadedPredicates.length, 2, 'autoloaded predicate count');
@@ -5688,7 +5765,7 @@ answer(ok) :-
5688
5765
  },
5689
5766
  },
5690
5767
  {
5691
- name: 'JavaScript run top-level goals participate in interop autoloading',
5768
+ name: 'JavaScript run top-level goals participate in bundled-library autoloading',
5692
5769
  run: () => {
5693
5770
  const result = runEyeProlog('', { goal: 'member(X,[a,b])' });
5694
5771
  assertEqual(result.stdout, 'member(a, "ab").\nmember(b, "ab").\n', 'stdout');
@@ -6192,7 +6269,10 @@ answer(ok) :-
6192
6269
  assertEqual(eyePrologInteropAutoload['call_residue_vars/2'], 'atts', 'call_residue_vars/2 canonical interop autoload');
6193
6270
  assertEqual(eyePrologInteropAutoload['time/1'], 'iso_ext', 'time/1 canonical interop autoload');
6194
6271
  assertEqual(eyePrologInteropAutoload['.../2'], 'iso_ext', '.../2 canonical interop autoload');
6195
- assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'EyeProlog-only set_nth0/4 is not autoloadable');
6272
+ assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'set_nth0/4 is outside the conservative interop subset');
6273
+ assertEqual(eyePrologLibraryAutoload['set_nth0/4'], 'lists', 'complete library autoload includes EyeProlog-only exports');
6274
+ assertEqual(eyePrologLibraryAutoload['pairs_keys_values/3'], 'pairs', 'complete library autoload includes library(pairs)');
6275
+ assertEqual(eyePrologLibraryAutoloadModules.length, 35, 'all bundled src/lib modules are indexed for autoload');
6196
6276
  assertEqual(eyePrologNativeLibraryIndicators.length, 58, 'host-supported library count');
6197
6277
  assertEqual(eyePrologNativeLibraryIndicators.slice(0, 3).join(','), 'call_nth/2,freeze/2,dif/2', 'control and constraint predicates requiring host support');
6198
6278
  assertEqual(eyePrologNativeLibraryIndicators.includes('random/1'), true, 'stateful random/1 is classified as host-supported');
@@ -1860,10 +1860,11 @@ additional logical answer.
1860
1860
  `analyzeNegation`. It returns `stdout`, the solver's numeric `stats`, and a
1861
1861
  nullable `haltCode`; it does not write to the process streams.
1862
1862
 
1863
- When `run` receives an already parsed `Program`, canonical interop imports
1863
+ When `run` receives an already parsed `Program`, bundled-library imports
1864
1864
  needed only by its host-supplied goals are added to that Program before solving,
1865
- just as they are while source text is parsed. Pass `autoload: false` when the
1866
- Program must retain only explicitly imported predicates.
1865
+ just as they are while source text is parsed. The autoload index covers every
1866
+ exported predicate in the bundled `src/lib/` modules. Pass `autoload: false`
1867
+ when the Program must retain only explicitly imported predicates.
1867
1868
 
1868
1869
  For applications that inspect or prepare a theory before running it, use
1869
1870
  `Program` directly:
@@ -6303,8 +6304,9 @@ as a compatibility facade over the canonical `lists`, `between`, `iso_ext`,
6303
6304
  and `freeze` modules. The published Prologue, `call_nth/2`, and `length/2`
6304
6305
  quads are retained as offline regressions. `src/standard-library.js` registers the module sources and private control and
6305
6306
  constraint adapters for Node and browser resolution. Explicit `use_module/1-2`
6306
- loads remain supported; outside strict ISO mode, the conservative interop
6307
- autoloader may also load a module for one uniquely mapped common predicate.
6307
+ loads remain supported; outside strict ISO mode, the bundled-library autoloader
6308
+ may also load the canonical owner of any exported `src/lib/` predicate. The
6309
+ autoload index is generated from the libraries' `module/2` declarations.
6308
6310
  The core registry remains available through `createDefaultRegistry()` and
6309
6311
  `getDefaultRegistry()` for low-level embedding. The stricter Part 1 +
6310
6312
  Corrigenda registry is exposed as `createStrictIsoRegistry()` and
@@ -6382,7 +6384,7 @@ bindings make one aligned subterm pair sufficient. For example:
6382
6384
 
6383
6385
  <!-- eyeprolog-library-catalog:end -->
6384
6386
 
6385
- #### Interoperability profile and conservative autoloading
6387
+ #### Interoperability profile and bundled-library autoloading
6386
6388
 
6387
6389
  EyeProlog keeps four related concepts separate:
6388
6390
 
@@ -6391,7 +6393,7 @@ EyeProlog keeps four related concepts separate:
6391
6393
  | **ISO core** | The documented ISO predicate profile built into the processor. No EyeProlog library import is involved. |
6392
6394
  | **EyeProlog library surface** | Every module and exported predicate listed in the catalog above. Programs normally access these with `use_module/1-2`. |
6393
6395
  | **Interoperability profile** | A deliberately smaller set of library names and predicate interfaces that EyeProlog intends to keep source-compatible with Trealla and Scryer where practical. |
6394
- | **Autoload map** | An even smaller convenience table that gives selected unqualified predicates one canonical EyeProlog provider. |
6396
+ | **Autoload index** | A generated index of every predicate exported by the bundled `src/lib/` modules, with one canonical provider per unambiguous predicate. |
6395
6397
 
6396
6398
  These layers answer different questions. A predicate may be implemented entirely
6397
6399
  as ordinary Prolog and still be outside the cross-processor interoperability
@@ -6452,9 +6454,9 @@ EyeProlog API belongs to the shared profile. `call_nth/2`, `time/1`, and
6452
6454
  prints elapsed time, EyeProlog inference count, and MLips in Trealla-style form,
6453
6455
  for example `% Time elapsed 0.832s, 65551 Inferences, 0.079 MLips`; `... //0`
6454
6456
  describes an arbitrary number of input elements. Together they let the
6455
- Trealla/Scryer DCG hand-off benchmark run in EyeProlog without source changes
6456
- (assuming the usual list library is already imported in an interactive
6457
- session). The focused modules have one canonical implementation owner per
6457
+ Trealla/Scryer DCG hand-off benchmark run in EyeProlog without source changes;
6458
+ the interactive top level uses the same bundled-predicate autoloader as file and
6459
+ CLI/API goal execution. The focused modules have one canonical implementation owner per
6458
6460
  predicate. `library(prologue)` re-exports those same owners, so legacy code can
6459
6461
  combine the facade with `library(lists)`, `library(iso_ext)`, and
6460
6462
  `library(freeze)` in either import order without an accidental collision.
@@ -6488,25 +6490,41 @@ too few parameters raises `existence_error(lambda_parameter, ...)`. EyeProlog
6488
6490
  uses its ISO `copy_term/2` implementation for the fresh-copy step and does not
6489
6491
  require a separate `copy_term_nat/2` predicate.
6490
6492
 
6491
- Autoloading is a convenience layered on top of the interoperability profile; it
6492
- is not a general search through all EyeProlog libraries. When a source program
6493
- or an explicit CLI/API goal is built, an otherwise undefined **unqualified**
6494
- predicate may be autoloaded only when the interop table assigns it one canonical
6495
- provider. The interactive top level keeps ordinary library imports explicit;
6496
- `time/1` is available there because it is also a normal EyeProlog runtime
6497
- extension. For example, the canonical build-time providers are:
6493
+ Autoloading is a convenience layered on top of the module system and is
6494
+ independent of the smaller interoperability profile. At build time EyeProlog
6495
+ uses a generated index derived from every bundled `src/lib/*.pl` `module/2`
6496
+ export list. An otherwise unresolved predicate in source, initialization code, an explicit
6497
+ CLI/API goal, or an interactive top-level query therefore autoloads its canonical
6498
+ bundled provider.
6499
+ For example:
6498
6500
 
6499
6501
  | Predicate | Canonical autoload provider |
6500
6502
  | --- | --- |
6501
6503
  | `member/2` | `library(lists)` |
6502
- | `call_nth/2` | `library(iso_ext)` |
6503
- | `time/1` | `library(iso_ext)` |
6504
- | `.../2` | `library(iso_ext)` |
6504
+ | `pairs_keys_values/3` | `library(pairs)` |
6505
+ | `uppercase/2` | `library(strings)` |
6506
+ | `smallest_divisor_from/3` | `library(primes)` |
6505
6507
  | `between/3` | `library(between)` |
6506
6508
 
6507
- Predicates outside that table require an explicit import even when EyeProlog
6508
- provides them. Explicit imports therefore remain the clearest way to state
6509
- library dependencies:
6509
+ The resolution order is deliberately conservative with respect to Prolog
6510
+ semantics: a predicate already defined by the program wins; ISO/standard
6511
+ built-ins are not replaced by an autoloaded library; an explicit module import
6512
+ wins over autoloading; only then is the bundled autoload index consulted.
6513
+ Facade modules such as `library(prologue)` may re-export predicates from focused
6514
+ modules; the generated index chooses the unique module that actually defines
6515
+ the predicate. If more than one bundled module genuinely defines the same
6516
+ export, EyeProlog reports an import ambiguity and requires explicit
6517
+ `use_module/1-2` rather than guessing. The interactive top level applies this
6518
+ same resolution after a query has been parsed. Autoloading therefore supplies
6519
+ predicates, not retroactive syntax: a library that introduces operators (for
6520
+ example `library(clpz)` and `ins`) must still be explicitly imported before a
6521
+ query or source term uses those operators.
6522
+
6523
+ The index is generated by `npm run generate:autoload` into
6524
+ `src/library-autoload-index.js`, and the documentation regression suite checks
6525
+ that it is synchronized with the current `src/lib/` sources. Explicit imports
6526
+ remain the clearest way to state dependencies when portability or module intent
6527
+ should be visible in the source:
6510
6528
 
6511
6529
  ```text
6512
6530
  :- use_module(library(lists)).
@@ -6515,7 +6533,8 @@ library dependencies:
6515
6533
 
6516
6534
  Use `--no-autoload`, or the JavaScript option `autoload: false`, when every
6517
6535
  library dependency should be explicit. `--iso-strict` always disables EyeProlog
6518
- library autoloading.
6536
+ library autoloading, so strict ISO execution never gains procedures from this
6537
+ implementation convenience.
6519
6538
 
6520
6539
  `-w` / `--warnings` reports explicit dependencies on non-profile libraries and
6521
6540
  calls to non-profile predicates from otherwise common modules. `--portable`
@@ -6927,9 +6946,17 @@ terminal signal handling: `Ctrl-C`
6927
6946
  therefore terminates the current EyeProlog process immediately, and on POSIX
6928
6947
  terminals `Ctrl-Z` suspends it in the usual shell-managed way. This remains a
6929
6948
  host top-level convention rather than an ISO/IEC 13211-1 language feature. A
6930
- period-terminated query with no solutions prints `false.`; a solution without
6931
- visible variable bindings prints `true.`. Answer substitutions are rendered as
6932
- valid Prolog syntax under the current operator table: when a bound value would
6949
+ period-terminated query with no solutions prints `false.`. A solution without
6950
+ visible variable bindings prints `true.` only when it also has no pending
6951
+ residual goals. Residual constraints are part of the displayed answer even when
6952
+ the attributed variable was created inside a called predicate and is not a
6953
+ visible query variable; the top level assigns such variables generated names
6954
+ like `_A`. For example, if `ffalse :- freeze(_, false).`, the query `ffalse.`
6955
+ displays `freeze:freeze(_A, false).`, and
6956
+ `call_residue_vars(ffalse, Vs).` displays
6957
+ `Vs = [_A], freeze:freeze(_A, false).` rather than implying that the returned
6958
+ variable is unconstrained. Answer substitutions are rendered as valid Prolog
6959
+ syntax under the current operator table: when a bound value would
6933
6960
  not be a valid right operand of the displayed `=/2`, EyeProlog adds parentheses,
6934
6961
  for example `T = (a = b).` rather than the invalid `T = a = b.`. When an answer
6935
6962
  ends in a graphic token, the top level inserts layout before its terminating
@@ -6994,9 +7021,9 @@ make the observed question explicit.
6994
7021
  | `-h`, `--help` | Show usage |
6995
7022
  | `-p`, `--proof` | Print `why/2` explanations |
6996
7023
  | `-q`, `--quads` | Run embedded quad tests and fail if any do not hold |
6997
- | `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions (including `table` and `:+`) and disable interop autoloading |
7024
+ | `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions (including `table` and `:+`) and disable bundled-library autoloading |
6998
7025
  | `--portable` | Enforce the conservative EyeProlog/Trealla/Scryer interoperability profile |
6999
- | `--no-autoload` | Disable conservative interop predicate autoloading |
7026
+ | `--no-autoload` | Disable bundled-library predicate autoloading |
7000
7027
  | `-s`, `--stats` | Print final solver and memory statistics to stderr after execution |
7001
7028
  | `-v`, `--version` | Print the package version |
7002
7029
  | `-w`, `--warnings` | Print non-fatal portability warnings |
@@ -7011,8 +7038,8 @@ retains the Part 1 prefix `(?-)/1` operator and treats `-->/2` as ordinary Part
7011
7038
  `phrase/2-3`. Part 2 module directives and EyeProlog libraries are rejected,
7012
7039
  the implementation-specific `occurs_check` flag is absent, and the normal-profile
7013
7040
  `table` declaration is unavailable. Normal mode continues to support Parts
7014
- 2–3 and EyeProlog extensions, plus conservative interop autoloading for the
7015
- profile documented above.
7041
+ 2–3 and EyeProlog extensions, plus generated autoloading across the bundled
7042
+ `src/lib/` library exports documented above.
7016
7043
 
7017
7044
  Inputs may be local files, HTTP(S) URLs, or one `-` for stdin. The bare command
7018
7045
  `eyeprolog` starts the normal REPL; `eyeprolog --iso-strict` starts the strict
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { parseClauses } from '../src/parser.js';
6
+ import { ATOM, COMPOUND, NUMBER, Env, properListItems } from '../src/term.js';
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9
+ const libDir = path.join(root, 'src', 'lib');
10
+ const outputFile = path.join(root, 'src', 'library-autoload-index.js');
11
+
12
+ function predicateIndicator(nameTerm, arityTerm, extraArity = 0) {
13
+ if (nameTerm?.type !== ATOM || arityTerm?.type !== NUMBER || !/^\d+$/.test(arityTerm.name)) return null;
14
+ const arity = Number(arityTerm.name) + extraArity;
15
+ return { name: nameTerm.name, arity, key: `${nameTerm.name}/${arity}` };
16
+ }
17
+
18
+ function moduleExports(term) {
19
+ const items = properListItems(term, new Env());
20
+ if (items == null) throw new Error('module export list is not a proper list');
21
+ const exports = [];
22
+ for (const item of items) {
23
+ if (item?.type === COMPOUND && item.name === 'op' && item.arity === 3) continue;
24
+ if (item?.type !== COMPOUND || !['/', '//'].includes(item.name) || item.arity !== 2) {
25
+ throw new Error(`unsupported module export: ${item?.name ?? '<unknown>'}`);
26
+ }
27
+ const indicator = predicateIndicator(item.args[0], item.args[1], item.name === '//' ? 2 : 0);
28
+ if (indicator == null) throw new Error('invalid predicate indicator in module export');
29
+ exports.push(indicator);
30
+ }
31
+ return exports;
32
+ }
33
+
34
+ function localDefinitionKey(clause) {
35
+ const head = clause?.head;
36
+ if (head == null) return null;
37
+ if (head.type === COMPOUND && head.name === ':-' && head.arity === 1) return null;
38
+ if (head.type === COMPOUND && head.name === '-->' && head.arity === 2) {
39
+ const nt = head.args[0];
40
+ if (nt?.type !== ATOM && nt?.type !== COMPOUND) return null;
41
+ return `${nt.name}/${nt.arity + 2}`;
42
+ }
43
+ if (head.type !== ATOM && head.type !== COMPOUND) return null;
44
+ return `${head.name}/${head.arity}`;
45
+ }
46
+
47
+ const candidates = new Map();
48
+ const modules = [];
49
+ for (const filename of fs.readdirSync(libDir).filter((name) => name.endsWith('.pl')).sort()) {
50
+ const source = fs.readFileSync(path.join(libDir, filename), 'utf8');
51
+ const clauses = parseClauses(source, { filename: `src/lib/${filename}`, sourceMetadata: false });
52
+ const definitions = new Set(clauses.map(localDefinitionKey).filter(Boolean));
53
+ let moduleName = null;
54
+ let exports = null;
55
+ for (const clause of clauses) {
56
+ const directive = clause?.head?.type === COMPOUND && clause.head.name === ':-' && clause.head.arity === 1
57
+ ? clause.head.args[0]
58
+ : null;
59
+ if (directive?.type !== COMPOUND || directive.name !== 'module' || directive.arity !== 2) continue;
60
+ if (directive.args[0]?.type !== ATOM) throw new Error(`${filename}: invalid module name`);
61
+ moduleName = directive.args[0].name;
62
+ exports = moduleExports(directive.args[1]);
63
+ break;
64
+ }
65
+ if (moduleName == null || exports == null) throw new Error(`${filename}: missing module/2 declaration`);
66
+ modules.push(moduleName);
67
+ for (const indicator of exports) {
68
+ const list = candidates.get(indicator.key) ?? [];
69
+ list.push({ library: moduleName, local: definitions.has(indicator.key) });
70
+ candidates.set(indicator.key, list);
71
+ }
72
+ }
73
+
74
+ const providers = {};
75
+ const ambiguous = {};
76
+ for (const [key, list] of [...candidates.entries()].sort(([a], [b]) => a.localeCompare(b))) {
77
+ if (list.length === 1) {
78
+ providers[key] = list[0].library;
79
+ continue;
80
+ }
81
+ const local = list.filter((entry) => entry.local);
82
+ if (local.length === 1) {
83
+ providers[key] = local[0].library;
84
+ continue;
85
+ }
86
+ ambiguous[key] = list.map((entry) => entry.library).sort();
87
+ }
88
+
89
+ const renderObject = (value) => JSON.stringify(value, null, 2)
90
+ .replace(/^\{/m, 'Object.freeze({')
91
+ .replace(/\}$/m, '})');
92
+ const text = `// Generated by tools/generate-library-autoload-index.mjs.\n` +
93
+ `// Do not edit by hand; module/2 export lists under src/lib/ are the source of truth.\n\n` +
94
+ `export const eyePrologLibraryAutoload = ${renderObject(providers)};\n\n` +
95
+ `export const eyePrologAmbiguousLibraryAutoload = ${renderObject(ambiguous)};\n\n` +
96
+ `export const eyePrologLibraryAutoloadModules = Object.freeze(${JSON.stringify(modules.sort(), null, 2)});\n`;
97
+ if (process.argv.includes('--check')) {
98
+ const current = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf8') : '';
99
+ if (current !== text) {
100
+ console.error(`${path.relative(root, outputFile)} is stale; run npm run generate:autoload`);
101
+ process.exit(1);
102
+ }
103
+ console.log(`${path.relative(root, outputFile)} is up to date`);
104
+ } else {
105
+ fs.writeFileSync(outputFile, text);
106
+ console.log(`wrote ${path.relative(root, outputFile)} (${Object.keys(providers).length} predicates, ${Object.keys(ambiguous).length} ambiguous)`);
107
+ }