eyeprolog 1.5.91 → 1.5.93

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.
Files changed (44) hide show
  1. package/conformance-report.md +3 -3
  2. package/package.json +1 -1
  3. package/test/README.md +19 -0
  4. package/test/conformance/README.md +1 -1
  5. package/test/conformance/THIRD_PARTY.md +18 -8
  6. package/test/conformance/cases/iso/swipl_call_n_builds_control_construct.pl +8 -0
  7. package/test/conformance/cases/iso/swipl_catch_partial_unification_rethrow.pl +9 -0
  8. package/test/conformance/cases/iso/swipl_keysort_remaining_errors.pl +10 -0
  9. package/test/conformance/cases/iso/swipl_length_errors.pl +8 -0
  10. package/test/conformance/cases/iso/swipl_memberchk_binding_retention.pl +6 -0
  11. package/test/conformance/cases/iso/swipl_operator_table_self_named.pl +16 -0
  12. package/test/conformance/cases/iso/swipl_sort_partial_list_errors.pl +8 -0
  13. package/test/conformance/cases/iso/swipl_write_canonical_var_and_curly.pl +8 -0
  14. package/test/conformance/cases/lists/swipl_aggregate_sum_count.pl +14 -0
  15. package/test/conformance/cases/lists/swipl_assoc_del_min_roundtrip.pl +27 -0
  16. package/test/conformance/cases/lists/swipl_assoc_is_assoc_malformed.pl +37 -0
  17. package/test/conformance/cases/lists/swipl_assoc_is_assoc_valid.pl +10 -0
  18. package/test/conformance/cases/lists/swipl_lists_foldl_empty.pl +15 -0
  19. package/test/conformance/cases/lists/swipl_lists_reverse_mode.pl +13 -0
  20. package/test/conformance/cases/lists/swipl_ordsets_intersection_difference.pl +13 -0
  21. package/test/conformance/cases/lists/swipl_ordsets_is_ordset.pl +21 -0
  22. package/test/conformance/cases/lists/swipl_ugraphs_top_sort.pl +8 -0
  23. package/test/conformance/errors/iso/swipl_operator_clash_syntax_error.pl +5 -0
  24. package/test/conformance/expected/iso/swipl_call_n_builds_control_construct.pl +1 -0
  25. package/test/conformance/expected/iso/swipl_catch_partial_unification_rethrow.pl +1 -0
  26. package/test/conformance/expected/iso/swipl_keysort_remaining_errors.pl +1 -0
  27. package/test/conformance/expected/iso/swipl_length_errors.pl +1 -0
  28. package/test/conformance/expected/iso/swipl_memberchk_binding_retention.pl +1 -0
  29. package/test/conformance/expected/iso/swipl_operator_table_self_named.pl +1 -0
  30. package/test/conformance/expected/iso/swipl_sort_partial_list_errors.pl +1 -0
  31. package/test/conformance/expected/iso/swipl_write_canonical_var_and_curly.pl +4 -0
  32. package/test/conformance/expected/lists/swipl_aggregate_sum_count.pl +2 -0
  33. package/test/conformance/expected/lists/swipl_assoc_del_min_roundtrip.pl +3 -0
  34. package/test/conformance/expected/lists/swipl_assoc_is_assoc_malformed.pl +3 -0
  35. package/test/conformance/expected/lists/swipl_assoc_is_assoc_valid.pl +1 -0
  36. package/test/conformance/expected/lists/swipl_lists_foldl_empty.pl +2 -0
  37. package/test/conformance/expected/lists/swipl_lists_reverse_mode.pl +1 -0
  38. package/test/conformance/expected/lists/swipl_ordsets_intersection_difference.pl +1 -0
  39. package/test/conformance/expected/lists/swipl_ordsets_is_ordset.pl +2 -0
  40. package/test/conformance/expected/lists/swipl_ugraphs_top_sort.pl +1 -0
  41. package/test/conformance/expected-errors/iso/swipl_operator_clash_syntax_error.txt +1 -0
  42. package/test/run-all.mjs +2 -0
  43. package/test/run-properties.mjs +295 -0
  44. package/the-art-of-eyeprolog.md +1 -1
@@ -24,8 +24,8 @@ bytes, avoiding a second live fetch and its race window.
24
24
  | context | 11 | 0 | 0 | 0 | 11 |
25
25
  | control | 15 | 0 | 0 | 0 | 15 |
26
26
  | explicit-tabling | 6 | 0 | 0 | 0 | 6 |
27
- | iso | 251 | 219 | 0 | 0 | 470 |
28
- | lists | 52 | 3 | 0 | 0 | 55 |
27
+ | iso | 259 | 220 | 0 | 0 | 479 |
28
+ | lists | 61 | 3 | 0 | 0 | 64 |
29
29
  | modules | 2 | 0 | 0 | 0 | 2 |
30
30
  | negation | 8 | 0 | 19 | 0 | 27 |
31
31
  | proofs | 0 | 0 | 0 | 21 | 21 |
@@ -37,7 +37,7 @@ bytes, avoiding a second live fetch and its race window.
37
37
  | terms | 26 | 3 | 0 | 0 | 29 |
38
38
  | unification | 18 | 0 | 0 | 0 | 18 |
39
39
  | variables | 16 | 7 | 0 | 0 | 23 |
40
- | **Total** | **573** | **274** | **19** | **21** | **887** |
40
+ | **Total** | **590** | **275** | **19** | **21** | **905** |
41
41
 
42
42
  ## DCG conformance clarification
43
43
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.91",
6
+ "version": "1.5.93",
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
@@ -17,6 +17,7 @@ node test/run-neumerkel-tests.mjs # upstream-fetch harness
17
17
  node test/run-examples.mjs
18
18
  node test/run-playground.mjs
19
19
  node test/run-architecture.mjs
20
+ node test/run-properties.mjs # seeded random-term invariant checks (see below)
20
21
  node test/run-openrulebench.mjs
21
22
  node test/run-http-json.mjs
22
23
  node test/run-interop.mjs # requires the comparison engines
@@ -26,6 +27,24 @@ node test/run-benchmark-tests.mjs # benchmark harness
26
27
  These runners retain their existing options; there is no separate npm alias for
27
28
  each one. Focused checks do not replace the full release gate.
28
29
 
30
+ `run-properties.mjs` is different in kind from the rest of the suite: instead
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
42
+ lines) so a failure names exactly which trial of which property broke rather
43
+ 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
45
+ failure is exactly reproducible by rerunning it -- change it only
46
+ deliberately, and say why, never to make a transient failure disappear.
47
+
29
48
  For performance measurements, use `npm run benchmark`. Save a local baseline with
30
49
  `npm run benchmark -- --save .benchmarks/baseline.json`.
31
50
 
@@ -138,7 +138,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
138
138
  Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
139
139
  are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
140
140
 
141
- The corpus has 470 cases in `iso/` and 887 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The strict-reader WG17 syntax matrix is discovered dynamically and executed live by the Neumerkel gate, the same as its other seven TU Wien sources; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
141
+ The corpus has 479 cases in `iso/` and 905 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The strict-reader WG17 syntax matrix is discovered dynamically and executed live by the Neumerkel gate, the same as its other seven TU Wien sources; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
142
142
 
143
143
  ## Updating expected output
144
144
 
@@ -102,14 +102,24 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102
102
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
103
103
  SOFTWARE.
104
104
 
105
- ## SWI-Prolog core tests
106
-
107
- Selected standard-core operator and finite-tree unification cases under `iso/`
108
- are adapted from `tests/core/test_op.pl` and
109
- `tests/core/test_occurs_check.pl` in
110
- [SWI-Prolog](https://github.com/SWI-Prolog/swipl-devel). Each adapted file
111
- identifies its upstream source. The cases were rewritten for EyeProlog's query and
112
- exact-output harness.
105
+ ## SWI-Prolog core and library tests
106
+
107
+ Selected standard-core operator, finite-tree unification, arithmetic, sort,
108
+ list, exception, and syntax cases under `iso/` are adapted from files under
109
+ `tests/core_lang/` and `tests/core_text/` in
110
+ [SWI-Prolog](https://github.com/SWI-Prolog/swipl-devel) -- named
111
+ `tests/core/test_op.pl` and `tests/core/test_occurs_check.pl` in older
112
+ checkouts, before that directory was split. Each adapted file identifies its
113
+ upstream source file.
114
+
115
+ Selected standard-library cases under the topic directories (not `iso/`,
116
+ since these exercise EyeProlog's own bundled `src/lib/` modules rather than
117
+ ISO core semantics) are adapted from files under `tests/library/` in the same
118
+ project -- `test_lists.pl`, `test_ordsets.pl`, `test_assoc.pl`,
119
+ `test_aggregate.pl`, and `test_ugraphs.pl`. Each adapted file identifies its
120
+ upstream source file and the `library(...)` module it needs.
121
+
122
+ The cases were rewritten for EyeProlog's query and exact-output harness.
113
123
 
114
124
  BSD 2-Clause License
115
125
 
@@ -0,0 +1,8 @@
1
+ % Adapted from SWI-Prolog tests/core_lang/test_call.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ %% goal: call_n_builds_control_construct(X0, X1, X2)
4
+
5
+ call_n_builds_control_construct(IfThenElse, Disj, Conj) :-
6
+ findall(X, call(;((true -> X = a)), X = b), IfThenElse),
7
+ findall(X, call(;(X = a), X = b), Disj),
8
+ findall(X, call(','(X = a), X = b), Conj).
@@ -0,0 +1,9 @@
1
+ % Adapted from SWI-Prolog tests/core_lang/test_call.pl (catch, partial_unification).
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ %% goal: catch_partial_unification_rethrow(X0)
4
+
5
+ t3_rethrow :- throw(f(_, 2)).
6
+ t2_rethrow(X) :- catch(t3_rethrow, f(2, 1), X = 2).
7
+ t1_rethrow(X) :- catch(t2_rethrow(X), f(1, 2), X = 1).
8
+
9
+ catch_partial_unification_rethrow(X) :- t1_rethrow(X).
@@ -0,0 +1,10 @@
1
+ % Adapted from SWI-Prolog tests/core_lang/test_sort.pl (keysort tests a, b, c;
2
+ % the pair-type case (d) is already covered by corrigenda_keysort_pair_type.pl).
3
+ % See test/conformance/THIRD_PARTY.md.
4
+ %% goal: keysort_remaining_errors(X0, X1, X2)
5
+
6
+ keysort_remaining_errors(Open, Improper, ElementOpen) :-
7
+ catch(keysort([a-b, b-b, a-b | _], _), error(Open, _), true),
8
+ L = [a-b, b-b | a],
9
+ catch(keysort(L, _), error(Improper, _), true),
10
+ catch(keysort([a-b, _], _), error(ElementOpen, _), true).
@@ -0,0 +1,8 @@
1
+ % Adapted from SWI-Prolog tests/core_lang/test_bips.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ %% goal: length_errors(X0, X1, X2)
4
+
5
+ length_errors(NegLen, NonIntAtom, NonIntFloat) :-
6
+ catch(length(_, -2), error(NegLen, _), true),
7
+ catch(length(_, a), error(NonIntAtom, _), true),
8
+ catch(length(_, 0.1), error(NonIntFloat, _), true).
@@ -0,0 +1,6 @@
1
+ % Adapted from SWI-Prolog tests/core_lang/test_list.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ %% goal: memberchk_binding_retention(X0)
4
+
5
+ memberchk_binding_retention(X) :-
6
+ memberchk(f(X, a), [f(x, b), f(y, a)]).
@@ -0,0 +1,16 @@
1
+ % Adapted from SWI-Prolog tests/core_text/test_syntax.pl (iso_op_table_6).
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- op(100, fy, fy).
4
+ :- op(100, xfy, xfy).
5
+ :- op(100, yfx, yfx).
6
+ :- op(100, yf, yf).
7
+
8
+ %% goal: operator_table_self_named(X0, X1, X2, X3, X4, X5)
9
+
10
+ operator_table_self_named(R1, R2, R3, R4, R5, R6) :-
11
+ T1 = (fy fy 1), T1 == fy(fy(1)), R1 = T1,
12
+ T2 = (1 xfy 2 xfy 3), T2 == xfy(1, xfy(2, 3)), R2 = T2,
13
+ T3 = (1 xfy 2 yfx 3), T3 == xfy(1, yfx(2, 3)), R3 = T3,
14
+ T4 = (fy 2 yf), T4 == fy(yf(2)), R4 = T4,
15
+ T5 = (1 yf yf), T5 == yf(yf(1)), R5 = T5,
16
+ T6 = (1 yfx 2 yfx 3), T6 == yfx(yfx(1, 2), 3), R6 = T6.
@@ -0,0 +1,8 @@
1
+ % Adapted from SWI-Prolog tests/core_lang/test_sort.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ %% goal: sort_partial_list_errors(X0, X1)
4
+
5
+ sort_partial_list_errors(Open, Improper) :-
6
+ catch(sort([a, b, a | _], _), error(Open, _), true),
7
+ L = [a, b | a],
8
+ catch(sort(L, _), error(Improper, _), true).
@@ -0,0 +1,8 @@
1
+ % Adapted from SWI-Prolog tests/core_text/test_write.pl (write_canonical tests).
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ %% goal: write_canonical_var_and_curly(ok)
4
+
5
+ write_canonical_var_and_curly(ok) :-
6
+ write_canonical('$VAR'(1)), nl,
7
+ write_canonical({a, b}), nl,
8
+ write_canonical(x(B, B)), nl.
@@ -0,0 +1,14 @@
1
+ % Adapted from SWI-Prolog tests/library/test_aggregate.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(aggregate)).
4
+ :- use_module(library(between)).
5
+
6
+ %% goal: aggregate_sum_template(X0)
7
+
8
+ aggregate_sum_template(Sum) :-
9
+ aggregate(sum(X), between(1, 2, X), Sum), !.
10
+
11
+ %% goal: aggregate_count_existential(X0)
12
+
13
+ aggregate_count_existential(Count) :-
14
+ aggregate(count, X ^ between(1, 2, X), Count), !.
@@ -0,0 +1,27 @@
1
+ % Adapted from SWI-Prolog tests/library/test_assoc.pl (random_min, del_empty,
2
+ % del_no_member; a fixed key list stands in for SWI's random/1-seeded one).
3
+ % See test/conformance/THIRD_PARTY.md.
4
+ :- use_module(library(assoc)).
5
+
6
+ del_min_assoc_to_list(A, L) :- del_min_assoc_to_list(A, [], L).
7
+ del_min_assoc_to_list(t, L, L) :- !.
8
+ del_min_assoc_to_list(A, Rest, [Key - Val | Result]) :-
9
+ del_min_assoc(A, Key, Val, A1) -> is_assoc(A1), del_min_assoc_to_list(A1, Rest, Result).
10
+
11
+ %% goal: assoc_del_min_roundtrip(X0)
12
+
13
+ assoc_del_min_roundtrip(L) :-
14
+ list_to_assoc([5-e, 3-c, 8-h, 1-a, 4-d, 7-g, 2-b, 6-f, 10-j, 9-i, 11-k], A),
15
+ del_min_assoc_to_list(A, L), !.
16
+
17
+ %% goal: assoc_del_min_empty_fails(ok)
18
+
19
+ assoc_del_min_empty_fails(ok) :-
20
+ empty_assoc(A),
21
+ \+ del_min_assoc(A, _, _, _), !.
22
+
23
+ %% goal: assoc_del_min_no_member_fails(ok)
24
+
25
+ assoc_del_min_no_member_fails(ok) :-
26
+ list_to_assoc([1-a, 2-b, 4-c], A),
27
+ \+ del_min_assoc(3, A, _, _), !.
@@ -0,0 +1,37 @@
1
+ % Adapted from SWI-Prolog tests/library/test_assoc.pl (unbalanced_1/2/3,
2
+ % unordered_1/2/3, bad_structure_1/2, bad_structure_keys_not_ground).
3
+ % See test/conformance/THIRD_PARTY.md.
4
+ :- use_module(library(assoc)).
5
+
6
+ %% goal: assoc_is_assoc_rejects_unbalanced(ok)
7
+
8
+ assoc_is_assoc_rejects_unbalanced(ok) :-
9
+ \+ is_assoc(t(5, 1, <, t(3, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
10
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
11
+ \+ is_assoc(t(5, 1, -, t(3, 1, -, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
12
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
13
+ \+ is_assoc(t(5, 1, -, t(3, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
14
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, >, t, t))))),
15
+ !.
16
+
17
+ %% goal: assoc_is_assoc_rejects_unordered(ok)
18
+
19
+ assoc_is_assoc_rejects_unordered(ok) :-
20
+ \+ is_assoc(t(5, 1, -, t(3, 1, <, t(2, 1, >, t, t(1, 1, -, t, t)), t(4, 1, -, t, t)),
21
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
22
+ \+ is_assoc(t(5, 1, -, t(4, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
23
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
24
+ \+ is_assoc(t(5, 1, -, t(3, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(6, 1, -, t, t)),
25
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
26
+ !.
27
+
28
+ %% goal: assoc_is_assoc_rejects_bad_structure(ok)
29
+
30
+ assoc_is_assoc_rejects_bad_structure(ok) :-
31
+ \+ is_assoc(t(5, 1, =, t(3, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
32
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
33
+ \+ is_assoc(t(5, 1, -, t(3, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
34
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, f, t))))),
35
+ \+ is_assoc(t(5, 1, -, t(3-_X, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
36
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
37
+ !.
@@ -0,0 +1,10 @@
1
+ % Adapted from SWI-Prolog tests/library/test_assoc.pl (wirth_85).
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(assoc)).
4
+
5
+ %% goal: assoc_is_assoc_valid(ok)
6
+
7
+ assoc_is_assoc_valid(ok) :-
8
+ is_assoc(t(5, 1, -, t(3, 1, <, t(2, 1, <, t(1, 1, -, t, t), t), t(4, 1, -, t, t)),
9
+ t(8, 1, -, t(7, 1, <, t(6, 1, -, t, t), t), t(10, 1, -, t(9, 1, -, t, t), t(11, 1, -, t, t))))),
10
+ !.
@@ -0,0 +1,15 @@
1
+ % Adapted from SWI-Prolog tests/library/test_apply.pl (foldl empty tests).
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(lists)).
4
+
5
+ false_foldl(_, _, _) :- fail.
6
+
7
+ %% goal: lists_foldl_empty_atom_starter(X0)
8
+
9
+ lists_foldl_empty_atom_starter(V) :-
10
+ foldl(false_foldl, [], foo, V), V == foo, !.
11
+
12
+ %% goal: lists_foldl_empty_unbound_starter(ok)
13
+
14
+ lists_foldl_empty_unbound_starter(ok) :-
15
+ foldl(false_foldl, [], VA, VB), VA == VB, !.
@@ -0,0 +1,13 @@
1
+ % Adapted from SWI-Prolog tests/library/test_lists.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(lists)).
4
+
5
+ %% goal: lists_reverse_mode(X0)
6
+
7
+ % A trailing cut keeps this deterministic: nothing else stops the search
8
+ % for a second (nonexistent) solution once L's unique correct length is
9
+ % found, since nothing else bounds how long a next candidate open list
10
+ % could grow while still failing to match (see SWI's own "must be
11
+ % deterministic" comment on this test).
12
+ lists_reverse_mode(L) :-
13
+ reverse(L, [a, b]), L == [b, a], !.
@@ -0,0 +1,13 @@
1
+ % Adapted from SWI-Prolog tests/library/test_ordsets.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(ordsets)).
4
+
5
+ %% goal: ordsets_intersection_difference(X0, X1, X2, X3, X4)
6
+
7
+ ordsets_intersection_difference(R1, R2, R3, R4, R5) :-
8
+ ord_intersection([], [], X1, Y1), R1 = X1 - Y1,
9
+ ord_intersection([a, b, c], [b], X2, Y2), R2 = X2 - Y2,
10
+ ord_intersection([a], [a, b, c], X3, Y3), R3 = X3 - Y3,
11
+ ord_intersection([b], [a, b, c], X4, Y4), R4 = X4 - Y4,
12
+ ord_intersection([c], [a, b, c], X5, Y5), R5 = X5 - Y5,
13
+ !.
@@ -0,0 +1,21 @@
1
+ % Adapted from SWI-Prolog tests/library/test_ordsets.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(ordsets)).
4
+
5
+ %% goal: ordsets_is_ordset_basic(ok)
6
+
7
+ ordsets_is_ordset_basic(ok) :-
8
+ is_ordset([a, b, c]), !.
9
+
10
+ % Genuinely interesting: is_ordset/1 must check standard order of terms
11
+ % rather than being implemented as a cheap sort(L,L) check -- that would
12
+ % wrongly succeed here, since X and Y are indistinguishable unbound
13
+ % variables at the point [Y,X] is checked.
14
+ %% goal: ordsets_is_ordset_unbound_pair(ok)
15
+
16
+ unbound_pair(_).
17
+
18
+ ordsets_is_ordset_unbound_pair(ok) :-
19
+ unbound_pair(T),
20
+ T = a(X, Y),
21
+ \+ is_ordset([Y, X]), !.
@@ -0,0 +1,8 @@
1
+ % Adapted from SWI-Prolog tests/library/test_ugraphs.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ :- use_module(library(ugraphs)).
4
+
5
+ %% goal: ugraphs_top_sort_linear(X0)
6
+
7
+ ugraphs_top_sort_linear(L) :-
8
+ top_sort([1-[2], 2-[3], 3-[]], L), !.
@@ -0,0 +1,5 @@
1
+ % Adapted from SWI-Prolog tests/core_text/test_syntax.pl (op_3): `:-` is xfx
2
+ % (non-associative), so a chained `a:-b:-c` is a genuine operator-priority
3
+ % clash, not a parse of either association. See test/conformance/THIRD_PARTY.md.
4
+ %% goal: answer
5
+ answer :- X = (a:-b:-c).
@@ -0,0 +1 @@
1
+ call_n_builds_control_construct("a", "ab", []).
@@ -0,0 +1 @@
1
+ catch_partial_unification_rethrow(1).
@@ -0,0 +1 @@
1
+ keysort_remaining_errors(instantiation_error, type_error(list, [a - b, b - b | a]), instantiation_error).
@@ -0,0 +1 @@
1
+ length_errors(domain_error(not_less_than_zero, -2), type_error(integer, a), type_error(integer, 0.1)).
@@ -0,0 +1 @@
1
+ memberchk_binding_retention(y).
@@ -0,0 +1 @@
1
+ operator_table_self_named(fy fy 1, 1 xfy 2 xfy 3, 1 xfy 2 yfx 3, fy 2 yf, 1 yf yf, 1 yfx 2 yfx 3).
@@ -0,0 +1 @@
1
+ sort_partial_list_errors(instantiation_error, type_error(list, "ab"||a)).
@@ -0,0 +1,4 @@
1
+ '$VAR'(1)
2
+ {}(','(a,b))
3
+ x(_A,_A)
4
+ write_canonical_var_and_curly(ok).
@@ -0,0 +1,2 @@
1
+ aggregate_sum_template(3).
2
+ aggregate_count_existential(2).
@@ -0,0 +1,3 @@
1
+ assoc_del_min_roundtrip([1 - a, 2 - b, 3 - c, 4 - d, 5 - e, 6 - f, 7 - g, 8 - h, 9 - i, 10 - j, 11 - k]).
2
+ assoc_del_min_empty_fails(ok).
3
+ assoc_del_min_no_member_fails(ok).
@@ -0,0 +1,3 @@
1
+ assoc_is_assoc_rejects_unbalanced(ok).
2
+ assoc_is_assoc_rejects_unordered(ok).
3
+ assoc_is_assoc_rejects_bad_structure(ok).
@@ -0,0 +1 @@
1
+ assoc_is_assoc_valid(ok).
@@ -0,0 +1,2 @@
1
+ lists_foldl_empty_atom_starter(foo).
2
+ lists_foldl_empty_unbound_starter(ok).
@@ -0,0 +1 @@
1
+ lists_reverse_mode("ba").
@@ -0,0 +1 @@
1
+ ordsets_intersection_difference([] - [], "b" - [], "a" - "bc", "b" - "ac", "c" - "ab").
@@ -0,0 +1,2 @@
1
+ ordsets_is_ordset_basic(ok).
2
+ ordsets_is_ordset_unbound_pair(ok).
@@ -0,0 +1 @@
1
+ ugraphs_top_sort_linear([1, 2, 3]).
@@ -0,0 +1 @@
1
+ parse line 5: non-associative operator :- requires parentheses
package/test/run-all.mjs CHANGED
@@ -12,6 +12,7 @@ import { runExamples } from './run-examples.mjs';
12
12
  import { runBookExamples } from './run-book-examples.mjs';
13
13
  import { runOpenRuleBenchChecks } from './run-openrulebench.mjs';
14
14
  import { runArchitecture } from './run-architecture.mjs';
15
+ import { runProperties } from './run-properties.mjs';
15
16
  import { runCleanup } from './run-cleanup.mjs';
16
17
  import { runHttpJson } from './run-http-json.mjs';
17
18
  import { runNeumerkel } from './run-neumerkel.mjs';
@@ -27,6 +28,7 @@ await runStandalone(async (reporter) => {
27
28
  runIsoPart2Amendment(reporter);
28
29
  runOpenRuleBenchChecks(reporter);
29
30
  runArchitecture(reporter);
31
+ runProperties(reporter);
30
32
  runCleanup(reporter);
31
33
  await runHttpJson(reporter);
32
34
  await runRegression(reporter);
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env node
2
+ // Property-based / round-trip regression tests.
3
+ //
4
+ // Every other conformance and regression case in this suite is a single,
5
+ // hand-picked input: a human decided which term, which list, which pair of
6
+ // operators was worth checking. That is precise, but it only ever catches
7
+ // what someone already thought to write down. This file instead generates
8
+ // many random ground terms from a seeded PRNG and checks invariants that
9
+ // must hold for ANY input, not just the ones on record -- the kind of edge
10
+ // case (an empty atom, an embedded quote, a duplicate sort key, a term
11
+ // whose functor happens to collide with an operator) that a hand-written
12
+ // corpus tends to miss by omission rather than by design.
13
+ //
14
+ // Each trial is its own reporter.test() entry, the same reason the Neumerkel
15
+ // gate reports every answer description as its own test (see
16
+ // test/neumerkel.mjs): a single aggregate pass/fail line for a whole
17
+ // property would force a reader to dig through a raw error string to find
18
+ // which of many trials actually broke, instead of seeing it named outright.
19
+ //
20
+ // The seed is fixed, not time-based: a failure must be exactly reproducible
21
+ // by re-running this file, not a flake that vanishes on the next run. Only
22
+ // change SEED deliberately (and say why in the commit), never to make a
23
+ // failure go away. Trial counts are tuned to keep the whole file in the
24
+ // 10-20 second range as part of `npm test`; if a new property needs more
25
+ // trials to be meaningful, trim another property's count to make room
26
+ // rather than letting the total creep up unnoticed.
27
+ import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
28
+ import { run } from '../src/index.js';
29
+
30
+ const SEED = 20260913;
31
+ const MAX_DEPTH = 3;
32
+
33
+ // mulberry32: a small, fast, deterministic PRNG. Good enough for generating
34
+ // test inputs -- this is not a security context -- and its whole point here
35
+ // is determinism, which Math.random() cannot offer.
36
+ function mulberry32(seed) {
37
+ let a = seed >>> 0;
38
+ return function next() {
39
+ a |= 0; a = (a + 0x6d2b79f5) | 0;
40
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
41
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
42
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
43
+ };
44
+ }
45
+
46
+ // One PRNG stream, shared across every property in file order, so the
47
+ // overall run is still governed by the single SEED at the top: rerunning
48
+ // this file reruns the exact same sequence of generated inputs everywhere,
49
+ // not just within one property.
50
+ const rng = mulberry32(SEED);
51
+
52
+ function pick(list) { return list[Math.floor(rng() * list.length)]; }
53
+ function int(lo, hi) { return lo + Math.floor(rng() * (hi - lo + 1)); }
54
+
55
+ // Atoms deliberately include the awkward cases a hand-written corpus tends
56
+ // to skip: the empty atom, an embedded quote, digits-first, and an atom
57
+ // that is itself a list-notation special form.
58
+ const ATOM_POOL = [
59
+ 'a', 'b', 'c', 'foo', 'bar', 'baz',
60
+ "''", "'A weird atom'", "'with''quote'", "'123abc'", "'[]'", "'{}'",
61
+ ];
62
+ // A separate, narrower pool for properties that build/compare atom *text*
63
+ // (atom_concat, string_concat, atom_codes/chars): plain lowercase words only,
64
+ // so concatenation and character decomposition stay easy to reason about
65
+ // without also re-deriving quoting rules inside the property itself.
66
+ const WORD_POOL = ['a', 'b', 'c', 'foo', 'bar', 'baz', 'quux', 'x', 'yz', 'hello'];
67
+ const FUNCTOR_POOL = ['f', 'g', 'h', 'node', 'pair', 'wrap'];
68
+
69
+ function genAtomic() {
70
+ switch (pick(['atom', 'int', 'negint', 'bigint', 'float'])) {
71
+ case 'atom': return pick(ATOM_POOL);
72
+ case 'int': return String(int(0, 1000));
73
+ case 'negint': return String(-int(1, 1000));
74
+ case 'bigint': {
75
+ let digits = String(int(1, 9));
76
+ for (let i = 0; i < int(20, 40); i++) digits += int(0, 9);
77
+ return digits;
78
+ }
79
+ case 'float': {
80
+ const sign = rng() < 0.5 ? '-' : '';
81
+ return `${sign}${int(0, 999)}.${String(int(0, 999)).padStart(3, '0')}`;
82
+ }
83
+ default: throw new Error('unreachable');
84
+ }
85
+ }
86
+
87
+ function genTerm(depth = MAX_DEPTH) {
88
+ if (depth <= 0 || rng() < 0.4) return genAtomic();
89
+ if (rng() < 0.5) {
90
+ const items = Array.from({ length: int(0, 4) }, () => genTerm(depth - 1));
91
+ return `[${items.join(', ')}]`;
92
+ }
93
+ const args = Array.from({ length: int(1, 3) }, () => genTerm(depth - 1));
94
+ return `${pick(FUNCTOR_POOL)}(${args.join(', ')})`;
95
+ }
96
+
97
+ function genCompound() {
98
+ // Force a compound (never atomic) so univ round-tripping is meaningful.
99
+ const args = Array.from({ length: int(1, 4) }, () => genTerm(MAX_DEPTH - 1));
100
+ return `${pick(FUNCTOR_POOL)}(${args.join(', ')})`;
101
+ }
102
+
103
+ function genList(depth = 1) { return `[${Array.from({ length: int(0, 6) }, () => genTerm(depth)).join(', ')}]`; }
104
+ function genWord() { return pick(WORD_POOL); }
105
+ function genSmallInt() { return int(0, 1000); }
106
+ function genNonZeroInt() { const n = int(1, 1000); return rng() < 0.5 ? -n : n; }
107
+
108
+ // Runs one goal and confirms it actually succeeded, throwing with the goal
109
+ // text attached (so a failure names exactly what to rerun) otherwise.
110
+ //
111
+ // stats.completed_goal_lists is the one reliable success signal, not the
112
+ // printed answer text: a goal left with unbound variables (routine here,
113
+ // since `\+` discards the bindings it made) prints nothing at all despite
114
+ // succeeding, and a ground goal that succeeds is echoed back in full,
115
+ // untaken branches included -- so a literal marker atom anywhere in the
116
+ // 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
+ const result = run(prelude, { goal });
120
+ if (result.stats.completed_goal_lists < 1) {
121
+ throw new Error(`property goal did not hold (failed or errored): ${goal}`);
122
+ }
123
+ return result;
124
+ }
125
+
126
+ // Registers `trials` individually-reported trials of one property. genFn()
127
+ // builds a fresh random goal (as a string) each time; it alone decides what
128
+ // varies between trials, so it is called with no arguments (the shared
129
+ // module-level `rng` is what actually advances).
130
+ function property(reporter, name, trials, genFn, options) {
131
+ for (let trial = 0; trial < trials; trial++) {
132
+ reporter.test(`${name} (trial ${trial + 1}/${trials})`, () => {
133
+ const goal = genFn();
134
+ try {
135
+ solve(goal, options);
136
+ } catch (error) {
137
+ throw new Error(`${name}: trial ${trial} (seed ${SEED}) failed -- ${error.message}`);
138
+ }
139
+ });
140
+ }
141
+ }
142
+
143
+ export function runProperties(reporter = new TestReporter()) {
144
+ reporter.section('Property-based round-trip checks');
145
+
146
+ property(reporter, 'write_term_to_chars / read_term_from_chars round-trips any ground term', 20, () =>
147
+ `T = (${genTerm()}), write_term_to_chars(T, [quoted(true)], C0), ` +
148
+ `append(C0, ".", C), read_term_from_chars(C, T2, []), T == T2`,
149
+ { use: ['charsio'] });
150
+
151
+ property(reporter, 'sort/2 is idempotent, ordered, and duplicate-free', 20, () =>
152
+ // No adjacent pair may fail to be strictly increasing: sort/2 removes
153
+ // duplicates per ISO 8.4.3, so a non-strict (@>=) neighbor is a bug.
154
+ // append/3 enumerating every split is plain ISO core, so this needs no
155
+ // recursive helper predicate of its own.
156
+ `L = ${genList()}, sort(L, S1), sort(S1, S2), S1 == S2, ` +
157
+ `\\+ (append(_, [X, Y|_], S1), X @>= Y)`);
158
+
159
+ property(reporter, 'append/3 and length/2 agree on the combined length', 20, () =>
160
+ `A = ${genList()}, B = ${genList()}, append(A, B, C), length(A, LA), length(B, LB), length(C, LC), ` +
161
+ `LC =:= LA + LB, ` +
162
+ // append/3's "split" mode must invert cleanly: recovering A from C at
163
+ // the same split point must reproduce the same B.
164
+ `append(A, B2, C), B2 == B`);
165
+
166
+ property(reporter, 'compare/3 gives mirrored results for swapped arguments', 15, () =>
167
+ `X = (${genTerm(2)}), Y = (${genTerm(2)}), compare(O1, X, Y), compare(O2, Y, X), ` +
168
+ `( O1 == (<) -> O2 == (>) ; O1 == (>) -> O2 == (<) ; O1 == (=), O2 == (=) )`);
169
+
170
+ property(reporter, 'keysort/2 is stable across duplicate keys', 20, () => {
171
+ // Keys drawn from a small range to force duplicates; each value is the
172
+ // pair's original position, so stability can be checked directly:
173
+ // among equal keys, positions must stay in ascending (original) order.
174
+ const pairs = Array.from({ length: int(2, 10) }, (_, index) => `${int(0, 3)}-${index}`);
175
+ // No adjacent pair may go backward: keys must never decrease, and where
176
+ // two keys tie, the original index (the value half) must not decrease
177
+ // either -- that is exactly what "stable" means.
178
+ return `L = [${pairs.join(', ')}], keysort(L, S), ` +
179
+ `\\+ (append(_, [K1-V1, K2-V2|_], S), (K1 @> K2 ; K1 == K2, V1 > V2))`;
180
+ });
181
+
182
+ property(reporter, 'univ (=..) round-trips any compound term', 15, () =>
183
+ `T = (${genCompound()}), T =.. L, T3 =.. L, T3 == T`);
184
+
185
+ property(reporter, 'copy_term/2 preserves variable sharing without aliasing the original', 20, () => {
186
+ // Alternate sharing shapes: (shared, shared, fresh), (fresh, shared, shared).
187
+ const filler = genAtomic();
188
+ const template = pick([
189
+ `f(X, X, ${filler})`,
190
+ `g(${filler}, X, X)`,
191
+ `h(X, ${filler}, X)`,
192
+ ]);
193
+ return `T = ${template}, copy_term(T, C), variant(T, C), ` +
194
+ // The two positions that shared a variable in T must still be
195
+ // identical to each other in the copy (structure preserved) ...
196
+ `T =.. [_, TA, TB, TC], C =.. [_, CA, CB, CC], ` +
197
+ `( (TA == TB, TA \\== ${filler}) -> CA == CB ; true ), ` +
198
+ `( (TB == TC, TB \\== ${filler}) -> CB == CC ; true ), ` +
199
+ `( (TA == TC, TA \\== ${filler}) -> CA == CC ; true )`;
200
+ });
201
+
202
+ property(reporter, 'reverse/2 is its own inverse', 15, () =>
203
+ `L = ${genList()}, reverse(L, R), reverse(R, L2), L == L2`);
204
+
205
+ property(reporter, 'atom_codes/2 round-trips any atom', 15, () =>
206
+ `atom_codes(${genWord()}, C0), atom_codes(A2, C0), atom_codes(A2, C1), C0 == C1`);
207
+
208
+ property(reporter, 'atom_chars/2 round-trips any atom', 15, () =>
209
+ `atom_chars(${genWord()}, C0), atom_chars(A2, C0), atom_chars(A2, C1), C0 == C1`);
210
+
211
+ property(reporter, 'char_code/2 round-trips any character', 15, () => {
212
+ const code = int(32, 126); // printable ASCII, safe to embed as a bare integer
213
+ return `char_code(Ch, ${code}), char_code(Ch2, ${code}), Ch == Ch2`;
214
+ });
215
+
216
+ property(reporter, 'number_codes/2 round-trips any integer', 15, () =>
217
+ `number_codes(${genNonZeroInt()}, C), number_codes(N, C), number_codes(N, C2), C == C2`);
218
+
219
+ property(reporter, 'succ/2 agrees with +1', 15, () => {
220
+ const n = int(0, 100000);
221
+ return `succ(${n}, S), S =:= ${n} + 1, succ(P, S), P =:= ${n}`;
222
+ });
223
+
224
+ property(reporter, 'abs/1 and sign/1 reconstruct the original integer', 15, () => {
225
+ const n = genNonZeroInt();
226
+ return `X is ${n}, A is abs(X), A >= 0, S is sign(X), V is S * A, V =:= X`;
227
+ });
228
+
229
+ property(reporter, 'nth0/3 and nth1/3 agree on the same element', 15, () => {
230
+ const list = `[${Array.from({ length: int(1, 6) }, () => genTerm(1)).join(', ')}]`;
231
+ const pick0 = int(0, 100); // reduced mod the list's actual length below
232
+ return `L = ${list}, length(L, Len), I is ${pick0} mod Len, ` +
233
+ `nth0(I, L, X), I1 is I + 1, nth1(I1, L, X2), X == X2`;
234
+ });
235
+
236
+ property(reporter, 'last/2 names the element append/3 would split off', 15, () => {
237
+ const list = `[${Array.from({ length: int(1, 6) }, () => genTerm(1)).join(', ')}]`;
238
+ return `L = ${list}, last(L, X), append(_, [X], L)`;
239
+ });
240
+
241
+ property(reporter, 'min_list/2 and max_list/2 bound every element', 15, () => {
242
+ const list = `[${Array.from({ length: int(1, 8) }, () => genNonZeroInt()).join(', ')}]`;
243
+ return `L = ${list}, min_list(L, Mn), max_list(L, Mx), \\+ (member(E, L), (E < Mn ; E > Mx))`;
244
+ });
245
+
246
+ property(reporter, 'sum_list/2 is additive under append/3', 15, () => {
247
+ const a = `[${Array.from({ length: int(0, 5) }, () => genSmallInt()).join(', ')}]`;
248
+ const b = `[${Array.from({ length: int(0, 5) }, () => genSmallInt()).join(', ')}]`;
249
+ return `A = ${a}, B = ${b}, sum_list(A, SA), sum_list(B, SB), append(A, B, C), sum_list(C, SC), ` +
250
+ `SC =:= SA + SB`;
251
+ });
252
+
253
+ // atom_concat/3 associativity needs the SAME three words on both sides of
254
+ // the regrouping -- three independent genWord() calls per side would pick
255
+ // different words each time and prove nothing.
256
+ property(reporter, 'atom_concat/3 regroups the same three atoms consistently', 15, () => {
257
+ const [w1, w2, w3] = [genWord(), genWord(), genWord()];
258
+ return `atom_concat(${w1}, ${w2}, AB), atom_concat(AB, ${w3}, ABC1), ` +
259
+ `atom_concat(${w2}, ${w3}, BC), atom_concat(${w1}, BC, ABC2), ABC1 == ABC2`;
260
+ });
261
+
262
+ property(reporter, 'string_concat/3 regroups the same three atoms consistently', 15, () => {
263
+ const [w1, w2, w3] = [genWord(), genWord(), genWord()];
264
+ return `string_concat(${w1}, ${w2}, AB), string_concat(AB, ${w3}, ABC1), ` +
265
+ `string_concat(${w2}, ${w3}, BC), string_concat(${w1}, BC, ABC2), ABC1 == ABC2`;
266
+ }, { use: ['strings'] });
267
+
268
+ property(reporter, 'atom_string/2 round-trips any atom', 12, () =>
269
+ `atom_string(${genWord()}, S), atom_string(A2, S), atom_string(A2, S2), S == S2`,
270
+ { use: ['strings'] });
271
+
272
+ property(reporter, 'number_string/2 round-trips any integer', 12, () =>
273
+ `number_string(${genNonZeroInt()}, S), number_string(N, S), number_string(N, S2), S == S2`,
274
+ { use: ['strings'] });
275
+
276
+ property(reporter, 'list_to_set/2 is idempotent', 15, () =>
277
+ `L = ${genList()}, list_to_set(L, S1), list_to_set(S1, S2), S1 == S2`,
278
+ { use: ['lists'] });
279
+
280
+ property(reporter, 'permutation/2 preserves length and distinct-element order', 12, () => {
281
+ const list = `[${Array.from({ length: int(0, 5) }, () => genTerm(1)).join(', ')}]`;
282
+ return `L = ${list}, once(permutation(L, P)), length(L, N), length(P, N), sort(L, SL), sort(P, SP), SL == SP`;
283
+ });
284
+
285
+ property(reporter, 'between/3 generates exactly the requested inclusive range', 10, () => {
286
+ const lo = int(-5, 5);
287
+ const hi = lo + int(0, 8);
288
+ return `findall(X, between(${lo}, ${hi}, X), L), length(L, N), N =:= ${hi} - ${lo} + 1, ` +
289
+ `nth0(0, L, F), F =:= ${lo}, last(L, La), La =:= ${hi}`;
290
+ });
291
+
292
+ reporter.sectionTotal('property-based round-trip checks');
293
+ }
294
+
295
+ if (isMainModule(import.meta.url)) await runStandalone(runProperties);
@@ -10438,7 +10438,7 @@ must preserve the same observable outcome. Additional normal-mode syntax may
10438
10438
  accept texts outside the strict grammar, but it may not reinterpret an accepted
10439
10439
  standard case.
10440
10440
 
10441
- The file-based conformance corpus contains 887 cases, including 470 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
10441
+ The file-based conformance corpus contains 905 cases, including 479 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
10442
10442
  Separate exact-output suites check 229 normal examples and 61 proof examples; all executable chapter programs are parsed and their declared goals are executed. The nine-case
10443
10443
  playground contract suite imports the production worker, sends real reasoning
10444
10444
  requests through its message protocol, and crawls the served module graph for