eyeprolog 1.3.94 → 1.3.96

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.
@@ -238,12 +238,13 @@ npm run generate
238
238
 
239
239
  ## Chapter 39: Built-in predicates by programming role
240
240
 
241
- - [01-answer.pl](chapter-39/01-answer.pl) — The EyeProlog library
242
- - [02-answer-2.pl](chapter-39/02-answer-2.pl)
243
- - [03-answer-3.pl](chapter-39/03-answer-3.pl)
244
- - [04-cost.pl](chapter-39/04-cost.pl)
245
- - [05-message.pl](chapter-39/05-message.pl)
246
- - [06-task.pl](chapter-39/06-task.pl)
241
+ - [01-program.pl](chapter-39/01-program.pl) — The EyeProlog library
242
+ - [02-answer.pl](chapter-39/02-answer.pl)
243
+ - [03-answer-2.pl](chapter-39/03-answer-2.pl)
244
+ - [04-answer-3.pl](chapter-39/04-answer-3.pl)
245
+ - [05-cost.pl](chapter-39/05-cost.pl)
246
+ - [06-message.pl](chapter-39/06-message.pl)
247
+ - [07-task.pl](chapter-39/07-task.pl)
247
248
 
248
249
  ## Chapter 40: Running EyeProlog: command line and corpus
249
250
 
@@ -0,0 +1,4 @@
1
+ % From The Art of EyeProlog, Chapter 39 — The EyeProlog library.
2
+ ?- dif(f(X,A),f(Y,B)), ( true ; A = B ).
3
+ dif(f(X, A), f(Y, B))
4
+ ; A = B, dif(X, Y).
@@ -1,4 +1,4 @@
1
- % From The Art of EyeProlog, Chapter 39 — The EyeProlog library.
1
+ % From The Art of EyeProlog, Chapter 39.
2
2
  :- use_module(library(dates)).
3
3
  :- use_module(library(between), [between/3]).
4
4
  :- use_module(library(random)).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.94",
6
+ "version": "1.3.96",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/quads.js CHANGED
@@ -152,17 +152,25 @@ function checkAlternative(program, quad, alternative, options, context) {
152
152
  // portably outlaw the implementation's chosen STO outcome. This is the
153
153
  // case behind issue #60's `false, unexpected` example.
154
154
  const stoPermitsUnexpected = context.declaresSto && actual.stoObserved && leaf.unexpected && !leaf.sto;
155
- if (!stoPermitsUnexpected && (leaf.unexpected ? matches : !matches)) {
155
+ if (leaf.unexpected) {
156
+ // `unexpected` is a negative assertion about the leaf at this answer
157
+ // position. Once the observed answer differs, the assertion is proved;
158
+ // it does not consume that answer or require the query to end afterward.
159
+ // Likewise, an STO-declared query may accept an implementation-dependent
160
+ // answer at this point without imposing any later answer-sequence check.
161
+ if (stoPermitsUnexpected) return { ok: true };
162
+ if (matches) return { ok: false };
163
+ if (actual.undecided && leafNeedsMoreSearch(leaf, actual, position)) {
164
+ return undecidedResult(actual, alternative);
165
+ }
166
+ return { ok: true };
167
+ }
168
+ if (!matches) {
156
169
  if (actual.undecided && leafNeedsMoreSearch(leaf, actual, position)) {
157
170
  return undecidedResult(actual, alternative);
158
171
  }
159
172
  return { ok: false };
160
173
  }
161
- // An unexpected error description is a negative assertion about that
162
- // particular error pattern. A different exception may be present and is
163
- // checked by other descriptions; do not make the final 'no error' test
164
- // turn a successful negative match back into a failure.
165
- if (leaf.unexpected && leaf.error != null) return { ok: true };
166
174
  if (leaf.more) return { ok: true };
167
175
  if (!leaf.unexpected && (leaf.false || leaf.loops || leaf.error != null)) {
168
176
  if (actual.undecided) return undecidedResult(actual, alternative);
@@ -400,6 +400,20 @@ function regressionCases() {
400
400
  'dif/2 residuals are minimal without auxiliary terms');
401
401
  assertEqual(residuals.stderr, '', 'dif/2 normalization display stderr');
402
402
 
403
+ const perfection = runCli([], {
404
+ input:
405
+ 'dif(f(X,A),f(Y,B)), ( true ; A = B ).\n' +
406
+ ';\n' +
407
+ 'halt.\n',
408
+ });
409
+ assertEqual(perfection.status, 0, 'dif/2 branch-local projection status');
410
+ assertEqual(perfection.stdout,
411
+ '?- dif(f(X, A), f(Y, B))\n' +
412
+ '; A = B, dif(X, Y).\n' +
413
+ '?- ',
414
+ 'dif/2 residual projection is re-evaluated after branch-local bindings');
415
+ assertEqual(perfection.stderr, '', 'dif/2 branch-local projection stderr');
416
+
403
417
  const pendingDifCount = (text) => {
404
418
  const solver = new Solver(Program.parse(''), { registry: getEyePrologRegistry() });
405
419
  const solutions = solver.solve([parseGoalText(text)], new Env(), 0);
@@ -951,6 +965,64 @@ why(
951
965
  assertEqual(result.stdout, 'quads: 13 run, 13 passed, 0 failed.\n', 'quad report');
952
966
  },
953
967
  },
968
+ {
969
+ name: 'runQuads treats unexpected as a negative assertion on the next leaf (issue #83)',
970
+ run: () => {
971
+ const source = `:- use_module(library(prologue)).
972
+ ` +
973
+ `40
974
+ ` +
975
+ `?- member(X,"abc").
976
+ ` +
977
+ ` X = a
978
+ ` +
979
+ `; X = b
980
+ ` +
981
+ `; X = c.
982
+ ` +
983
+ ` X = a
984
+ ` +
985
+ `; X = c, unexpected.
986
+
987
+ ` +
988
+ `41
989
+ ` +
990
+ `?- member(X, Xs).
991
+ ` +
992
+ ` Xs = [X|_A]
993
+ ` +
994
+ `; ... .
995
+ ` +
996
+ ` Xs = [X|_A]
997
+ ` +
998
+ `; Xs = [_A,X|_B]
999
+ ` +
1000
+ `; Xs = [_A,_B,X|_C]
1001
+ ` +
1002
+ `; ... .
1003
+ ` +
1004
+ ` Xs = [X|_A]
1005
+ ` +
1006
+ `; Xs = [], unexpected.
1007
+ `;
1008
+ const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'unexpected-quad.pl' }]));
1009
+ assertEqual(result.total, 5, 'quad total');
1010
+ assertEqual(result.passed, 5, 'quad passed');
1011
+ assertEqual(result.failed, 0, 'quad failed');
1012
+ assertEqual(result.stdout, 'quads: 5 run, 5 passed, 0 failed.\n', 'quad report');
1013
+
1014
+ const forbidden = publicApi.runQuads(`:- use_module(library(prologue)).
1015
+ ` +
1016
+ `?- member(X,[a,c]).
1017
+ ` +
1018
+ ` X = a
1019
+ ` +
1020
+ `; X = c, unexpected.
1021
+ `);
1022
+ assertEqual(forbidden.total, 1, 'forbidden next answer total');
1023
+ assertEqual(forbidden.failed, 1, 'matching unexpected next answer fails');
1024
+ },
1025
+ },
954
1026
  {
955
1027
  name: 'runQuads distinguishes query variables from renamed throw variables',
956
1028
  run: () => {
@@ -6293,7 +6293,15 @@ answers `Y=1`, `Y=2`, and `Y=3`.
6293
6293
  can still unify. Its residual store is normalized by logical implication:
6294
6294
  symmetric or equivalent constraints share one residual, and a stronger
6295
6295
  constraint removes weaker ones regardless of insertion order. Independent
6296
- disequalities remain separate.
6296
+ disequalities remain separate. Residual projection is re-evaluated in each
6297
+ solution environment, so a compound disequality is kept intact until later
6298
+ bindings make one aligned subterm pair sufficient. For example:
6299
+
6300
+ ```eyeprolog
6301
+ ?- dif(f(X,A),f(Y,B)), ( true ; A = B ).
6302
+ dif(f(X, A), f(Y, B))
6303
+ ; A = B, dif(X, Y).
6304
+ ```
6297
6305
 
6298
6306
  <!-- eyeprolog-library-catalog:start -->
6299
6307
 
@@ -7045,8 +7053,12 @@ accepted as a negative answer.
7045
7053
  Answer descriptions support ordered answers separated by `;`, acceptable
7046
7054
  alternatives separated by `|`, `true`, `false`, standard error descriptions,
7047
7055
  and the `unexpected` annotation for an answer that must not occur (`inattendue`
7048
- is its synonym). Variables named in the query keep their identity inside answer
7049
- descriptions; variables introduced only by a description are fresh. For example,
7056
+ is its synonym). In an ordered answer sequence, `unexpected` is a negative
7057
+ assertion about the answer at that position: once the next observed answer does
7058
+ not match the annotated leaf, that description succeeds and does not require the
7059
+ query to have no further answers. Variables named in the query keep their
7060
+ identity inside answer descriptions; variables introduced only by a description
7061
+ are fresh. For example,
7050
7062
  a query `throw(g(X))` is described by `throw(g(_X))`, while
7051
7063
  `throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
7052
7064
  variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. The `maybe` annotation describes