eyeprolog 1.5.6 → 1.5.7
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 +1 -1
- package/package.json +1 -1
- package/src/parser.js +4 -12
- package/src/program.js +19 -131
- package/the-art-of-eyeprolog.md +7 -6
- package/why-eyeprolog.md +3 -2
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
## Eyelet forward rules (`:+`)
|
|
60
|
-
Normal mode executes top-level `Conclusion :+ Premise` rules through the bundled Prolog `library(eyelet)` driver when no explicit `-g/--goal` is supplied. `true :+ Goal` prints answers and `false :+ Goal` emits a `fuse/1`; JavaScript `run()` follows the same rule. The module exports `:+`, `stable/1`, and `becomes/2`, and implements rule selection, fixed-point rounds, skolemization, state replacement, and duplicate suppression in Prolog. JavaScript only
|
|
60
|
+
Normal mode executes top-level `Conclusion :+ Premise` rules through the bundled Prolog `library(eyelet)` driver when no explicit `-g/--goal` is supplied. `true :+ Goal` prints answers and `false :+ Goal` emits a `fuse/1`; JavaScript `run()` follows the same rule. The module exports `:+`, `stable/1`, and `becomes/2`, and implements rule selection, fixed-point rounds, skolemization, state replacement, and duplicate suppression in Prolog. JavaScript retains only syntax recognition, static dependency/autoload planning, driver bootstrap, and private mutability/output adapters; it does not implement the fixed-point semantics. Strict ISO mode disables this extension.
|
|
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
|
|
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -92,10 +92,7 @@ const graphicAtomChars = '#$&*+-./<=>?@^~\\:';
|
|
|
92
92
|
// canonical notation. Commas remain separators except inside parentheses.
|
|
93
93
|
const INFIX_OPERATORS = new Map([
|
|
94
94
|
[':-', { precedence: 1, associativity: 'none' }],
|
|
95
|
-
[':+', { precedence: 1, associativity: 'none' }], // EyeProlog forward-rule extension
|
|
96
|
-
['?-', { precedence: 1, associativity: 'none' }], // quad label extension
|
|
97
95
|
['-->', { precedence: 1, associativity: 'none' }],
|
|
98
|
-
['|', { precedence: 96, associativity: 'right' }],
|
|
99
96
|
[';', { precedence: 101, associativity: 'right' }],
|
|
100
97
|
['->', { precedence: 151, associativity: 'right' }],
|
|
101
98
|
[',', { precedence: 201, associativity: 'right' }],
|
|
@@ -133,7 +130,6 @@ const INFIX_OPERATORS = new Map([
|
|
|
133
130
|
]);
|
|
134
131
|
const PREFIX_OPERATORS = new Map([
|
|
135
132
|
['?-', { precedence: 1, strict: true }],
|
|
136
|
-
['table', { precedence: 51, strict: true }],
|
|
137
133
|
['\\+', { precedence: 301, strict: false }],
|
|
138
134
|
['+', { precedence: 1001, strict: false }],
|
|
139
135
|
['-', { precedence: 1001, strict: false }],
|
|
@@ -229,14 +225,10 @@ export function createParserOperatorState(definitions = [], includeDefaults = tr
|
|
|
229
225
|
prefixOperators: includeDefaults ? new Map(PREFIX_OPERATORS) : new Map(),
|
|
230
226
|
postfixOperators: new Map(),
|
|
231
227
|
};
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
state.infixOperators.delete('?-');
|
|
237
|
-
state.infixOperators.delete('|');
|
|
238
|
-
state.infixOperators.delete(':+');
|
|
239
|
-
state.prefixOperators.delete('table');
|
|
228
|
+
if (includeDefaults && options.isoStrict !== true) {
|
|
229
|
+
for (const [priority, specifier, name] of [...PART3_OPERATOR_DEFINITIONS, ...QUAD_OPERATOR_DEFINITIONS]) {
|
|
230
|
+
defineParserOperator(state, priority, specifier, name);
|
|
231
|
+
}
|
|
240
232
|
}
|
|
241
233
|
for (const definition of definitions) {
|
|
242
234
|
const [priority, specifier, name] = Array.isArray(definition)
|
package/src/program.js
CHANGED
|
@@ -65,22 +65,12 @@ function collectMultiplicitySensitiveDependencies(goal, out = []) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
function scalarNodeKey(term) {
|
|
69
|
-
return isScalar(term) ? `${term.type}:${term.name}` : null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Some source-level self recursion is structurally finite even though the
|
|
73
|
-
// predicate dependency graph contains a self-edge. The deep-taxonomy family
|
|
74
|
-
// is the canonical case: p(X,n10000) calls p(X,n9999), eventually reaching a
|
|
75
|
-
// fact. When one scalar argument forms an acyclic transition graph and every
|
|
76
|
-
// other argument is carried through unchanged, depth-first execution is both
|
|
77
|
-
// terminating and much cheaper than constructing the complete Datalog model.
|
|
78
68
|
// Cut-guided tail recursion is usually a deterministic search loop rather than
|
|
79
|
-
// a cyclic relation.
|
|
69
|
+
// a cyclic relation. Tabling such loops can retain every intermediate numeric
|
|
80
70
|
// state (for example prime scanning in Goldbach) and turn a bounded depth-first
|
|
81
|
-
// search into an enormous table.
|
|
71
|
+
// search into an enormous table. Keep this deliberately narrow: each recursive
|
|
82
72
|
// clause must contain exactly one direct self-call and that call must be the last
|
|
83
|
-
// body goal.
|
|
73
|
+
// body goal. Branching recursion such as Takeuchi therefore remains eligible
|
|
84
74
|
// for recursion planning and explicit tabling.
|
|
85
75
|
function hasDirectCutTailRecursion(group) {
|
|
86
76
|
if (group.hasCut !== true || group.clauses.length === 0) return false;
|
|
@@ -104,96 +94,6 @@ function hasDirectCutTailRecursion(group) {
|
|
|
104
94
|
return sawRecursive;
|
|
105
95
|
}
|
|
106
96
|
|
|
107
|
-
function hasAcyclicScalarSelfRecursion(group) {
|
|
108
|
-
if (group.arity === 0 || group.clauses.length === 0) return false;
|
|
109
|
-
for (let position = 0; position < group.arity; position++) {
|
|
110
|
-
const edges = new Map();
|
|
111
|
-
let sawRecursive = false;
|
|
112
|
-
let valid = true;
|
|
113
|
-
for (const clause of group.clauses) {
|
|
114
|
-
if (isCompactBinaryClause(clause)) {
|
|
115
|
-
if (group.arity !== 2) { valid = false; break; }
|
|
116
|
-
if (clause.bodyName == null) continue;
|
|
117
|
-
if (clause.bodyName !== group.name) { valid = false; break; }
|
|
118
|
-
const headType = position === 0 ? clause.head0Type : clause.head1Type;
|
|
119
|
-
const headName = position === 0 ? clause.head0Name : clause.head1Name;
|
|
120
|
-
const bodyType = position === 0 ? clause.body0Type : clause.body1Type;
|
|
121
|
-
const bodyName = position === 0 ? clause.body0Name : clause.body1Name;
|
|
122
|
-
if (!['atom', 'string', 'number'].includes(headType) ||
|
|
123
|
-
!['atom', 'string', 'number'].includes(bodyType)) { valid = false; break; }
|
|
124
|
-
const other = position === 0 ? 1 : 0;
|
|
125
|
-
const ohType = other === 0 ? clause.head0Type : clause.head1Type;
|
|
126
|
-
const ohName = other === 0 ? clause.head0Name : clause.head1Name;
|
|
127
|
-
const obType = other === 0 ? clause.body0Type : clause.body1Type;
|
|
128
|
-
const obName = other === 0 ? clause.body0Name : clause.body1Name;
|
|
129
|
-
const carried = (ohType === 'var' && obType === 'var' && ohName === obName) ||
|
|
130
|
-
(['atom', 'string', 'number'].includes(ohType) && ohType === obType && ohName === obName);
|
|
131
|
-
if (!carried) { valid = false; break; }
|
|
132
|
-
const from = `${headType}:${headName}`;
|
|
133
|
-
const to = `${bodyType}:${bodyName}`;
|
|
134
|
-
sawRecursive = true;
|
|
135
|
-
let targets = edges.get(from);
|
|
136
|
-
if (targets == null) edges.set(from, targets = new Set());
|
|
137
|
-
targets.add(to);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (clause.head?.type !== COMPOUND || clause.head.name !== group.name || clause.head.arity !== group.arity) {
|
|
142
|
-
valid = false; break;
|
|
143
|
-
}
|
|
144
|
-
if ((clause.body?.length ?? 0) === 0) continue;
|
|
145
|
-
if (clause.body.length !== 1) { valid = false; break; }
|
|
146
|
-
const body = clause.body[0];
|
|
147
|
-
if (body?.type !== COMPOUND || body.name !== group.name || body.arity !== group.arity ||
|
|
148
|
-
(body.module ?? group.module) !== group.module) { valid = false; break; }
|
|
149
|
-
const from = scalarNodeKey(clause.head.args[position]);
|
|
150
|
-
const to = scalarNodeKey(body.args[position]);
|
|
151
|
-
if (from == null || to == null) { valid = false; break; }
|
|
152
|
-
for (let i = 0; i < group.arity; i++) {
|
|
153
|
-
if (i === position) continue;
|
|
154
|
-
const h = clause.head.args[i];
|
|
155
|
-
const b = body.args[i];
|
|
156
|
-
if (h.type === VAR && b.type === VAR && h.name === b.name) continue;
|
|
157
|
-
const hk = scalarNodeKey(h);
|
|
158
|
-
const bk = scalarNodeKey(b);
|
|
159
|
-
if (hk != null && hk === bk) continue;
|
|
160
|
-
valid = false;
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
if (!valid) break;
|
|
164
|
-
sawRecursive = true;
|
|
165
|
-
let targets = edges.get(from);
|
|
166
|
-
if (targets == null) edges.set(from, targets = new Set());
|
|
167
|
-
targets.add(to);
|
|
168
|
-
}
|
|
169
|
-
if (!valid || !sawRecursive) continue;
|
|
170
|
-
|
|
171
|
-
// Use Kahn's algorithm rather than recursive DFS here. Programs generated
|
|
172
|
-
// from large taxonomies can contain hundreds of thousands of scalar
|
|
173
|
-
// transitions, and recursing once per edge would overflow the JavaScript
|
|
174
|
-
// call stack before Prolog execution even begins.
|
|
175
|
-
const indegree = new Map();
|
|
176
|
-
for (const [node, targets] of edges) {
|
|
177
|
-
if (!indegree.has(node)) indegree.set(node, 0);
|
|
178
|
-
for (const next of targets) indegree.set(next, (indegree.get(next) ?? 0) + 1);
|
|
179
|
-
}
|
|
180
|
-
const ready = [];
|
|
181
|
-
for (const [node, degree] of indegree) if (degree === 0) ready.push(node);
|
|
182
|
-
let consumed = 0;
|
|
183
|
-
for (let cursor = 0; cursor < ready.length; cursor++) {
|
|
184
|
-
const node = ready[cursor];
|
|
185
|
-
consumed++;
|
|
186
|
-
for (const next of edges.get(node) ?? []) {
|
|
187
|
-
const degree = indegree.get(next) - 1;
|
|
188
|
-
indegree.set(next, degree);
|
|
189
|
-
if (degree === 0) ready.push(next);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (consumed === indegree.size) return true;
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
97
|
function preparedBundledLibraryCacheKey(program, options) {
|
|
198
98
|
const filename = String(options.filename ?? '');
|
|
199
99
|
if (filename !== standardLibrarySources.get('clpz')?.filename) return null;
|
|
@@ -562,10 +462,8 @@ export class Program {
|
|
|
562
462
|
);
|
|
563
463
|
group.listTailRecursive = directRecursiveComponent && !group.cutRecursive &&
|
|
564
464
|
hasStrictListTailRecursion(group);
|
|
565
|
-
const acyclicScalarRecursion = plannedRecursive && hasAcyclicScalarSelfRecursion(group);
|
|
566
465
|
const cutTailRecursion = plannedRecursive && hasDirectCutTailRecursion(group);
|
|
567
466
|
const multiplicitySensitive = [...multiplicitySensitiveRoots].some((root) => reachableIndexes(root, deps).has(start));
|
|
568
|
-
group.acyclicScalarRecursion = acyclicScalarRecursion;
|
|
569
467
|
group.cutTailRecursion = cutTailRecursion;
|
|
570
468
|
group.multiplicitySensitive = multiplicitySensitive;
|
|
571
469
|
group.depthFirstRecursive = plannedRecursive && !explicitlyTabled;
|
|
@@ -1168,13 +1066,15 @@ function bundledLibraryModule(program, module) {
|
|
|
1168
1066
|
function groupDependencies(group) {
|
|
1169
1067
|
const dependencies = [];
|
|
1170
1068
|
for (const clause of group.clauses) {
|
|
1171
|
-
//
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1174
|
-
// autoloaded exactly like calls in ordinary rules. Derived :+ rules may
|
|
1175
|
-
// themselves occur in a conclusion, so recurse through that shape too.
|
|
1069
|
+
// Forward rules store executable premises inside their heads instead of in
|
|
1070
|
+
// ordinary clause bodies. Include those premises in static dependency
|
|
1071
|
+
// analysis so portability checks see the same calls that runtime will see.
|
|
1176
1072
|
if (group.name === ':+') {
|
|
1177
|
-
|
|
1073
|
+
collectForwardRulePremiseDependencies(
|
|
1074
|
+
clause.head,
|
|
1075
|
+
(goal, out) => out.push(...collectGoalDependencies(goal, false, true)),
|
|
1076
|
+
dependencies,
|
|
1077
|
+
);
|
|
1178
1078
|
}
|
|
1179
1079
|
if (isCompactBinaryClause(clause)) {
|
|
1180
1080
|
if (clause.bodyName != null) {
|
|
@@ -1194,16 +1094,16 @@ function groupDependencies(group) {
|
|
|
1194
1094
|
return dependencies;
|
|
1195
1095
|
}
|
|
1196
1096
|
|
|
1197
|
-
function
|
|
1097
|
+
function collectForwardRulePremiseDependencies(term, collect, out) {
|
|
1198
1098
|
if (term?.type !== COMPOUND) return out;
|
|
1199
1099
|
if (term.name === ':+' && term.arity === 2) {
|
|
1200
|
-
|
|
1201
|
-
|
|
1100
|
+
collect(term.args[1], out);
|
|
1101
|
+
collectForwardRulePremiseDependencies(term.args[0], collect, out);
|
|
1202
1102
|
return out;
|
|
1203
1103
|
}
|
|
1204
1104
|
if (term.name === ',' && term.arity === 2) {
|
|
1205
|
-
|
|
1206
|
-
|
|
1105
|
+
collectForwardRulePremiseDependencies(term.args[0], collect, out);
|
|
1106
|
+
collectForwardRulePremiseDependencies(term.args[1], collect, out);
|
|
1207
1107
|
}
|
|
1208
1108
|
return out;
|
|
1209
1109
|
}
|
|
@@ -1257,24 +1157,12 @@ function collectAutoloadGoalDependencies(goal, out = []) {
|
|
|
1257
1157
|
return out;
|
|
1258
1158
|
}
|
|
1259
1159
|
|
|
1260
|
-
function forwardRuleAutoloadDependencies(term, out = []) {
|
|
1261
|
-
if (term?.type !== COMPOUND) return out;
|
|
1262
|
-
if (term.name === ':+' && term.arity === 2) {
|
|
1263
|
-
collectAutoloadGoalDependencies(term.args[1], out);
|
|
1264
|
-
forwardRuleAutoloadDependencies(term.args[0], out);
|
|
1265
|
-
return out;
|
|
1266
|
-
}
|
|
1267
|
-
if (term.name === ',' && term.arity === 2) {
|
|
1268
|
-
forwardRuleAutoloadDependencies(term.args[0], out);
|
|
1269
|
-
forwardRuleAutoloadDependencies(term.args[1], out);
|
|
1270
|
-
}
|
|
1271
|
-
return out;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
1160
|
function groupAutoloadDependencies(group) {
|
|
1275
1161
|
const dependencies = [];
|
|
1276
1162
|
for (const clause of group.clauses) {
|
|
1277
|
-
if (group.name === ':+')
|
|
1163
|
+
if (group.name === ':+') {
|
|
1164
|
+
collectForwardRulePremiseDependencies(clause.head, collectAutoloadGoalDependencies, dependencies);
|
|
1165
|
+
}
|
|
1278
1166
|
if (isCompactBinaryClause(clause)) {
|
|
1279
1167
|
if (clause.bodyName != null) {
|
|
1280
1168
|
dependencies.push({
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1699,12 +1699,13 @@ EyeProlog source does not need a separate `dynamic/1` declaration merely to use
|
|
|
1699
1699
|
that state with `becomes/2`.
|
|
1700
1700
|
|
|
1701
1701
|
The `:+` reasoning algorithm is therefore Prolog code in `src/lib/eyelet.pl`, not
|
|
1702
|
-
a second JavaScript implementation. The
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
or the public `library(eyelet)`
|
|
1702
|
+
a second JavaScript implementation. The remaining JavaScript references have
|
|
1703
|
+
non-semantic roles: the parser declares the normal-profile operator, program
|
|
1704
|
+
analysis scans forward-rule premises for dependency/autoload planning, execution
|
|
1705
|
+
bootstraps the private `eyelet:eyelet_run/3` entry point, and two private library
|
|
1706
|
+
adapters bridge database mutability and answer/fuse events to the host. Those
|
|
1707
|
+
adapters are not part of the strict ISO registry or the public `library(eyelet)`
|
|
1708
|
+
export surface.
|
|
1708
1709
|
|
|
1709
1710
|
The JavaScript convenience `run()` function selects this forward mode when no
|
|
1710
1711
|
explicit `goal` or `goals` option is supplied. Advanced embedders can inspect a
|
package/why-eyeprolog.md
CHANGED
|
@@ -147,8 +147,9 @@ and executes those rules with the Prolog fixed-point driver in the bundled
|
|
|
147
147
|
`library(eyelet)`. That module owns the Eyelet semantics—rule selection,
|
|
148
148
|
fixed-point iteration, skolemization, `stable/1`, `becomes/2`, and the
|
|
149
149
|
`true :+ Goal` / `false :+ Goal` control conventions—rather than duplicating the
|
|
150
|
-
reasoning algorithm in JavaScript. The
|
|
151
|
-
|
|
150
|
+
reasoning algorithm in JavaScript. The remaining JavaScript support is limited to
|
|
151
|
+
syntax recognition, static dependency/autoload planning, driver bootstrap, and
|
|
152
|
+
private database-mutability/output-event adapters. Strict ISO mode removes the operator.
|
|
152
153
|
Because resource exhaustion is not logical failure, normal execution has no hidden
|
|
153
154
|
depth cutoff; an explicitly requested depth limit raises `resource_error(depth_limit)`.
|
|
154
155
|
|