eyeprolog 1.3.86 → 1.3.88
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/ARCHITECTURE.md +3 -1
- package/src/iso.js +14 -10
- package/src/term.js +19 -5
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/run-iso-strict.mjs +2 -2
- package/test/run-regression.mjs +52 -3
- package/the-art-of-eyeprolog.md +12 -6
package/package.json
CHANGED
package/src/ARCHITECTURE.md
CHANGED
|
@@ -36,7 +36,9 @@ Annotations are persistent across `Env.clone()` and therefore backtrack with the
|
|
|
36
36
|
substitution. Language services may attach immutable constraint descriptors,
|
|
37
37
|
but the unconstrained unification hot path only performs a null check; descriptor
|
|
38
38
|
validation and reindexing run only in environments that actually contain
|
|
39
|
-
annotations.
|
|
39
|
+
annotations. Descriptors can optionally define logical subsumption so the
|
|
40
|
+
store retains only its strongest pending constraints. `dif/2` is the first
|
|
41
|
+
descriptor-based user of this mechanism.
|
|
40
42
|
|
|
41
43
|
`atts.js` layers Prolog-visible attributed variables over that same persistent
|
|
42
44
|
environment. Per-module attributes follow the current variable representative.
|
package/src/iso.js
CHANGED
|
@@ -211,6 +211,19 @@ function difConstraint(left, right) {
|
|
|
211
211
|
skipAttributeHooks: true,
|
|
212
212
|
}) ? 'pending' : 'entailed';
|
|
213
213
|
},
|
|
214
|
+
subsumes(other, env) {
|
|
215
|
+
if (other?.kind !== 'dif') return false;
|
|
216
|
+
// dif(A,B) implies dif(C,D) exactly when C=D entails A=B. Apply the
|
|
217
|
+
// most general finite-tree unifier for the other's forbidden equality;
|
|
218
|
+
// if this equality makes our terms identical, every instance forbidden
|
|
219
|
+
// by the other constraint is already forbidden by this one.
|
|
220
|
+
const probe = env.clone();
|
|
221
|
+
if (!unify(other.left, other.right, probe, {
|
|
222
|
+
skipVariableConstraints: true,
|
|
223
|
+
skipAttributeHooks: true,
|
|
224
|
+
})) return true;
|
|
225
|
+
return identical(left, right, probe);
|
|
226
|
+
},
|
|
214
227
|
residualGoal() {
|
|
215
228
|
return compound('dif', [left, right]);
|
|
216
229
|
},
|
|
@@ -1756,15 +1769,6 @@ function writeOptionBoolean(value, env, option) {
|
|
|
1756
1769
|
return value.name === 'true';
|
|
1757
1770
|
}
|
|
1758
1771
|
|
|
1759
|
-
function writeOptionSpacing(value, env, option) {
|
|
1760
|
-
value = deref(value, env);
|
|
1761
|
-
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
1762
|
-
if (value.type !== ATOM || !['minimal', 'standard'].includes(value.name)) {
|
|
1763
|
-
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1764
|
-
}
|
|
1765
|
-
return value.name;
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
1772
|
function writeVariableNames(value, env, option) {
|
|
1769
1773
|
value = deref(value, env);
|
|
1770
1774
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
@@ -1806,7 +1810,7 @@ function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = n
|
|
|
1806
1810
|
else if (option.name === 'double_quotes' && !solver?.isoStrict) {
|
|
1807
1811
|
result.doubleQuotes = writeOptionBoolean(option.args[0], env, option);
|
|
1808
1812
|
} else if (option.name === 'spacing' && !solver?.isoStrict) {
|
|
1809
|
-
result.minimalOperatorSpacing =
|
|
1813
|
+
result.minimalOperatorSpacing = !writeOptionBoolean(option.args[0], env, option);
|
|
1810
1814
|
} else {
|
|
1811
1815
|
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1812
1816
|
}
|
package/src/term.js
CHANGED
|
@@ -465,9 +465,24 @@ export class Env {
|
|
|
465
465
|
typeof constraint.status !== 'function') {
|
|
466
466
|
throw new TypeError('variable constraint requires variables(env) and status(env)');
|
|
467
467
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
this._setNormalizedVariableConstraints([...(this._variableConstraints ?? []), constraint]);
|
|
469
|
+
}
|
|
470
|
+
_setNormalizedVariableConstraints(constraints) {
|
|
471
|
+
// Descriptors may define logical subsumption. Keep the strongest pending
|
|
472
|
+
// constraints so equivalent, symmetric, or weaker residual goals do not
|
|
473
|
+
// accumulate, while unrelated descriptor kinds remain untouched.
|
|
474
|
+
const normalized = [];
|
|
475
|
+
candidateLoop:
|
|
476
|
+
for (const candidate of constraints) {
|
|
477
|
+
for (const existing of normalized) {
|
|
478
|
+
if (existing === candidate || existing.subsumes?.(candidate, this) === true) continue candidateLoop;
|
|
479
|
+
}
|
|
480
|
+
for (let index = normalized.length - 1; index >= 0; index--) {
|
|
481
|
+
if (candidate.subsumes?.(normalized[index], this) === true) normalized.splice(index, 1);
|
|
482
|
+
}
|
|
483
|
+
normalized.push(candidate);
|
|
484
|
+
}
|
|
485
|
+
this._variableConstraints = normalized.length === 0 ? null : new Set(normalized);
|
|
471
486
|
this._reindexVariableConstraints();
|
|
472
487
|
}
|
|
473
488
|
variableConstraints(kind = null) {
|
|
@@ -487,8 +502,7 @@ export class Env {
|
|
|
487
502
|
if (status === 'violated') return false;
|
|
488
503
|
if (status !== 'entailed') pending.add(constraint);
|
|
489
504
|
}
|
|
490
|
-
this.
|
|
491
|
-
this._reindexVariableConstraints();
|
|
505
|
+
this._setNormalizedVariableConstraints(pending);
|
|
492
506
|
return true;
|
|
493
507
|
}
|
|
494
508
|
_reindexVariableConstraints() {
|
|
@@ -124,7 +124,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
124
124
|
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and interop 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
|
-
| 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(
|
|
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,...)`. |
|
|
128
128
|
|
|
129
129
|
Normal mode provides documented module and DCG compatibility profiles whose
|
|
130
130
|
features overlap standardized Part 2 and Part 3 facilities. They are extensions
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -285,13 +285,13 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
285
285
|
|
|
286
286
|
const strictSpacingError = capture(() => run('', {
|
|
287
287
|
isoStrict: true,
|
|
288
|
-
goal: 'write_term(1+1,[spacing(
|
|
288
|
+
goal: 'write_term(1+1,[spacing(false)])',
|
|
289
289
|
}));
|
|
290
290
|
equal(strictSpacingError.formal, 'domain_error(write_option)', 'strict spacing extension rejection');
|
|
291
291
|
|
|
292
292
|
const normal = run('', { goal: 'write_term("ab",[double_quotes(true)])' });
|
|
293
293
|
includes(normal.stdout, '"ab"', 'normal-profile extension remains available');
|
|
294
|
-
includes(run('', { goal: 'write_term(1+1,[spacing(
|
|
294
|
+
includes(run('', { goal: 'write_term(1+1,[spacing(true)])' }).stdout,
|
|
295
295
|
'1 + 1', 'normal-profile spacing extension remains available');
|
|
296
296
|
});
|
|
297
297
|
|
package/test/run-regression.mjs
CHANGED
|
@@ -210,6 +210,55 @@ function regressionCases() {
|
|
|
210
210
|
'freeze/2 residual projection terminates alongside dif/2 (issue #73)');
|
|
211
211
|
},
|
|
212
212
|
},
|
|
213
|
+
{
|
|
214
|
+
name: 'dif/2 removes symmetric and logically weaker residual constraints (issue #79)',
|
|
215
|
+
run: () => {
|
|
216
|
+
const residuals = runCli([], {
|
|
217
|
+
input:
|
|
218
|
+
'dif(X,Y),dif(Y,X).\n' +
|
|
219
|
+
'dif(X,Y),dif(X-Y,Z-Z).\n' +
|
|
220
|
+
'dif(X-Y,Z-Z),dif(X,Y).\n' +
|
|
221
|
+
'dif(X,a),dif(X,b).\n' +
|
|
222
|
+
'dif(X,a),dif(Y,a),X=Y.\n' +
|
|
223
|
+
'halt.\n',
|
|
224
|
+
});
|
|
225
|
+
assertEqual(residuals.status, 0, 'dif/2 normalization display status');
|
|
226
|
+
assertEqual(residuals.stdout,
|
|
227
|
+
'?- dif(X, Y).\n' +
|
|
228
|
+
'?- dif(X, Y).\n' +
|
|
229
|
+
'?- dif(X, Y).\n' +
|
|
230
|
+
'?- dif(X, a), dif(X, b).\n' +
|
|
231
|
+
'?- X = Y, dif(Y, a).\n' +
|
|
232
|
+
'?- ',
|
|
233
|
+
'dif/2 residuals are minimal independent of insertion order');
|
|
234
|
+
assertEqual(residuals.stderr, '', 'dif/2 normalization display stderr');
|
|
235
|
+
|
|
236
|
+
const pendingDifCount = (text) => {
|
|
237
|
+
const solver = new Solver(Program.parse(''), { registry: getEyePrologRegistry() });
|
|
238
|
+
const solutions = solver.solve([parseGoalText(text)], new Env(), 0);
|
|
239
|
+
const answer = solutions.next();
|
|
240
|
+
assertEqual(answer.done, false, `${text} succeeds`);
|
|
241
|
+
const count = answer.value.variableConstraints('dif').length;
|
|
242
|
+
solutions.return();
|
|
243
|
+
return count;
|
|
244
|
+
};
|
|
245
|
+
assertEqual(pendingDifCount('dif(X,Y),dif(Y,X)'), 1,
|
|
246
|
+
'symmetric disequalities share one descriptor');
|
|
247
|
+
assertEqual(pendingDifCount('dif(X-Y,Z-Z),dif(X,Y)'), 1,
|
|
248
|
+
'stronger later disequality replaces the weaker descriptor');
|
|
249
|
+
assertEqual(pendingDifCount('dif(X,a),dif(X,b)'), 2,
|
|
250
|
+
'independent disequalities are both retained');
|
|
251
|
+
assertEqual(pendingDifCount('dif(X,a),dif(Y,a),X=Y'), 1,
|
|
252
|
+
'later aliasing coalesces newly equivalent disequalities');
|
|
253
|
+
for (const prefix of [
|
|
254
|
+
'dif(X,Y),dif(X-Y,Z-Z)',
|
|
255
|
+
'dif(X-Y,Z-Z),dif(X,Y)',
|
|
256
|
+
]) {
|
|
257
|
+
assertEqual(run('', { goal: `${prefix},X=Y` }).stats.completed_goal_lists, 0,
|
|
258
|
+
`${prefix} retains the stronger disequality semantics`);
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
},
|
|
213
262
|
{
|
|
214
263
|
name: 'library(atts) provides Scryer-style attributed-variable hooks',
|
|
215
264
|
run: () => {
|
|
@@ -4078,8 +4127,8 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
4078
4127
|
run: () => {
|
|
4079
4128
|
const source = [
|
|
4080
4129
|
'emit :-',
|
|
4081
|
-
' write_term(1+1, [spacing(
|
|
4082
|
-
' write_term(1+1, [spacing(
|
|
4130
|
+
' write_term(1+1, [spacing(false)]), put_char(\'|\'),',
|
|
4131
|
+
' write_term(1+1, [spacing(true)]).',
|
|
4083
4132
|
'',
|
|
4084
4133
|
].join('\n');
|
|
4085
4134
|
assertEqual(run(source, { goal: 'emit' }).stdout, '1+1|1 + 1emit.\n',
|
|
@@ -4104,7 +4153,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
4104
4153
|
}
|
|
4105
4154
|
assertEqual(variableError?.formal, 'instantiation_error', 'variable spacing value');
|
|
4106
4155
|
|
|
4107
|
-
for (const value of ['
|
|
4156
|
+
for (const value of ['minimal', 'standard', '1', '0']) {
|
|
4108
4157
|
let domainError = null;
|
|
4109
4158
|
try {
|
|
4110
4159
|
run('', { goal: `write_term(1+1,[spacing(${value})])` });
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5439,11 +5439,11 @@ still retained where adjacent graphic tokens would otherwise merge, as in
|
|
|
5439
5439
|
`double_quotes(true|false)`: `true` lets eligible character/code lists use the
|
|
5440
5440
|
current `double_quotes` representation, while strict ISO mode rejects this
|
|
5441
5441
|
implementation-specific write option. Normal mode also accepts the
|
|
5442
|
-
implementation-specific `spacing(
|
|
5443
|
-
only separators required to avoid lexical ambiguity, while `
|
|
5442
|
+
implementation-specific boolean `spacing(true|false)` option: `false` emits
|
|
5443
|
+
only separators required to avoid lexical ambiguity, while `true` adds
|
|
5444
5444
|
conventional layout around operators. For example,
|
|
5445
|
-
`write_term(1+1,[spacing(
|
|
5446
|
-
`write_term(1+1,[spacing(
|
|
5445
|
+
`write_term(1+1,[spacing(false)])` emits `1+1` and
|
|
5446
|
+
`write_term(1+1,[spacing(true)])` emits `1 + 1`. The REPL always follows
|
|
5447
5447
|
the minimal-separator rule, so `X=1*1` is displayed as `X = 1*1`, while a
|
|
5448
5448
|
separator is retained in `X = a+ -b` because the adjacent graphic tokens would
|
|
5449
5449
|
otherwise merge. Strict ISO mode rejects both extension options.
|
|
@@ -6139,7 +6139,7 @@ the beginning. A peek does not mark the stream as past-end.
|
|
|
6139
6139
|
| `write(+Term)`, `write(+Stream,+Term)` | Writes readable operator notation without quoting atoms merely because quoting would be required for reparsing. Number variables are enabled. |
|
|
6140
6140
|
| `writeq(+Term)`, `writeq(+Stream,+Term)` | Like `write`, but quotes atoms when required for unambiguous input syntax. |
|
|
6141
6141
|
| `write_canonical(+Term)`, `write_canonical(+Stream,+Term)` | Writes quoted canonical functor notation while ignoring operators and without interpreting *$VAR/1*. |
|
|
6142
|
-
| `write_term(+Term,+Options)`, `write_term(+Stream,+Term,+Options)` | Writes with *quoted(true or false)*, *ignore_ops(true or false)*, *numbervars(true or false)*, and *variable_names([Name=Variable,...])*. Normal mode also supports *double_quotes(true or false)* and *spacing(
|
|
6142
|
+
| `write_term(+Term,+Options)`, `write_term(+Stream,+Term,+Options)` | Writes with *quoted(true or false)*, *ignore_ops(true or false)*, *numbervars(true or false)*, and *variable_names([Name=Variable,...])*. Normal mode also supports *double_quotes(true or false)* and *spacing(true or false)*. |
|
|
6143
6143
|
|
|
6144
6144
|
Term input uses the program's current operator table and the same ISO quoted-character
|
|
6145
6145
|
syntax as source text, including backslash-terminated octal and hexadecimal
|
|
@@ -6284,6 +6284,12 @@ otherwise it delays the goal until `Term` becomes nonvariable. Suspensions are
|
|
|
6284
6284
|
kept in the logical environment, so bindings and backtracking remain isolated
|
|
6285
6285
|
between solution branches.
|
|
6286
6286
|
|
|
6287
|
+
`dif(?Left,?Right)` posts a delayed finite-tree disequality when its arguments
|
|
6288
|
+
can still unify. Its residual store is normalized by logical implication:
|
|
6289
|
+
symmetric or equivalent constraints share one residual, and a stronger
|
|
6290
|
+
constraint removes weaker ones regardless of insertion order. Independent
|
|
6291
|
+
disequalities remain separate.
|
|
6292
|
+
|
|
6287
6293
|
<!-- eyeprolog-library-catalog:start -->
|
|
6288
6294
|
|
|
6289
6295
|
| Module | Exported predicate indicators | Primary role |
|
|
@@ -7796,7 +7802,7 @@ Notable implementation boundaries are:
|
|
|
7796
7802
|
- `write_term/2-3` implements the Part 1 plus Corrigendum 3 `quoted/1`,
|
|
7797
7803
|
`ignore_ops/1`, `numbervars/1`, and `variable_names/1` option surface,
|
|
7798
7804
|
including option validation and traversal rules; normal mode also offers
|
|
7799
|
-
`double_quotes(true|false)` and `spacing(
|
|
7805
|
+
`double_quotes(true|false)` and `spacing(true|false)` as explicitly
|
|
7800
7806
|
implementation-specific extensions, which strict mode rejects;
|
|
7801
7807
|
- unification consistently performs an occurs check, rejecting rational-tree
|
|
7802
7808
|
bindings accepted as extensions by some systems.
|