eyeprolog 1.5.2 → 1.5.4
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 +5 -5
- package/package.json +1 -1
- package/src/execute.js +1 -16
- package/src/lib/eyelet.pl +43 -0
- package/src/library-autoload-index.js +3 -0
- package/src/program.js +108 -3
- package/src/standard-library.js +2 -0
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/run-regression.mjs +48 -3
- package/the-art-of-eyeprolog.md +39 -30
- package/why-eyeprolog.md +11 -2
package/README.md
CHANGED
|
@@ -31,8 +31,6 @@ Run EyeProlog without installing it globally:
|
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
npx --yes eyeprolog
|
|
34
|
-
?- use_module(library(lists)).
|
|
35
|
-
true.
|
|
36
34
|
?- member(X, [prolog, logic]).
|
|
37
35
|
X = prolog
|
|
38
36
|
; X = logic.
|
|
@@ -59,11 +57,13 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
|
|
|
59
57
|
```
|
|
60
58
|
|
|
61
59
|
## Native forward rules (`:+`)
|
|
62
|
-
|
|
63
60
|
Normal mode executes top-level `Conclusion :+ Premise` rules natively when no explicit
|
|
64
61
|
`-g/--goal` is supplied. `true :+ Goal` prints answers and `false :+ Goal` emits a
|
|
65
|
-
`fuse/1`; JavaScript `run()` follows the same rule.
|
|
66
|
-
|
|
62
|
+
`fuse/1`; JavaScript `run()` follows the same rule. The bundled `library(eyelet)`
|
|
63
|
+
exports the `:+` operator, `stable/1`, and `becomes/2`. Strict ISO mode disables this
|
|
64
|
+
extension. Bundled `src/lib/` predicates autoload in files, CLI/API goals, and the REPL;
|
|
65
|
+
use `--no-autoload` to require explicit imports, and explicitly import libraries that introduce
|
|
66
|
+
operators before using their syntax. See [*The Art of EyeProlog*](the-art-of-eyeprolog.md) for the full semantics.
|
|
67
67
|
|
|
68
68
|
## Links
|
|
69
69
|
|
package/package.json
CHANGED
package/src/execute.js
CHANGED
|
@@ -82,8 +82,6 @@ export function executeForwardRules(program, solver, {
|
|
|
82
82
|
predeclareForwardTerms(program, solver, group);
|
|
83
83
|
setSingletonFact(program, 'closure', [numberTerm(0)]);
|
|
84
84
|
setSingletonFact(program, 'limit', [numberTerm(-1)]);
|
|
85
|
-
setSingletonFact(program, 'count', [atom('fm'), numberTerm(0)], true);
|
|
86
|
-
setSingletonFact(program, 'count', [atom('mf'), numberTerm(0)], true);
|
|
87
85
|
|
|
88
86
|
const seenAnswers = new Set();
|
|
89
87
|
let rounds = 0;
|
|
@@ -188,11 +186,6 @@ export function executeForwardRules(program, solver, {
|
|
|
188
186
|
break;
|
|
189
187
|
}
|
|
190
188
|
|
|
191
|
-
for (const name of ['fm', 'mf']) {
|
|
192
|
-
const count = keyedCount(program, name);
|
|
193
|
-
if (count !== 0) onDiagnostic(`*** ${name}=${count}\n`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
189
|
return { haltCode, rounds, derived };
|
|
197
190
|
}
|
|
198
191
|
|
|
@@ -370,12 +363,4 @@ function singletonInteger(program, name, fallback) {
|
|
|
370
363
|
return fallback;
|
|
371
364
|
}
|
|
372
365
|
|
|
373
|
-
|
|
374
|
-
const group = program.findGroup('count', 2, 'user');
|
|
375
|
-
if (group == null) return 0;
|
|
376
|
-
for (let i = group.clauses.length - 1; i >= 0; i--) {
|
|
377
|
-
const args = group.clauses[i].head?.args;
|
|
378
|
-
if (args?.[0]?.type === ATOM && args[0].name === key && args[1]?.type === NUMBER) return Number(args[1].name);
|
|
379
|
-
}
|
|
380
|
-
return 0;
|
|
381
|
-
}
|
|
366
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
% Eyelet forward-reasoning support for EyeProlog.
|
|
2
|
+
%
|
|
3
|
+
% The :+/2 closure itself is executed natively by EyeProlog. This module owns
|
|
4
|
+
% the two state helpers used by Eyelet programs and exports the :+ operator so
|
|
5
|
+
% source files can opt into the complete surface with one use_module/1.
|
|
6
|
+
|
|
7
|
+
:- module(eyelet, [op(1200, xfx, :+), stable/1, becomes/2]).
|
|
8
|
+
|
|
9
|
+
:- use_module(library(iso_ext), [forall/2]).
|
|
10
|
+
:- use_module(library(lists), [member/2]).
|
|
11
|
+
|
|
12
|
+
:- meta_predicate(becomes(0, 0)).
|
|
13
|
+
|
|
14
|
+
% stable(+Level)
|
|
15
|
+
% Fail until the native forward-rule driver has reached the requested closure
|
|
16
|
+
% level. Asking for a higher level extends the driver's target limit.
|
|
17
|
+
stable(Level) :-
|
|
18
|
+
call(user:limit(Limit)),
|
|
19
|
+
( Limit < Level
|
|
20
|
+
-> user:retract(limit(Limit)),
|
|
21
|
+
user:assertz(limit(Level))
|
|
22
|
+
; true
|
|
23
|
+
),
|
|
24
|
+
call(user:closure(Closure)),
|
|
25
|
+
Level =< Closure.
|
|
26
|
+
|
|
27
|
+
% becomes(:From, :To)
|
|
28
|
+
% Linear implication over dynamic state: prove and retract every conjunct in
|
|
29
|
+
% From, then assert every conjunct in To.
|
|
30
|
+
becomes(From, To) :-
|
|
31
|
+
catch(call(user:From), _, fail),
|
|
32
|
+
conj_list(From, Old),
|
|
33
|
+
forall(member(Clause, Old), user:retract(Clause)),
|
|
34
|
+
conj_list(To, New),
|
|
35
|
+
forall(member(Clause, New), user:assertz(Clause)).
|
|
36
|
+
|
|
37
|
+
conj_list(true, []).
|
|
38
|
+
conj_list(Goal, [Goal]) :-
|
|
39
|
+
Goal \= (_, _),
|
|
40
|
+
Goal \= false,
|
|
41
|
+
!.
|
|
42
|
+
conj_list((A, B), [A|Rest]) :-
|
|
43
|
+
conj_list(B, Rest).
|
|
@@ -69,6 +69,7 @@ export const eyePrologLibraryAutoload = Object.freeze({
|
|
|
69
69
|
"bb_get/2": "debug",
|
|
70
70
|
"bb_global_get/2": "debug",
|
|
71
71
|
"bb_put/2": "debug",
|
|
72
|
+
"becomes/2": "eyelet",
|
|
72
73
|
"between/3": "between",
|
|
73
74
|
"call_nth/2": "iso_ext",
|
|
74
75
|
"call_residue_vars/2": "atts",
|
|
@@ -264,6 +265,7 @@ export const eyePrologLibraryAutoload = Object.freeze({
|
|
|
264
265
|
"slice/4": "lists",
|
|
265
266
|
"smallest_divisor_from/3": "primes",
|
|
266
267
|
"split/3": "strings",
|
|
268
|
+
"stable/1": "eyelet",
|
|
267
269
|
"start_tabling/2": "tabling",
|
|
268
270
|
"string_concat/3": "strings",
|
|
269
271
|
"substring/4": "strings",
|
|
@@ -322,6 +324,7 @@ export const eyePrologLibraryAutoloadModules = Object.freeze([
|
|
|
322
324
|
"debug",
|
|
323
325
|
"dif",
|
|
324
326
|
"error",
|
|
327
|
+
"eyelet",
|
|
325
328
|
"format",
|
|
326
329
|
"freeze",
|
|
327
330
|
"gensym",
|
package/src/program.js
CHANGED
|
@@ -1131,6 +1131,14 @@ function bundledLibraryModule(program, module) {
|
|
|
1131
1131
|
function groupDependencies(group) {
|
|
1132
1132
|
const dependencies = [];
|
|
1133
1133
|
for (const clause of group.clauses) {
|
|
1134
|
+
// Native forward rules store their executable premise in the :+/2 head
|
|
1135
|
+
// rather than in the ordinary clause body. Treat that premise as a goal
|
|
1136
|
+
// dependency so bundled predicates such as stable/1 and becomes/2 can be
|
|
1137
|
+
// autoloaded exactly like calls in ordinary rules. Derived :+ rules may
|
|
1138
|
+
// themselves occur in a conclusion, so recurse through that shape too.
|
|
1139
|
+
if (group.name === ':+') {
|
|
1140
|
+
dependencies.push(...forwardRuleDependencies(clause.head));
|
|
1141
|
+
}
|
|
1134
1142
|
if (isCompactBinaryClause(clause)) {
|
|
1135
1143
|
if (clause.bodyName != null) {
|
|
1136
1144
|
dependencies.push({
|
|
@@ -1149,6 +1157,103 @@ function groupDependencies(group) {
|
|
|
1149
1157
|
return dependencies;
|
|
1150
1158
|
}
|
|
1151
1159
|
|
|
1160
|
+
function forwardRuleDependencies(term, out = []) {
|
|
1161
|
+
if (term?.type !== COMPOUND) return out;
|
|
1162
|
+
if (term.name === ':+' && term.arity === 2) {
|
|
1163
|
+
out.push(...collectGoalDependencies(term.args[1], false, true));
|
|
1164
|
+
forwardRuleDependencies(term.args[0], out);
|
|
1165
|
+
return out;
|
|
1166
|
+
}
|
|
1167
|
+
if (term.name === ',' && term.arity === 2) {
|
|
1168
|
+
forwardRuleDependencies(term.args[0], out);
|
|
1169
|
+
forwardRuleDependencies(term.args[1], out);
|
|
1170
|
+
}
|
|
1171
|
+
return out;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Autoloading needs a slightly richer dependency walk than recursion analysis.
|
|
1175
|
+
// Meta-predicates such as forall/2 and setof/3 are themselves callable
|
|
1176
|
+
// procedures while also containing executable goals. The recursion graph
|
|
1177
|
+
// intentionally looks through those wrappers; the autoloader must see both the
|
|
1178
|
+
// wrapper and any statically visible nested calls so a bundled library can be
|
|
1179
|
+
// loaded before execution reaches it.
|
|
1180
|
+
function collectAutoloadGoalDependencies(goal, out = []) {
|
|
1181
|
+
if (goal?.type === ATOM) {
|
|
1182
|
+
out.push({ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module });
|
|
1183
|
+
return out;
|
|
1184
|
+
}
|
|
1185
|
+
if (goal?.type !== COMPOUND) return out;
|
|
1186
|
+
if (goal.name === ',' && goal.arity === 2) {
|
|
1187
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1188
|
+
collectAutoloadGoalDependencies(goal.args[1], out);
|
|
1189
|
+
return out;
|
|
1190
|
+
}
|
|
1191
|
+
if ((goal.name === ';' || goal.name === '->') && goal.arity === 2) {
|
|
1192
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1193
|
+
collectAutoloadGoalDependencies(goal.args[1], out);
|
|
1194
|
+
return out;
|
|
1195
|
+
}
|
|
1196
|
+
if ((goal.name === '\\+' || goal.name === 'not' || goal.name === 'tnot' || goal.name === 'once') && goal.arity === 1) {
|
|
1197
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1198
|
+
return out;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
out.push({ key: `${goal.name}/${goal.arity}`, name: goal.name, arity: goal.arity, module: goal.module });
|
|
1202
|
+
|
|
1203
|
+
if (goal.name === 'forall' && goal.arity === 2) {
|
|
1204
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1205
|
+
collectAutoloadGoalDependencies(goal.args[1], out);
|
|
1206
|
+
} else if ((goal.name === 'findall' || goal.name === 'bagof' || goal.name === 'setof' || goal.name === 'sumall') && goal.arity === 3) {
|
|
1207
|
+
collectAutoloadGoalDependencies(goal.args[1], out);
|
|
1208
|
+
} else if (goal.name === 'countall' && goal.arity === 2) {
|
|
1209
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1210
|
+
} else if ((goal.name === 'aggregate_min' || goal.name === 'aggregate_max') && goal.arity === 5) {
|
|
1211
|
+
collectAutoloadGoalDependencies(goal.args[2], out);
|
|
1212
|
+
} else if (goal.name === 'catch' && goal.arity === 3) {
|
|
1213
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1214
|
+
collectAutoloadGoalDependencies(goal.args[2], out);
|
|
1215
|
+
} else if ((goal.name === 'call_cleanup' || goal.name === 'setup_call_cleanup') && (goal.arity === 2 || goal.arity === 3)) {
|
|
1216
|
+
for (const arg of goal.args) collectAutoloadGoalDependencies(arg, out);
|
|
1217
|
+
} else if (goal.name === 'call' && goal.arity === 1) {
|
|
1218
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1219
|
+
}
|
|
1220
|
+
return out;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function forwardRuleAutoloadDependencies(term, out = []) {
|
|
1224
|
+
if (term?.type !== COMPOUND) return out;
|
|
1225
|
+
if (term.name === ':+' && term.arity === 2) {
|
|
1226
|
+
collectAutoloadGoalDependencies(term.args[1], out);
|
|
1227
|
+
forwardRuleAutoloadDependencies(term.args[0], out);
|
|
1228
|
+
return out;
|
|
1229
|
+
}
|
|
1230
|
+
if (term.name === ',' && term.arity === 2) {
|
|
1231
|
+
forwardRuleAutoloadDependencies(term.args[0], out);
|
|
1232
|
+
forwardRuleAutoloadDependencies(term.args[1], out);
|
|
1233
|
+
}
|
|
1234
|
+
return out;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function groupAutoloadDependencies(group) {
|
|
1238
|
+
const dependencies = [];
|
|
1239
|
+
for (const clause of group.clauses) {
|
|
1240
|
+
if (group.name === ':+') dependencies.push(...forwardRuleAutoloadDependencies(clause.head));
|
|
1241
|
+
if (isCompactBinaryClause(clause)) {
|
|
1242
|
+
if (clause.bodyName != null) {
|
|
1243
|
+
dependencies.push({
|
|
1244
|
+
key: `${clause.bodyName}/2`,
|
|
1245
|
+
name: clause.bodyName,
|
|
1246
|
+
arity: 2,
|
|
1247
|
+
module: clause.module ?? group.module,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
for (const goal of clause.body) collectAutoloadGoalDependencies(goal, dependencies);
|
|
1253
|
+
}
|
|
1254
|
+
return dependencies;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1152
1257
|
function parseInteropGoalInputs(inputs, options, program) {
|
|
1153
1258
|
if (inputs == null) return [];
|
|
1154
1259
|
const values = Array.isArray(inputs) ? inputs : [inputs];
|
|
@@ -1164,7 +1269,7 @@ function parseInteropGoalInputs(inputs, options, program) {
|
|
|
1164
1269
|
|
|
1165
1270
|
function extraGoalDependencies(goals) {
|
|
1166
1271
|
const dependencies = [];
|
|
1167
|
-
for (const goal of goals)
|
|
1272
|
+
for (const goal of goals) collectAutoloadGoalDependencies(goal, dependencies);
|
|
1168
1273
|
return dependencies;
|
|
1169
1274
|
}
|
|
1170
1275
|
|
|
@@ -1190,7 +1295,7 @@ function libraryAutoloadRequests(program, extraGoals = []) {
|
|
|
1190
1295
|
const requests = new Map();
|
|
1191
1296
|
for (const group of program.groups.values()) {
|
|
1192
1297
|
if (bundledLibraryModule(program, group.module)) continue;
|
|
1193
|
-
for (const dependency of
|
|
1298
|
+
for (const dependency of groupAutoloadDependencies(group)) {
|
|
1194
1299
|
const targetModule = dependency.module ?? group.module;
|
|
1195
1300
|
if (procedureResolvedBeforeAutoload(program, dependency, targetModule)) continue;
|
|
1196
1301
|
const library = autoloadLibraryFor(dependency);
|
|
@@ -1206,7 +1311,7 @@ function libraryAutoloadRequests(program, extraGoals = []) {
|
|
|
1206
1311
|
}
|
|
1207
1312
|
}
|
|
1208
1313
|
for (const goal of program.initializations) {
|
|
1209
|
-
for (const dependency of
|
|
1314
|
+
for (const dependency of collectAutoloadGoalDependencies(goal)) {
|
|
1210
1315
|
const targetModule = dependency.module ?? 'user';
|
|
1211
1316
|
if (procedureResolvedBeforeAutoload(program, dependency, targetModule)) continue;
|
|
1212
1317
|
const library = autoloadLibraryFor(dependency);
|
package/src/standard-library.js
CHANGED
|
@@ -28,6 +28,7 @@ const moduleFiles = Object.freeze({
|
|
|
28
28
|
debug: 'debug.pl',
|
|
29
29
|
dif: 'dif.pl',
|
|
30
30
|
error: 'error.pl',
|
|
31
|
+
eyelet: 'eyelet.pl',
|
|
31
32
|
format: 'format.pl',
|
|
32
33
|
freeze: 'freeze.pl',
|
|
33
34
|
gensym: 'gensym.pl',
|
|
@@ -102,6 +103,7 @@ export const eyePrologPortableLibraryIndicators = Object.freeze([
|
|
|
102
103
|
'sat/1', 'taut/2', 'labeling/1', 'sat_count/2', 'random_labeling/2', 'weighted_maximum/3',
|
|
103
104
|
'lt/2', 'gt/2', 'le/2', 'ge/2',
|
|
104
105
|
'difference/3',
|
|
106
|
+
'stable/1', 'becomes/2',
|
|
105
107
|
'seq/3', 'seqq/3',
|
|
106
108
|
'debug/1', 'debug/3', 'nodebug/1', 'bb_get/2', 'bb_put/2', 'bb_b_put/2', 'bb_global_get/2',
|
|
107
109
|
'*/1', '$/1', '$-/1',
|
|
@@ -121,7 +121,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
121
121
|
| 5.5.6 Side effects | Normal mode adds `statistics/0-2`, cleanup/library state, and optional proof/statistics host instrumentation. | The strict registry excludes Prolog-visible statistics/cleanup/library adapters; ordinary Part 1 I/O/database/flag/operator side effects remain. Host proof/statistics collection observes execution through the embedding API rather than adding a strict Prolog goal effect. **covered** — strict registry/execution regression. |
|
|
122
122
|
| 5.5.7 Control constructs | `tnot/1` and normal-profile execution optimizations | `tnot/1` and the normal-profile explicit `table` declaration are absent. |
|
|
123
123
|
| 5.5.8 Flags | `occurs_check` | Absent in strict mode. |
|
|
124
|
-
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and
|
|
124
|
+
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and bundled-library autoloaded predicates | Strict registry contains only the Part 1 + Corrigenda core registry. |
|
|
125
125
|
| 5.5.10 Evaluable functors | Normal mode additionally accepts the EyeProlog evaluable atom `e`; the remaining arithmetic functors accepted by strict mode are the Part 1 + Corrigenda set. | Strict mode rejects `e/0` as non-evaluable and retains the Corrigendum arithmetic additions. **covered** — strict extension-boundary regression plus `src/iso-arithmetic.js`. |
|
|
126
126
|
| 5.5.11 Reserved atoms | None | None. |
|
|
127
127
|
| Cor.3 5.5.12 Options | Extra library/host options may exist outside core option lists | Normal mode additionally accepts the EyeProlog `write_term/2-3` options `double_quotes(true|false)` and `spacing(true|false)`. `spacing(false)` emits only lexically required separators; `spacing(true)` adds conventional layout around operators. Strict core excludes these implementation-specific extensions and accepts only the Part 1 plus Corrigendum 3 write-option surface; unknown extension options raise `domain_error(write_option,...)`. |
|
package/test/run-regression.mjs
CHANGED
|
@@ -4784,6 +4784,35 @@ function documentationSyncCases() {
|
|
|
4784
4784
|
assertEqual(result.stderr, '', 'stderr');
|
|
4785
4785
|
},
|
|
4786
4786
|
},
|
|
4787
|
+
{
|
|
4788
|
+
name: 'reference docs match explicit tabling and current runtime extensions',
|
|
4789
|
+
run: () => {
|
|
4790
|
+
const readme = fs.readFileSync(path.join(packageRoot, 'README.md'), 'utf8');
|
|
4791
|
+
const book = fs.readFileSync(path.join(packageRoot, 'the-art-of-eyeprolog.md'), 'utf8');
|
|
4792
|
+
const profile = fs.readFileSync(path.join(packageRoot, 'why-eyeprolog.md'), 'utf8');
|
|
4793
|
+
const combined = `${readme}
|
|
4794
|
+
${book}
|
|
4795
|
+
${profile}`;
|
|
4796
|
+
for (const stale of [
|
|
4797
|
+
'Automatic tabling',
|
|
4798
|
+
'Automatic hybrid reasoning',
|
|
4799
|
+
'eligible positive recursive groups are tabled automatically',
|
|
4800
|
+
'--no-auto-table',
|
|
4801
|
+
'conservative interop autoloading',
|
|
4802
|
+
]) assertNotIncludes(combined, stale, `stale documentation phrase ${stale}`);
|
|
4803
|
+
assertIncludes(book, 'including recursive calls, use depth-first resolution unless the source', 'ordinary recursion is depth-first');
|
|
4804
|
+
assertIncludes(book, 'explicitly declares `:- table p/n.`', 'tabling is explicit');
|
|
4805
|
+
assertIncludes(book, 'The autoload index covers every', 'generic bundled-library autoload');
|
|
4806
|
+
assertIncludes(book, 'interactive top-level query therefore autoloads its canonical', 'REPL autoload');
|
|
4807
|
+
assertIncludes(book, 'Autoloading therefore supplies', 'autoload syntax boundary');
|
|
4808
|
+
assertIncludes(book, 'Residual constraints are part of the displayed answer even when', 'top-level hidden residuals');
|
|
4809
|
+
assertIncludes(book, 'EyeProlog normal mode also accepts `:+`', 'native forward-rule extension');
|
|
4810
|
+
assertIncludes(book, '`library(eyelet)`', 'Eyelet library surface');
|
|
4811
|
+
assertIncludes(readme, '`library(eyelet)`', 'README Eyelet library surface');
|
|
4812
|
+
assertIncludes(profile, 'an unresolved unqualified predicate may autoload its unique provider', 'Why EyeProlog autoload policy');
|
|
4813
|
+
assertIncludes(readme, 'Bundled `src/lib/` predicates autoload in files, CLI/API goals,', 'README autoload summary');
|
|
4814
|
+
},
|
|
4815
|
+
},
|
|
4787
4816
|
{
|
|
4788
4817
|
name: 'WG17 syntax status matches its executable-coverage manifest',
|
|
4789
4818
|
run: () => {
|
|
@@ -5442,6 +5471,20 @@ function apiCases() {
|
|
|
5442
5471
|
assertEqual(result.haltCode, null, 'forward halt code');
|
|
5443
5472
|
},
|
|
5444
5473
|
},
|
|
5474
|
+
{
|
|
5475
|
+
name: 'native forward rules autoload library(eyelet) helpers from :+ premises',
|
|
5476
|
+
run: () => {
|
|
5477
|
+
const result = run(`
|
|
5478
|
+
:- dynamic(state/1).
|
|
5479
|
+
state(a).
|
|
5480
|
+
changed :+ becomes(state(a), state(b)).
|
|
5481
|
+
seed :+ state(b).
|
|
5482
|
+
ready :+ seed, stable(1).
|
|
5483
|
+
true :+ ready.
|
|
5484
|
+
`);
|
|
5485
|
+
assertEqual(result.stdout, 'ready.\n', 'forward helper stdout');
|
|
5486
|
+
},
|
|
5487
|
+
},
|
|
5445
5488
|
{
|
|
5446
5489
|
name: 'run exposes false/0 as an always-failing built-in',
|
|
5447
5490
|
run: () => {
|
|
@@ -6260,7 +6303,7 @@ answer(ok) :-
|
|
|
6260
6303
|
assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
|
|
6261
6304
|
assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
|
|
6262
6305
|
assertEqual(registeredNativeEyePrologLibraryNames().length, 58, 'public host-supported EyeProlog library count');
|
|
6263
|
-
assertEqual(eyePrologPortableLibraryIndicators.length,
|
|
6306
|
+
assertEqual(eyePrologPortableLibraryIndicators.length, 243, 'portable Prolog library count');
|
|
6264
6307
|
assertEqual(eyePrologInteropLibraryIndicators.length, 189, 'cross-implementation interop profile count');
|
|
6265
6308
|
assertEqual(eyePrologInteropLibraryModules.length, 23, 'common explicit library module profile count');
|
|
6266
6309
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
@@ -6272,11 +6315,13 @@ answer(ok) :-
|
|
|
6272
6315
|
assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'set_nth0/4 is outside the conservative interop subset');
|
|
6273
6316
|
assertEqual(eyePrologLibraryAutoload['set_nth0/4'], 'lists', 'complete library autoload includes EyeProlog-only exports');
|
|
6274
6317
|
assertEqual(eyePrologLibraryAutoload['pairs_keys_values/3'], 'pairs', 'complete library autoload includes library(pairs)');
|
|
6275
|
-
assertEqual(
|
|
6318
|
+
assertEqual(eyePrologLibraryAutoload['stable/1'], 'eyelet', 'complete library autoload includes Eyelet stable/1');
|
|
6319
|
+
assertEqual(eyePrologLibraryAutoload['becomes/2'], 'eyelet', 'complete library autoload includes Eyelet becomes/2');
|
|
6320
|
+
assertEqual(eyePrologLibraryAutoloadModules.length, 36, 'all bundled src/lib modules are indexed for autoload');
|
|
6276
6321
|
assertEqual(eyePrologNativeLibraryIndicators.length, 58, 'host-supported library count');
|
|
6277
6322
|
assertEqual(eyePrologNativeLibraryIndicators.slice(0, 3).join(','), 'call_nth/2,freeze/2,dif/2', 'control and constraint predicates requiring host support');
|
|
6278
6323
|
assertEqual(eyePrologNativeLibraryIndicators.includes('random/1'), true, 'stateful random/1 is classified as host-supported');
|
|
6279
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
6324
|
+
assertEqual(eyePrologLibraryIndicators.length, 301, 'complete non-ISO EyeProlog library surface');
|
|
6280
6325
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
6281
6326
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
6282
6327
|
assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -30,7 +30,7 @@ program with EyeProlog means learning to move comfortably between these views.
|
|
|
30
30
|
|
|
31
31
|
EyeProlog implements a broad ISO Prolog profile with facts, clauses, terms, lists,
|
|
32
32
|
control, arithmetic, dynamic predicates, operators, streams, and standard
|
|
33
|
-
built-ins.
|
|
33
|
+
built-ins. Explicit
|
|
34
34
|
tabling, explicit integrity checks, and proof output are implementation
|
|
35
35
|
capabilities around that standards-based foundation. EyeProlog does not attempt to
|
|
36
36
|
claim formal certification of every ISO processor edge case.
|
|
@@ -175,7 +175,8 @@ Approach each example through the same six moves:
|
|
|
175
175
|
This rhythm deliberately joins declarative reading, operational reading, and
|
|
176
176
|
program construction. Readers new to logic programming can follow Parts I–III
|
|
177
177
|
in order. Experienced Prolog programmers can begin with Chapters 3, 13, and
|
|
178
|
-
17 to see
|
|
178
|
+
17 to see how EyeProlog combines ordinary depth-first Prolog with explicit
|
|
179
|
+
tabling, native forward rules, and inspectable proofs.
|
|
179
180
|
Chapter 41 gives further routes through the material.
|
|
180
181
|
|
|
181
182
|
### When a run surprises you
|
|
@@ -1680,16 +1681,16 @@ is a forward rule. When a loaded program contains such rules and no explicit
|
|
|
1680
1681
|
top-level goal overrides them, the engine runs a native closure driver rather
|
|
1681
1682
|
than a Prolog meta-interpreter. It repeatedly solves premises against the
|
|
1682
1683
|
current program, asserts novel conjuncts from successful conclusions, and
|
|
1683
|
-
continues until no new conclusion is added. This is the execution path used
|
|
1684
|
-
|
|
1684
|
+
continues until no new conclusion is added. This is the execution path used when
|
|
1685
|
+
Eyelet sources are run directly by EyeProlog.
|
|
1685
1686
|
|
|
1686
1687
|
Two conclusions have control meaning. `true :+ Goal` is a query and prints each
|
|
1687
1688
|
distinct successful instance of `Goal`. `false :+ Goal` is an integrity fuse:
|
|
1688
1689
|
on success EyeProlog prints `fuse(Goal)` and returns halt status 2. Variables
|
|
1689
1690
|
that occur only in an ordinary derived conclusion are existential and become
|
|
1690
1691
|
`sk_0`, `sk_1`, and so on; a derived conclusion that is itself a `:+` rule keeps
|
|
1691
|
-
its variables universal.
|
|
1692
|
-
|
|
1692
|
+
its variables universal. The bundled `library(eyelet)` exports the `:+` operator together with
|
|
1693
|
+
`stable/1` and `becomes/2`, while closure selection and assertion are native.
|
|
1693
1694
|
|
|
1694
1695
|
The JavaScript convenience `run()` function selects this forward mode when no
|
|
1695
1696
|
explicit `goal` or `goals` option is supplied. Advanced embedders can inspect a
|
|
@@ -5685,10 +5686,11 @@ so `edge/2` and `edge/3` are different predicates.
|
|
|
5685
5686
|
|
|
5686
5687
|
Execution is goal-directed rather than complete bottom-up saturation. Goals in
|
|
5687
5688
|
a body normally run from left to right; the solver may select a ready
|
|
5688
|
-
deterministic built-in early as a pure filter. Ordinary user-defined calls
|
|
5689
|
-
depth-first resolution
|
|
5690
|
-
|
|
5691
|
-
negation
|
|
5689
|
+
deterministic built-in early as a pure filter. Ordinary user-defined calls,
|
|
5690
|
+
including recursive calls, use depth-first resolution unless the source
|
|
5691
|
+
explicitly declares `:- table p/n.`. `\+/1` is negation as failure, not
|
|
5692
|
+
classical negation; the separate `tnot/1` extension provides well-founded
|
|
5693
|
+
semantics for eligible finite Datalog components.
|
|
5692
5694
|
|
|
5693
5695
|
EyeProlog supports cut, operator declarations, dynamic database updates, grouped
|
|
5694
5696
|
solutions, exceptions, flags, initialization and inclusion directives, and
|
|
@@ -5840,21 +5842,27 @@ time. Source facts are not echoed as new conclusions, and duplicate answers
|
|
|
5840
5842
|
are suppressed. Answers are not asserted back into the running program.
|
|
5841
5843
|
Supported output syntax is designed to be readable as Prolog input accepted by EyeProlog.
|
|
5842
5844
|
|
|
5843
|
-
####
|
|
5845
|
+
#### Explicit tabling and recursion planning
|
|
5844
5846
|
|
|
5845
|
-
The program loader
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
because positive least-fixed-point tabling does not define unstratified
|
|
5851
|
-
negation. Nonrecursive groups use indexed, depth-first resolution.
|
|
5847
|
+
The program loader analyzes predicate dependencies and recursion so the solver
|
|
5848
|
+
can choose semantics-preserving indexes and fast paths. That analysis does
|
|
5849
|
+
**not** decide whether a predicate is tabled. Ordinary predicates—including
|
|
5850
|
+
recursive ones—use indexed, depth-first Prolog resolution unless their source
|
|
5851
|
+
explicitly declares `:- table p/n.`.
|
|
5852
5852
|
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5853
|
+
An explicitly tabled positive recursive predicate is coordinated through an
|
|
5854
|
+
answer table: recurring calls consume answers already found, new answers are
|
|
5855
|
+
recorded, and evaluation continues toward a fixed point. For eligible large,
|
|
5856
|
+
finite, function-free Datalog components, EyeProlog may represent that declared
|
|
5857
|
+
table as one shared most-general relation or an indexed least model. For other
|
|
5858
|
+
declared tables the engine may infer structurally bound input positions to
|
|
5859
|
+
improve table reuse. These are implementation choices inside an explicit table
|
|
5860
|
+
declaration; they never opt an undeclared predicate into tabling.
|
|
5861
|
+
|
|
5862
|
+
Ordinary `\+/1` remains ISO-style negation as failure. The separate `tnot/1`
|
|
5863
|
+
extension explicitly requests well-founded evaluation for eligible finite,
|
|
5864
|
+
range-restricted Datalog dependencies. Strict ISO mode exposes neither `table`
|
|
5865
|
+
nor `tnot/1`.
|
|
5858
5866
|
|
|
5859
5867
|
#### Query execution
|
|
5860
5868
|
|
|
@@ -5872,8 +5880,8 @@ answers. The host:
|
|
|
5872
5880
|
7. prints each answer and, only when requested, its `why/2` explanation.
|
|
5873
5881
|
|
|
5874
5882
|
Goal selection affects host execution rather than the program's logical meaning.
|
|
5875
|
-
One goal's answers are not asserted for later goals, although
|
|
5876
|
-
tables may be reused during the solver run. For stable output, queries for
|
|
5883
|
+
One goal's answers are not asserted for later goals, although explicitly
|
|
5884
|
+
declared tables may be reused during the solver run. For stable output, queries for
|
|
5877
5885
|
known predicates are grouped by the source order in which their predicate
|
|
5878
5886
|
groups first appear; goals within one group retain their supplied order.
|
|
5879
5887
|
Queries for predicates with no group follow the known groups.
|
|
@@ -6270,12 +6278,12 @@ so side effects occur in Prolog execution order.
|
|
|
6270
6278
|
|
|
6271
6279
|
### The EyeProlog library
|
|
6272
6280
|
|
|
6273
|
-
EyeProlog exposes **
|
|
6281
|
+
EyeProlog exposes **301 distinct non-ISO library and normal-extension predicate
|
|
6274
6282
|
indicators** in addition to the 129 indicators in its isolated ISO profile.
|
|
6275
|
-
**
|
|
6283
|
+
**243 are defined entirely as ordinary Prolog clauses** in focused modules under
|
|
6276
6284
|
`src/lib/`; **58 use host support** for control, attributed variables,
|
|
6277
6285
|
constraints, or observability. The ISO and library catalogs therefore cover
|
|
6278
|
-
**
|
|
6286
|
+
**430 distinct predicate indicators**. Normal-mode controls and observability
|
|
6279
6287
|
relations that do not belong to a library, such as `call_cleanup/2`,
|
|
6280
6288
|
`setup_call_cleanup/3`, `statistics/0,2`, and `tnot/1`, are documented in their
|
|
6281
6289
|
own sections rather than counted as library predicates. `time/1` is additionally
|
|
@@ -6288,7 +6296,7 @@ The sources are `src/lib/aggregate.pl`, `src/lib/arithmetic.pl`,
|
|
|
6288
6296
|
`src/lib/charsio.pl`, `src/lib/clpb.pl`, `src/lib/clpz.pl`,
|
|
6289
6297
|
`src/lib/comparison.pl`, `src/lib/dates.pl`, `src/lib/dcgs.pl`,
|
|
6290
6298
|
`src/lib/debug.pl`, `src/lib/dif.pl`, `src/lib/error.pl`,
|
|
6291
|
-
`src/lib/format.pl`, `src/lib/freeze.pl`, `src/lib/gensym.pl`,
|
|
6299
|
+
`src/lib/eyelet.pl`, `src/lib/format.pl`, `src/lib/freeze.pl`, `src/lib/gensym.pl`,
|
|
6292
6300
|
`src/lib/iso_ext.pl`, `src/lib/lambda.pl`, `src/lib/lists.pl`,
|
|
6293
6301
|
`src/lib/ordsets.pl`, `src/lib/pairs.pl`, `src/lib/pio.pl`,
|
|
6294
6302
|
`src/lib/primes.pl`, `src/lib/prologue.pl`, `src/lib/random.pl`,
|
|
@@ -6360,6 +6368,7 @@ bindings make one aligned subterm pair sufficient. For example:
|
|
|
6360
6368
|
| `library(debug)` | `*/1`, `$/1`, `$-/1`, `debug/1`, `debug/3`, `nodebug/1`, `bb_get/2`, `bb_put/2`, `bb_b_put/2`, `bb_global_get/2` | Declarative debug operators and constraint-library blackboards |
|
|
6361
6369
|
| `library(dif)` | `dif/2` | Common module facade over native delayed disequality |
|
|
6362
6370
|
| `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 |
|
|
6371
|
+
| `library(eyelet)` | `stable/1`, `becomes/2` | Eyelet forward-reasoning state helpers; the `:+` operator is exported by the module and executed natively by EyeProlog |
|
|
6363
6372
|
| `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 |
|
|
6364
6373
|
| `library(freeze)` | `freeze/2` | Delayed goals |
|
|
6365
6374
|
| `library(gensym)` | `gensym/2`, `reset_gensym/1` | Process-local generated atoms |
|
|
@@ -7475,7 +7484,7 @@ recursion as one technique.
|
|
|
7475
7484
|
| [Deep taxonomy: 10](https://github.com/eyereasoner/eyeprolog/blob/main/examples/deep-taxonomy-10.pl) | A small generated hierarchy is readable by hand and establishes the benchmark shape. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/deep-taxonomy-10.pl) |
|
|
7476
7485
|
| [Deep Taxonomy 100](https://github.com/eyereasoner/eyeprolog/blob/main/examples/deep-taxonomy-100.pl) | A 100-step taxonomy chain that exercises deep recursive closure and side-label derivation. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/deep-taxonomy-100.pl) |
|
|
7477
7486
|
| [Deep taxonomy: 1,000](https://github.com/eyereasoner/eyeprolog/blob/main/examples/deep-taxonomy-1000.pl) | The same logical theory tests indexing and recursive closure at a realistic depth. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/deep-taxonomy-1000.pl) |
|
|
7478
|
-
| [Deep Taxonomy 10000](https://github.com/eyereasoner/eyeprolog/blob/main/examples/deep-taxonomy-10000.pl) | A 10,000-step taxonomy chain used as a large-depth
|
|
7487
|
+
| [Deep Taxonomy 10000](https://github.com/eyereasoner/eyeprolog/blob/main/examples/deep-taxonomy-10000.pl) | A 10,000-step taxonomy chain used as a large-depth ordinary-recursion and closure stress test. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/deep-taxonomy-10000.pl) |
|
|
7479
7488
|
| [Deep taxonomy: 100,000](https://github.com/eyereasoner/eyeprolog/blob/main/examples/deep-taxonomy-100000.pl) | A stress case separates semantic simplicity from implementation scale. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/deep-taxonomy-100000.pl) |
|
|
7480
7489
|
| [Family cousins](https://github.com/eyereasoner/eyeprolog/blob/main/examples/family-cousins.pl) | Several relational joins derive kinship beyond a simple transitive closure. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/family-cousins.pl) |
|
|
7481
7490
|
| [Graph reachability](https://github.com/eyereasoner/eyeprolog/blob/main/examples/graph-reachability.pl) | A visited list bounds cyclic traversal and makes explicit negative test cases finite. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/graph-reachability.pl) · [proof](https://github.com/eyereasoner/eyeprolog/blob/main/examples/proof/graph-reachability.pl) |
|
package/why-eyeprolog.md
CHANGED
|
@@ -143,12 +143,21 @@ This avoids a heuristic silently changing answer multiplicity, termination, or
|
|
|
143
143
|
resource behavior for an otherwise ordinary recursive predicate.
|
|
144
144
|
|
|
145
145
|
Forward chaining is explicit too. Normal mode recognizes `Conclusion :+ Premise`
|
|
146
|
-
and executes those rules with a native fixed-point driver
|
|
147
|
-
`
|
|
146
|
+
and executes those rules with a native fixed-point driver. The bundled
|
|
147
|
+
`library(eyelet)` exports the `:+` operator, `stable/1`, and `becomes/2`, including
|
|
148
|
+
Eyelet's `true :+ Goal` query and `false :+ Goal` fuse conventions. Strict ISO mode
|
|
148
149
|
removes the operator. Because resource exhaustion is not logical failure, normal
|
|
149
150
|
execution has no hidden depth cutoff; an explicitly requested depth limit raises
|
|
150
151
|
`resource_error(depth_limit)`.
|
|
151
152
|
|
|
153
|
+
Library convenience is explicit at the profile boundary too. Outside strict ISO
|
|
154
|
+
mode, an unresolved unqualified predicate may autoload its unique provider from
|
|
155
|
+
the bundled `src/lib/` modules after program definitions, standard built-ins,
|
|
156
|
+
and explicit imports have had precedence. `--no-autoload` disables this
|
|
157
|
+
convenience, and strict ISO mode disables it unconditionally. Autoloading happens
|
|
158
|
+
after parsing, so libraries that introduce operators still require an explicit
|
|
159
|
+
`use_module/1-2` before that syntax is read.
|
|
160
|
+
|
|
152
161
|
Definite clause grammars follow the ISO Part 3 difference-list model. Internal
|
|
153
162
|
fast paths make deep finite sequence processing economical while preserving
|
|
154
163
|
relational modes. The checked
|