eyeprolog 1.5.93 → 1.5.94

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.5.93",
6
+ "version": "1.5.94",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/test/README.md CHANGED
@@ -29,19 +29,26 @@ each one. Focused checks do not replace the full release gate.
29
29
 
30
30
  `run-properties.mjs` is different in kind from the rest of the suite: instead
31
31
  of hand-picked inputs, it generates many random ground terms from a seeded
32
- PRNG and checks invariants that must hold for any input -- write/read
33
- round-tripping, `sort/2` ordering and idempotence, `append/3`/`length/2`
34
- agreement, `compare/3` symmetry, `keysort/2` stability, `=..` round-tripping,
35
- `copy_term/2` sharing, `reverse/2` involution, `atom_codes/2`/`atom_chars/2`/
36
- `char_code/2`/`number_codes/2` round-tripping, `succ/2` and `abs/1`/`sign/1`
37
- arithmetic identities, `nth0/3`/`nth1/3` agreement, `last/2`, `min_list/2`/
38
- `max_list/2` bounds, `sum_list/2` additivity, `atom_concat/3`/`string_concat/3`
39
- regrouping, `atom_string/2`/`number_string/2` round-tripping, `list_to_set/2`
40
- idempotence, `permutation/2`, and `between/3` range generation -- roughly 25
41
- properties, each reported as its own trial (currently ~390 individual test
32
+ PRNG and checks invariants that must hold for any input. It covers term/list/
33
+ arithmetic/string library predicates (write/read round-tripping, `sort/2`
34
+ ordering and idempotence, `append/3`/`length/2` agreement, `compare/3`
35
+ symmetry, `keysort/2` stability, `=..` round-tripping, `copy_term/2` sharing,
36
+ `reverse/2` involution, `atom_codes/2`/`atom_chars/2`/`char_code/2`/
37
+ `number_codes/2` round-tripping, `succ/2` and `abs/1`/`sign/1` arithmetic
38
+ identities, `nth0/3`/`nth1/3` agreement, `last/2`, `min_list/2`/`max_list/2`
39
+ bounds, `sum_list/2` additivity, `atom_concat/3`/`string_concat/3` regrouping,
40
+ `atom_string/2`/`number_string/2` round-tripping, `list_to_set/2` idempotence,
41
+ `permutation/2`, `between/3` range generation, `split/3`/`join/3` inversion)
42
+ and, more broadly, control constructs, exceptions, the database, and grammar
43
+ rules (`catch/3` ball delivery, `findall/3` order/duplicate/no-match
44
+ behavior, double negation, the exact `existence_error` indicator for an
45
+ undefined predicate, `assertz/1`+`retract/1` leaving no trace, a DCG list
46
+ rule accepting exactly the list it was built from, `bagof/3` agreeing with
47
+ `findall/3`, and `numbervars/3` counting distinct variables) -- around 35
48
+ properties, each reported as its own trial (currently ~530 individual test
42
49
  lines) so a failure names exactly which trial of which property broke rather
43
50
  than leaving a reader to dig through one aggregate error. The whole file
44
- still runs in under two seconds. The seed (`SEED` in that file) is fixed so a
51
+ still runs in about two seconds. The seed (`SEED` in that file) is fixed so a
45
52
  failure is exactly reproducible by rerunning it -- change it only
46
53
  deliberately, and say why, never to make a transient failure disappear.
47
54
 
@@ -100,7 +100,9 @@ function genCompound() {
100
100
  return `${pick(FUNCTOR_POOL)}(${args.join(', ')})`;
101
101
  }
102
102
 
103
- function genList(depth = 1) { return `[${Array.from({ length: int(0, 6) }, () => genTerm(depth)).join(', ')}]`; }
103
+ function genList(depth = 1, minLen = 0, maxLen = 6) {
104
+ return `[${Array.from({ length: int(minLen, maxLen) }, () => genTerm(depth)).join(', ')}]`;
105
+ }
104
106
  function genWord() { return pick(WORD_POOL); }
105
107
  function genSmallInt() { return int(0, 1000); }
106
108
  function genNonZeroInt() { const n = int(1, 1000); return rng() < 0.5 ? -n : n; }
@@ -114,8 +116,8 @@ function genNonZeroInt() { const n = int(1, 1000); return rng() < 0.5 ? -n : n;
114
116
  // succeeding, and a ground goal that succeeds is echoed back in full,
115
117
  // untaken branches included -- so a literal marker atom anywhere in the
116
118
  // goal text can appear in that echo without ever having been reached.
117
- function solve(goal, { use = [] } = {}) {
118
- const prelude = use.map((lib) => `:- use_module(library(${lib})).\n`).join('');
119
+ function solve(goal, { use = [], preamble = '' } = {}) {
120
+ const prelude = use.map((lib) => `:- use_module(library(${lib})).\n`).join('') + preamble;
119
121
  const result = run(prelude, { goal });
120
122
  if (result.stats.completed_goal_lists < 1) {
121
123
  throw new Error(`property goal did not hold (failed or errored): ${goal}`);
@@ -289,6 +291,66 @@ export function runProperties(reporter = new TestReporter()) {
289
291
  `nth0(0, L, F), F =:= ${lo}, last(L, La), La =:= ${hi}`;
290
292
  });
291
293
 
294
+ // --- Broader coverage: control constructs, exceptions, the database, and
295
+ // grammar rules, not just term/list/arithmetic/string library predicates.
296
+
297
+ property(reporter, 'catch/3 delivers the thrown ball unchanged', 15, () =>
298
+ `B = (${genTerm()}), catch(throw(B), Ball, true), Ball == B`);
299
+
300
+ property(reporter, 'findall/3 over member/2 reproduces the list exactly, in order, duplicates included', 15, () => {
301
+ const list = genList();
302
+ return `L = ${list}, findall(X, member(X, L), L2), L2 == L`;
303
+ });
304
+
305
+ property(reporter, 'findall/3 leaves its template variable unbound when nothing matches', 6, () =>
306
+ `findall(X, (member(_, ${genList()}), fail), Bag), Bag == [], var(X)`);
307
+
308
+ property(reporter, 'double negation reports success without ever binding anything', 20, () => {
309
+ const list = genList(1);
310
+ // Half the time probe an element genuinely in the list; half the time
311
+ // probe one that (almost certainly) is not, so both the success and
312
+ // the failure side of \+ \+ get exercised across the run.
313
+ const probe = rng() < 0.5 ? pick(FUNCTOR_POOL) : genTerm(1);
314
+ return `L = ${list}, X = (${probe}), ` +
315
+ `( member(X, L) -> \\+ \\+ member(X, L) ; \\+ member(X, L) )`;
316
+ });
317
+
318
+ property(reporter, 'calling an undefined predicate names its own exact indicator', 15, () => {
319
+ const name = `zz_property_undefined_${int(0, 1000000)}`;
320
+ const arity = int(0, 4);
321
+ const args = Array.from({ length: arity }, () => '_').join(', ');
322
+ const call = arity === 0 ? name : `${name}(${args})`;
323
+ return `catch(${call}, error(existence_error(procedure, PI), _), true), PI == ${name}/${arity}`;
324
+ });
325
+
326
+ property(reporter, 'assertz/1 then retract/1 leaves no trace behind', 15, () => {
327
+ const term = genTerm();
328
+ return `assertz(zz_property_fact(${term})), zz_property_fact(X), X == (${term}), ` +
329
+ `retract(zz_property_fact(${term})), \\+ zz_property_fact(_)`;
330
+ });
331
+
332
+ property(reporter, 'a DCG list rule accepts exactly the list it was built from', 15, () => {
333
+ const list = genList(1);
334
+ return `L = ${list}, phrase(zz_property_seq(L), L)`;
335
+ }, { preamble: 'zz_property_seq([]) --> [].\nzz_property_seq([H|T]) --> [H], zz_property_seq(T).\n' });
336
+
337
+ property(reporter, 'bagof/3 over member/2 agrees with findall/3 on a non-empty list', 15, () => {
338
+ const list = genList(1, 1, 6); // at least one element: bagof/3 has no "no solutions" answer
339
+ return `L = ${list}, bagof(X, member(X, L), Bag), Bag == L`;
340
+ });
341
+
342
+ property(reporter, 'numbervars/3 assigns exactly as many numbers as distinct variables', 15, () => {
343
+ const arity = int(2, 5);
344
+ const slots = int(1, arity);
345
+ const args = Array.from({ length: arity }, () => `V${int(1, slots)}`);
346
+ return `T = f(${args.join(', ')}), numbervars(T, 0, End), End =:= ${slots}`;
347
+ });
348
+
349
+ property(reporter, 'split/3 and join/3 (library(strings)) invert each other', 12, () => {
350
+ const words = Array.from({ length: int(2, 5) }, () => genWord());
351
+ return `split('${words.join(',')}', ',', Parts), join(Parts, ',', S), S == '${words.join(',')}'`;
352
+ }, { use: ['strings'] });
353
+
292
354
  reporter.sectionTotal('property-based round-trip checks');
293
355
  }
294
356