eyeprolog 1.3.45 → 1.3.47

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.45",
6
+ "version": "1.3.47",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -0,0 +1,8 @@
1
+ // ISO/IEC 13211-1 implementation-defined processor limits.
2
+ // Keep these values in one place so Prolog flags and all predicates that
3
+ // expose representation limits cannot drift apart.
4
+
5
+ // A finite arity limit keeps the Part 1 max_arity flag truthful. 65,535 is
6
+ // comfortably representable by the JavaScript term model while giving source
7
+ // parsing and term construction one stable ISO-visible boundary.
8
+ export const ISO_MAX_ARITY = 65535;
package/src/iso.js CHANGED
@@ -14,6 +14,7 @@ import { formatTermForWrite } from './write.js';
14
14
  import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
15
15
  import { INVALID_UTF8_SENTINEL } from './io.js';
16
16
  import { CharacterRepresentationError, isStrictIsoPcsCharacter, isStrictIsoPcsCodePoint } from './iso-character.js';
17
+ import { ISO_MAX_ARITY } from './iso-limits.js';
17
18
  import {
18
19
  characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
19
20
  } from './syntax-scan.js';
@@ -389,18 +390,26 @@ function* functorBuiltin({ goal, env }) {
389
390
  if (unify(goal.args[1], name, next) && unify(goal.args[2], numberTerm(term.arity), next)) yield next;
390
391
  return;
391
392
  }
393
+
392
394
  const name = deref(goal.args[1], env);
393
- const arity = requireInteger(goal.args[2], env);
394
- if (arity < 0n) throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[2], env));
395
- if (arity > BigInt(Number.MAX_SAFE_INTEGER)) throw new PrologError('representation_error(max_arity)');
395
+ const arityTerm = deref(goal.args[2], env);
396
+ // ISO 8.5.1.3 orders construction-mode diagnostics. A compound Name is an
397
+ // atomic type error (before an Arity type error); only an atomic non-atom
398
+ // used for positive arity receives type_error(atom,...).
399
+ if (name.type === VAR) throw new PrologError('instantiation_error');
400
+ if (arityTerm.type === VAR) throw new PrologError('instantiation_error');
401
+ if (name.type === COMPOUND) throw new PrologError('type_error(atomic)', name);
402
+ if (arityTerm.type !== NUMBER || !isDecimalInteger(arityTerm.name)) {
403
+ throw new PrologError('type_error(integer)', arityTerm);
404
+ }
405
+ const arity = BigInt(arityTerm.name);
406
+ if (arity > 0n && name.type !== ATOM) throw new PrologError('type_error(atom)', name);
407
+ if (arity > BigInt(ISO_MAX_ARITY)) throw new PrologError('representation_error(max_arity)');
408
+ if (arity < 0n) throw new PrologError('domain_error(not_less_than_zero)', arityTerm);
396
409
  if (arity === 0n) {
397
- if (name.type === VAR) throw new PrologError('instantiation_error');
398
- if (name.type === COMPOUND) throw new PrologError('type_error(atomic)', name);
399
410
  if (unify(goal.args[0], name, next)) yield next;
400
411
  return;
401
412
  }
402
- if (name.type === VAR) throw new PrologError('instantiation_error');
403
- if (name.type !== ATOM) throw new PrologError('type_error(atom)', name);
404
413
  const id = ++isoFresh;
405
414
  if (unify(goal.args[0], compound(name.name, Array.from({ length: Number(arity) }, (_, i) => variable(`__functor${id}_${i}`))), next)) yield next;
406
415
  }
@@ -420,6 +429,11 @@ function* univBuiltin({ goal, env }) {
420
429
  const term = deref(goal.args[0], env);
421
430
  const next = env.clone();
422
431
  if (term.type !== VAR) {
432
+ // 8.5.3.3(b) also applies in decomposition mode: a fixed second argument
433
+ // that is neither a list nor a partial list is an error, not failure.
434
+ if (listKind(goal.args[1], env) === 'nonlist') {
435
+ throw new PrologError('type_error(list)', deref(goal.args[1], env));
436
+ }
423
437
  const items = term.type === COMPOUND ? [atom(term.name), ...term.args] : [term];
424
438
  if (unify(goal.args[1], listFromItems(items), next)) yield next;
425
439
  return;
@@ -438,6 +452,7 @@ function* univBuiltin({ goal, env }) {
438
452
  return;
439
453
  }
440
454
  const name = requireAtom(items[0], env);
455
+ if (items.length - 1 > ISO_MAX_ARITY) throw new PrologError('representation_error(max_arity)');
441
456
  if (unify(goal.args[0], compound(name.name, items.slice(1)), next)) yield next;
442
457
  }
443
458
 
@@ -519,20 +534,15 @@ function* clauseBuiltin({ solver, goal, env }) {
519
534
  const head = deref(goal.args[0], env);
520
535
  if (head.type === VAR) throw new PrologError('instantiation_error');
521
536
  if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
522
- callableOrVariable(goal.args[1], env);
523
537
  const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
524
- if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head)) {
525
- throw new PrologError('permission_error(access, private_procedure)', indicator);
526
- }
527
538
  const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
528
- if (!group) return;
529
- // ISO 7.5.3 makes dynamic procedures public and static user-defined
530
- // procedures private by default. EyeProlog's normal profile keeps static
531
- // clauses inspectable for proof tooling; strict core mode restores the ISO
532
- // access rule used by clause/2.
533
- if (solver.isoStrict && !group.dynamic) {
539
+ // ISO 8.8.1.3 places private-procedure access before Body callability.
540
+ if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head) ||
541
+ (solver.isoStrict && group && !group.dynamic)) {
534
542
  throw new PrologError('permission_error(access, private_procedure)', indicator);
535
543
  }
544
+ callableOrVariable(goal.args[1], env);
545
+ if (!group) return;
536
546
  for (const clause of group.clauses) {
537
547
  const pair = compound('$clause', [clause.head, clauseBodyTerm(clause.body)]);
538
548
  const copied = freshCopy(pair, new Env());
@@ -649,7 +659,7 @@ function predicateIndicatorParts(term, env) {
649
659
  if (name.type !== ATOM) throw new PrologError('type_error(atom)', name);
650
660
  const integer = BigInt(arity.name);
651
661
  if (integer < 0n) throw new PrologError('domain_error(not_less_than_zero)', arity);
652
- if (integer > BigInt(Number.MAX_SAFE_INTEGER)) throw new PrologError('representation_error(max_arity)');
662
+ if (integer > BigInt(ISO_MAX_ARITY)) throw new PrologError('representation_error(max_arity)');
653
663
  return { name: name.name, arity: Number(integer), indicator };
654
664
  }
655
665
 
@@ -688,7 +698,8 @@ function* setPrologFlagBuiltin({ solver, goal, env }) {
688
698
  if (flag.type !== ATOM) throw new PrologError('type_error(atom)', flag);
689
699
  const definition = solver.prologFlags.get(flag.name);
690
700
  if (!definition) throw new PrologError('domain_error(prolog_flag)', flag);
691
- if (value.type !== ATOM || !definition.allowed.includes(value.name)) {
701
+ const expectedType = definition.valueType ?? ATOM;
702
+ if (value.type !== expectedType || !definition.allowed.includes(value.name)) {
692
703
  throw new PrologError('domain_error(flag_value)', compound('+', [flag, value]));
693
704
  }
694
705
  if (!definition.changeable) throw new PrologError('permission_error(modify, flag)', flag);
@@ -1761,7 +1772,7 @@ function validCharacterCode(value, solver = null) {
1761
1772
  function listToAtomInput(list, env, kind, solver = null) {
1762
1773
  const { items, tail } = listElements(list, env);
1763
1774
  if (tail.type === VAR || items.some((item) => item.type === VAR)) throw new PrologError('instantiation_error');
1764
- if (tail.type !== ATOM || tail.name !== '[]') throw new PrologError('type_error(list)', tail);
1775
+ if (tail.type !== ATOM || tail.name !== '[]') throw new PrologError('type_error(list)', list);
1765
1776
  if (kind === 'chars') {
1766
1777
  const invalid = items.find((item) => !oneChar(item));
1767
1778
  if (invalid) throw new PrologError('type_error(character)', invalid);
@@ -2072,11 +2083,13 @@ function flatFindallTemplate(template) {
2072
2083
 
2073
2084
  function* findallBuiltin({ solver, goal, env }) {
2074
2085
  const [template, innerGoal, bag] = goal.args;
2086
+ // ISO 8.10.1.3 diagnoses Goal before Instances when both are erroneous.
2087
+ const invoked = callable(innerGoal, env);
2075
2088
  assertListOrPartial(bag, env);
2076
2089
  const collector = solver.cloneForInnerGoal(10000000);
2077
2090
  let compact = flatFindallTemplate(template);
2078
2091
  let collected = compact == null ? [] : null;
2079
- for (const answerEnv of collector.solve([callable(innerGoal, env)], env.clone(), 0)) {
2092
+ for (const answerEnv of collector.solve([invoked], env.clone(), 0)) {
2080
2093
  if (compact != null && compact.push(template, answerEnv)) continue;
2081
2094
  if (compact != null) {
2082
2095
  collected = compact.materialize();
@@ -2135,8 +2148,9 @@ function sortedUnique(items) {
2135
2148
 
2136
2149
  function allSolutionsBuiltin(asSet) {
2137
2150
  return function* ({ solver, goal, env }) {
2138
- assertListOrPartial(goal.args[2], env);
2151
+ // ISO 8.10.2.3/8.10.3.3 diagnose the iterated goal before Instances.
2139
2152
  const { iterated, quantified } = bagGoalParts(goal.args[1], env);
2153
+ assertListOrPartial(goal.args[2], env);
2140
2154
  const free = freeVariables(iterated, goal.args[0], quantified, env);
2141
2155
  const collector = solver.cloneForInnerGoal(10000000);
2142
2156
  const groups = [];
@@ -2214,7 +2228,11 @@ function* callBuiltin({ solver, goal, env }) {
2214
2228
  function* callClosureBuiltin({ solver, goal, env }) {
2215
2229
  const closure = callable(goal.args[0], env);
2216
2230
  const existing = closure.type === COMPOUND ? closure.args : [];
2217
- const invoked = compound(closure.name, [...existing, ...goal.args.slice(1)]);
2231
+ const extra = goal.args.slice(1);
2232
+ if (existing.length + extra.length > ISO_MAX_ARITY) {
2233
+ throw new PrologError('representation_error(max_arity)');
2234
+ }
2235
+ const invoked = compound(closure.name, [...existing, ...extra]);
2218
2236
  if (closure.module != null) invoked.module = closure.module;
2219
2237
  const child = solver.cloneForInnerGoal();
2220
2238
  try {
package/src/parser.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { ATOM, COMPOUND, atom, compound, cons, emptyList, numberTerm, variable } from './term.js';
4
4
  import { continuesGraphicToken, isTerminatingFullStop } from './syntax-scan.js';
5
5
  import { CharacterRepresentationError, isStrictIsoPcsCodePoint } from './iso-character.js';
6
+ import { ISO_MAX_ARITY } from './iso-limits.js';
6
7
 
7
8
 
8
9
  export class NumberRepresentationError extends Error {
@@ -723,6 +724,7 @@ class Parser {
723
724
  }
724
725
  while (true) {
725
726
  args.push(this.parseTerm(ARG_MIN_PRECEDENCE, false, false, true));
727
+ if (args.length > ISO_MAX_ARITY) throw new NumberRepresentationError('representation_error(max_arity)');
726
728
  if (this.token.type !== TOK.COMMA) break;
727
729
  this.advance();
728
730
  }
package/src/solver.js CHANGED
@@ -13,6 +13,7 @@ import { StreamManager } from './io.js';
13
13
  import { clpzStateConsistent } from './clpz.js';
14
14
  import { hardHeapLimit, softHeapLimit, usedHeapSize } from './platform.js';
15
15
  import { evaluateWfs, relationForGroup, truthOfGroundGoal } from './wfs.js';
16
+ import { ISO_MAX_ARITY } from './iso-limits.js';
16
17
  import { evaluatePositiveDatalog, relationForDatalogGroup, datalogCandidateIndexes } from './datalog.js';
17
18
 
18
19
  let freshCounter = 0;
@@ -961,7 +962,7 @@ function defaultPrologFlags(unknown = 'error', strictIso = false) {
961
962
  ['debug', { value: compound('off', []), allowed: ['on', 'off'], changeable: true }],
962
963
  ['max_integer', { value: null, allowed: [], changeable: false }],
963
964
  ['min_integer', { value: null, allowed: [], changeable: false }],
964
- ['max_arity', { value: compound('unbounded', []), allowed: ['unbounded'], changeable: false }],
965
+ ['max_arity', { value: numberTerm(ISO_MAX_ARITY), allowed: [String(ISO_MAX_ARITY)], valueType: NUMBER, changeable: false }],
965
966
  ['unknown', { value: compound(unknown, []), allowed: ['error', 'fail', 'warning'], changeable: true }],
966
967
  ['double_quotes', { value: compound('chars', []), allowed: ['chars', 'codes', 'atom'], changeable: true }],
967
968
  ['occurs_check', { value: compound('true', []), allowed: ['true', 'error'], changeable: true }],
@@ -31,9 +31,9 @@ error-ordering alternative to an individual executable assertion.
31
31
  | Standard area | Status | Current evidence |
32
32
  | --- | --- | --- |
33
33
  | Clause 6 — tokens, terms, lists, operators, quoted text | audit | Complete vendored WG17 syntax matrix, `lexical_and_curly_terms`, `scryer_lexical_terms`, operator suites, syntax-error cases, quoted-layout/escape error cases, writer/read-back regressions, and strict ASCII PCS/collation boundary tests. The implementation-defined 6.5/6.6 character-model decisions are now closed; wider shall-by-shall lexical mapping remains open. |
34
- | 7.1-7.3 — term types, term order, unification | audit | Standard-order, identity, finite-tree and occurs-check suites, Corrigendum 2 term predicates, plus strict checks for the required `variable < float < integer < atom < compound` type order and PCS-based atom collation. |
34
+ | 7.1-7.3 — term types, term order, unification | audit | Standard-order, identity, finite-tree and occurs-check suites, Corrigendum 2 term predicates, plus strict checks for the required `variable < float < integer < atom < compound` type order, PCS-based atom collation, and the finite `max_arity` representation boundary used by term construction. |
35
35
  | 7.4 — Prolog text and directives | audit | All Part 1 directive indicators are parsed; include/ensure-loaded/operator/flag/character-conversion behavior has executable coverage. Preparation-time `char_conversion/2` affects later unquoted source text and respects `char_conversion=off`. Strict preparation now enforces declaration-before-clause ordering for `dynamic/1`, `multifile/1`, and `discontiguous/1`, cross-text `multifile/1`, discontiguous clause grouping, empty declared procedures, include textual-replacement behavior, and one-time initialization per prepared program. The remaining shall-by-shall mapping is still open. |
36
- | 7.5-7.6 — database and term/clause conversion | audit | Dynamic database and logical-update-view suites. Strict mode restores Part 1 private-static/public-dynamic `clause/2` access. Public/private and multi-text requirements still need complete mapping. |
36
+ | 7.5-7.6 — database and term/clause conversion | audit | Dynamic database and logical-update-view suites. Strict mode restores Part 1 private-static/public-dynamic `clause/2` access; focused strict checks now cover `current_predicate/1`, `clause/2`, `asserta/1`, `assertz/1`, `retract/1`, Corrigendum 2 `retractall/1`, and `abolish/1` errors plus empty-procedure lifetime. The remaining clause-conversion shall-by-shall mapping is still open. |
37
37
  | 7.7 — execution and backtracking | audit | Control/search suites. Strict mode disables EyeProlog automatic tabling, cycle guards, and recursive numeric shortcuts so core execution uses ordinary clause selection/backtracking. |
38
38
  | 7.8 — control constructs and exceptions | audit | call, cut, conjunction, disjunction (including failed branches after callee-local cuts), if-then, catch/throw, renamed-copy tests. |
39
39
  | 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Exceptional-value/error-precedence matrix remains to be exhaustively enumerated. |
@@ -69,6 +69,21 @@ normal profile retains Unicode scalar character data as an explicit extension.
69
69
  The complete WG17 syntax matrix remains green under this narrower strict
70
70
  boundary.
71
71
 
72
+
73
+ The subsequent audit also reconciles the processor's arity limit: EyeProlog no
74
+ longer advertises `max_arity=unbounded` while enforcing a hidden host ceiling.
75
+ The implementation-defined Part 1 value is now `65535`, and the same boundary
76
+ is used by parsing and the standard predicates whose error tables prescribe
77
+ `representation_error(max_arity)`. Focused strict tests also lock the corrected
78
+ 8.5 term-construction errors, 8.8 clause-access precedence, 8.10 all-solutions
79
+ goal/list precedence, database update errors, and Corrigendum 3 variable
80
+ metadata traversal/write naming.
81
+
82
+ The public WG17 `number_chars/2` comparison used in preparation of Corrigendum
83
+ 2 has additionally been checked against the current strict profile. That
84
+ external comparison is useful independent evidence, but it is not yet vendored
85
+ as an offline release gate; the exit criterion below therefore remains open.
86
+
72
87
  A further issue #65 audit closes the Part 1 flag family and tightens the 8.14
73
88
  term-I/O/operator error rules. The strict flag registry now distinguishes a
74
89
  standard value that is valid but not selectable from a value outside the
@@ -95,6 +110,15 @@ EyeProlog-only evaluable atom `e`, while the Corrigendum arithmetic additions
95
110
  remain available. These corrections narrow the remaining `audit` rows but do
96
111
  not by themselves close the full shall-by-shall/error-order exit criteria.
97
112
 
113
+ The next Corrigendum 2 pass closes two smaller prescribed-error gaps.
114
+ `call/2..8` now raises `representation_error(max_arity)` when appending its
115
+ extra arguments would make the resulting goal exceed the declared `max_arity`,
116
+ rather than falling through to a procedure-existence error. Reverse
117
+ `atom_chars/2` and `atom_codes/2` now report the complete improper `List`
118
+ argument as the culprit of `type_error(list, List)`, as required by the
119
+ Corrigendum 2 replacement error clauses. Dedicated strict-core regressions pin
120
+ both behaviors.
121
+
98
122
  ## Strict-core boundary
99
123
 
100
124
  `isoStrict: true` is intentionally a **Part 1 + Corrigenda 1-3** mode. It does
@@ -65,7 +65,7 @@ Status values are:
65
65
  | 9.1.3.1 | Integer division rounding function `rndI` | Truncation toward zero, matching the `integer_rounding_function=toward_zero` flag. | **defined** — BigInt division for `//` in `src/iso-arithmetic.js`. |
66
66
  | 7.11.2.1 | Whether preparation-time `Convc` affects execution-time `Convc` | Yes: mappings created while Prolog text is prepared are retained and initialize the solver's execution-time conversion map. | **defined** — `src/parser.js`, `src/program.js`, `src/solver.js`. |
67
67
  | 7.11.2.2 | Effect when `debug=on` | The flag is accepted and stored; it does not change goal semantics or enable a debugger. | **defined** — `src/solver.js`; no semantic branch depends on `debug`. |
68
- | 7.11.2.3 | Default `max_arity` | `unbounded` in the Prolog model, subject to host memory and practical JavaScript array/index limits. | **defined** — `src/solver.js`; relevant guards report representation/resource errors. |
68
+ | 7.11.2.3 | Default `max_arity` | `65535`. The same finite limit is used by strict parsing and the standard predicates that prescribe `representation_error(max_arity)`. | **defined** — `src/iso-limits.js`, `src/solver.js`, `src/parser.js`, `src/iso.js`. |
69
69
  | 7.11.2.5 | Default `double_quotes` | `chars`. | **defined** — `src/solver.js`, parser flag state. |
70
70
  | 7.12.1 | Second argument of `error/2` | The default context term is the atom `eyeprolog`. A few implementation-specific diagnostics may deliberately supply a more specific context term. | **defined** — `formalErrorTerm()` in `src/iso.js`. |
71
71
  | 7.12.2(f) | Implementation-defined representation limits | Strict character and character-code operations are limited to the selected ASCII PCS/collating integers 0..127. Normal mode extends characters to Unicode scalar values. Arity/integer values are modeled as unbounded but may hit host/resource limits. Float input overflow uses the implementation-specific `max_float`/`min_float` representation names documented by the STC-oriented tests. | **defined** — parser/ISO numeric and character guards. |
@@ -11,12 +11,12 @@ compliance audit and the remaining work before a full conformance claim.
11
11
  | Clause 6 lexical and term syntax | tokenizer, operator parser, lists, curly terms, quotes, numeric syntax, comments, strict ASCII PCS/collation | `scryer_lexical_terms`, `lexical_and_curly_terms`, `double_quoted_lists`, `corrigendum1_double_quote_operator`, `wg17_syntax_high_risk`, `wg17_invalid_octal_escape`, `wg17_unterminated_quoted_token`, `wg17_literal_newline_in_quote`, `wg17_non_iso_escape`, strict PCS/collation tests in `run-iso-strict.mjs`, syntax error cases |
12
12
  | Clause 7 term order and unification | finite-tree unification, identity, standard order, errors | `unification_control_information`, `swipl_occurs_check`, `term_modes_and_ordering`, `logtalk_compare_standard_order` |
13
13
  | Clause 7 control and exceptions | call, cut, conjunction, disjunction, if-then-else, catch and throw | `cut_control`, `control_and_terms`, `exceptions_and_flags`, `corrigenda_catch_callability`, `throw_copies_ball` |
14
- | 8.2-8.5 term predicates | unification, Corrigendum 2 tests, comparison, sorting, creation and decomposition | `corrigenda_term_predicates`, `corrigenda_sort_keysort`, `logtalk_arg_unification`, `logtalk_univ`, associated error cases, including Corrigendum 2 `keysort/2` variable/non-pair errors |
14
+ | 8.2-8.5 term predicates | unification, Corrigendum 2 tests, comparison, sorting, creation and decomposition | `corrigenda_term_predicates`, `corrigenda_sort_keysort`, `logtalk_arg_unification`, `logtalk_univ`, associated error cases, Corrigendum 2 `keysort/2` variable/non-pair errors, and strict `functor/3` / `=../2` error-order plus max-arity checks |
15
15
  | 8.6-8.7 arithmetic predicates | `is/2` and all six arithmetic comparisons | `arithmetic`, comparison cases, isolated evaluation errors |
16
- | 8.8-8.10 database and solutions | logical update view, dynamic mutation, all-solutions grouping | `dynamic_database`, `trealla_logical_update_view`, `corrigenda_retractall`, `grouped_solutions_and_clauses` |
17
- | 8.11-8.14 streams and term I/O | text/binary streams, properties, units, read/write options and operators | `streams_and_term_io`, `operators`, Corrigendum 3 option cases, stream error cases, strict 8.14.1-8.14.4 option/error-order tests, and issue #65 checks for stream-term/property validation, text-vs-binary permissions, EOF/flush/position, and open/close semantics |
18
- | 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin` |
19
- | 8.16 atomic processing | atoms, characters, codes and number conversion with prescribed errors | `atomic_term_processing`, focused forward/reverse cases, parenthesized-number rejection, Logtalk-derived error cases |
16
+ | 8.8-8.10 database and solutions | logical update view, dynamic mutation, all-solutions grouping | `dynamic_database`, `trealla_logical_update_view`, `corrigenda_retractall`, `grouped_solutions_and_clauses`, plus strict clause/database error and all-solutions precedence checks |
17
+ | 8.11-8.14 streams and term I/O | text/binary streams, properties, units, read/write options and operators | `streams_and_term_io`, `operators`, Corrigendum 3 option cases, stream error cases, strict 8.14.1-8.14.4 option/error-order tests, Corrigendum 3 variable metadata/write naming checks, and issue #65 checks for stream-term/property validation, text-vs-binary permissions, EOF/flush/position, and open/close semantics |
18
+ | 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin`, plus strict Corrigendum 2 resulting-goal `max_arity` error coverage |
19
+ | 8.16 atomic processing | atoms, characters, codes and number conversion with prescribed errors | `atomic_term_processing`, focused forward/reverse cases, parenthesized-number rejection, Logtalk-derived error cases, and strict Corrigendum 2 improper-list culprit checks for `atom_chars/2` and `atom_codes/2` |
20
20
  | 8.17 flags and hooks | complete required Part 1 flag set, selected defaults, standard value domains/changeability, halt and character conversion | `exceptions_and_flags`, `remaining_builtins_and_directives`, flag error cases, and the strict complete-flag audit in `run-iso-strict.mjs` |
21
21
  | Clause 9 evaluable functors | integer, float, rounding, transcendental and bitwise operations | `arithmetic`, `corrigenda_arithmetic`, `corrigenda_atan2_zero`, `corrigenda_integer_negative_power`, strict exclusion of the normal-mode `e` evaluable extension and zero-arity unknown-evaluable error checks |
22
22
  | Module compatibility profile (related to ISO/IEC 13211-2 and later WG17 amendment work) | module declarations, exports, imports, qualification, meta-predicate context | `modules/qualified_call`, `modules/selective_library_import`, `dcg_module_nonterminal_indicator` |
@@ -22,8 +22,9 @@ target and the normal-mode module/DCG compatibility families. [ISO-COMPLIANCE.md
22
22
  processor-requirement ledger for the Part 1 strict-core audit. This suite is
23
23
  still not an independent certification of every processor requirement, lexical
24
24
  edge, or option combination. Selected prescribed-error sequences are now
25
- checked exactly (including ISO 8.14.1-8.14.4), while the remaining built-in
26
- error-order matrix is tracked in the compliance ledger. Cases under `iso/`
25
+ checked exactly (including term construction, database/all-solutions predicates,
26
+ and ISO 8.14.1-8.14.4), while the remaining built-in error-order matrix is
27
+ tracked in the compliance ledger. Cases under `iso/`
27
28
  identify standards-derived behavior; other directories cover EyeProlog host
28
29
  contracts and extensions. EyeProlog-only execution features such as automatic
29
30
  tabling and `tnot/1` well-founded negation are outside the Part 1 strict-core
@@ -1,3 +1,3 @@
1
1
  answer(7, red, caught(eyeprolog), first, caught(eyeprolog)).
2
2
  answer(7, red, caught(eyeprolog), second, caught(eyeprolog)).
3
- flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true)]).
3
+ flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, 65535), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true)]).
@@ -1 +1 @@
1
- error(type_error(atom), foo(a))
1
+ error(type_error(atomic), foo(a))
@@ -55,7 +55,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
55
55
  'current_prolog_flag(integer_rounding_function,toward_zero)',
56
56
  'current_prolog_flag(char_conversion,on)',
57
57
  'current_prolog_flag(debug,off)',
58
- 'current_prolog_flag(max_arity,unbounded)',
58
+ 'current_prolog_flag(max_arity,65535)',
59
59
  'current_prolog_flag(unknown,error)',
60
60
  'current_prolog_flag(double_quotes,chars)',
61
61
  ]) equal(answers(goalText), 1, goalText);
@@ -71,8 +71,8 @@ export function runIsoStrict(reporter = new TestReporter()) {
71
71
  'permission_error(modify, flag)', 'bounded valid-but-fixed value');
72
72
  equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(integer_rounding_function,down)' })).formal,
73
73
  'permission_error(modify, flag)', 'rounding valid-but-fixed value');
74
- equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(max_arity,unbounded)' })).formal,
75
- 'permission_error(modify, flag)', 'max_arity fixed value');
74
+ equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(max_arity,65535)' })).formal,
75
+ 'permission_error(modify, flag)', 'max_arity valid-but-fixed value');
76
76
  equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(max_integer,unbounded)' })).formal,
77
77
  'domain_error(flag_value)', 'max_integer has no value when unbounded');
78
78
  equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(unknown,maybe)' })).formal,
@@ -81,6 +81,15 @@ export function runIsoStrict(reporter = new TestReporter()) {
81
81
  'domain_error(prolog_flag)', 'unsupported flag');
82
82
  });
83
83
 
84
+ reporter.test('keeps max_arity consistent with term and predicate-indicator limits', () => {
85
+ equal(capture(() => run('', { isoStrict: true, goal: 'functor(_,foo,65536)' })).formal,
86
+ 'representation_error(max_arity)', 'functor/3 arity boundary');
87
+ equal(capture(() => run('', { isoStrict: true, goal: 'abolish(foo/65536)' })).formal,
88
+ 'representation_error(max_arity)', 'abolish/1 arity boundary');
89
+ equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(max_arity,unbounded)' })).formal,
90
+ 'domain_error(flag_value)', 'unbounded is not the selected max_arity value');
91
+ });
92
+
84
93
  reporter.test('preparation-time char_conversion affects later unquoted source only', () => {
85
94
  const program = Program.parse(
86
95
  ":- char_conversion(x,y).\np(x).\nquoted('x').\n:- set_prolog_flag(char_conversion,off).\nraw(x).\n",
@@ -153,6 +162,27 @@ export function runIsoStrict(reporter = new TestReporter()) {
153
162
  includes(normal.stdout, '"ab"', 'normal-profile extension remains available');
154
163
  });
155
164
 
165
+ reporter.test('follows Corrigendum 3 variable metadata traversal and write naming', () => {
166
+ const readMetadata = run('', {
167
+ isoStrict: true,
168
+ goal: "read_term(f(B,A,B,C,D,E),[variables([B,A,C,D,E]),variable_names(['B'=B,'A'=A,'C'=C,'_D'=D]),singletons(['A'=A,'C'=C,'_D'=D])])",
169
+ ioOptions: { input: 'f(B,A,B,C,_D,_).' },
170
+ });
171
+ equal(readMetadata.stats.completed_goal_lists, 1, 'read metadata order');
172
+
173
+ const named = run('', {
174
+ isoStrict: true,
175
+ goal: "write_term(f(X,Y,X),[quoted(true),variable_names([z=X,a=X,y=Y])])",
176
+ });
177
+ includes(named.stdout, 'f(z,y,z)', 'leftmost variable name wins');
178
+
179
+ const ignoredNonVariable = run('', {
180
+ isoStrict: true,
181
+ goal: "write_term(X,[variable_names([ignored=42,x=X])])",
182
+ });
183
+ includes(ignoredNonVariable.stdout, 'x', 'non-variable right side is permitted and ignored');
184
+ });
185
+
156
186
  reporter.test('follows the ISO 8.14.1.3 read_term/3 error order', () => {
157
187
  equal(capture(() => run('', { isoStrict: true, goal: 'read_term(f(a),_,[X])' })).formal,
158
188
  'instantiation_error', 'partial/variable option before stream domain');
@@ -213,6 +243,45 @@ export function runIsoStrict(reporter = new TestReporter()) {
213
243
  'type_error(atom)', 'operator type');
214
244
  });
215
245
 
246
+ reporter.test('follows ISO term-construction error types and precedence', () => {
247
+ equal(capture(() => run('', { isoStrict: true, goal: 'functor(_,foo(a),1)' })).formal,
248
+ 'type_error(atomic)', 'functor compound name');
249
+ equal(capture(() => run('', { isoStrict: true, goal: 'functor(_,foo(a),bad)' })).formal,
250
+ 'type_error(atomic)', 'functor name error before arity type');
251
+ equal(capture(() => run('', { isoStrict: true, goal: "'=..'(foo,bar)" })).formal,
252
+ 'type_error(list)', '=../2 fixed non-list');
253
+ });
254
+
255
+ reporter.test('enforces Corrigendum 2 call/N max-arity errors', () => {
256
+ equal(capture(() => run('', {
257
+ isoStrict: true,
258
+ goal: 'functor(T,f,65535), call(T,x)',
259
+ })).formal, 'representation_error(max_arity)', 'call/2 resulting arity');
260
+ });
261
+
262
+ reporter.test('reports the complete List culprit for Corrigendum 2 atomic conversions', () => {
263
+ for (const goal of ['atom_chars(A,[a|foo])', 'atom_codes(A,[97|foo])']) {
264
+ const error = capture(() => run('', { isoStrict: true, goal }));
265
+ equal(error.formal, 'type_error(list)', `${goal} formal`);
266
+ includes(error.message, '[', `${goal} complete list culprit`);
267
+ if (error.message.endsWith(', foo)')) throw new Error(`${goal}: reported only the improper tail`);
268
+ }
269
+ });
270
+
271
+ reporter.test('follows ISO clause/2 private-procedure error precedence', () => {
272
+ equal(capture(() => run('p.\n', { isoStrict: true, goal: 'clause(atom(_),4)' })).formal,
273
+ 'permission_error(access, private_procedure)', 'private procedure before body callability');
274
+ });
275
+
276
+ reporter.test('validates all-solutions goals before their output lists', () => {
277
+ for (const predicate of ['findall', 'bagof', 'setof']) {
278
+ equal(capture(() => run('', { isoStrict: true, goal: `${predicate}(X,Y,foo)` })).formal,
279
+ 'instantiation_error', `${predicate}/3 variable goal`);
280
+ equal(capture(() => run('', { isoStrict: true, goal: `${predicate}(X,4,foo)` })).formal,
281
+ 'type_error(callable)', `${predicate}/3 non-callable goal`);
282
+ }
283
+ });
284
+
216
285
  reporter.test('uses the Part 1 predefined operator table', () => {
217
286
  const program = Program.parse('', { isoStrict: true });
218
287
  equal(program.operators.has('fx\u0000?-'), true, 'fx ?-');
@@ -253,6 +322,47 @@ export function runIsoStrict(reporter = new TestReporter()) {
253
322
  equal(answers.length, 1, 'answer count');
254
323
  });
255
324
 
325
+ reporter.test('covers ISO database predicate errors and empty-procedure lifetime', () => {
326
+ const errors = [
327
+ ['current_predicate(4)', '', 'type_error(predicate_indicator)'],
328
+ ['asserta(_)', '', 'instantiation_error'],
329
+ ['asserta(4)', '', 'type_error(callable)'],
330
+ ['asserta((atom(_):-true))', '', 'permission_error(modify, static_procedure)'],
331
+ ['assertz(_)', '', 'instantiation_error'],
332
+ ['assertz(4)', '', 'type_error(callable)'],
333
+ ['retract(_)', '', 'instantiation_error'],
334
+ ['retract(4)', '', 'type_error(callable)'],
335
+ ['retract(atom(_))', '', 'permission_error(modify, static_procedure)'],
336
+ ['retractall(_)', '', 'instantiation_error'],
337
+ ['retractall(3)', '', 'type_error(callable)'],
338
+ ['retractall(retractall(_))', '', 'permission_error(modify, static_procedure)'],
339
+ ['abolish(foo/_)', '', 'instantiation_error'],
340
+ ['abolish(foo)', '', 'type_error(predicate_indicator)'],
341
+ ['abolish(foo/a)', '', 'type_error(integer)'],
342
+ ['abolish(4/1)', '', 'type_error(atom)'],
343
+ ['abolish(foo/(-1))', '', 'domain_error(not_less_than_zero)'],
344
+ ['abolish(atom/1)', '', 'permission_error(modify, static_procedure)'],
345
+ ];
346
+ for (const [goal, source, formal] of errors) {
347
+ equal(capture(() => run(source, { isoStrict: true, goal })).formal, formal, goal);
348
+ }
349
+
350
+ const retained = run(':- dynamic(p/1).\np(a).\n', {
351
+ isoStrict: true,
352
+ goal: 'retractall(p(_)), current_predicate(p/1), \\+ clause(p(_),_)',
353
+ });
354
+ equal(retained.stats.completed_goal_lists, 1, 'retractall keeps the dynamic procedure');
355
+
356
+ const empty = run(':- dynamic(empty/1).\n', { isoStrict: true, goal: 'current_predicate(empty/1)' });
357
+ equal(empty.stats.completed_goal_lists, 1, 'declared empty procedure exists');
358
+
359
+ const abolished = run(':- dynamic(q/1).\nq(a).\n', {
360
+ isoStrict: true,
361
+ goal: 'abolish(q/1), \\+ current_predicate(q/1)',
362
+ });
363
+ equal(abolished.stats.completed_goal_lists, 1, 'abolish removes the procedure');
364
+ });
365
+
256
366
  reporter.test('disables automatic tabling and recursion guards', () => {
257
367
  const group = Program.parse('p :- p.\n', { isoStrict: true }).findGroup('p', 0);
258
368
  equal(group?.recursive, false, 'recursive planner');
@@ -6004,7 +6004,7 @@ silently changing a static program.
6004
6004
  | `debug` | `off` | `on`, `off` | yes |
6005
6005
  | `max_integer` | no current value because `bounded=false` | not applicable | no |
6006
6006
  | `min_integer` | no current value because `bounded=false` | not applicable | no |
6007
- | `max_arity` | `unbounded` | `unbounded` | no |
6007
+ | `max_arity` | `65535` | `65535` | no |
6008
6008
  | `unknown` | `error` | `error`, `fail`, `warning` | yes |
6009
6009
  | `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
6010
6010
  | `occurs_check` | `true` | `true`, `error` | yes |