eyeprolog 1.5.11 → 1.5.13

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 CHANGED
@@ -61,7 +61,7 @@ Normal mode executes top-level `Conclusion :+ Premise` rules through the bundled
61
61
 
62
62
  Bundled `src/lib/` predicates autoload in files, CLI/API goals, and the REPL; use `--no-autoload` to require explicit imports, and explicitly import libraries that introduce operators before using their syntax. See [*The Art of EyeProlog*](the-art-of-eyeprolog.md) for the full semantics.
63
63
 
64
- All 32 bundled modules that overlap Scryer's current `src/lib/` surface now cover Scryer's exported predicates, while the stricter Trealla/Scryer intersection remains a separate portability profile. Runtime-dependent library primitives follow one ownership rule: `src/lib/<module>.pl` calls private adapters from `src/<module>-host.js`; pure Prolog libraries need no host file. This includes the full Scryer `library(files)` surface, Scryer character-list paths in `library(pio)` with atom-path compatibility, chars/UTF-8/term/Base64 helpers, the completed arithmetic/time/`iso_ext` surfaces, and the full Scryer `library(crypto)`. Filesystem and OS side effects require Node; crypto uses Node's backend where needed, with Web Crypto also used for secure random bytes when available. Non-integral rational conversions remain structural `rdiv(Numerator,Denominator)` terms until rational numbers become processor numeric values.
64
+ All 32 bundled modules that overlap Scryer's current `src/lib/` surface cover Scryer's exported predicates, and all 26 bundled modules with public-module counterparts in Trealla's current `library/` surface cover those Trealla exports as well. The stricter Trealla/Scryer intersection remains a separate portability profile. Runtime-dependent library primitives follow one ownership rule: `src/lib/<module>.pl` calls private adapters from `src/<module>-host.js`; pure Prolog libraries need no host file. This includes the full Scryer `library(files)` surface, Scryer character-list paths in `library(pio)` with atom-path compatibility, chars/UTF-8/term/Base64 helpers, the completed arithmetic/time/`iso_ext` surfaces, and the full Scryer `library(crypto)`. Filesystem and OS side effects require Node; crypto uses Node's backend where needed, with Web Crypto also used for secure random bytes when available. Non-integral rational conversions remain structural `rdiv(Numerator,Denominator)` terms until rational numbers become processor numeric values.
65
65
 
66
66
  ## Links
67
67
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.11",
6
+ "version": "1.5.13",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/execute.js CHANGED
@@ -51,6 +51,7 @@ export function executeGoals(program, solver, goals, { onAnswer = () => {} } = {
51
51
  function currentWriteOptions(program, solver) {
52
52
  return {
53
53
  doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
54
+ doubleBar: !solver.isoStrict,
54
55
  operators: [...program.operators.values()],
55
56
  quoted: true,
56
57
  };
package/src/iso.js CHANGED
@@ -1988,6 +1988,7 @@ function writeBuiltin(mode) {
1988
1988
  generateVariableNames: true,
1989
1989
  variableNameState: solver.writeVariableState,
1990
1990
  operators: solver.program.operators.values(),
1991
+ doubleBar: !solver.isoStrict,
1991
1992
  }));
1992
1993
  yield env;
1993
1994
  };
@@ -2019,6 +2020,7 @@ function* writeTermBuiltin({ solver, goal, env }) {
2019
2020
  generateVariableNames: true,
2020
2021
  variableNameState: solver.writeVariableState,
2021
2022
  operators: solver.program.operators.values(),
2023
+ doubleBar: !solver.isoStrict,
2022
2024
  }));
2023
2025
  yield env;
2024
2026
  }
@@ -1,8 +1,17 @@
1
1
  /** Portable solution aggregation relations. */
2
2
 
3
- :- module(aggregate, [sumall/3, aggregate_min/5, aggregate_max/5]).
3
+ /* Trealla-compatible aggregate_all/3 and aggregate/3 behavior is adapted
4
+ from Trealla Prolog library/aggregate.pl (MIT),
5
+ Copyright (c) 2020 Andrew George Davison. */
6
+
7
+ :- module(aggregate, [
8
+ sumall/3, aggregate_min/5, aggregate_max/5,
9
+ aggregate_all/3, aggregate/3
10
+ ]).
4
11
 
5
12
  :- meta_predicate(sumall('?', 0, '?')).
13
+ :- meta_predicate(aggregate_all('?', 0, '?')).
14
+ :- meta_predicate(aggregate('?', 0, '?')).
6
15
  :- meta_predicate(aggregate_min('?', '?', 0, '?', '?')).
7
16
  :- meta_predicate(aggregate_max('?', '?', 0, '?', '?')).
8
17
 
@@ -31,3 +40,71 @@ aggregate__reverse([X|Xs], Acc, Out) :- aggregate__reverse(Xs, [X|Acc], Out).
31
40
  aggregate__last_pair([Pair], Pair).
32
41
  aggregate__last_pair([_|Pairs], Pair) :- aggregate__last_pair(Pairs, Pair).
33
42
 
43
+
44
+ % Trealla-compatible aggregate templates. These are source-level relations;
45
+ % EyeProlog keeps its older sumall/3 and aggregate_min/max helpers alongside
46
+ % them for backward compatibility.
47
+ aggregate_all(Aggregate, Goal, Result) :-
48
+ aggregate__init(Aggregate, Template, Initial),
49
+ findall(Template, Goal, Values),
50
+ aggregate__compute(Values, Aggregate, Initial, Result).
51
+
52
+ aggregate(Aggregate, Goal, Result) :-
53
+ aggregate__init(Aggregate, Template, Initial),
54
+ bagof(Template, Goal, Values),
55
+ aggregate__compute(Values, Aggregate, Initial, Result).
56
+
57
+ aggregate__compute([], Aggregate, Initial, Result) :-
58
+ aggregate__finish(Aggregate, Initial, Result).
59
+ aggregate__compute([Value|Values], Aggregate, Acc0, Result) :-
60
+ aggregate__next(Aggregate, Acc0, Value, Acc),
61
+ aggregate__compute(Values, Aggregate, Acc, Result).
62
+
63
+ aggregate__init(Aggregate, _, _) :- var(Aggregate), !,
64
+ throw(error(instantiation_error, aggregate/3)).
65
+ aggregate__init(count, '', 0) :- !.
66
+ aggregate__init(sum(X), X, 0) :- !.
67
+ aggregate__init(mul(X), X, 1) :- !.
68
+ aggregate__init(min(X), X, sup) :- !.
69
+ aggregate__init(max(X), X, inf) :- !.
70
+ aggregate__init(bag(X), X, []) :- !.
71
+ aggregate__init(set(X), X, []) :- !.
72
+ aggregate__init((F,G), (X,Y), (A,B)) :- !,
73
+ aggregate__init(F, X, A),
74
+ aggregate__init(G, Y, B).
75
+ aggregate__init(Aggregate, _, _) :-
76
+ throw(error(type_error(aggregate, Aggregate), aggregate/3)).
77
+
78
+ aggregate__next(count, A, '', C) :- C is A + 1.
79
+ aggregate__next(sum(_), A, B, C) :- C is A + B.
80
+ aggregate__next(mul(_), A, B, C) :- C is A * B.
81
+ aggregate__next(min(_), A, B, C) :- aggregate__min(A, B, C).
82
+ aggregate__next(max(_), A, B, C) :- aggregate__max(A, B, C).
83
+ aggregate__next(bag(_), A, B, [B|A]).
84
+ aggregate__next(set(_), A, B, [B|A]).
85
+ aggregate__next((F,G), (A,B), (X,Y), (C,D)) :-
86
+ aggregate__next(F, A, X, C),
87
+ aggregate__next(G, B, Y, D).
88
+
89
+ aggregate__finish(count, A, A).
90
+ aggregate__finish(sum(_), A, A).
91
+ aggregate__finish(mul(_), A, A).
92
+ aggregate__finish(min(_), A, A).
93
+ aggregate__finish(max(_), A, A).
94
+ aggregate__finish(bag(_), A, B) :- aggregate__reverse(A, [], B).
95
+ aggregate__finish(set(_), A, B) :- aggregate__reverse(A, [], R), sort(R, B).
96
+ aggregate__finish((F,G), (X,Y), (A,B)) :-
97
+ aggregate__finish(F, X, A),
98
+ aggregate__finish(G, Y, B).
99
+
100
+ aggregate__min(sup, X, X) :- !.
101
+ aggregate__min(X, sup, X) :- !.
102
+ aggregate__min(inf, _, inf) :- !.
103
+ aggregate__min(_, inf, inf) :- !.
104
+ aggregate__min(X, Y, R) :- R is min(X, Y).
105
+
106
+ aggregate__max(inf, X, X) :- !.
107
+ aggregate__max(X, inf, X) :- !.
108
+ aggregate__max(sup, _, sup) :- !.
109
+ aggregate__max(_, sup, sup) :- !.
110
+ aggregate__max(X, Y, R) :- R is max(X, Y).
package/src/lib/error.pl CHANGED
@@ -11,6 +11,7 @@
11
11
  type_error/3,
12
12
  representation_error/1,
13
13
  resource_error/1,
14
+ resource_error/2,
14
15
  call_with_error_context/2
15
16
  ]).
16
17
 
@@ -118,6 +119,7 @@ type_error(Type, Term) :- throw(error(type_error(Type, Term), [])).
118
119
  type_error(Type, Term, Context) :- throw(error(type_error(Type, Term), Context)).
119
120
  representation_error(Flag) :- throw(error(representation_error(Flag), [])).
120
121
  resource_error(Resource) :- throw(error(resource_error(Resource), [])).
122
+ resource_error(Resource, Context) :- throw(error(resource_error(Resource), Context)).
121
123
 
122
124
  call_with_error_context(Goal, Pair) :-
123
125
  catch(Goal, error(Error, Context), throw(error(Error, [Pair|Context]))).
package/src/lib/freeze.pl CHANGED
@@ -1,8 +1,9 @@
1
- /** Attributed-variable implementation of Scryer-compatible freeze/2. */
1
+ /** Attributed-variable freeze/2 plus Trealla-compatible frozen/2 inspection. */
2
2
 
3
- :- module(freeze, [freeze/2]).
3
+ :- module(freeze, [freeze/2, frozen/2]).
4
4
 
5
5
  :- use_module(library(atts)).
6
+ :- use_module(library(iso_ext), [copy_term/3]).
6
7
  :- meta_predicate(freeze(-, 0)).
7
8
  :- attribute frozen/1.
8
9
 
@@ -27,6 +28,23 @@ freeze(X, Goal) :-
27
28
  put_atts(Fresh, frozen(frozen_goal(Goal))),
28
29
  Fresh = X.
29
30
 
31
+ % Trealla-compatible frozen/2. copy_term/3 already projects both native and
32
+ % Prolog-defined attributed-variable residual goals. Re-unifying the fresh
33
+ % structural copy aliases those projected variables back to Term without
34
+ % instantiating them, so Goal describes the suspensions on the original term.
35
+ frozen(Term, Goal) :-
36
+ copy_term(Term, Copy, Goals),
37
+ Term = Copy,
38
+ freeze__goals_conjunction(Goals, Goal).
39
+
40
+ freeze__goals_conjunction([], true).
41
+ freeze__goals_conjunction([Goal|Goals], Conjunction) :-
42
+ freeze__goals_conjunction_(Goals, Goal, Conjunction).
43
+
44
+ freeze__goals_conjunction_([], Goal, Goal).
45
+ freeze__goals_conjunction_([Goal|Goals], Acc, Conjunction) :-
46
+ freeze__goals_conjunction_(Goals, (Acc, Goal), Conjunction).
47
+
30
48
  frozen_goals(Frozen, Goals) :-
31
49
  frozen_goals(Frozen, Goals, []).
32
50
 
package/src/lib/lists.pl CHANGED
@@ -4,6 +4,11 @@
4
4
  member/2,
5
5
  memberchk/2,
6
6
  select/3,
7
+ selectchk/3,
8
+ subtract/3,
9
+ union/3,
10
+ intersection/3,
11
+ is_set/1,
7
12
  append/2,
8
13
  append/3,
9
14
  last/2,
@@ -14,6 +19,8 @@
14
19
  nth1/4,
15
20
  reverse/2,
16
21
  length/2,
22
+ exclude/3,
23
+ include/3,
17
24
  maplist/2,
18
25
  maplist/3,
19
26
  maplist/4,
@@ -22,6 +29,13 @@
22
29
  maplist/7,
23
30
  maplist/8,
24
31
  maplist/9,
32
+ tasklist/2,
33
+ tasklist/3,
34
+ tasklist/4,
35
+ tasklist/5,
36
+ tasklist/6,
37
+ tasklist/7,
38
+ tasklist/8,
25
39
  foldl/4,
26
40
  foldl/5,
27
41
  foldl/6,
@@ -50,6 +64,16 @@
50
64
  :- meta_predicate(foldl(3, '?', '?', '?')).
51
65
  :- meta_predicate(foldl(4, '?', '?', '?', '?')).
52
66
  :- meta_predicate(foldl(5, '?', '?', '?', '?', '?')).
67
+ :- meta_predicate(include(1, '?', '?')).
68
+ :- meta_predicate(exclude(1, '?', '?')).
69
+ :- meta_predicate(tasklist(1, '?')).
70
+ :- meta_predicate(tasklist(2, '?', '?')).
71
+ :- meta_predicate(tasklist(3, '?', '?', '?')).
72
+ :- meta_predicate(tasklist(4, '?', '?', '?', '?')).
73
+ :- meta_predicate(tasklist(5, '?', '?', '?', '?', '?')).
74
+ :- meta_predicate(tasklist(6, '?', '?', '?', '?', '?', '?')).
75
+ :- meta_predicate(tasklist(7, '?', '?', '?', '?', '?', '?', '?')).
76
+
53
77
 
54
78
  % Common pure-Prolog library predicates for EyeProlog.
55
79
  %
@@ -99,6 +123,27 @@ maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es], [F|Fs], [G|Gs], [H|Hs])
99
123
  eyeprolog__call8(Closure, A, B, C, D, E, F, G, H),
100
124
  maplist(Closure, As, Bs, Cs, Ds, Es, Fs, Gs, Hs).
101
125
 
126
+ include(_, [], []).
127
+ include(Predicate, [X|Xs], Included) :-
128
+ ( call(Predicate, X) -> Included = [X|Rest] ; Included = Rest ),
129
+ include(Predicate, Xs, Rest).
130
+
131
+ exclude(_, [], []).
132
+ exclude(Predicate, [X|Xs], Excluded) :-
133
+ ( call(Predicate, X) -> Excluded = Rest ; Excluded = [X|Rest] ),
134
+ exclude(Predicate, Xs, Rest).
135
+
136
+ % Trealla runs tasklist/* items through its task scheduler. EyeProlog has no
137
+ % task scheduler, so the compatibility surface deliberately uses deterministic
138
+ % sequential maplist semantics while preserving solutions and failures.
139
+ tasklist(Goal, A) :- maplist(Goal, A).
140
+ tasklist(Goal, A, B) :- maplist(Goal, A, B).
141
+ tasklist(Goal, A, B, C) :- maplist(Goal, A, B, C).
142
+ tasklist(Goal, A, B, C, D) :- maplist(Goal, A, B, C, D).
143
+ tasklist(Goal, A, B, C, D, E) :- maplist(Goal, A, B, C, D, E).
144
+ tasklist(Goal, A, B, C, D, E, F) :- maplist(Goal, A, B, C, D, E, F).
145
+ tasklist(Goal, A, B, C, D, E, F, G) :- maplist(Goal, A, B, C, D, E, F, G).
146
+
102
147
  append([], []).
103
148
  append([Xs|Xss], Ys) :-
104
149
  append(Xs, Rest, Ys),
@@ -115,6 +160,25 @@ memberchk(X, Xs) :- member(X, Xs), !.
115
160
  select(X, [X|Xs], Xs).
116
161
  select(X, [Y|Ys], [Y|Zs]) :- select(X, Ys, Zs).
117
162
 
163
+ selectchk(X, Xs, Rest) :- select(X, Xs, Rest), !.
164
+
165
+ subtract([], _, []) :- !.
166
+ subtract([X|Xs], Ys, Rest) :- memberchk(X, Ys), !, subtract(Xs, Ys, Rest).
167
+ subtract([X|Xs], Ys, [X|Rest]) :- subtract(Xs, Ys, Rest).
168
+
169
+ union([], Ys, Ys).
170
+ union([X|Xs], Ys, Union) :- member(X, Ys), !, union(Xs, Ys, Union).
171
+ union([X|Xs], Ys, [X|Union]) :- union(Xs, Ys, Union).
172
+
173
+ intersection([], _, []).
174
+ intersection([X|Xs], Ys, [X|Intersection]) :- member(X, Ys), !, intersection(Xs, Ys, Intersection).
175
+ intersection([_|Xs], Ys, Intersection) :- intersection(Xs, Ys, Intersection).
176
+
177
+ is_set(Set) :-
178
+ sort(Set, Sorted),
179
+ length(Set, Length),
180
+ length(Sorted, Length).
181
+
118
182
  last([X], X).
119
183
  last([_|Xs], X) :- last(Xs, X).
120
184
 
package/src/lib/random.pl CHANGED
@@ -1,13 +1,13 @@
1
1
  /** Reproducible pseudo-random values.
2
2
 
3
- random/3 is EyeProlog's explicit-state interface. maybe/0,
3
+ random/3 is EyeProlog's explicit-state interface. maybe/0, maybe/1, maybe/2,
4
4
  random/1, random_integer/3, and set_random/1 are the mutable-seed library
5
5
  interface used for compatibility. random/1 uses EyeProlog's private native
6
6
  state step for hot-loop performance; random/3 remains the portable explicit-
7
7
  state Park-Miller generator, and both retain the same sequence.
8
8
  */
9
9
 
10
- :- module(random, [maybe/0, random/1, random/3, random_integer/3, set_random/1]).
10
+ :- module(random, [maybe/0, maybe/1, maybe/2, random/1, random/3, random_integer/3, set_random/1]).
11
11
 
12
12
  :- use_module(library(iso_ext), [bb_get/2, bb_put/2]).
13
13
  :- use_module(library(error), [instantiation_error/1, type_error/3]).
@@ -15,6 +15,15 @@
15
15
  maybe :-
16
16
  random_integer(0, 2, 0).
17
17
 
18
+ maybe(Probability) :-
19
+ random(Value),
20
+ Value < Probability.
21
+
22
+ maybe(K, N) :-
23
+ Probability is K / N,
24
+ random(Value),
25
+ Value < Probability.
26
+
18
27
 
19
28
  random(Value) :-
20
29
  eyeprolog__random_value(Value).
@@ -2,10 +2,10 @@
2
2
 
3
3
  EyeProlog tables predicates only when requested explicitly with
4
4
  `:- table ...`. The library keeps the portable start_tabling/2 and
5
- abolish_all_tables/0 surface used by Scryer/Trealla-compatible source.
5
+ abolish_all_tables/0 and abolish_table/1 surface used by Scryer/Trealla-compatible source.
6
6
  */
7
7
 
8
- :- module(tabling, [start_tabling/2, abolish_all_tables/0, op(1150, fx, table)]).
8
+ :- module(tabling, [start_tabling/2, abolish_all_tables/0, abolish_table/1, op(1150, fx, table)]).
9
9
 
10
10
  :- meta_predicate(start_tabling(?, 0)).
11
11
 
@@ -14,3 +14,24 @@ start_tabling(_, Worker) :-
14
14
 
15
15
  abolish_all_tables :-
16
16
  eyeprolog__abolish_all_tables.
17
+
18
+
19
+ abolish_table(Spec) :-
20
+ ( var(Spec) -> throw(error(instantiation_error, abolish_table/1))
21
+ ; tabling__abolish_table(Spec)
22
+ ).
23
+
24
+ tabling__abolish_table((A,B)) :- !,
25
+ tabling__abolish_table(A),
26
+ tabling__abolish_table(B).
27
+ tabling__abolish_table(Name//Arity) :-
28
+ atom(Name), integer(Arity), Arity >= 0, !,
29
+ ExpandedArity is Arity + 2,
30
+ tabling__abolish_table(Name/ExpandedArity).
31
+ tabling__abolish_table(Name/Arity) :-
32
+ atom(Name), integer(Arity), Arity >= 0, !,
33
+ ( eyeprolog__abolish_table(Name, Arity) -> true
34
+ ; throw(error(existence_error(table, Name/Arity), abolish_table/1))
35
+ ).
36
+ tabling__abolish_table(Spec) :-
37
+ throw(error(type_error(predicate_indicator, Spec), abolish_table/1)).
@@ -50,10 +50,13 @@ export const eyePrologLibraryAutoload = Object.freeze({
50
50
  "$-/1": "debug",
51
51
  "$/1": "debug",
52
52
  "abolish_all_tables/0": "tabling",
53
+ "abolish_table/1": "tabling",
53
54
  "add_edges/3": "ugraphs",
54
55
  "add_vertices/3": "ugraphs",
56
+ "aggregate_all/3": "aggregate",
55
57
  "aggregate_max/5": "aggregate",
56
58
  "aggregate_min/5": "aggregate",
59
+ "aggregate/3": "aggregate",
57
60
  "all_different/1": "clpz",
58
61
  "all_distinct/1": "clpz",
59
62
  "append/2": "lists",
@@ -139,6 +142,7 @@ export const eyePrologLibraryAutoload = Object.freeze({
139
142
  "edges/2": "ugraphs",
140
143
  "element/3": "clpz",
141
144
  "empty_assoc/1": "assoc",
145
+ "exclude/3": "lists",
142
146
  "expmod/4": "arithmetic",
143
147
  "fd_dom/2": "clpz",
144
148
  "fd_inf/2": "clpz",
@@ -161,6 +165,7 @@ export const eyePrologLibraryAutoload = Object.freeze({
161
165
  "format/2": "format",
162
166
  "format/3": "format",
163
167
  "freeze/2": "freeze",
168
+ "frozen/2": "freeze",
164
169
  "ge/2": "comparison",
165
170
  "gen_assoc/3": "assoc",
166
171
  "gen_int/1": "between",
@@ -181,13 +186,16 @@ export const eyePrologLibraryAutoload = Object.freeze({
181
186
  "hex_bytes/2": "crypto",
182
187
  "if_/3": "reif",
183
188
  "in/2": "clpz",
189
+ "include/3": "lists",
184
190
  "indomain/1": "clpz",
185
191
  "ins/2": "clpz",
186
192
  "instantiation_error/0": "error",
187
193
  "instantiation_error/1": "error",
188
194
  "integer_si/1": "si",
195
+ "intersection/3": "lists",
189
196
  "is_assoc/1": "assoc",
190
197
  "is_ordset/1": "ordsets",
198
+ "is_set/1": "lists",
191
199
  "join/3": "strings",
192
200
  "label/1": "clpz",
193
201
  "labeling/1": "clpb",
@@ -226,6 +234,8 @@ export const eyePrologLibraryAutoload = Object.freeze({
226
234
  "max_list/2": "lists",
227
235
  "max_sleep_time/1": "time",
228
236
  "maybe/0": "random",
237
+ "maybe/1": "random",
238
+ "maybe/2": "random",
229
239
  "member/2": "lists",
230
240
  "memberchk/2": "lists",
231
241
  "memberd_t/3": "reif",
@@ -309,12 +319,14 @@ export const eyePrologLibraryAutoload = Object.freeze({
309
319
  "representation_error/1": "error",
310
320
  "reset_gensym/1": "gensym",
311
321
  "resource_error/1": "error",
322
+ "resource_error/2": "error",
312
323
  "reverse/2": "lists",
313
324
  "same_length/2": "lists",
314
325
  "sat_count/2": "clpb",
315
326
  "sat/1": "clpb",
316
327
  "scalar_product/4": "clpz",
317
328
  "select/3": "lists",
329
+ "selectchk/3": "lists",
318
330
  "seq/3": "dcgs",
319
331
  "seqq/3": "dcgs",
320
332
  "serialized/2": "clpz",
@@ -333,11 +345,19 @@ export const eyePrologLibraryAutoload = Object.freeze({
333
345
  "statistics/2": "time",
334
346
  "string_concat/3": "strings",
335
347
  "substring/4": "strings",
348
+ "subtract/3": "lists",
336
349
  "succ/2": "iso_ext",
337
350
  "sum_list/2": "lists",
338
351
  "sum/3": "clpz",
339
352
  "sumall/3": "aggregate",
340
353
  "take/3": "lists",
354
+ "tasklist/2": "lists",
355
+ "tasklist/3": "lists",
356
+ "tasklist/4": "lists",
357
+ "tasklist/5": "lists",
358
+ "tasklist/6": "lists",
359
+ "tasklist/7": "lists",
360
+ "tasklist/8": "lists",
341
361
  "taut/2": "clpb",
342
362
  "term_attributed_variables/2": "atts",
343
363
  "term_si/1": "si",
@@ -357,6 +377,7 @@ export const eyePrologLibraryAutoload = Object.freeze({
357
377
  "type_error/2": "error",
358
378
  "type_error/3": "error",
359
379
  "ugraph_union/3": "ugraphs",
380
+ "union/3": "lists",
360
381
  "unsetenv/1": "os",
361
382
  "uppercase/2": "strings",
362
383
  "uuid_string/2": "uuid",
package/src/parser.js CHANGED
@@ -937,9 +937,37 @@ class Parser {
937
937
  this.parserFlagState.doubleQuotes === 'chars'
938
938
  ? atom(character)
939
939
  : numberTerm(character.codePointAt(0)));
940
- let list = emptyList();
941
- for (let i = items.length - 1; i >= 0; i--) list = cons(items[i], list);
942
- return list;
940
+ let tail = emptyList();
941
+
942
+ // Trealla-compatible double-bar right splice (issue #88): in normal
943
+ // mode, a double-quoted chars/codes prefix may be followed by `||Tail`.
944
+ // It is equivalent to the corresponding list prefix with Tail as its
945
+ // final list tail, e.g. "ab"||T is [a,b|T] with double_quotes(chars).
946
+ // The extension binds at priority 1 (tighter than ordinary operators)
947
+ // and is deliberately absent from the strict ISO profile.
948
+ if (!this.strictIso && this.token.type === TOK.BAR) {
949
+ const state = {
950
+ pos: this.pos,
951
+ line: this.line,
952
+ previousToken: this.previousToken,
953
+ token: this.token,
954
+ };
955
+ this.advance();
956
+ if (this.token.type === TOK.BAR) {
957
+ this.advance();
958
+ tail = this.parseTerm(operatorStrength(1), false, allowBar, false);
959
+ } else {
960
+ // A single bar remains ordinary bar syntax. We had to advance once
961
+ // to distinguish it from `||`, so restore the tokenizer state.
962
+ this.pos = state.pos;
963
+ this.line = state.line;
964
+ this.previousToken = state.previousToken;
965
+ this.token = state.token;
966
+ }
967
+ }
968
+
969
+ for (let i = items.length - 1; i >= 0; i--) tail = cons(items[i], tail);
970
+ return tail;
943
971
  }
944
972
  if (this.token.type === TOK.NUMBER) {
945
973
  const value = this.token.text;
package/src/repl.js CHANGED
@@ -746,6 +746,7 @@ function formatAnswer(engine, state, variables, env) {
746
746
  operatorAtomsAsArgs: true,
747
747
  dottedGraphicAtoms: true,
748
748
  doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
749
+ doubleBar: !state.strictIso,
749
750
  };
750
751
  let generated = 0;
751
752
 
@@ -844,6 +845,7 @@ function formatError(engine, state, error) {
844
845
  quoted: true,
845
846
  operators: [...state.program.operators.values()],
846
847
  variableNames,
848
+ doubleBar: !state.strictIso,
847
849
  })}.`;
848
850
  }
849
851
  const message = error?.message ?? String(error);
@@ -121,8 +121,8 @@ export const eyePrologNativeLibraryIndicators = Object.freeze([
121
121
  'random/1',
122
122
  ]);
123
123
  export const eyePrologPortableLibraryIndicators = Object.freeze([
124
- 'sumall/3', 'aggregate_min/5', 'aggregate_max/5',
125
- 'freeze/2',
124
+ 'sumall/3', 'aggregate_min/5', 'aggregate_max/5', 'aggregate_all/3', 'aggregate/3',
125
+ 'freeze/2', 'frozen/2',
126
126
  'expmod/4', 'lcm/3', 'lsb/2', 'msb/2', 'popcount/2',
127
127
  'empty_assoc/1', 'assoc_to_list/2', 'get_assoc/3', 'put_assoc/4',
128
128
  'assoc_to_keys/2', 'assoc_to_values/2', 'del_assoc/4', 'del_max_assoc/4',
@@ -141,7 +141,7 @@ export const eyePrologPortableLibraryIndicators = Object.freeze([
141
141
  '*/1', '$/1', '$-/1',
142
142
  'must_be/2', 'can_be/2', 'instantiation_error/0', 'instantiation_error/1',
143
143
  'domain_error/2', 'domain_error/3', 'type_error/2', 'type_error/3',
144
- 'representation_error/1', 'resource_error/1', 'call_with_error_context/2',
144
+ 'representation_error/1', 'resource_error/1', 'resource_error/2', 'call_with_error_context/2',
145
145
  'format_/4', 'format/2', 'format/3', 'portray_clause_/3',
146
146
  'portray_clause/1', 'portray_clause/2', 'listing/1',
147
147
  'gensym/2', 'reset_gensym/1',
@@ -150,10 +150,11 @@ export const eyePrologPortableLibraryIndicators = Object.freeze([
150
150
  '^/3', '^/4', '^/5', '^/6', '^/7', '^/8', '^/9', '^/10',
151
151
  '\\/1', '\\/2', '\\/3', '\\/4', '\\/5', '\\/6', '\\/7', '\\/8',
152
152
  '+\\/2', '+\\/3', '+\\/4', '+\\/5', '+\\/6', '+\\/7', '+\\/8', '+\\/9',
153
- 'member/2', 'memberchk/2', 'select/3', 'append/2', 'append/3', 'last/2', 'same_length/2',
153
+ 'member/2', 'memberchk/2', 'select/3', 'selectchk/3', 'subtract/3', 'union/3', 'intersection/3', 'is_set/1', 'append/2', 'append/3', 'last/2', 'same_length/2',
154
154
  'nth0/3', 'nth0/4', 'nth1/3', 'nth1/4', 'set_nth0/4', 'take/3', 'drop/3', 'slice/4', 'reverse/2',
155
- 'length/2', 'maplist/2', 'maplist/3', 'maplist/4', 'maplist/5',
156
- 'maplist/6', 'maplist/7', 'maplist/8', 'maplist/9', 'foldl/4', 'foldl/5', 'foldl/6',
155
+ 'length/2', 'exclude/3', 'include/3', 'maplist/2', 'maplist/3', 'maplist/4', 'maplist/5',
156
+ 'maplist/6', 'maplist/7', 'maplist/8', 'maplist/9',
157
+ 'tasklist/2', 'tasklist/3', 'tasklist/4', 'tasklist/5', 'tasklist/6', 'tasklist/7', 'tasklist/8', 'foldl/4', 'foldl/5', 'foldl/6',
157
158
  'sum_list/2', 'min_list/2', 'max_list/2', 'list_to_set/2',
158
159
  'transpose/2', 'list_max/2', 'list_min/2', 'permutation/2',
159
160
  'is_ordset/1', 'list_to_ord_set/2', 'ord_add_element/3', 'ord_del_element/3',
@@ -166,7 +167,7 @@ export const eyePrologPortableLibraryIndicators = Object.freeze([
166
167
  'phrase_from_file/2', 'phrase_from_file/3', 'phrase_from_stream/2', 'phrase_to_file/2',
167
168
  'phrase_to_file/3', 'phrase_to_stream/2',
168
169
  'smallest_divisor_from/3',
169
- 'maybe/0', 'random/3', 'random_integer/3', 'set_random/1',
170
+ 'maybe/0', 'maybe/1', 'maybe/2', 'random/3', 'random_integer/3', 'set_random/1',
170
171
  ',/3', ';/3', '=/3', 'cond_t/3', 'dif/3', 'if_/3', 'memberd_t/3',
171
172
  'tfilter/3', 'tmember/2', 'tmember_t/3', 'tpartition/4',
172
173
  'atom_si/1', 'integer_si/1', 'atomic_si/1', 'list_si/1', 'character_si/1',
@@ -175,7 +176,7 @@ export const eyePrologPortableLibraryIndicators = Object.freeze([
175
176
  'number_string/2', 'atom_string/2', 'term_string/2', 'string_concat/3',
176
177
  'contains/2', 'matches/2', 'join/3', 'substring/4',
177
178
  'numbervars/3', 'copy_term_nat/2',
178
- 'start_tabling/2', 'abolish_all_tables/0',
179
+ 'start_tabling/2', 'abolish_all_tables/0', 'abolish_table/1',
179
180
  'max_sleep_time/1', 'current_time/1', 'format_time/4',
180
181
  'add_edges/3', 'add_vertices/3', 'complement/2', 'compose/3', 'connect_ugraph/3',
181
182
  'del_edges/3', 'del_vertices/3', 'edges/2', 'neighbors/3', 'neighbours/3',
@@ -1,4 +1,6 @@
1
- // Runtime table-cache lifecycle adapter for library(tabling).
1
+ // Runtime table-cache lifecycle adapters for library(tabling).
2
+
3
+ import { ATOM, NUMBER, deref } from './term.js';
2
4
 
3
5
  function* abolishAllTablesBuiltin({ solver, env }) {
4
6
  solver.memo.clear();
@@ -15,8 +17,43 @@ function* abolishAllTablesBuiltin({ solver, env }) {
15
17
  yield env;
16
18
  }
17
19
 
20
+ function clearPredicateFromMemo(memo, prefix) {
21
+ for (const key of [...memo.keys()]) {
22
+ if (key.startsWith(prefix)) memo.delete(key);
23
+ }
24
+ }
25
+
26
+ function clearPredicateTables(solver, module, name, arity) {
27
+ const broadKey = `${module}:${name}/${arity}`;
28
+ clearPredicateFromMemo(solver.memo, `${broadKey}:`);
29
+ solver.subsumptiveMemo.delete(broadKey);
30
+ for (const scope of solver.innerTableScopes.values()) {
31
+ clearPredicateFromMemo(scope.memo, `${broadKey}:`);
32
+ scope.subsumptiveMemo.delete(broadKey);
33
+ }
34
+ }
35
+
36
+ function* abolishTableBuiltin({ solver, goal, env }) {
37
+ const name = deref(goal.args[0], env);
38
+ const arity = deref(goal.args[1], env);
39
+ if (name.type !== ATOM || arity.type !== NUMBER || !/^\d+$/.test(arity.name)) return;
40
+ const arityValue = Number(arity.name);
41
+ const matches = [...solver.program.groups.values()].filter((group) =>
42
+ group.name === name.name && group.arity === arityValue && group.tabled === true);
43
+ if (matches.length === 0) return;
44
+ for (const group of matches) clearPredicateTables(solver, group.module, group.name, group.arity);
45
+ // A completed WFS/Datalog model can depend on a tabled predicate; these
46
+ // caches do not expose per-predicate dependency keys, so invalidate them
47
+ // conservatively while preserving unrelated explicit memo tables.
48
+ solver.wfsModels.clear();
49
+ solver.datalogModels.clear();
50
+ solver.tableCoordinator = null;
51
+ yield env;
52
+ }
53
+
18
54
  export const tablingHostBuiltins = {
19
55
  register(registry) {
20
56
  registry.add('eyeprolog__abolish_all_tables', 0, abolishAllTablesBuiltin, { deterministic: true, eyePrologLibrary: true });
57
+ registry.add('eyeprolog__abolish_table', 2, abolishTableBuiltin, { deterministic: true, eyePrologLibrary: true });
21
58
  },
22
59
  };
package/src/term.js CHANGED
@@ -1067,14 +1067,18 @@ function writeString(value, quoteStrings) {
1067
1067
  return out + '"';
1068
1068
  }
1069
1069
 
1070
- function quotedListText(term, env, doubleQuotes) {
1070
+ function quotedListSplice(term, env, doubleQuotes) {
1071
1071
  if (doubleQuotes !== 'chars' && doubleQuotes !== 'codes') return null;
1072
1072
  const characters = [];
1073
1073
  let cursor = term;
1074
1074
  while (true) {
1075
1075
  cursor = deref(cursor, env);
1076
- if (isEmptyList(cursor)) return characters.length === 0 ? null : characters.join('');
1077
- if (!isCons(cursor)) return null;
1076
+ if (isEmptyList(cursor)) {
1077
+ return characters.length === 0 ? null : { text: characters.join(''), tail: null };
1078
+ }
1079
+ if (!isCons(cursor)) {
1080
+ return characters.length === 0 ? null : { text: characters.join(''), tail: cursor };
1081
+ }
1078
1082
  const item = deref(cursor.args[0], env);
1079
1083
  if (doubleQuotes === 'chars') {
1080
1084
  if (item.type !== ATOM || Array.from(item.name).length !== 1) return null;
@@ -1090,8 +1094,12 @@ function quotedListText(term, env, doubleQuotes) {
1090
1094
  }
1091
1095
 
1092
1096
  function writeList(term, env, options) {
1093
- const quotedText = quotedListText(term, env, options.doubleQuotes);
1094
- if (quotedText != null) return writeString(quotedText, true);
1097
+ const quotedSplice = quotedListSplice(term, env, options.doubleQuotes);
1098
+ if (quotedSplice != null && (quotedSplice.tail == null || options.doubleBar === true)) {
1099
+ const prefix = writeString(quotedSplice.text, true);
1100
+ if (quotedSplice.tail == null) return prefix;
1101
+ return `${prefix}||${termToString(quotedSplice.tail, env, true, options)}`;
1102
+ }
1095
1103
  const parts = [];
1096
1104
  let cursor = term;
1097
1105
  while (true) {
@@ -1110,6 +1118,10 @@ export function termToString(term, env = new Env(), quoteStrings = true, options
1110
1118
  options = {
1111
1119
  ...options,
1112
1120
  doubleQuotes: options.doubleQuotes ?? 'chars',
1121
+ // termToString is also used for context-free processor-error messages.
1122
+ // Keep the normal-profile `||` extension opt-in here so strict errors do
1123
+ // not accidentally emit syntax that the strict parser rejects.
1124
+ doubleBar: options.doubleBar === true,
1113
1125
  readVariableNames: options.readVariableNames instanceof Map ? options.readVariableNames : new Map(),
1114
1126
  usedReadVariableNames: options.usedReadVariableNames instanceof Set ? options.usedReadVariableNames : new Set(),
1115
1127
  };
package/src/write.js CHANGED
@@ -152,14 +152,18 @@ function writeString(value) {
152
152
  return out + '"';
153
153
  }
154
154
 
155
- function quotedListText(term, env, doubleQuotes) {
155
+ function quotedListSplice(term, env, doubleQuotes) {
156
156
  if (doubleQuotes !== 'chars' && doubleQuotes !== 'codes') return null;
157
157
  const characters = [];
158
158
  let cursor = term;
159
159
  while (true) {
160
160
  cursor = deref(cursor, env);
161
- if (isEmptyList(cursor)) return characters.length === 0 ? null : characters.join('');
162
- if (!isCons(cursor)) return null;
161
+ if (isEmptyList(cursor)) {
162
+ return characters.length === 0 ? null : { text: characters.join(''), tail: null };
163
+ }
164
+ if (!isCons(cursor)) {
165
+ return characters.length === 0 ? null : { text: characters.join(''), tail: cursor };
166
+ }
163
167
  const item = deref(cursor.args[0], env);
164
168
  if (doubleQuotes === 'chars') {
165
169
  if (item.type !== ATOM || Array.from(item.name).length !== 1) return null;
@@ -331,8 +335,15 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
331
335
  }
332
336
 
333
337
  if (!options.ignoreOps && isCons(resolved)) {
334
- const quotedText = quotedListText(resolved, env, options.doubleQuotes);
335
- if (quotedText != null) return writeString(quotedText);
338
+ const quotedSplice = quotedListSplice(resolved, env, options.doubleQuotes);
339
+ if (quotedSplice != null && (quotedSplice.tail == null || options.doubleBar)) {
340
+ const prefix = writeString(quotedSplice.text);
341
+ if (quotedSplice.tail == null) return prefix;
342
+ // `||` is a normal-profile syntax extension with priority 1, so a
343
+ // looser operator in the right operand must be parenthesized to keep the
344
+ // emitted text readable as the same term.
345
+ return `${prefix}||${format(quotedSplice.tail, env, options, table, 1, 'term')}`;
346
+ }
336
347
  const parts = [];
337
348
  let cursor = resolved;
338
349
  while (true) {
@@ -442,6 +453,9 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
442
453
  ignoreOps: options.ignoreOps === true,
443
454
  numbervars: options.numbervars !== false,
444
455
  doubleQuotes: options.doubleQuotes,
456
+ // `||` is a normal-profile syntax extension. Public normal-mode writers
457
+ // enable it by default; strict callers explicitly turn it off.
458
+ doubleBar: options.doubleBar !== false,
445
459
  variableNames: options.generateVariableNames === true
446
460
  ? printableGeneratedVariableNames(term, env, explicitVariableNames, options.variableNameState)
447
461
  : printableReadVariableNames(term, env, explicitVariableNames),
@@ -113,7 +113,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
113
113
 
114
114
  | Part 1 extension hook | EyeProlog normal-profile feature | Strict-core disposition |
115
115
  | --- | --- | --- |
116
- | 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, and embedded quad syntax | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax is rejected. Unicode PCS membership/classification is implementation defined and therefore shared with normal mode rather than treated as an extension. |
116
+ | 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, embedded quad syntax, and Trealla-compatible `"text"||Tail` right-splicing for double-quoted `chars`/`codes` lists | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax and double-bar right-splicing are rejected. Unicode PCS membership/classification is implementation defined and therefore shared with normal mode rather than treated as an extension. |
117
117
  | 5.5.2 Predefined operators | Part 3 `|` and EyeProlog's labelable infix `(?-)/2`; CLP(Z) operators when that library is imported | Only the Part 1 operator table is predefined; a conforming `op/3` may still add permitted operators. |
118
118
  | 5.5.3 Character-conversion mapping | No non-identity initial `Convc` extension | Identity initial mapping. |
119
119
  | 5.5.4 Types | The normal JavaScript API exposes an implementation-specific string term `stringTerm(Text)`. It is disjoint from the five Part 1 term types; normal term order places it after atoms and before compound terms, and normal `atomic/1` treats it as atomic. It has no Prolog source token syntax (double-quoted source still follows `double_quotes`), is non-callable for term-to-clause conversion, is not evaluable as an arithmetic expression, and writes as a double-quoted host string. | Strict mode rejects a programmatic string term at program/goal entry with `representation_error(term)`, so the additional type cannot enter the Part 1 execution domain. **defined** — `src/term.js`, `src/program.js`, `src/solver.js`; strict API-boundary regression. |
@@ -1,4 +1,4 @@
1
1
  scryer_escapes(ok).
2
2
  scryer_numbers(-1, 1, 1, 1, 1).
3
3
  scryer_comments(7, 7).
4
- scryer_terms({1}, "a", [a, b | c], []).
4
+ scryer_terms({1}, "a", "ab"||c, []).
@@ -1 +1 @@
1
- answer(append_to_improper_tail, [a, b | tail]).
1
+ answer(append_to_improper_tail, "ab"||tail).
@@ -1 +1 @@
1
- answer([a, b | tail]).
1
+ answer("ab"||tail).
@@ -10,4 +10,4 @@ value(compound, pair(3, nested(atom, "xy"))).
10
10
  value(arity_zero_atom, nil).
11
11
  value(empty_list, []).
12
12
  value(proper_list, "abc").
13
- value(improper_list, [a, b | tail]).
13
+ value(improper_list, "ab"||tail).
@@ -57,6 +57,23 @@ export function runArchitecture(reporter = new TestReporter()) {
57
57
  }
58
58
  if (issues.length > 0) throw new Error(issues.join('\n'));
59
59
  });
60
+ reporter.test('overlapping bundled libraries cover the pinned Trealla export surface', () => {
61
+ const snapshotPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'trealla-library-exports.json');
62
+ const expected = JSON.parse(fs.readFileSync(snapshotPath, 'utf8'));
63
+ const issues = [];
64
+ for (const [module, indicators] of Object.entries(expected)) {
65
+ const file = path.join(ROOT, 'lib', `${module}.pl`);
66
+ if (!fs.existsSync(file)) {
67
+ issues.push(`missing src/lib/${module}.pl`);
68
+ continue;
69
+ }
70
+ const actual = new Set(moduleExportIndicators(fs.readFileSync(file, 'utf8'), `src/lib/${module}.pl`));
71
+ for (const indicator of indicators) {
72
+ if (!actual.has(indicator)) issues.push(`${module}: missing Trealla export ${indicator}`);
73
+ }
74
+ }
75
+ if (issues.length > 0) throw new Error(issues.join('\n'));
76
+ });
60
77
  reporter.test('legacy grab-bag library host files are absent', () => {
61
78
  for (const name of ['library-host.js', 'scryer-compat.js']) {
62
79
  if (fs.existsSync(path.join(ROOT, name))) throw new Error(`${name} must stay split into module-owned hosts`);
@@ -2441,7 +2441,7 @@ c4 ?- call((!;1)).
2441
2441
  '?- Xs = [].\n' +
2442
2442
  '?- true.\n' +
2443
2443
  '?- true.\n' +
2444
- '?- Xs0 = [a | Xs].\n' +
2444
+ '?- Xs0 = "a"||Xs.\n' +
2445
2445
  '?- ',
2446
2446
  'stdout');
2447
2447
  assertEqual(result.stderr, '', 'stderr');
@@ -6493,7 +6493,7 @@ answer(ok) :-
6493
6493
  assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
6494
6494
  assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
6495
6495
  assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
6496
- assertEqual(library.defs.size, 215, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and generic library adapters');
6496
+ assertEqual(library.defs.size, 216, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and generic library adapters');
6497
6497
  assertEqual(registry.get('eyeprolog__dynify', 1), null, 'Eyelet dynify adapter is absent from the ISO registry');
6498
6498
  assertEqual(Boolean(library.get('eyeprolog__dynify', 1)), true, 'Eyelet dynify adapter is an internal EyeProlog library primitive');
6499
6499
  assertEqual(registry.get('eyeprolog__eyelet_emit', 2), null, 'Eyelet event adapter is absent from the ISO registry');
@@ -6517,7 +6517,7 @@ answer(ok) :-
6517
6517
  assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
6518
6518
  assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
6519
6519
  assertEqual(registeredNativeEyePrologLibraryNames().length, 109, 'public host-supported EyeProlog library count');
6520
- assertEqual(eyePrologPortableLibraryIndicators.length, 259, 'portable Prolog library count');
6520
+ assertEqual(eyePrologPortableLibraryIndicators.length, 280, 'portable Prolog library count');
6521
6521
  assertEqual(eyePrologInteropLibraryIndicators.length, 218, 'cross-implementation interop profile count');
6522
6522
  assertEqual(eyePrologInteropLibraryModules.length, 26, 'common explicit library module profile count');
6523
6523
  assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
@@ -6535,7 +6535,7 @@ answer(ok) :-
6535
6535
  assertEqual(eyePrologNativeLibraryIndicators.length, 109, 'host-supported library count');
6536
6536
  assertEqual(eyePrologNativeLibraryIndicators.includes('call_nth/2'), true, 'call_nth/2 remains classified as host-supported');
6537
6537
  assertEqual(eyePrologNativeLibraryIndicators.includes('random/1'), true, 'stateful random/1 is classified as host-supported');
6538
- assertEqual(eyePrologLibraryIndicators.length, 368, 'complete non-ISO EyeProlog library surface');
6538
+ assertEqual(eyePrologLibraryIndicators.length, 389, 'complete non-ISO EyeProlog library surface');
6539
6539
  assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
6540
6540
  assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
6541
6541
  assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
@@ -6588,6 +6588,51 @@ answer(ok) :-
6588
6588
  assertEqual(program.findGroup('random', 3)?.module, 'random', 'random/3 is imported from library(random)');
6589
6589
  },
6590
6590
  },
6591
+ {
6592
+ name: 'Trealla library overlap predicates execute with EyeProlog semantics',
6593
+ run: () => {
6594
+ const program = Program.parse(`
6595
+ :- use_module(library(aggregate)).
6596
+ :- use_module(library(error)).
6597
+ :- use_module(library(freeze)).
6598
+ :- use_module(library(lists)).
6599
+ :- use_module(library(random)).
6600
+ :- use_module(library(tabling)).
6601
+
6602
+ positive(X) :- X > 0.
6603
+ inc(X, Y) :- Y is X + 1.
6604
+
6605
+ :- dynamic(edge/2).
6606
+ :- table(path/2).
6607
+ path(X, Y) :- edge(X, Y).
6608
+ path(X, Z) :- edge(X, Y), path(Y, Z).
6609
+
6610
+ trealla_overlap_probe :-
6611
+ aggregate_all(count, member(_, [a,b,c]), 3),
6612
+ aggregate_all(sum(X), member(X, [1,2,3]), 6),
6613
+ aggregate_all(bag(X), member(X, [a,b]), [a,b]),
6614
+ aggregate_all(set(X), member(X, [b,a,b]), [a,b]),
6615
+ aggregate(sum(X), member(K-X, [a-1,a-2,b-3]), 3), K = a,
6616
+ aggregate(sum(Y), J^member(J-Y, [a-1,a-2,b-3]), 6),
6617
+ selectchk(b, [a,b,c], [a,c]),
6618
+ subtract([a,b,a,c], [a], [b,c]),
6619
+ union([a,b], [b,c], [a,b,c]),
6620
+ intersection([a,b,c], [b,d,c], [b,c]),
6621
+ is_set([a,b,c]), \\+ is_set([a,a]),
6622
+ include(positive, [-1,2,0,3], [2,3]),
6623
+ exclude(positive, [-1,2,0,3], [-1,0]),
6624
+ tasklist(inc, [1,2,3], [2,3,4]),
6625
+ maybe(1.0), maybe(1, 1),
6626
+ freeze(F, true), frozen(F, Frozen), Frozen \\== true, F = ready,
6627
+ catch(resource_error(memory, context), error(resource_error(memory), context), true),
6628
+ assertz(edge(a,b)), path(a,b),
6629
+ retract(edge(a,b)), assertz(edge(a,c)),
6630
+ abolish_table(path/2), \\+ path(a,b), path(a,c),
6631
+ catch(abolish_table(missing/1), error(existence_error(table, missing/1), abolish_table/1), true).
6632
+ `);
6633
+ assertEqual(run(program, { goal: 'trealla_overlap_probe' }).stdout, 'trealla_overlap_probe.\n', 'Trealla overlap execution');
6634
+ },
6635
+ },
6591
6636
  {
6592
6637
  name: 'Prologue facade re-exports canonical library predicates without conflicts',
6593
6638
  run: () => {
@@ -6947,6 +6992,44 @@ function whiteBoxCases() {
6947
6992
  assertEqual(`${quotedAtom.type}:${quotedAtom.name}`, 'atom:aλ', 'atom');
6948
6993
  },
6949
6994
  },
6995
+ {
6996
+ name: 'double-bar syntax splices double-quoted list prefixes (issue #88)',
6997
+ run: () => {
6998
+ const chars = parseGoalText('p("ab"||Tail)').args[0];
6999
+ assertEqual(chars.args[0].name, 'a', 'chars first element');
7000
+ assertEqual(chars.args[1].args[0].name, 'b', 'chars second element');
7001
+ assertEqual(chars.args[1].args[1].type, 'var', 'chars partial tail type');
7002
+ assertEqual(chars.args[1].args[1].name, 'Tail', 'chars partial tail name');
7003
+ assertEqual(termToString(chars, new Env(), true, { doubleBar: true }), '"ab"||Tail', 'chars readback');
7004
+
7005
+ const codes = parseGoalText('p("aλ" || Tail)', { doubleQuotes: 'codes' }).args[0];
7006
+ assertEqual(codes.args[0].name, '97', 'codes first element');
7007
+ assertEqual(codes.args[1].args[0].name, '955', 'codes second element');
7008
+ assertEqual(termToString(codes, new Env(), true, { doubleQuotes: 'codes', doubleBar: true }), '"aλ"||Tail', 'codes readback');
7009
+
7010
+ const nested = parseGoalText('p(["ab"|Tail])');
7011
+ assertEqual(termToString(nested), 'p(["ab" | Tail])', 'single bar remains list syntax');
7012
+
7013
+ for (const options of [{ isoStrict: true }, { doubleQuotes: 'atom' }]) {
7014
+ let threw = false;
7015
+ try { parseGoalText('p("ab"||Tail)', options); } catch (_) { threw = true; }
7016
+ assertEqual(threw, true, `double-bar rejected for ${JSON.stringify(options)}`);
7017
+ }
7018
+ },
7019
+ },
7020
+ {
7021
+ name: 'REPL prints partial character lists with double-bar syntax (issue #88)',
7022
+ run: () => {
7023
+ const result = runCli([], {
7024
+ input: 'phrase("Think of this text, and much more.", S0,S).\nhalt.\n',
7025
+ });
7026
+ assertEqual(result.status, 0, 'REPL exit status');
7027
+ assertIncludes(result.stdout,
7028
+ 'S0 = "Think of this text, and much more."||S.',
7029
+ 'Trealla-compatible partial string answer');
7030
+ assertNotIncludes(result.stdout, "S0 = ['T'", 'legacy expanded list answer');
7031
+ },
7032
+ },
6950
7033
  {
6951
7034
  name: 'double_quotes directives affect subsequent source text',
6952
7035
  run: () => {
@@ -0,0 +1,28 @@
1
+ {
2
+ "aggregate": ["aggregate_all/3", "aggregate/3"],
3
+ "arithmetic": ["lsb/2", "msb/2", "popcount/2", "number_to_rational/2", "rational_numerator_denominator/3"],
4
+ "assoc": ["empty_assoc/1", "is_assoc/1", "assoc_to_list/2", "assoc_to_keys/2", "assoc_to_values/2", "gen_assoc/3", "get_assoc/3", "get_assoc/5", "list_to_assoc/2", "map_assoc/2", "map_assoc/3", "max_assoc/3", "min_assoc/3", "ord_list_to_assoc/2", "put_assoc/4", "del_assoc/4", "del_min_assoc/4", "del_max_assoc/4"],
5
+ "atts": ["get_attr/3", "put_attr/3", "del_attr/2"],
6
+ "charsio": ["char_type/2", "get_line_to_chars/3", "get_single_char/1", "get_n_chars/3"],
7
+ "clpb": ["sat/1", "taut/2", "labeling/1", "sat_count/2", "weighted_maximum/3", "random_labeling/2"],
8
+ "clpz": ["#>/2", "#</2", "#>=/2", "#=</2", "#=/2", "#\\=/2", "#\\/1", "#<==>/2", "#==>/2", "#<==/2", "#\\//2", "#\\/2", "#/\\/2", "in/2", "ins/2", "all_different/1", "all_distinct/1", "nvalue/2", "sum/3", "scalar_product/4", "tuples_in/2", "labeling/2", "label/1", "indomain/1", "lex_chain/1", "serialized/2", "global_cardinality/2", "global_cardinality/3", "circuit/1", "cumulative/1", "cumulative/2", "disjoint2/1", "element/3", "automaton/3", "automaton/8", "zcompare/3", "chain/2", "fd_var/1", "fd_inf/2", "fd_sup/2", "fd_size/2", "fd_dom/2", "clpz_t/2", "#=/3", "#</3"],
9
+ "debug": ["*/1", "$/1", "$-/1"],
10
+ "dif": ["dif/2"],
11
+ "error": ["must_be/2", "can_be/2", "instantiation_error/0", "domain_error/2", "type_error/2", "representation_error/1", "resource_error/1", "resource_error/2", "instantiation_error/1", "domain_error/3", "type_error/3", "call_with_error_context/2"],
12
+ "format": ["format_/4", "format/2", "format/3", "portray_clause_/3", "portray_clause/1", "portray_clause/2", "listing/1"],
13
+ "freeze": ["freeze/2", "frozen/2"],
14
+ "gensym": ["gensym/2", "reset_gensym/1"],
15
+ "lambda": ["^/3", "^/4", "^/5", "^/6", "^/7", "^/8", "^/9", "^/10", "\\/1", "\\/2", "\\/3", "\\/4", "\\/5", "\\/6", "\\/7", "\\/8", "+\\/2", "+\\/3", "+\\/4", "+\\/5", "+\\/6", "+\\/7", "+\\/8", "+\\/9"],
16
+ "lists": ["member/2", "memberchk/2", "select/3", "selectchk/3", "append/2", "append/3", "subtract/3", "union/3", "intersection/3", "is_set/1", "nth1/3", "nth0/3", "nth1/4", "nth0/4", "last/2", "same_length/2", "transpose/2", "sum_list/2", "max_list/2", "min_list/2", "list_to_set/2", "reverse/2", "exclude/3", "include/3", "permutation/2", "foldl/4", "foldl/5", "foldl/6", "maplist/2", "maplist/3", "maplist/4", "maplist/5", "maplist/6", "maplist/7", "maplist/8", "tasklist/2", "tasklist/3", "tasklist/4", "tasklist/5", "tasklist/6", "tasklist/7", "tasklist/8"],
17
+ "ordsets": ["is_ordset/1", "list_to_ord_set/2", "ord_add_element/3", "ord_del_element/3", "ord_selectchk/3", "ord_intersect/2", "ord_intersect/3", "ord_intersection/3", "ord_intersection/4", "ord_disjoint/2", "ord_subtract/3", "ord_union/2", "ord_union/3", "ord_union/4", "ord_subset/2", "ord_empty/1", "ord_memberchk/2", "ord_symdiff/3", "ord_seteq/2", "ord_intersection/2"],
18
+ "pairs": ["pairs_keys_values/3", "pairs_keys/2", "pairs_values/2", "group_pairs_by_key/2", "map_list_to_pairs/3"],
19
+ "pio": ["phrase_from_file/2", "phrase_from_file/3", "phrase_to_file/2", "phrase_to_file/3", "phrase_to_stream/2"],
20
+ "random": ["maybe/0", "maybe/1", "maybe/2", "random_integer/3", "set_random/1"],
21
+ "reif": ["if_/3", "=/3", ",/3", ";/3", "cond_t/3", "dif/3", "memberd_t/3", "tfilter/3", "tmember/2", "tmember_t/3", "tpartition/4"],
22
+ "si": ["atom_si/1", "integer_si/1", "atomic_si/1", "list_si/1", "character_si/1", "term_si/1", "chars_si/1", "dif_si/2", "not_si/1", "when_si/2"],
23
+ "tabling": ["start_tabling/2", "abolish_all_tables/0", "abolish_table/1"],
24
+ "time": ["current_time/1", "format_time/4"],
25
+ "ugraphs": ["add_edges/3", "add_vertices/3", "complement/2", "compose/3", "del_edges/3", "del_vertices/3", "edges/2", "neighbors/3", "neighbours/3", "reachable/3", "top_sort/2", "top_sort/3", "transitive_closure/2", "transpose_ugraph/2", "vertices/2", "vertices_edges_to_ugraph/3", "ugraph_union/3", "connect_ugraph/3"],
26
+ "uuid": ["uuidv4/1", "uuidv4_string/1", "uuid_string/2"],
27
+ "when": ["when/2"]
28
+ }
@@ -474,6 +474,12 @@ Prolog programs accepted by EyeProlog are built from terms:
474
474
  - compound terms: `point(3, 4)`, `reading(temp, 91)`;
475
475
  - lists: `[]`, `[red, green, blue]`, `[Head | Tail]`.
476
476
 
477
+ In normal mode, double-quoted character/code lists also support Trealla's
478
+ right-splice notation. With `double_quotes(chars)`, `"ab"||Tail` is shorthand
479
+ for `[a,b|Tail]`; with `double_quotes(codes)`, it denotes `[97,98|Tail]`. The
480
+ splice is not available when `double_quotes(atom)` is active, and
481
+ `--iso-strict` rejects it as an implementation-specific syntax extension.
482
+
477
483
  Plain atom constants begin with a lowercase ASCII letter. Variables begin with
478
484
  an uppercase letter or underscore. The bare `_` is anonymous and every
479
485
  occurrence is fresh. `_Name` is a named variable; repeated occurrences refer to
@@ -5492,8 +5498,10 @@ still retained where adjacent graphic tokens would otherwise merge, as in
5492
5498
  `write_term/2-3` supports `quoted/1`, `ignore_ops/1`, `numbervars/1`, and
5493
5499
  `variable_names/1`. Normal mode additionally accepts the EyeProlog extension
5494
5500
  `double_quotes(true|false)`: `true` lets eligible character/code lists use the
5495
- current `double_quotes` representation, while strict ISO mode rejects this
5496
- implementation-specific write option. Normal mode also accepts the
5501
+ current `double_quotes` representation. Proper lists can therefore be written
5502
+ as `"text"`, while a partial list such as `[a,b|Tail]` is written as
5503
+ `"ab"||Tail`. Strict ISO mode rejects this implementation-specific write
5504
+ option. Normal mode also accepts the
5497
5505
  implementation-specific boolean `spacing(true|false)` option: `false` emits
5498
5506
  only separators required to avoid lexical ambiguity, while `true` adds
5499
5507
  conventional layout around operators. For example,
@@ -5588,6 +5596,8 @@ lowercase ASCII letter. Variables begin with uppercase or underscore. The bare
5588
5596
  `_` is fresh each time. Single quotes delimit quoted atoms; double quotes use
5589
5597
  ISO double-quoted-list notation. Integers, decimals, scientific notation,
5590
5598
  binary/octal/hexadecimal integers, and character-code constants are accepted.
5599
+ Normal mode additionally accepts the Trealla-compatible `"text"||Tail`
5600
+ right-splice for double-quoted `chars`/`codes` lists; strict ISO mode does not.
5591
5601
 
5592
5602
  The processor character set is shared by normal and `--iso-strict` modes because
5593
5603
  Part 1 makes it implementation defined rather than an extension boundary.
@@ -6298,12 +6308,12 @@ so side effects occur in Prolog execution order.
6298
6308
 
6299
6309
  ### The EyeProlog library
6300
6310
 
6301
- EyeProlog exposes **368 distinct non-ISO library and normal-extension predicate
6311
+ EyeProlog exposes **389 distinct non-ISO library and normal-extension predicate
6302
6312
  indicators** in addition to the 129 indicators in its isolated ISO profile.
6303
- **259 are defined entirely as ordinary Prolog clauses** in focused modules under
6313
+ **280 are defined entirely as ordinary Prolog clauses** in focused modules under
6304
6314
  `src/lib/`; **109 use host support** for control, attributed variables,
6305
6315
  constraints, character conversion, filesystem/OS access, timing, cryptography,
6306
- or observability. The ISO and library catalogs therefore cover **497 distinct predicate indicators**. A normal-runtime predicate that is intentionally
6316
+ or observability. The ISO and library catalogs therefore cover **518 distinct predicate indicators**. A normal-runtime predicate that is intentionally
6307
6317
  re-exported by a compatibility module is counted once in this library surface:
6308
6318
  for example `call_cleanup/2` and `setup_call_cleanup/3` are exported by
6309
6319
  `library(iso_ext)`, while `time/1` and `statistics/2` are available from
@@ -6382,7 +6392,7 @@ bindings make one aligned subterm pair sufficient. For example:
6382
6392
 
6383
6393
  | Module | Exported predicate indicators | Primary role |
6384
6394
  | --- | --- | --- |
6385
- | `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5` | Aggregation |
6395
+ | `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5`, `aggregate_all/3`, `aggregate/3` | Aggregation, including Trealla-compatible aggregate templates |
6386
6396
  | `library(arithmetic)` | `expmod/4`, `lcm/3`, `lsb/2`, `msb/2`, `number_to_rational/2`, `number_to_rational/3`, `popcount/2`, `rational_numerator_denominator/3` | Scryer-compatible arithmetic helpers and rational-form conversion |
6387
6397
  | `library(assoc)` | `empty_assoc/1`, `assoc_to_keys/2`, `assoc_to_list/2`, `assoc_to_values/2`, `del_assoc/4`, `del_max_assoc/4`, `del_min_assoc/4`, `gen_assoc/3`, `get_assoc/3`, `get_assoc/5`, `is_assoc/1`, `list_to_assoc/2`, `map_assoc/2`, `map_assoc/3`, `max_assoc/3`, `min_assoc/3`, `ord_list_to_assoc/2`, `put_assoc/4` | AVL association trees; reused from the shared portable source |
6388
6398
  | `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, `call_residue_vars/2` | Attributed variables |
@@ -6396,26 +6406,26 @@ bindings make one aligned subterm pair sufficient. For example:
6396
6406
  | `library(dcgs)` | `-->/2`, `.../2`, `phrase/2`, `phrase/3`, `phrase/4`, `phrase/5`, `seq/3`, `seqq/3` | Full Scryer DCG export surface; nonterminals are shown at their expanded arities |
6397
6407
  | `library(debug)` | `*/1`, `$/1`, `$-/1`, `debug/1`, `debug/3`, `nodebug/1`, `bb_get/2`, `bb_put/2`, `bb_b_put/2` | Declarative debug operators plus compatibility re-exports of the canonical `iso_ext` blackboard |
6398
6408
  | `library(dif)` | `dif/2` | Common module facade over native delayed disequality |
6399
- | `library(error)` | `must_be/2`, `can_be/2`, `instantiation_error/0`, `instantiation_error/1`, `domain_error/2`, `domain_error/3`, `type_error/2`, `type_error/3`, `representation_error/1`, `resource_error/1`, `call_with_error_context/2` | Error checking and construction |
6409
+ | `library(error)` | `must_be/2`, `can_be/2`, `instantiation_error/0`, `instantiation_error/1`, `domain_error/2`, `domain_error/3`, `type_error/2`, `type_error/3`, `representation_error/1`, `resource_error/1`, `resource_error/2`, `call_with_error_context/2` | Error checking and construction |
6400
6410
  | `library(eyelet)` | `stable/1`, `becomes/2` | Eyelet forward-reasoning driver and state helpers; the `:+` operator is exported by the module and its fixed point is implemented in Prolog |
6401
6411
  | `library(files)` | `delete_directory/1`, `delete_file/1`, `directory_exists/1`, `directory_files/2`, `file_access_time/2`, `file_copy/2`, `file_creation_time/2`, `file_exists/1`, `file_modification_time/2`, `file_size/2`, `make_directory/1`, `make_directory_path/1`, `path_canonical/2`, `path_segments/2`, `rename_file/2`, `working_directory/2` | Full Scryer filesystem surface backed by the Node host where filesystem access is required |
6402
6412
  | `library(format)` | `format_/4`, `format/2`, `format/3`, `listing/1`, `portray_clause_/3`, `portray_clause/1`, `portray_clause/2` | Formatted DCG text and output; `format_/4` and `portray_clause_/3` are the expanded nonterminals |
6403
- | `library(freeze)` | `freeze/2` | Delayed goals |
6413
+ | `library(freeze)` | `freeze/2`, `frozen/2` | Delayed goals and residual suspension inspection |
6404
6414
  | `library(gensym)` | `gensym/2`, `reset_gensym/1` | Process-local generated atoms |
6405
6415
  | `library(iso_ext)` | `bb_b_put/2`, `bb_get/2`, `bb_put/2`, `call_cleanup/2`, `call_nth/2`, `call_residue_vars/2`, `call_with_inference_limit/3`, `cfor/3`, `copy_term/3`, `copy_term_nat/2`, `countall/2`, `findall/4`, `forall/2`, `partial_string/1`, `partial_string/3`, `partial_string_tail/2`, `setup_call_cleanup/3`, `succ/2`, `time/1`, `variant/2` | Scryer ISO extensions plus EyeProlog compatibility helpers |
6406
6416
  | `library(lambda)` | `^/3`, `^/4`, `^/5`, `^/6`, `^/7`, `^/8`, `^/9`, `^/10`, `\/1`, `\/2`, `\/3`, `\/4`, `\/5`, `\/6`, `\/7`, `\/8`, `+\/2`, `+\/3`, `+\/4`, `+\/5`, `+\/6`, `+\/7`, `+\/8`, `+\/9` | Higher-order lambda notation |
6407
- | `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`, `maplist/9`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `list_max/2`, `list_min/2`, `permutation/2`, `transpose/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` | List relations and shared matrix/permutation helpers |
6417
+ | `library(lists)` | `member/2`, `memberchk/2`, `select/3`, `selectchk/3`, `subtract/3`, `union/3`, `intersection/3`, `is_set/1`, `append/2`, `append/3`, `last/2`, `same_length/2`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `reverse/2`, `length/2`, `include/3`, `exclude/3`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `maplist/9`, `tasklist/2`, `tasklist/3`, `tasklist/4`, `tasklist/5`, `tasklist/6`, `tasklist/7`, `tasklist/8`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `list_max/2`, `list_min/2`, `permutation/2`, `transpose/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` | List relations and shared matrix/permutation helpers; `tasklist/*` is a sequential compatibility fallback |
6408
6418
  | `library(ordsets)` | `is_ordset/1`, `list_to_ord_set/2`, `ord_add_element/3`, `ord_del_element/3`, `ord_disjoint/2`, `ord_empty/1`, `ord_intersect/2`, `ord_intersect/3`, `ord_intersection/2`, `ord_intersection/3`, `ord_intersection/4`, `ord_memberchk/2`, `ord_selectchk/3`, `ord_seteq/2`, `ord_subset/2`, `ord_subtract/3`, `ord_symdiff/3`, `ord_union/2`, `ord_union/3`, `ord_union/4` | Ordered-set relations; reused upstream source |
6409
6419
  | `library(os)` | `argv/1`, `getenv/2`, `pid/1`, `raw_argv/1`, `setenv/2`, `shell/1`, `shell/2`, `unsetenv/1` | Environment, shell, PID, and command-line access backed by the Node host |
6410
6420
  | `library(pairs)` | `pairs_keys_values/3`, `pairs_keys/2`, `pairs_values/2`, `group_pairs_by_key/2`, `map_list_to_pairs/3` | Key-value pair support |
6411
6421
  | `library(pio)` | `phrase_from_file/2`, `phrase_from_file/3`, `phrase_from_stream/2`, `phrase_to_file/2`, `phrase_to_file/3`, `phrase_to_stream/2` | Scryer-compatible eager DCG file/stream I/O with character-list and atom paths |
6412
6422
  | `library(primes)` | `smallest_divisor_from/3` | Prime factor support |
6413
6423
  | `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` | Legacy facade over canonical focused modules |
6414
- | `library(random)` | `maybe/0`, `random/1`, `random/3`, `random_integer/3`, `set_random/1` | Common mutable-seed interface plus EyeProlog's pure state-threaded generator |
6424
+ | `library(random)` | `maybe/0`, `maybe/1`, `maybe/2`, `random/1`, `random/3`, `random_integer/3`, `set_random/1` | Trealla-compatible probability helpers plus the common mutable-seed interface and EyeProlog's pure state-threaded generator |
6415
6425
  | `library(reif)` | `,/3`, `;/3`, `=/3`, `cond_t/3`, `dif/3`, `if_/3`, `memberd_t/3`, `tfilter/3`, `tmember/2`, `tmember_t/3`, `tpartition/4` | Reified conditions and list filtering; reused upstream source |
6416
6426
  | `library(si)` | `atom_si/1`, `integer_si/1`, `atomic_si/1`, `list_si/1`, `character_si/1`, `term_si/1`, `chars_si/1`, `dif_si/2`, `not_si/1`, `when_si/2` | Sufficient-instantiation checks used by CLP(Z) |
6417
6427
  | `library(strings)` | `matches/3`, `split/3`, `replace/4`, `lowercase/2`, `uppercase/2`, `trim/2`, `number_string/2`, `atom_string/2`, `term_string/2`, `string_concat/3`, `contains/2`, `matches/2`, `join/3`, `substring/4` | Text relations |
6418
- | `library(tabling)` | `abolish_all_tables/0`, `start_tabling/2` | Common helpers for explicitly declared tabling |
6428
+ | `library(tabling)` | `abolish_all_tables/0`, `abolish_table/1`, `start_tabling/2` | Common helpers for explicitly declared tabling, including targeted invalidation |
6419
6429
  | `library(terms)` | `numbervars/3`, `copy_term_nat/2` | Term operations used by bundled libraries |
6420
6430
  | `library(time)` | `current_time/1`, `format_time/4`, `max_sleep_time/1`, `sleep/1`, `statistics/2`, `time/1` | Clock timestamps, sleep limits, timing/statistics, and the expanded `format_time//2` nonterminal |
6421
6431
  | `library(ugraphs)` | `add_edges/3`, `add_vertices/3`, `complement/2`, `compose/3`, `connect_ugraph/3`, `del_edges/3`, `del_vertices/3`, `edges/2`, `neighbors/3`, `neighbours/3`, `reachable/3`, `top_sort/2`, `top_sort/3`, `transitive_closure/2`, `transpose_ugraph/2`, `ugraph_union/3`, `vertices/2`, `vertices_edges_to_ugraph/3` | Directed graph relations; reused upstream source |
@@ -6452,7 +6462,13 @@ narrower than either implementation's union of exports; EyeProlog's explicit-sta
6452
6462
  interfaces. Separately, all 32 bundled EyeProlog modules whose basenames overlap
6453
6463
  Scryer's current `src/lib/` tree are checked against the frozen export snapshot in
6454
6464
  `test/scryer-library-exports.json` and cover the corresponding Scryer public
6455
- predicate surface.
6465
+ predicate surface. The same structural check is now applied to Trealla: the 26
6466
+ bundled modules that have public-module counterparts in Trealla `library/` cover
6467
+ Trealla's exported predicates at pinned upstream commit
6468
+ `f7a93bd521c07a4841f5123348111dd005918c89`, recorded in
6469
+ `test/trealla-library-exports.json`. This is module-overlap coverage, not a claim
6470
+ that EyeProlog bundles Trealla's native host libraries such as `curl`, `gsl`,
6471
+ `janus`, `raylib`, `socket`, or `sqlite3`.
6456
6472
 
6457
6473
  Source reuse is preferred over translation. `clpb.pl`, `ordsets.pl`,
6458
6474
  `reif.pl`, and `ugraphs.pl` retain the upstream Prolog algorithms and license
@@ -6466,7 +6482,12 @@ compatibility. `files.pl` covers Scryer's complete filesystem export surface,
6466
6482
  while `os.pl` covers its environment/shell process-context surface. Their actual
6467
6483
  filesystem and OS side effects are isolated in module-owned Node adapters.
6468
6484
  `crypto.pl` exposes Scryer's complete public crypto surface; its strict Trealla/Scryer overlap is `hex_bytes/2`,
6469
- `crypto_n_random_bytes/2`, and `crypto_data_hash/3`. The
6485
+ `crypto_n_random_bytes/2`, and `crypto_data_hash/3`. Trealla-specific overlap additions include `aggregate_all/3` and
6486
+ `aggregate/3`, `frozen/2`, the set/filter/list helpers and `tasklist/2-8`,
6487
+ `maybe/1-2`, `resource_error/2`, and `abolish_table/1`. Because EyeProlog has no
6488
+ Trealla task scheduler, `tasklist/2-8` deliberately executes sequentially with
6489
+ `maplist`-equivalent success/failure semantics; it does not promise Trealla's
6490
+ parallel scheduling behavior. The
6470
6491
  [portable library overlap example](https://github.com/eyereasoner/eyeprolog/blob/main/examples/portable-library-overlap.pl)
6471
6492
  composes Boolean constraints, ordered sets, graphs, reification, delayed goals,
6472
6493
  generated names, character conversion, transposition, and explicit table
@@ -8017,7 +8038,9 @@ Notable implementation boundaries are:
8017
8038
  - module and ISO/IEC TS 13211-3-oriented DCG compatibility profiles are supported in normal mode, without a complete Part 2/Part 3 certification claim;
8018
8039
  - variables cannot occupy functor or predicate position;
8019
8040
  - double-quoted text follows `double_quotes` exactly; the default `chars` value
8020
- matches Trealla and Scryer and may be changed to `codes` or `atom`;
8041
+ matches Trealla and Scryer and may be changed to `codes` or `atom`; normal
8042
+ mode additionally accepts Trealla-compatible `"text"||Tail` right-splicing
8043
+ for the `chars` and `codes` values, while strict mode rejects that syntax;
8021
8044
  - `write_term/2-3` implements the Part 1 plus Corrigendum 3 `quoted/1`,
8022
8045
  `ignore_ops/1`, `numbervars/1`, and `variable_names/1` option surface,
8023
8046
  including option validation and traversal rules; normal mode also offers