eyeprolog 1.5.68 → 1.5.70
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/package.json +1 -1
- package/src/lib/error.pl +27 -6
- package/src/solver.js +48 -0
- package/test/regression/cases-regression.mjs +88 -1
package/package.json
CHANGED
package/src/lib/error.pl
CHANGED
|
@@ -55,6 +55,7 @@ error__must_be(acyclic, Term) :- !,
|
|
|
55
55
|
error__must_be(list, Term) :- !,
|
|
56
56
|
error__list(Term, Term, must_be/2, complete).
|
|
57
57
|
error__must_be(list(Type), Term) :- !,
|
|
58
|
+
error__require_type(Type, must_be/2),
|
|
58
59
|
error__list(Term, Term, must_be/2, complete),
|
|
59
60
|
error__proper_list_of(Term, Type).
|
|
60
61
|
error__must_be(character, Term) :- !,
|
|
@@ -75,10 +76,30 @@ error__must_be(pair, Term) :- !,
|
|
|
75
76
|
error__must_be(not_less_than_zero, Term) :- !,
|
|
76
77
|
must_be(integer, Term),
|
|
77
78
|
( Term >= 0 -> true ; domain_error(not_less_than_zero, Term) ).
|
|
78
|
-
error__must_be(Type,
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
error__must_be(Type, _) :-
|
|
80
|
+
error__must_be_throw(type_error(type, Type)).
|
|
81
|
+
|
|
82
|
+
% Check the type descriptor before accepting a variable or an empty list.
|
|
83
|
+
% The variable guard also prevents validation from instantiating a descriptor.
|
|
84
|
+
error__require_type(Type, Predicate) :- var(Type), !,
|
|
85
|
+
throw(error(instantiation_error, [predicate-Predicate])).
|
|
86
|
+
error__require_type(list(Type), Predicate) :- !,
|
|
87
|
+
error__require_type(Type, Predicate).
|
|
88
|
+
error__require_type(Type, Predicate) :-
|
|
89
|
+
( error__known_type(Type) -> true
|
|
90
|
+
; throw(error(type_error(type, Type), [predicate-Predicate])) ).
|
|
91
|
+
|
|
92
|
+
error__known_type(integer).
|
|
93
|
+
error__known_type(atom).
|
|
94
|
+
error__known_type(number).
|
|
95
|
+
error__known_type(var).
|
|
96
|
+
error__known_type(ground).
|
|
97
|
+
error__known_type(acyclic).
|
|
98
|
+
error__known_type(list).
|
|
99
|
+
error__known_type(character).
|
|
100
|
+
error__known_type(chars).
|
|
101
|
+
error__known_type(pair).
|
|
102
|
+
error__known_type(not_less_than_zero).
|
|
82
103
|
|
|
83
104
|
% A list ends in []; a partial list ends in a variable (including a variable
|
|
84
105
|
% alone). Inspect before matching so validation never completes a partial list.
|
|
@@ -102,8 +123,8 @@ error__proper_list_of(Term, _) :-
|
|
|
102
123
|
).
|
|
103
124
|
|
|
104
125
|
can_be(Type, Term) :-
|
|
105
|
-
(
|
|
106
|
-
|
|
126
|
+
error__require_type(Type, can_be/2),
|
|
127
|
+
( var(Term) -> true
|
|
107
128
|
; error__can_be(Type, Term)
|
|
108
129
|
).
|
|
109
130
|
|
package/src/solver.js
CHANGED
|
@@ -838,6 +838,16 @@ export class Solver {
|
|
|
838
838
|
continue;
|
|
839
839
|
}
|
|
840
840
|
|
|
841
|
+
const appendIterator = bundledAppendIterator(this, group, goal, env);
|
|
842
|
+
if (appendIterator != null) {
|
|
843
|
+
const firstResult = appendIterator.next();
|
|
844
|
+
if (firstResult.done) break;
|
|
845
|
+
goals = rest;
|
|
846
|
+
env = firstResult.value;
|
|
847
|
+
depth++;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
|
|
841
851
|
const comparisonIterator = bundledCompareSiIterator(this, group, goal, env);
|
|
842
852
|
if (comparisonIterator != null) {
|
|
843
853
|
const firstResult = comparisonIterator.next();
|
|
@@ -1896,6 +1906,44 @@ function* bundledBetweenSolutions(solver, goal, env, state) {
|
|
|
1896
1906
|
state.pending = false;
|
|
1897
1907
|
}
|
|
1898
1908
|
|
|
1909
|
+
function bundledAppendIterator(solver, group, goal, env) {
|
|
1910
|
+
if (solver.registry.eyePrologLibrary !== true || group.bundledLibrary !== true ||
|
|
1911
|
+
group.module !== 'lists' || group.name !== 'append' || group.arity !== 3 ||
|
|
1912
|
+
group.tabled || group.clauses.length !== 2) return null;
|
|
1913
|
+
// Preserve source-defined hook ordering and occurs-check diagnostics. Only
|
|
1914
|
+
// forward construction with a plain, unbound result is specialized.
|
|
1915
|
+
if (env._prologAttributes != null || env._delays != null ||
|
|
1916
|
+
solver.prologFlags.get('occurs_check')?.value?.name === 'error' ||
|
|
1917
|
+
deref(goal.args[2], env).type !== VAR) return null;
|
|
1918
|
+
|
|
1919
|
+
const items = [];
|
|
1920
|
+
const seen = new Set();
|
|
1921
|
+
let cursor = deref(goal.args[0], env);
|
|
1922
|
+
while (isCons(cursor)) {
|
|
1923
|
+
if (seen.has(cursor)) return null;
|
|
1924
|
+
seen.add(cursor);
|
|
1925
|
+
items.push(cursor.args[0]);
|
|
1926
|
+
if ((items.length & 255) === 0) solver.checkMemoryLimit(true);
|
|
1927
|
+
cursor = deref(cursor.args[1], env);
|
|
1928
|
+
}
|
|
1929
|
+
if (!isEmptyList(cursor)) return null;
|
|
1930
|
+
return bundledAppendSolutions(solver, goal, env, items);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function* bundledAppendSolutions(solver, goal, env, items) {
|
|
1934
|
+
// Copy just the prefix spine, preserving its variables and sharing the
|
|
1935
|
+
// suffix. One normal, occurs-checked unification replaces repeated clause
|
|
1936
|
+
// freshening and scans over every remaining variable-list suffix.
|
|
1937
|
+
let joined = goal.args[1];
|
|
1938
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
1939
|
+
joined = cons(items[i], joined);
|
|
1940
|
+
if ((i & 255) === 0) solver.checkMemoryLimit(true);
|
|
1941
|
+
}
|
|
1942
|
+
const next = env.clone();
|
|
1943
|
+
solver.stats.unify_calls++;
|
|
1944
|
+
if (unify(goal.args[2], joined, next)) yield next;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1899
1947
|
function bundledMemberIterator(solver, group, goal, env) {
|
|
1900
1948
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1901
1949
|
group.module !== 'lists' || group.name !== 'member' || group.arity !== 2 ||
|
|
@@ -28,6 +28,89 @@ import {
|
|
|
28
28
|
|
|
29
29
|
export function regressionCases() {
|
|
30
30
|
return [
|
|
31
|
+
{
|
|
32
|
+
name: 'must_be/2 and can_be/2 reject invalid type descriptors before checking values (issue #106)',
|
|
33
|
+
run: () => {
|
|
34
|
+
for (const predicate of ['must_be', 'can_be']) {
|
|
35
|
+
for (const type of ['nontype', '42', 'foo(integer)', 'list(nontype)', 'list(list(nontype))']) {
|
|
36
|
+
const culprit = type.startsWith('list(') ? 'nontype' : type;
|
|
37
|
+
for (const value of ['0', 'X', '[]']) {
|
|
38
|
+
const result = runEyeProlog(`answer(ok) :- catch((${predicate}(${type},${value}),fail),error(type_error(type,${culprit}),[predicate-${predicate}/2]),true),var(X).`, { goals: ['answer(X)'] });
|
|
39
|
+
assertIncludes(result.stdout, 'answer(ok)', `${predicate}(${type},${value})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const type of ['T', 'list(T)', 'list(list(T))']) {
|
|
43
|
+
const result = runEyeProlog(`answer(ok) :- catch((${predicate}(${type},[]),fail),error(instantiation_error,[predicate-${predicate}/2]),true),var(T).`, { goals: ['answer(X)'] });
|
|
44
|
+
assertIncludes(result.stdout, 'answer(ok)', `${predicate}(${type},[])`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const valid = [
|
|
48
|
+
['integer', '1'], ['atom', 'a'], ['number', '1.5'], ['var', 'X'],
|
|
49
|
+
['ground', 'f(a)'], ['acyclic', 'f(X)'], ['list', '[X]'],
|
|
50
|
+
['character', 'a'], ['chars', '[a,b]'], ['pair', 'a-b'],
|
|
51
|
+
['not_less_than_zero', '0'], ['list(integer)', '[1,2]'],
|
|
52
|
+
['list(list(integer))', '[[1],[]]'],
|
|
53
|
+
];
|
|
54
|
+
for (const [type, value] of valid) {
|
|
55
|
+
const result = runEyeProlog(`answer(ok) :- must_be(${type},${value}),can_be(${type},${value}),can_be(${type},Fresh),var(Fresh).`, { goals: ['answer(X)'] });
|
|
56
|
+
assertIncludes(result.stdout, 'answer(ok)', `supported type ${type}`);
|
|
57
|
+
}
|
|
58
|
+
const context = runEyeProlog('answer(ok) :- catch((call_with_error_context(can_be(nontype,X),outer-1),fail),error(type_error(type,nontype),[outer-1,predicate-can_be/2]),true),var(X).', { goals: ['answer(X)'] });
|
|
59
|
+
assertIncludes(context.stdout, 'answer(ok)', 'composable error context');
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'forward bundled append/3 matches the portable relation across sharing and fallback modes',
|
|
64
|
+
run: () => {
|
|
65
|
+
const goals = [
|
|
66
|
+
'append([],Y,Z),Y=tail',
|
|
67
|
+
'append([X,Y],[Y,X],Z),X=a,Y=b',
|
|
68
|
+
'append([X],T,Z),T=[X|Rest],Rest=tail',
|
|
69
|
+
'append([a,b],tail,Z)',
|
|
70
|
+
'append([X],[],X)',
|
|
71
|
+
'append([a],Z,Z)',
|
|
72
|
+
'append([X],[],Z),X=Z',
|
|
73
|
+
'append([a],T,Z),T=Z',
|
|
74
|
+
'append([a],[],[b])',
|
|
75
|
+
'append([a|T],[b],[a,c,b])',
|
|
76
|
+
'append([a|bad],[],Z)',
|
|
77
|
+
'append(X,Y,[a,b])',
|
|
78
|
+
'(append([a],[],Z);append([b],[],Z))',
|
|
79
|
+
'freeze(X,Y=woke),append([X],[],Z),X=a',
|
|
80
|
+
'dif(X,a),append([X],[],Z),X=b',
|
|
81
|
+
'set_prolog_flag(occurs_check,error),catch(append([X],[],X),error(E,_),true)',
|
|
82
|
+
];
|
|
83
|
+
for (const goal of goals) {
|
|
84
|
+
const source = `answer(X,Y,Z,T,Rest,E) :- ${goal}.`;
|
|
85
|
+
const options = { goals: ['answer(X,Y,Z,T,Rest,E)'] };
|
|
86
|
+
const optimized = runEyeProlog(source, options);
|
|
87
|
+
const portable = runEyeProlog(
|
|
88
|
+
'portable_append([],Ys,Ys). portable_append([X|Xs],Ys,[X|Zs]) :- portable_append(Xs,Ys,Zs).\n' +
|
|
89
|
+
source.replaceAll('append(', 'portable_append('), options);
|
|
90
|
+
assertEqual(optimized.stdout, portable.stdout, goal);
|
|
91
|
+
assertEqual(optimized.stderr, portable.stderr, `${goal} diagnostics`);
|
|
92
|
+
}
|
|
93
|
+
const custom = runEyeProlog('append(custom,_,ok).', { goals: ['append(custom,[],Z)'] });
|
|
94
|
+
assertIncludes(custom.stdout, 'append(custom, [], ok)', 'user definition');
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'forward append/3 constructs long shared-variable lists within a bounded heap and time',
|
|
99
|
+
run: () => {
|
|
100
|
+
const script = `
|
|
101
|
+
import {run} from './src/index.js';
|
|
102
|
+
const result=run('answer(ok) :- length(P,8192),append(P,[1],L1),append(P,[2],L2),compare(R,L1,L2),compare_si(S,L1,L2),R==(<),S==(<).', {goals:['answer(X)']});
|
|
103
|
+
if (!result.stdout.includes('answer(ok)')) throw new Error(result.stdout+'\\n'+result.stderr);
|
|
104
|
+
console.log('ok');
|
|
105
|
+
`;
|
|
106
|
+
const result = spawnSync(process.execPath, ['--max-old-space-size=128', '--stack-size=256', '--input-type=module', '--eval', script], {
|
|
107
|
+
cwd: packageRoot, encoding: 'utf8', timeout: 10000,
|
|
108
|
+
});
|
|
109
|
+
if (result.error) throw result.error;
|
|
110
|
+
assertEqual(result.status, 0, result.stderr);
|
|
111
|
+
assertIncludes(result.stdout, 'ok', 'long forward append');
|
|
112
|
+
},
|
|
113
|
+
},
|
|
31
114
|
{
|
|
32
115
|
name: 'autoload follows declared meta-predicates and closure arities (issue #105)',
|
|
33
116
|
run: () => {
|
|
@@ -114,8 +197,12 @@ export function regressionCases() {
|
|
|
114
197
|
{
|
|
115
198
|
name: 'REPL prints the complete 8192-cell timed comparison answer (issue #105)',
|
|
116
199
|
run: () => {
|
|
200
|
+
// Keep the large variable-list answer that overflowed REPL printing,
|
|
201
|
+
// but prepend the differing elements to avoid expensive append/3 setup.
|
|
202
|
+
// The adjacent cases cover long shared-prefix comparison and append/3
|
|
203
|
+
// backtracking separately.
|
|
117
204
|
const result = runCli([], { input:
|
|
118
|
-
'length(_,I),I=13,N is 2^I,length(P,N),
|
|
205
|
+
'length(_,I),I=13,N is 2^I,length(P,N),L1=[1|P],L2=[2|P],' +
|
|
119
206
|
'time(compare(R,L1,L2)),time(compare_si(S,L1,L2)).\n.\nhalt.\n',
|
|
120
207
|
timeout: 60000,
|
|
121
208
|
});
|