eyeprolog 1.3.94 → 1.3.95

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.94",
6
+ "version": "1.3.95",
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);
@@ -951,6 +951,64 @@ why(
951
951
  assertEqual(result.stdout, 'quads: 13 run, 13 passed, 0 failed.\n', 'quad report');
952
952
  },
953
953
  },
954
+ {
955
+ name: 'runQuads treats unexpected as a negative assertion on the next leaf (issue #83)',
956
+ run: () => {
957
+ const source = `:- use_module(library(prologue)).
958
+ ` +
959
+ `40
960
+ ` +
961
+ `?- member(X,"abc").
962
+ ` +
963
+ ` X = a
964
+ ` +
965
+ `; X = b
966
+ ` +
967
+ `; X = c.
968
+ ` +
969
+ ` X = a
970
+ ` +
971
+ `; X = c, unexpected.
972
+
973
+ ` +
974
+ `41
975
+ ` +
976
+ `?- member(X, Xs).
977
+ ` +
978
+ ` Xs = [X|_A]
979
+ ` +
980
+ `; ... .
981
+ ` +
982
+ ` Xs = [X|_A]
983
+ ` +
984
+ `; Xs = [_A,X|_B]
985
+ ` +
986
+ `; Xs = [_A,_B,X|_C]
987
+ ` +
988
+ `; ... .
989
+ ` +
990
+ ` Xs = [X|_A]
991
+ ` +
992
+ `; Xs = [], unexpected.
993
+ `;
994
+ const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'unexpected-quad.pl' }]));
995
+ assertEqual(result.total, 5, 'quad total');
996
+ assertEqual(result.passed, 5, 'quad passed');
997
+ assertEqual(result.failed, 0, 'quad failed');
998
+ assertEqual(result.stdout, 'quads: 5 run, 5 passed, 0 failed.\n', 'quad report');
999
+
1000
+ const forbidden = publicApi.runQuads(`:- use_module(library(prologue)).
1001
+ ` +
1002
+ `?- member(X,[a,c]).
1003
+ ` +
1004
+ ` X = a
1005
+ ` +
1006
+ `; X = c, unexpected.
1007
+ `);
1008
+ assertEqual(forbidden.total, 1, 'forbidden next answer total');
1009
+ assertEqual(forbidden.failed, 1, 'matching unexpected next answer fails');
1010
+ },
1011
+ },
954
1012
  {
955
1013
  name: 'runQuads distinguishes query variables from renamed throw variables',
956
1014
  run: () => {
@@ -7045,8 +7045,12 @@ accepted as a negative answer.
7045
7045
  Answer descriptions support ordered answers separated by `;`, acceptable
7046
7046
  alternatives separated by `|`, `true`, `false`, standard error descriptions,
7047
7047
  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,
7048
+ is its synonym). In an ordered answer sequence, `unexpected` is a negative
7049
+ assertion about the answer at that position: once the next observed answer does
7050
+ not match the annotated leaf, that description succeeds and does not require the
7051
+ query to have no further answers. Variables named in the query keep their
7052
+ identity inside answer descriptions; variables introduced only by a description
7053
+ are fresh. For example,
7050
7054
  a query `throw(g(X))` is described by `throw(g(_X))`, while
7051
7055
  `throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
7052
7056
  variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. The `maybe` annotation describes