eyeprolog 1.3.56 → 1.3.57

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.56",
6
+ "version": "1.3.57",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -393,9 +393,9 @@ function* functorBuiltin({ goal, env }) {
393
393
 
394
394
  const name = deref(goal.args[1], env);
395
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,...).
396
+ // Keep EyeProlog's deterministic construction-mode diagnostics aligned with
397
+ // the individual 8.5.1.3 conditions. Under 7.12, a call satisfying several
398
+ // error conditions at once does not acquire a globally mandated table order.
399
399
  if (name.type === VAR) throw new PrologError('instantiation_error');
400
400
  if (arityTerm.type === VAR) throw new PrologError('instantiation_error');
401
401
  if (name.type === COMPOUND) throw new PrologError('type_error(atomic)', name);
@@ -562,7 +562,9 @@ function* clauseBuiltin({ solver, goal, env }) {
562
562
  if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
563
563
  const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
564
564
  const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
565
- // ISO 8.8.1.3 places private-procedure access before Body callability.
565
+ // EyeProlog selects private-procedure access before Body callability when both
566
+ // conditions hold. ISO 7.12 permits an implementation-dependent choice when
567
+ // more than one error condition is simultaneously satisfied.
566
568
  if (isProcessorStaticProcedure(solver, head) ||
567
569
  (solver.isoStrict && group && !group.dynamic)) {
568
570
  throw new PrologError('permission_error(access, private_procedure)', indicator);
@@ -590,7 +592,7 @@ function requireClauseHead(head) {
590
592
  if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
591
593
  }
592
594
 
593
- function convertAssertedBody(term, culprit = term) {
595
+ export function convertClauseBodyTerm(term, culprit = term) {
594
596
  if (term.type === VAR) return compound('call', [term]);
595
597
  // ISO 7.6.2 converts each argument of conjunction, disjunction, and if-then
596
598
  // recursively when a term is converted to a clause body. This matters for
@@ -599,8 +601,8 @@ function convertAssertedBody(term, culprit = term) {
599
601
  // being stored and failing only if that branch is later executed.
600
602
  if (term.type === COMPOUND && [',', ';', '->'].includes(term.name) && term.arity === 2) {
601
603
  return compound(term.name, [
602
- convertAssertedBody(term.args[0], culprit),
603
- convertAssertedBody(term.args[1], culprit),
604
+ convertClauseBodyTerm(term.args[0], culprit),
605
+ convertClauseBodyTerm(term.args[1], culprit),
604
606
  ]);
605
607
  }
606
608
  if (term.type !== ATOM && term.type !== COMPOUND) {
@@ -643,7 +645,7 @@ function assertBuiltin(atStart) {
643
645
  return function* ({ solver, goal, env }) {
644
646
  const parts = clauseParts(goal.args[0], env);
645
647
  requireClauseHead(parts.head);
646
- const body = convertAssertedBody(parts.body);
648
+ const body = convertClauseBodyTerm(parts.body);
647
649
  assertModifiable(solver, parts.head, goal.module ?? 'user');
648
650
  const copied = freshCopy(compound('$clause', [parts.head, body]), env);
649
651
  solver.program.insertDynamicClause({
@@ -782,9 +784,9 @@ function* opBuiltin({ solver, goal, env }) {
782
784
  const priorityTerm = deref(goal.args[0], env);
783
785
  const specifierTerm = deref(goal.args[1], env);
784
786
 
785
- // ISO 8.14.3.3 prescribes the ordering below. In particular, all three
786
- // instantiation cases precede type/domain errors, and Operator list-shape
787
- // errors precede priority/specifier domain errors.
787
+ // Keep EyeProlog's selected 8.14.3.3 overlap order deterministic. Each
788
+ // individual condition remains observable; ISO 7.12 leaves the choice
789
+ // implementation dependent when several error conditions hold at once.
788
790
  if (priorityTerm.type === VAR) throw new PrologError('instantiation_error');
789
791
  if (specifierTerm.type === VAR) throw new PrologError('instantiation_error');
790
792
  const operator = opListPreflight(goal.args[2], env);
@@ -943,12 +945,11 @@ function optionList(term, env) {
943
945
  return items.map((item) => deref(item, env));
944
946
  }
945
947
 
946
- // read_term/3 and write_term/3 have prescribed error ordering in ISO 8.14.
947
- // In particular, a partial option list (or a variable option element) is
948
- // diagnosed before several stream errors, while the relative order of a
949
- // non-list Options term and an invalid stream-or-alias differs between the
950
- // read and write predicates. Split list-shape inspection from full option
951
- // validation so those predicates can follow the standard order exactly.
948
+ // Keep read_term/3 and write_term/3 diagnostics deterministic and make every
949
+ // 8.14 error condition independently testable. ISO 7.12 leaves the choice
950
+ // implementation dependent when several error conditions hold simultaneously,
951
+ // so this preflight order is an EyeProlog processor choice rather than a claim
952
+ // that the textual order of an error table is globally mandatory.
952
953
  function preflightOptionList(term, env) {
953
954
  const value = deref(term, env);
954
955
  if (value.type === VAR) throw new PrologError('instantiation_error');
@@ -1017,9 +1018,9 @@ function* openBuiltin({ solver, goal, env }) {
1017
1018
  const streamTarget = deref(goal.args[2], env);
1018
1019
  const optionTerm = goal.arity === 3 ? emptyList() : goal.args[3];
1019
1020
 
1020
- // ISO 8.11.5.3 + Corrigenda 2/3. Keep each prescribed condition
1021
- // observable on its own and apply the published ordering for deterministic
1022
- // diagnostics when several arguments are erroneous.
1021
+ // ISO 8.11.5.3 + Corrigenda 2/3. Keep each required condition observable
1022
+ // on its own. When several conditions hold, the order below is EyeProlog's
1023
+ // deterministic 7.12 processor choice unless a procedural rule constrains it.
1023
1024
  if (source.type === VAR) throw new PrologError('instantiation_error');
1024
1025
  if (mode.type === VAR) throw new PrologError('instantiation_error');
1025
1026
  const preflight = preflightOptionList(optionTerm, env);
@@ -1183,16 +1184,21 @@ function isStreamPropertyPattern(value) {
1183
1184
  function* streamPropertyBuiltin({ solver, goal, env }) {
1184
1185
  const reference = deref(goal.args[0], env);
1185
1186
  const propertyPattern = deref(goal.args[1], env);
1187
+ let fixedStreamId = null;
1186
1188
  if (reference.type !== VAR) {
1187
- const id = streamTermReference(goal.args[0], env);
1188
- if (!solver.io.resolve(id)) throw new PrologError('domain_error(stream)', reference);
1189
+ // 8.11.8.1 enumerates (Stream, Property) pairs for currently open
1190
+ // streams. A ground term with the implementation's stream-term shape is
1191
+ // still a stream-term even when its stream is no longer open; in that
1192
+ // case the enumeration simply has no matching pair. domain_error(stream)
1193
+ // is reserved for a term which is not a stream-term at all.
1194
+ fixedStreamId = streamTermReference(goal.args[0], env);
1189
1195
  }
1190
1196
  if (!isStreamPropertyPattern(propertyPattern)) {
1191
1197
  throw new PrologError('domain_error(stream_property)', propertyPattern);
1192
1198
  }
1193
1199
  const streams = reference.type === VAR
1194
1200
  ? [...solver.io.streams.values()]
1195
- : [solver.io.resolve(streamTermReference(goal.args[0], env))];
1201
+ : [solver.io.resolve(fixedStreamId)].filter(Boolean);
1196
1202
  for (const stream of streams) {
1197
1203
  for (const property of streamProperties(stream)) {
1198
1204
  const next = env.clone();
@@ -1232,11 +1238,10 @@ function preflightInputUnitTarget(name, target, env, solver) {
1232
1238
  throw new PrologError('type_error(in_character)', value);
1233
1239
  }
1234
1240
  } else if (name.endsWith('code')) {
1235
- // ISO 8.12.1.3/8.12.2.3 puts the integer type check before stream
1236
- // diagnostics, but the integer-is-not-an-in-character-code
1237
- // representation error comes after the stream existence/mode/type/EOF
1238
- // conditions. Defer that range/repertoire check until the input entity
1239
- // itself has been inspected below.
1241
+ // Keep the selected 8.12 overlap behavior stable: integer type validation
1242
+ // is done here, while the in-character-code repertoire check is deferred
1243
+ // until after stream/entity diagnostics. ISO 7.12 permits this processor
1244
+ // choice when those error conditions overlap.
1240
1245
  if (value.type !== VAR && (value.type !== NUMBER || !isDecimalInteger(value.name))) {
1241
1246
  throw new PrologError('type_error(integer)', value);
1242
1247
  }
@@ -1290,10 +1295,9 @@ function inputUnitBuiltin(name) {
1290
1295
  if (!binary && unit != null && solver.isoStrict && !isStrictIsoPcsCharacter(unit)) {
1291
1296
  throw new PrologError('representation_error(character)');
1292
1297
  }
1293
- // For get_code/peek_code the published error table places an invalid
1294
- // in-character-code target after stream and input-entity errors. Doing
1295
- // this here prevents a fixed bad Code from masking (for example) an
1296
- // output-stream, binary-stream, past-EOF, or invalid-character error.
1298
+ // EyeProlog deliberately defers this representation check until after
1299
+ // stream and input-entity diagnostics. ISO 7.12 permits that deterministic
1300
+ // choice when several 8.12 error conditions hold simultaneously.
1297
1301
  const targetValue = deref(target, env);
1298
1302
  if (name.endsWith('code') && targetValue.type !== VAR &&
1299
1303
  !validStrictInputCharacterCode(targetValue, solver)) {
@@ -1596,9 +1600,9 @@ function* readTermBuiltin({ solver, goal, env }) {
1596
1600
  const optionTerm = goal.args[goal.arity - 1];
1597
1601
  const streamTerm = goal.arity === 3 ? deref(goal.args[0], env) : null;
1598
1602
 
1599
- // ISO 8.14.1.3 error order:
1600
- // a) variable stream; b) partial/options-variable; c) bad stream-or-alias;
1601
- // d) non-list options; e) invalid read option; then stream existence/mode.
1603
+ // EyeProlog's selected 8.14.1.3 overlap order. Each listed error condition
1604
+ // is covered independently; 7.12 makes simultaneous-error selection
1605
+ // implementation dependent unless another procedural requirement constrains it.
1602
1606
  if (streamTerm?.type === VAR) throw new PrologError('instantiation_error');
1603
1607
  const preflight = preflightOptionList(optionTerm, env);
1604
1608
  if (goal.arity === 3) streamReference(goal.args[0], env);
@@ -1712,9 +1716,9 @@ function* writeTermBuiltin({ solver, goal, env }) {
1712
1716
  const optionTerm = goal.args[goal.arity - 1];
1713
1717
  const streamTerm = goal.arity === 3 ? deref(goal.args[0], env) : null;
1714
1718
 
1715
- // ISO 8.14.2.3 error order:
1716
- // a) variable stream; b) partial/options-variable; c) non-list options;
1717
- // d) bad stream-or-alias; e) invalid write option; then stream errors.
1719
+ // EyeProlog's selected 8.14.2.3 overlap order. Each listed error condition
1720
+ // is covered independently; 7.12 makes simultaneous-error selection
1721
+ // implementation dependent unless another procedural requirement constrains it.
1718
1722
  if (streamTerm?.type === VAR) throw new PrologError('instantiation_error');
1719
1723
  const preflight = preflightOptionList(optionTerm, env);
1720
1724
  const optionItems = requireProperOptionList(preflight);
@@ -2187,7 +2191,9 @@ function flatFindallTemplate(template) {
2187
2191
 
2188
2192
  function* findallBuiltin({ solver, goal, env }) {
2189
2193
  const [template, innerGoal, bag] = goal.args;
2190
- // ISO 8.10.1.3 diagnoses Goal before Instances when both are erroneous.
2194
+ // EyeProlog validates Goal before Instances when both are erroneous. ISO 7.12
2195
+ // makes simultaneous-error selection implementation dependent; this order is
2196
+ // a documented/tested processor choice, not a blanket table-order rule.
2191
2197
  const invoked = callable(innerGoal, env);
2192
2198
  assertListOrPartial(bag, env);
2193
2199
  const collector = solver.cloneForInnerGoal(10000000);
@@ -2252,7 +2258,9 @@ function sortedUnique(items) {
2252
2258
 
2253
2259
  function allSolutionsBuiltin(asSet) {
2254
2260
  return function* ({ solver, goal, env }) {
2255
- // ISO 8.10.2.3/8.10.3.3 diagnose the iterated goal before Instances.
2261
+ // EyeProlog validates the iterated goal before Instances. ISO 7.12 makes
2262
+ // simultaneous-error selection implementation dependent; keep this stable as
2263
+ // a processor choice rather than describing it as mandatory table ordering.
2256
2264
  const { iterated, quantified } = bagGoalParts(goal.args[1], env);
2257
2265
  assertListOrPartial(goal.args[2], env);
2258
2266
  const free = freeVariables(iterated, goal.args[0], quantified, env);
package/src/program.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  parseGoalText,
13
13
  tryParseClausesFastInto,
14
14
  } from './parser.js';
15
- import { PrologError, getStrictIsoRegistry } from './iso.js';
15
+ import { PrologError, convertClauseBodyTerm, getStrictIsoRegistry } from './iso.js';
16
16
  import { currentWorkingDirectory, fs, path } from './platform.js';
17
17
  import {
18
18
  standardLibrarySources,
@@ -627,6 +627,14 @@ class ProgramBuilder {
627
627
  }
628
628
 
629
629
  if (!isDirectiveClause(clause)) {
630
+ if (program.strictIso && Array.isArray(clause.body) && clause.body.length > 0) {
631
+ // ISO 7.6.2 applies when source text is prepared as well as when a
632
+ // clause term is asserted dynamically. Keep the original variable
633
+ // objects while wrapping variable goals in call/1 so sharing with
634
+ // the head (and recursively through ;/2 and ->/2) is preserved for
635
+ // later clause/2 and retract/1 conversion back to terms.
636
+ clause.body = clause.body.map((goal) => convertClauseBodyTerm(goal));
637
+ }
630
638
  assertHeadIsDefinable(clause.head, program.strictIso);
631
639
  const head = clause.head;
632
640
  if (head.type !== ATOM && head.type !== COMPOUND) continue;
@@ -4,12 +4,21 @@ This matrix turns the broad built-in audit in `ISO-COMPLIANCE.md` into smaller,
4
4
  release-gated units. The normative baseline is ISO/IEC 13211-1:1995 plus
5
5
  Technical Corrigenda 1:2007, 2:2012, and 3:2017.
6
6
 
7
- A `covered` row has an executable assertion for the prescribed successful mode,
8
- failure behavior, or error condition. `not applicable` records a conditional
9
- standard branch that cannot occur under EyeProlog's documented processor
10
- choices. The table is intentionally explicit about what has and has not been
11
- closed: this tranche row-audits 8.2-8.5 and 8.15-8.17; 8.6-8.14 remain under
12
- the broader audit and will be expanded in later passes.
7
+ A `covered` row has executable evidence for the required successful mode,
8
+ failure behavior, or individual error condition. `not applicable` records a
9
+ conditional standard branch that cannot occur under EyeProlog's documented
10
+ processor choices. ISO 7.12 makes the reported error implementation dependent
11
+ when more than one error condition is simultaneously satisfied. Accordingly,
12
+ this matrix treats overlap tests as stable EyeProlog processor choices unless a
13
+ more specific procedural requirement or Corrigendum constrains the result; it
14
+ does not infer a universal priority merely from the textual order of an error
15
+ table.
16
+
17
+ The table is intentionally explicit about what has and has not been closed.
18
+ The current audit now covers the complete 8.2-8.17 built-in family at the
19
+ level of prescribed modes, success/failure behavior, individual error
20
+ conditions, and explicit not-applicable processor branches. Higher-level
21
+ Clause 7 and Clause 9 semantics remain tracked separately.
13
22
 
14
23
  The focused assertions added for this matrix live in `test/run-iso-strict.mjs`;
15
24
  existing file-based cases remain additional independent evidence.
@@ -102,6 +111,171 @@ The Corrigendum 2 additions have the same shape.
102
111
  | Cor.2 8.5.5 `term_variables/2` | supplied partial/list result | covered | Corrigendum cases |
103
112
  | Cor.2 8.5.5 error (a) | second argument neither partial list nor list -> list type error | covered | strict `term_variables(t,[X|foo])` |
104
113
 
114
+ ## 8.6 — arithmetic evaluation
115
+
116
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
117
+ | --- | --- | --- | --- |
118
+ | 8.6.1 `is/2` | evaluate the second argument and unify/check the result | covered | strict `X is 1+2, X=3`, arithmetic corpus |
119
+ | 8.6.1 failed result unification | a successfully evaluated value need not unify with the first argument | covered | strict `4 is 1+2` fails |
120
+ | 8.6.1 error (a) | variable expression -> instantiation error | covered | strict `X is Y` |
121
+ | 8.6.1 delegated expression errors | evaluation may report any applicable 7.9.2 error | covered | strict `X is foo` plus Clause 9 arithmetic/error suites |
122
+
123
+ ## 8.7 — arithmetic comparison
124
+
125
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
126
+ | --- | --- | --- | --- |
127
+ | 8.7.1 `(=:=)/2` | evaluate two expressions and test arithmetic equality | covered | strict `1 =:= 1`, arithmetic comparison corpus |
128
+ | 8.7.1 `(=\=)/2` | evaluate two expressions and test arithmetic inequality | covered | strict `1 =\= 2` |
129
+ | 8.7.1 `(<)/2` | arithmetic less-than | covered | strict `1 < 2` |
130
+ | 8.7.1 `(=<)/2` | arithmetic less-than-or-equal | covered | strict `1 =< 1` |
131
+ | 8.7.1 `(>)/2` | arithmetic greater-than | covered | strict `2 > 1` |
132
+ | 8.7.1 `(>=)/2` | arithmetic greater-than-or-equal | covered | strict `2 >= 2` |
133
+ | 8.7.1 error (a) | first expression variable -> instantiation error | covered | strict variable-first assertions across the comparison family |
134
+ | 8.7.1 error (b) | second expression variable -> instantiation error | covered | strict variable-second assertions across the comparison family |
135
+ | 8.7.1 delegated expression errors | either expression may report an applicable 7.9.2 error | covered | strict `foo =:= 1` plus Clause 9 error suites |
136
+ | 8.7 mixed integer/float operation | selected Part 1 I-to-F comparison operations, including conversion overflow | covered | dedicated strict mixed-comparison tests |
137
+
138
+ ## 8.8 — clause inspection
139
+
140
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
141
+ | --- | --- | --- | --- |
142
+ | 8.8.1 `clause/2` | enumerate clauses of a public user-defined procedure | covered | dynamic-clause tests and grouped-solutions/clause corpus |
143
+ | 8.8.1 error (a) | variable head -> instantiation error | covered | strict `clause(X,_)` |
144
+ | 8.8.1 error (b) | non-callable head -> callable type error | covered | strict `clause(4,_)` |
145
+ | 8.8.1 error (c) | private procedure -> access/private-procedure permission error | covered | strict static/private tests |
146
+ | 8.8.1 error (d) | fixed non-callable Body -> callable type error | covered | strict dynamic `clause(p(_),4)` coverage |
147
+ | 8.8.1 clause-to-term conversion | source variable goals become `call/1` while preserving sharing with the head | covered | strict `foo(X):-X` -> `clause(foo(C),call(C))`; nested `;/2` and `->/2` regressions |
148
+ | 8.8.2 `current_predicate/1` | enumerate/query user-defined predicate indicators, including empty declared procedures | covered | strict row audit and empty-procedure lifetime tests |
149
+ | 8.8.2 error (a) | fixed non-predicate-indicator -> predicate-indicator type error | covered | strict `current_predicate(4)` |
150
+
151
+ ## 8.9 — database modification
152
+
153
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
154
+ | --- | --- | --- | --- |
155
+ | 8.9.1 `asserta/1` | convert term to clause and insert before existing clauses | covered | dynamic-database corpus and strict body-conversion tests |
156
+ | 8.9.1 errors (a-b) | variable/non-callable Head -> instantiation/callable type error | covered | strict `asserta(_)`, `asserta(4)` |
157
+ | 8.9.1 error (c) | Body cannot be converted to a goal -> callable type error | covered | strict `asserta((p:-4))` and nested-control conversion tests |
158
+ | 8.9.1 error (d) | static procedure -> modify/static-procedure permission error | covered | strict static-procedure tests |
159
+ | 8.9.2 `assertz/1` | convert term to clause and insert after existing clauses | covered | dynamic-database corpus and strict body-conversion tests |
160
+ | 8.9.2 errors (a-d) | same Head/Body/static families as `asserta/1` | covered | strict `assertz(_)`, `assertz(4)`, `assertz((p:-4))`, static-procedure tests |
161
+ | 8.9.3 `retract/1` | retract matching dynamic clauses re-executably using the logical update view | covered | dynamic-database and logical-update-view suites |
162
+ | 8.9.3 errors (a-b) | variable/non-callable Head -> instantiation/callable type error | covered | strict `retract(_)`, `retract(4)` |
163
+ | Cor.2 8.9.3 error (c) | static procedure -> **modify**/static-procedure permission error | covered | strict `retract(atom(_))` and Corrigendum coverage |
164
+ | 8.9.3 clause-term matching | converted source body retains head/body variable identity when matched by `retract/1` | covered | strict `retract((foo(C):-call(C)))` regression |
165
+ | 8.9.4 `abolish/1` | remove a dynamic procedure | covered | strict lifetime tests |
166
+ | 8.9.4 errors (a-b) | variable indicator or variable Name/Arity component -> instantiation error | covered | strict `abolish(X)`, `abolish(foo/X)` |
167
+ | 8.9.4 error (c) | non-predicate-indicator -> predicate-indicator type error | covered | strict `abolish(foo)` |
168
+ | 8.9.4 error (d) | non-integer Arity -> integer type error | covered | strict `abolish(foo/a)` |
169
+ | 8.9.4 error (e) | non-atom Name -> atom type error | covered | strict `abolish(4/1)` |
170
+ | 8.9.4 error (f) | negative Arity -> non-negative domain error | covered | strict `abolish(foo/(-1))` |
171
+ | 8.9.4 finite-`max_arity` error (g) | Arity exceeds finite `max_arity` | not applicable | selected `max_arity=unbounded` |
172
+ | 8.9.4 error (h) | static procedure -> modify/static-procedure permission error | covered | strict `abolish(atom/1)` |
173
+ | Cor.2 8.9.5 `retractall/1` | remove all matching dynamic heads while retaining the procedure | covered | strict empty-procedure lifetime test and Corrigendum cases |
174
+ | Cor.2 8.9.5 errors (a-c) | variable/non-callable Head or static procedure | covered | strict `retractall(_)`, `retractall(4)`, static-procedure test |
175
+
176
+ ## 8.10 — all solutions
177
+
178
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
179
+ | --- | --- | --- | --- |
180
+ | 8.10.1 `findall/3` | collect renamed template instances in solution order; empty result succeeds with `[]` | covered | strict `[1,2]` assertion plus all-solutions corpus |
181
+ | 8.10.1 error (a) | variable Goal -> instantiation error | covered | strict `findall(X,Y,L)` |
182
+ | 8.10.1 error (b) | non-callable Goal -> callable type error | covered | strict `findall(X,4,L)` |
183
+ | 8.10.1 error (c) | Instances neither partial list nor list -> list type error | covered | strict `findall(X,true,foo)` |
184
+ | 8.10.2 `bagof/3` | non-empty grouped solution lists, re-executable over free-variable witnesses | covered | grouped-solutions corpus and strict `[1,2]` assertion |
185
+ | 8.10.2 empty solution set | goal fails rather than returning `[]` | covered | strict `bagof(X,fail,L)` |
186
+ | 8.10.2 errors (a-b) | variable/non-callable iterated-goal term -> instantiation/callable type error | covered | strict `bagof(X,Y,L)`, `bagof(X,4,L)` |
187
+ | 8.10.2 error (c) | Instances neither partial list nor list -> list type error | covered | strict `bagof(X,true,foo)` |
188
+ | 8.10.3 `setof/3` | grouped solutions sorted with duplicates removed | covered | strict duplicate/sort assertion plus grouped-solutions corpus |
189
+ | 8.10.3 empty solution set | goal fails rather than returning `[]` | covered | strict `setof(X,fail,L)` |
190
+ | 8.10.3 errors (a-b) | variable/non-callable iterated-goal term -> instantiation/callable type error | covered | strict `setof(X,Y,L)`, `setof(X,4,L)` |
191
+ | 8.10.3 error (c) | Instances neither partial list nor list -> list type error | covered | strict `setof(X,true,foo)` |
192
+ | 8.10.2-3 witness-group order where the standard leaves a choice | implementation dependent | covered | existing grouping tests document EyeProlog's stable selection without turning it into a cross-processor requirement |
193
+
194
+
195
+ ## 8.11 — stream selection and control
196
+
197
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
198
+ | --- | --- | --- | --- |
199
+ | 8.11.1 `current_input/1` | unify/query the current input stream-term | covered | strict current-input success assertion |
200
+ | 8.11.1 error | fixed argument is neither variable nor stream-term -> stream domain error | covered | strict `current_input(foo)` |
201
+ | 8.11.2 `current_output/1` | unify/query the current output stream-term | covered | strict current-output success assertion |
202
+ | 8.11.2 error | fixed argument is neither variable nor stream-term -> stream domain error | covered | strict `current_output(foo)` |
203
+ | 8.11.3 `set_input/1` | select an open input stream by stream-term or alias | covered | stream-selection corpus |
204
+ | 8.11.3 errors | variable; invalid stream-or-alias; missing stream; output stream | covered | dedicated strict assertions for all four conditions |
205
+ | 8.11.4 `set_output/1` | select an open output stream by stream-term or alias | covered | stream-selection corpus |
206
+ | 8.11.4 errors | variable; invalid stream-or-alias; missing stream; input stream | covered | dedicated strict assertions for all four conditions |
207
+ | 8.11.5 `open/4`, `open/3` | open a source/sink with standardized mode/options and return a fresh stream | covered | stream corpus plus strict successful/error probes |
208
+ | 8.11.5 errors: instantiation | variable source/mode or variable option element | covered | strict `open/4` assertions |
209
+ | 8.11.5 errors: argument shapes | non-atom mode; non-list options; non-variable stream output (Cor.2 uninstantiation) | covered | strict `open/4` assertions |
210
+ | 8.11.5 errors: domains | invalid source/sink, I/O mode, or stream option | covered | strict `open/4` assertions |
211
+ | 8.11.5 source/sink errors | missing read source or source/sink that cannot be opened | covered | strict host-I/O probes for existence/permission conditions |
212
+ | 8.11.5 alias collision | `alias(A)` already names an open stream -> open permission error with complete `alias(A)` culprit | covered | focused issue #65 alias-collision regression |
213
+ | 8.11.5 `reposition(true)` impossible | permission error when the requested stream cannot be repositioned | covered | stream option corpus and implementation-defined stream capability checks |
214
+ | 8.11.6 `close/2`, `close/1` | close an open non-standard stream and maintain current-stream fallbacks | covered | stream lifecycle corpus |
215
+ | 8.11.6 errors | variable stream/option element; non-list options; invalid stream-or-alias; invalid close option; missing stream | covered | strict close assertions |
216
+ | 8.11.6 `force(true)` | presence of `force(true)` suppresses resource/system close failure regardless of contradictory later `force(false)` | covered | dedicated two-order strict regression |
217
+ | 8.11.7 `flush_output/1`, `flush_output/0` | flush an output stream/current output | covered | strict success assertion and stream corpus |
218
+ | 8.11.7 errors | variable; invalid stream-or-alias; missing stream; input stream | covered | dedicated strict assertions |
219
+ | 8.11.8 `stream_property/2` | enumerate properties of currently open streams; order is implementation dependent | covered | stream-property corpus and strict checks |
220
+ | 8.11.8 stream-term error | fixed non-stream-term -> stream domain error | covered | strict `stream_property(foo,_)` |
221
+ | 8.11.8 property error | fixed non-stream-property -> stream-property domain error | covered | strict invalid-property assertion |
222
+ | 8.11.8 closed valid stream-term | no pair exists for a stream that is no longer open -> fail | covered | dedicated closed-stream regression |
223
+ | 8.11.8 `at_end_of_stream/0-1` | test current/named stream end or past-end state | covered | strict success and EOF corpus |
224
+ | 8.11.8 `at_end_of_stream/1` errors | variable; invalid stream-or-alias; missing stream | covered | dedicated strict assertions |
225
+ | 8.11.9 `set_stream_position/2` | set a repositionable open stream to a valid stream position | covered | dedicated positioned-stream success assertion |
226
+ | 8.11.9 errors | variable stream/position; invalid stream-or-alias/position; missing stream; non-repositionable stream | covered | dedicated strict assertions |
227
+
228
+ ## 8.12 — character input/output
229
+
230
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
231
+ | --- | --- | --- | --- |
232
+ | 8.12.1 `get_char/1-2` | input next text character or EOF indicator | covered | stream/character corpus and strict probes |
233
+ | 8.12.1 `get_code/1-2` | input next text character code or `-1` at EOF | covered | stream/code corpus and strict probes |
234
+ | 8.12.1 argument errors | variable stream; invalid fixed in-character; non-integer fixed code | covered | dedicated strict assertions |
235
+ | 8.12.1 stream errors | invalid stream-or-alias; missing stream; output stream; binary stream; past-end with `eof_action(error)` | covered | dedicated strict and constructed-stream assertions |
236
+ | 8.12.1 input entity/code representation | non-character input entity or fixed integer not an in-character code | covered | strict entity/code representation regressions; bad-code check is deferred until earlier stream/entity conditions are resolved |
237
+ | 8.12.2 `peek_char/1-2`, `peek_code/1-2` | inspect next character/code without changing stream position | covered | strict probes and stream corpus |
238
+ | 8.12.2 errors | corresponding 8.12.1 argument, stream, EOF, entity and code-representation conditions | covered | dedicated strict assertions including overlap regression |
239
+ | 8.12.3 `put_char/1-2` | output one text character | covered | output corpus |
240
+ | 8.12.3 `put_code/1-2` | output character selected by character code | covered | output corpus |
241
+ | 8.12.3 `nl/0-1` | output implementation-defined new-line character | covered | output corpus |
242
+ | 8.12.3 argument errors | variable character/code or wrong character/integer type | covered | dedicated strict assertions |
243
+ | 8.12.3 stream errors | variable/invalid/missing stream; input stream; binary stream | covered | dedicated strict and constructed-stream assertions |
244
+ | 8.12.3 character-code representation | integer outside processor character-code set | covered | strict `put_code/1` boundary assertion |
245
+
246
+ ## 8.13 — byte input/output
247
+
248
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
249
+ | --- | --- | --- | --- |
250
+ | 8.13.1 `get_byte/1-2` | input next byte or `-1` at EOF | covered | strict binary-stream success and byte corpus |
251
+ | 8.13.1 errors | variable stream; invalid fixed in-byte; invalid/missing/output stream; text stream; EOF-action conditions | covered | strict assertions and EOF stream corpus |
252
+ | 8.13.2 `peek_byte/1-2` | inspect next byte without changing position | covered | strict binary-stream success and byte corpus |
253
+ | 8.13.2 errors | corresponding fixed-byte, stream, text-stream, and EOF conditions | covered | strict assertions / shared byte-I/O implementation tests |
254
+ | 8.13.3 `put_byte/1-2` | output one byte to a binary stream | covered | strict binary-output success and byte corpus |
255
+ | 8.13.3 errors | variable byte/stream; non-byte value; invalid/missing/input stream; text stream | covered | dedicated strict assertions |
256
+
257
+ ## 8.14 — term input/output, operators, and character conversion
258
+
259
+ | Clause / predicate | Prescribed row | Status | Executable evidence |
260
+ | --- | --- | --- | --- |
261
+ | 8.14.1 `read_term/3`, `read_term/2`, `read/1-2` | read a term using the standardized read-option surface | covered | term-I/O corpus, Corrigendum 3 cases, strict success/error suite |
262
+ | 8.14.1 option/argument errors | stream/option instantiation, non-list options, invalid read option | covered | strict 8.14.1 individual-condition assertions |
263
+ | 8.14.1 stream/input errors | invalid/missing/output/binary stream, past-end state, invalid input character, syntax error | covered | strict and constructed-stream assertions |
264
+ | 8.14.1 conditional finite-number/arity representation branches | limits tied to finite selected processor bounds | not applicable where bound is unbounded | EyeProlog selects unbounded integers/`max_arity`; finite binary64 input limits remain documented separately |
265
+ | 8.14.1 Corrigendum options | `variables/1`, `variable_names/1`, `singletons/1` list traversal/unification behavior | covered | Corrigendum 3 metadata tests and STC #48 regression |
266
+ | 8.14.2 `write_term/3`, `write_term/2`, `write/1-2`, `writeq/1-2` | write terms under standardized write options | covered | term-I/O/write-back corpus and strict writer suite |
267
+ | 8.14.2 option/argument errors | stream/option instantiation, non-list options, invalid write option | covered | strict 8.14.2 assertions |
268
+ | 8.14.2 stream/output errors | invalid/missing/input/binary stream and character representation conditions | covered | strict and constructed-stream assertions |
269
+ | 8.14.2 contradictory write options | rightmost option applies | covered | writer option corpus |
270
+ | 8.14.3 `op/3` | add/remove operators subject to Part 1 restrictions | covered | operator corpus and preparation-time ordering tests |
271
+ | 8.14.3 errors | priority/specifier/name instantiation/type/domain plus protected-operator permission conditions | covered | strict `op/3` individual-condition suite |
272
+ | 8.14.4 `current_op/3` | enumerate/query current operator definitions | covered | operator corpus and strict success assertion |
273
+ | 8.14.4 errors | invalid priority/specifier/operator filters | covered | strict `current_op/3` individual-condition suite |
274
+ | 8.14.5 `char_conversion/2` | update character-conversion mapping | covered | preparation/runtime conversion tests and strict success assertion |
275
+ | 8.14.5 errors | input/output instantiation and processor-character representation conditions | covered | dedicated strict assertions |
276
+ | 8.14.6 `current_char_conversion/2` | enumerate/query current conversion mapping | covered | strict success assertion and conversion corpus |
277
+ | 8.14.6 errors | fixed input/output is not a character | covered | dedicated strict assertions |
278
+
105
279
  ## 8.15 — logic and control
106
280
 
107
281
  | Clause / predicate | Prescribed row | Status | Executable evidence |
@@ -190,16 +364,15 @@ built-in matrix does not have a gap at 8.17.
190
364
  | 8.17.4 variable status | instantiation error | covered | strict `halt(X)` |
191
365
  | 8.17.4 non-integer status | integer type error | covered | strict `halt(a)` |
192
366
 
193
- ## Remaining row-audit work
194
-
195
- This file deliberately does not relabel the whole 8.2-8.17 family as complete.
196
- The next row-by-row passes are:
367
+ ## Remaining audit work
197
368
 
198
- - 8.6-8.7 arithmetic predicates and expression-evaluation interactions;
199
- - 8.8-8.10 database and all-solutions predicates, including all conditional
200
- static/private/dynamic branches;
201
- - 8.11-8.14 streams, character/byte I/O, options, and term I/O, including the
202
- remaining option/mode cross-product.
369
+ The built-in **8.2-8.17 row audit is complete** at the level tracked by this
370
+ file: prescribed modes, success/failure behavior, individual error conditions,
371
+ and conditional/not-applicable processor branches all have explicit outcomes.
372
+ ISO 7.12 simultaneous-error selection remains an implementation-dependent
373
+ processor choice unless more specific normative text constrains it.
203
374
 
204
- Clause 9 exceptional-value rows and Clause 6/7 processor semantics remain
205
- tracked separately in `ISO-COMPLIANCE.md` and `ISO-PROCESSOR-REQUIREMENTS.md`.
375
+ The remaining #65 work is therefore above/below this built-in table: Clause 6
376
+ text/rejection mapping, Clause 7 processor/control semantics, Clause 9
377
+ exceptional-value rows, residual Clause 5 dependency rows, and the external
378
+ conversion/variable-name conformance corpora.
@@ -15,7 +15,7 @@ marked `covered` means EyeProlog has implementation and executable tests for
15
15
  that family and no known open defect in the listed behavior. A row marked
16
16
  `audit` means the family is implemented and tested, but the project has not yet
17
17
  mapped every normative `shall`, option combination, prescribed error, and
18
- error-ordering alternative to an individual executable assertion.
18
+ relevant implementation-dependent overlap choice to an explicit audit outcome.
19
19
 
20
20
  ## Processor compliance requirements
21
21
 
@@ -36,14 +36,14 @@ error-ordering alternative to an individual executable assertion.
36
36
  | 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 Unicode-scalar 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. |
37
37
  | 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 `max_arity=unbounded` compound-term model. The normal JavaScript API string term is now explicitly documented as a 5.5.4 extension and rejected at strict program/goal entry so it cannot add a sixth strict-core type. |
38
38
  | 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. |
39
- | 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 cover the database built-ins and empty-procedure lifetime. Runtime assertion conversion now applies 7.6.2 recursively through conjunction, disjunction, and if-then: nested variables become `call/1`, and an invalid nested branch is rejected while `asserta/1` or `assertz/1` converts the term instead of being stored for a later execution-time failure. Runtime protection includes the solver-native conjunction control construct `','/2` and, following STC #56's accepted-direction action item, `(:-)/1-2` for database modification/private access while leaving their call behavior distinct. The remaining clause-conversion shall-by-shall mapping is still open. |
39
+ | 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 cover the database built-ins and empty-procedure lifetime. Clause-body conversion now applies 7.6.2 both to prepared source clauses and runtime `asserta/1` / `assertz/1`: variables become `call/1`, `,/2`, `;/2`, and `->/2` are converted recursively, and head/body variable identity is preserved so `clause/2` and `retract/1` observe the converted term correctly. Runtime protection includes the solver-native conjunction control construct `','/2` and, following STC #56's accepted-direction action item, `(:-)/1-2` for database modification/private access while leaving their call behavior distinct. The remaining clause-conversion shall-by-shall mapping is still open. |
40
40
  | 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. |
41
41
  | 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. The 8.15 built-in slice (`\+/1`, `once/1`, `repeat/0`, Corrigendum 2 `call/2..8`, `false/0`) is now row-audited in [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md); the remaining general control-construct semantics ledger stays open. |
42
42
  | 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Strict mode now pins direct-variable precedence, 7.9.2 non-evaluable `F/N` errors (including STC #69), arithmetic numeric/integer type errors, float-only rounding conversions, the Part 1 mixed integer/float comparison conversion rule, I->F overflow before floating evaluable functors (STC #42), explicit `exp/1`/power underflow, and prescribed negative/zero power errors before later conversion overflow. Unbounded integer powers/shifts no longer leak host `RangeError`; finite-host exhaustion is normalized to `resource_error(memory)` in line with the Part 1 resource-error note and STC #21. The remaining exceptional-value/error-precedence rows are still being exhaustively enumerated. |
43
- | 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, and Corrigendum 3 writer cases. The audit now also covers write-mode creation/truncation, append creation, repositioned overwrite, flushing, EOF actions, close/current-stream handling, strict stream-position terms, stream-property validation, stream-term requirements, text-vs-binary permission errors, and the prescribed `alias(A)` culprit for `open/4` alias collisions. The latest 8.11/8.12 pass also makes `close/2`'s `force(true)` semantics presence-based and defers `get_code/1-2` / `peek_code/1-2` invalid in-character-code representation errors until after the earlier stream existence/mode/type/EOF/entity conditions. The full option/mode cross-product is still being mapped. |
43
+ | 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, and Corrigendum 3 writer cases. The complete 8.11-8.14 built-in mode/error family is now row-audited in [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md), including lifecycle/current-stream handling, stream positions, text/binary permissions, EOF actions, `force(true)`, input-code representation timing, term-I/O options, operators, and character conversion. `stream_property/2` now also distinguishes a malformed stream-term (domain error) from a valid stream-term whose stream is no longer open (ordinary failure). The higher-level 7.10 source/sink, buffering, positioning, and implementation-defined stream semantics remain under shall-by-shall audit. |
44
44
  | 7.11 — flags | covered | The complete Part 1 flag set, selected defaults, standard value domains, changeability, `current_prolog_flag/2`, and `set_prolog_flag/2` error behavior have dedicated strict tests. EyeProlog selects `bounded=false` and `integer_rounding_function=toward_zero`; valid alternative values of those fixed flags reach `permission_error(modify,flag,...)`, while `max_integer` and `min_integer` have no current value. STC #70 is recorded explicitly: EyeProlog has no separate finite procedure-arity ceiling, so the optional `max_procedure_arity` flag is absent while `max_arity` remains `unbounded`. Strict mode excludes the EyeProlog `occurs_check` extension. |
45
- | 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax/resource families and focused error cases. [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md) now gives explicit prescribed mode/error rows for 8.2-8.5 and 8.15-8.17, including Corrigendum additions and selected not-applicable finite-`max_arity` branches. Exact ISO 8.14.1-8.14.4 precedence and the recent 8.11/8.12 overlaps are also covered. The one-row map remains open for 8.6-8.14 and for the remaining Clause 7/9 exceptional-order interactions. |
46
- | 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md. The new [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md) closes 8.2-8.5 and 8.15-8.17 at one row per prescribed mode/error condition (with finite-`max_arity` branches explicitly marked not applicable under EyeProlog's `unbounded` choice). The still-open row audit is therefore narrowed to 8.6-8.14, especially database/all-solutions and the remaining stream/term-I/O option cross-product. |
45
+ | 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax/resource families and focused error cases. [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md) now gives explicit mode/error-condition outcomes across the complete 8.2-8.17 built-in family, including Corrigendum additions and not-applicable conditional branches. The audit records the 7.12 rule that simultaneous error conditions are implementation dependent: overlap regressions document stable EyeProlog choices rather than inventing a universal table-order requirement. Remaining work is concentrated in non-built-in Clause 7 semantics, Clause 9 exceptional interactions, and processor-level error conditions. |
46
+ | 8.2-8.17 — built-in predicates | covered | [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md) now records the complete Part 1 + Corrigenda built-in family at one row per prescribed mode, success/failure behavior, individual error condition, and conditional/not-applicable branch. The row audit exposed and fixed source/runtime 7.6.2 body conversion/variable sharing, strict host-string leakage, input-code error timing, `close/2` force handling, and closed-stream `stream_property/2` behavior. Simultaneous-error choices are documented as implementation dependent under 7.12 unless separately constrained. |
47
47
  | Clause 9 — evaluable functors | audit | Integer/float/rounding/transcendental/bitwise suites and corrigendum cases. Strict mode excludes the EyeProlog-only evaluable atom `e`, retains Corrigendum 2 arithmetic additions, reports unknown zero-arity evaluables with the required `F/0` culprit shape, distinguishes non-evaluable `F/N` errors from numeric/integer operand errors per 7.9.2 and STC #69, and enforces float-only rounding modes, performs I->F conversion before floating functors (including overflow), reports the explicit `exp/1`, `**/2`, and Corrigendum 2 `^/2` underflow conditions, preserves the power-specific undefined conditions ahead of conversion overflow, and uses the Part 1 integer-to-float rule for mixed arithmetic comparisons. Normal mode retains EyeProlog's exact mixed-type comparison and round-to-zero arithmetic behavior as extensions. Unbounded BigInt resource exhaustion is translated into the Prolog error model rather than leaking host exceptions. Host floating-point representation choices remain documented implementation-defined behavior; the remaining exceptional-value rows are still under audit. |
48
48
  | Corrigendum 1 | covered | Double-quoted atom/operator-priority corrections have dedicated cases. |
49
49
  | Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases. |
@@ -82,7 +82,7 @@ removes the artificial 65535 checks from source parsing, `functor/3`, `=../2`,
82
82
  predicate indicators, and Corrigendum 2 `call/N` closure expansion. Practical
83
83
  host exhaustion remains a resource condition rather than a declared term-arity
84
84
  boundary. Focused strict tests also lock the corrected 8.5 term-construction
85
- errors, 8.8 clause-access precedence, 8.10 all-solutions goal/list precedence,
85
+ errors, selected 8.8 clause-access and 8.10 all-solutions overlap behavior,
86
86
  database update errors, and Corrigendum 3 variable metadata traversal/write
87
87
  naming.
88
88
 
@@ -97,8 +97,9 @@ standard value that is valid but not selectable from a value outside the
97
97
  standard domain: attempts to change fixed `bounded` or
98
98
  `integer_rounding_function` to another standard value therefore reach the
99
99
  required permission error. `read_term/3`, `write_term/3`, `op/3`, and
100
- `current_op/3` now follow their prescribed error precedence, including cases
101
- where more than one argument is erroneous. Strict `write_term/2-3` accepts only
100
+ `current_op/3` now cover their required error conditions individually and keep
101
+ EyeProlog's chosen simultaneous-error behavior stable. Section 7.12 makes that
102
+ choice implementation dependent when several error conditions hold at once. Strict `write_term/2-3` accepts only
102
103
  the Part 1 plus Corrigendum 3 option surface; the normal-profile
103
104
  `double_quotes/1` write option remains an explicitly documented EyeProlog
104
105
  extension.
@@ -115,7 +116,7 @@ EOF, close, and current-stream behavior. Corrigendum 2 `keysort/2` variable and
115
116
  non-pair errors are corrected. Strict arithmetic no longer exposes the
116
117
  EyeProlog-only evaluable atom `e`, while the Corrigendum arithmetic additions
117
118
  remain available. These corrections narrow the remaining `audit` rows but do
118
- not by themselves close the full shall-by-shall/error-order exit criteria.
119
+ not by themselves close the full shall-by-shall/error-condition exit criteria.
119
120
 
120
121
  The next Corrigendum 2 pass closed the prescribed `call/2..8` max-arity
121
122
  branch for processors with a finite `max_arity`, and corrected reverse
@@ -128,8 +129,10 @@ expansion itself remains covered by strict regressions.
128
129
 
129
130
  The following Part 1 pass tightens more of the still-open prescribed-error and
130
131
  arithmetic matrix. `arg/3`, `atom_concat/3`, `sub_atom/5`, `number_chars/2`,
131
- `number_codes/2`, and `char_conversion/2` now follow the standard ordering when
132
- multiple error conditions are simultaneously present. Corrigendum 2 partial
132
+ `number_codes/2`, and `char_conversion/2` now cover their individual standard
133
+ error conditions and retain deterministic EyeProlog choices for overlapping
134
+ conditions; 7.12 explicitly leaves such simultaneous-error selection
135
+ implementation dependent unless another rule constrains it. Corrigendum 2 partial
133
136
  versus improper list distinctions are applied consistently to atomic and
134
137
  number conversion. Clause 9 strict evaluation now gives the prescribed
135
138
  number/integer operand diagnostics, checks direct-variable instantiation first,
@@ -187,6 +190,29 @@ now documented under 5.5.4, while strict `Program` construction and strict
187
190
  keeps the host extension available in normal mode without allowing it to become
188
191
  a sixth term type in the Part 1 strict execution domain.
189
192
 
193
+ A row-level 8.6-8.10 pass then turns arithmetic predicates, clause/database
194
+ operations, and all-solutions predicates into explicit mode/error-condition
195
+ entries. During that audit, strict source preparation exposed a 7.6.2 defect:
196
+ a clause such as `foo(X) :- X.` was executed as a variable goal but was not
197
+ stored in the converted `call(X)` form, so `clause(foo(C),call(C))` and the
198
+ corresponding `retract/1` pattern lost the required head/body variable identity.
199
+ Strict preparation now performs the same recursive body conversion used by
200
+ runtime assertion while preserving the original variable objects. The pass
201
+ also records the 7.12 rule for simultaneous errors, so deterministic overlap
202
+ tests are treated as processor choices unless more specific normative text
203
+ constrains them.
204
+
205
+ The subsequent 8.11-8.14 pass completes the built-in row audit. Stream
206
+ selection/control, character/code input and output, byte I/O, term I/O,
207
+ operators, and character conversion now each have explicit success/mode and
208
+ individual-error outcomes in `ISO-BUILTIN-MODE-ERROR-MATRIX.md`. During this
209
+ pass `stream_property/2` exposed a lifecycle distinction: a syntactically valid
210
+ stream-term for a stream that has been closed denotes no currently open stream
211
+ property pair and therefore fails, whereas a term that is not a stream-term is
212
+ still a `domain_error(stream, ...)`. This leaves the remaining #65 checklist
213
+ focused on Clause 6/7/9 processor semantics and external corpora rather than
214
+ unmapped built-in tables.
215
+
190
216
  ## Post-Corrigendum STC cross-check
191
217
 
192
218
  The public WG17 STC draft is tracked as useful defect-discovery material, but it
@@ -261,8 +287,9 @@ ISO/IEC 13211-1 processor” until all of the following are true:
261
287
  3. the external WG17 conversion and variable-name conformity corpora have
262
288
  joined the now-complete syntax corpus in strict core mode, with every
263
289
  difference explained or fixed;
264
- 4. prescribed modes, errors, side effects, and relevant error precedence for
265
- every Part 1 built-in have executable coverage;
290
+ 4. prescribed modes, individual error conditions, side effects, and any
291
+ precedence actually constrained by procedural rules or Corrigenda have
292
+ executable coverage, while 7.12 simultaneous-error choices are documented;
266
293
  5. every `audit gap` recorded in the ISO 5.4 decision index is either fixed or
267
294
  explicitly excluded from the strict-conformance claim; and
268
295
  6. an external conformance run has found no unexplained deviations.
@@ -12,9 +12,9 @@ compliance audit and the remaining work before a full conformance claim.
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
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, and the one-row-per-mode/error 8.2-8.5 slice in `ISO-BUILTIN-MODE-ERROR-MATRIX.md`, including conditional finite-`max_arity` branches |
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`, plus strict clause/database error and all-solutions precedence checks, recursive 7.6.2 assertion-body conversion through `,/2`, `;/2`, and `->/2`, runtime static/private protection for conjunction, and STC #56 `(:-)/1-2` database protection |
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, open/close semantics, presence-based `force(true)` close handling, late `get_code/peek_code` in-character-code representation errors, and the exact `alias(A)` collision culprit |
15
+ | 8.6-8.7 arithmetic predicates | `is/2` and all six arithmetic comparisons | `arithmetic`, comparison cases, isolated evaluation errors, and the explicit row-level 8.6-8.7 mode/error-condition audit |
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`, the complete row-level 8.8-8.10 mode/error-condition audit, source/runtime 7.6.2 conversion through `,/2`, `;/2`, and `->/2` with preserved head/body variable sharing, selected deterministic overlap checks, runtime static/private protection for conjunction, and STC #56 `(:-)/1-2` database protection |
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, the complete row-level 8.11-8.14 mode/error audit, Corrigendum 3 variable metadata/write naming checks, and issue #65 checks for stream lifecycle/property validation (including closed-stream failure), text-vs-binary permissions, EOF/flush/position, open/close semantics, presence-based `force(true)` close handling, late `get_code/peek_code` in-character-code representation errors, and the exact `alias(A)` collision culprit |
18
18
  | 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin`, plus the complete 8.15 row-audit in `ISO-BUILTIN-MODE-ERROR-MATRIX.md` |
19
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 cases, and the complete 8.16 mode/error slice in `ISO-BUILTIN-MODE-ERROR-MATRIX.md` |
20
20
  | 8.17 flags and halt | complete required Part 1 flag set, selected defaults, standard value domains/changeability, and halt status handling | `exceptions_and_flags`, `remaining_builtins_and_directives`, the complete strict flag audit, halt cases, and explicit 8.17 rows in `ISO-BUILTIN-MODE-ERROR-MATRIX.md` |
@@ -17,7 +17,7 @@ certification claim.
17
17
  | Requirement | Status | Current evidence / remaining work |
18
18
  | --- | --- | --- |
19
19
  | 5.1(a) prepare conforming Prolog text | audit | parser/preparation tests, directives, include behavior, preparation-time operators/flags/character conversion, and the complete vendored WG17 syntax matrix; the remaining Clause 6/7.4 shall-by-shall map is still open |
20
- | 5.1(b) execute conforming Prolog goals | audit | Clause 7-9 suites plus the row-audited built-in slices in `ISO-BUILTIN-MODE-ERROR-MATRIX.md`; 8.6-8.14 and remaining Clause 7/9 semantic rows are still being enumerated |
20
+ | 5.1(b) execute conforming Prolog goals | audit | Clause 7-9 suites plus the now-complete 8.2-8.17 row-audited built-in family in `ISO-BUILTIN-MODE-ERROR-MATRIX.md`; remaining Clause 7/9 processor-semantic rows are still being enumerated |
21
21
  | 5.1(c) reject nonconforming text/read terms | audit | syntax-error corpus, WG17 negative syntax cases, strict extension rejection; exhaustive Clause 6 rejection mapping remains open |
22
22
  | 5.1(d) specify permitted variations | covered | `ISO-IMPLEMENTATION-DEFINED.md` records the Part 1 implementation-defined choices and implementation-specific extension families |
23
23
  | 5.1(e) offer a strictly conforming mode | covered | CLI `--iso-strict` and API `isoStrict: true`; registry/directive/operator/flag extension filtering plus disabled implementation-specific execution shortcuts |
@@ -27,7 +27,7 @@ certification claim.
27
27
  | Requirement | Status | Current evidence / remaining work |
28
28
  | --- | --- | --- |
29
29
  | 5.2 conforming and strictly conforming Prolog text boundary | audit | strict parser accepts Part 1 + Corrigenda syntax plus implementation-defined PCS choices and rejects implementation-specific language facilities; full Clause 6/7.4 dependency audit remains open |
30
- | 5.3 conforming and strictly conforming Prolog goal boundary | audit | strict registry/control/evaluable filtering and Clause 7-9 regression suites; remaining prescribed mode/error and goal-semantics rows remain open |
30
+ | 5.3 conforming and strictly conforming Prolog goal boundary | audit | strict registry/control/evaluable filtering and Clause 7-9 regression suites; the built-in 8.2-8.17 mode/error rows are closed, while remaining general goal/control and Clause 9 semantic rows stay open |
31
31
  | 5.4 accompanying documentation for implementation-defined and implementation-specific features | covered | `ISO-IMPLEMENTATION-DEFINED.md`, *The Art of EyeProlog*, strict-boundary documentation, and release-facing conformance ledgers |
32
32
 
33
33
  ## 5.5 — extension boundaries
@@ -43,7 +43,7 @@ certification claim.
43
43
  | 5.5.6 additional side effects | audit | host/proof/statistics/library effects are outside the strict registry or documented normal-mode behavior; exhaustive side-effect preservation mapping remains part of the Clause 7.7/7.10 audit |
44
44
  | 5.5.7 additional control constructs | covered | `tnot/1` and implementation-specific execution optimizations are absent/disabled in strict mode; standard control constructs remain separately audited |
45
45
  | 5.5.8 additional flags | covered | normal `occurs_check` extension is absent from strict mode; the Part 1 flag family is fully audited |
46
- | 5.5.9 additional built-in predicates and error forms | audit | strict registry excludes normal-profile library/native additions; `ISO-BUILTIN-MODE-ERROR-MATRIX.md` now closes 8.2-8.5 and 8.15-8.17 row-by-row, while 8.6-8.14 remain open |
46
+ | 5.5.9 additional built-in predicates and error forms | covered | strict registry excludes normal-profile library/native additions; `ISO-BUILTIN-MODE-ERROR-MATRIX.md` closes the complete 8.2-8.17 family row-by-row; simultaneous-error choices are documented per 7.12 rather than treated as a global table-order mandate |
47
47
  | 5.5.10 additional evaluable functors/types | audit | strict mode excludes normal-profile arithmetic extensions such as evaluable `e`; Clause 9 exceptional-value and error-precedence rows remain under audit |
48
48
  | 5.5.11 reserved atoms | not applicable | EyeProlog declares no reserved-atom extension; extension names remain ordinary atoms unless used in a documented syntactic/predicate/directive role |
49
49
 
@@ -23,10 +23,10 @@ profile and implementation extensions. The default registry covers the exact
23
23
  predicate indicators listed in Appendix B of the book across the Part 1 strict-core
24
24
  target and the normal-mode module/DCG compatibility families. [ISO-COMPLIANCE.md](ISO-COMPLIANCE.md) is the explicit
25
25
  release-facing ledger for the Part 1 strict-core audit. This suite is
26
- still not an independent certification of every processor requirement, lexical
27
- edge, or option combination. The 8.2-8.5 and 8.15-8.17 built-in slices are now
28
- tracked one prescribed mode/error row at a time; 8.6-8.14 and the remaining
29
- processor semantics stay explicitly open in the dedicated audit files. Cases under `iso/`
26
+ still not an independent certification of every processor requirement or lexical
27
+ edge. The complete 8.2-8.17 built-in family is now tracked one prescribed
28
+ mode/error row at a time; the remaining Clause 6/7/9 and processor semantics
29
+ stay explicitly open in the dedicated audit files. Cases under `iso/`
30
30
  identify standards-derived behavior; other directories cover EyeProlog host
31
31
  contracts and extensions. EyeProlog-only execution features such as automatic
32
32
  tabling and `tnot/1` well-founded negation are outside the Part 1 strict-core
@@ -237,7 +237,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
237
237
  includes(error.message, 'alias(user_input)', 'alias option culprit');
238
238
  });
239
239
 
240
- reporter.test('follows the ISO 8.14.1.3 read_term/3 error order', () => {
240
+ reporter.test('covers ISO 8.14.1.3 read_term/3 errors and selected overlaps', () => {
241
241
  equal(capture(() => run('', { isoStrict: true, goal: 'read_term(f(a),_,[X])' })).formal,
242
242
  'instantiation_error', 'partial/variable option before stream domain');
243
243
  equal(capture(() => run('', { isoStrict: true, goal: 'read_term(f(a),_,foo)' })).formal,
@@ -258,7 +258,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
258
258
  equal(solver.io.resolve('user_input').position, 0, 'invalid options are rejected before input');
259
259
  });
260
260
 
261
- reporter.test('follows the ISO 8.14.2.3 write_term/3 error order', () => {
261
+ reporter.test('covers ISO 8.14.2.3 write_term/3 errors and selected overlaps', () => {
262
262
  equal(capture(() => run('', { isoStrict: true, goal: 'write_term(f(a),a,[X])' })).formal,
263
263
  'instantiation_error', 'partial/variable option before stream domain');
264
264
  equal(capture(() => run('', { isoStrict: true, goal: 'write_term(f(a),a,foo)' })).formal,
@@ -271,7 +271,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
271
271
  'domain_error(write_option)', 'invalid option before stream permission');
272
272
  });
273
273
 
274
- reporter.test('follows the ISO 8.14.3.3 op/3 error order', () => {
274
+ reporter.test('covers ISO 8.14.3.3 op/3 errors and selected overlaps', () => {
275
275
  equal(capture(() => run('', { isoStrict: true, goal: 'op(a,X,0)' })).formal,
276
276
  'instantiation_error', 'specifier variable before priority type');
277
277
  equal(capture(() => run('', { isoStrict: true, goal: 'op(a,xfy,[X])' })).formal,
@@ -297,7 +297,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
297
297
  'type_error(atom)', 'operator type');
298
298
  });
299
299
 
300
- reporter.test('follows remaining Part 1 built-in error precedence', () => {
300
+ reporter.test('covers remaining Part 1 built-in error conditions and overlap choices', () => {
301
301
  const cases = [
302
302
  ['arg(a,X,_)', 'instantiation_error', 'arg/3 second-argument instantiation before index type'],
303
303
  ['atom_concat(1,X,Y)', 'instantiation_error', 'atom_concat/3 second/whole under-instantiation before first type'],
@@ -317,9 +317,9 @@ export function runIsoStrict(reporter = new TestReporter()) {
317
317
  });
318
318
 
319
319
  reporter.test('orders input-code representation errors after stream diagnostics', () => {
320
- // ISO 8.12.1.3/8.12.2.3: Code's integer type is checked early, but an
321
- // integer outside the in-character-code domain is the final listed error.
322
- // It must therefore not mask output/binary/past-EOF/entity errors.
320
+ // Keep EyeProlog's selected 8.12 overlap behavior stable: integer type
321
+ // errors are detected before stream access, while a valid integer outside
322
+ // the in-character-code domain is diagnosed after stream/entity errors.
323
323
  for (const predicate of ['get_code', 'peek_code']) {
324
324
  equal(capture(() => run('', { isoStrict: true, goal: `${predicate}(user_output,-2)` })).formal,
325
325
  'permission_error(input, stream)', `${predicate}/2 output stream before bad code`);
@@ -354,6 +354,216 @@ export function runIsoStrict(reporter = new TestReporter()) {
354
354
  'representation_error(in_character_code)', 'bad code once no earlier stream error applies');
355
355
  });
356
356
 
357
+ reporter.test('closes the ISO 8.11 stream selection/control rows', () => {
358
+ const errors = [
359
+ ['current_input(foo)', 'domain_error(stream)', 'current_input/1 stream term'],
360
+ ['current_output(foo)', 'domain_error(stream)', 'current_output/1 stream term'],
361
+ ['set_input(X)', 'instantiation_error', 'set_input/1 variable'],
362
+ ['set_input(f(a))', 'domain_error(stream_or_alias)', 'set_input/1 stream-or-alias domain'],
363
+ ['set_input(no_such_stream)', 'existence_error(stream)', 'set_input/1 closed/missing stream'],
364
+ ['set_input(user_output)', 'permission_error(input, stream)', 'set_input/1 output stream'],
365
+ ['set_output(X)', 'instantiation_error', 'set_output/1 variable'],
366
+ ['set_output(f(a))', 'domain_error(stream_or_alias)', 'set_output/1 stream-or-alias domain'],
367
+ ['set_output(no_such_stream)', 'existence_error(stream)', 'set_output/1 closed/missing stream'],
368
+ ['set_output(user_input)', 'permission_error(output, stream)', 'set_output/1 input stream'],
369
+ ['open(X,read,S,[])', 'instantiation_error', 'open/4 source instantiation'],
370
+ ['open(dummy,X,S,[])', 'instantiation_error', 'open/4 mode instantiation'],
371
+ ['open(dummy,read,S,[X])', 'instantiation_error', 'open/4 option instantiation'],
372
+ ['open(dummy,1,S,[])', 'type_error(atom)', 'open/4 mode type'],
373
+ ['open(dummy,read,S,foo)', 'type_error(list)', 'open/4 options list type'],
374
+ ['open(dummy,read,already_bound,[])', 'uninstantiation_error', 'open/4 stream output mode'],
375
+ ['open(4,read,S,[])', 'domain_error(source_sink)', 'open/4 source/sink domain'],
376
+ ['open(dummy,bad,S,[])', 'domain_error(io_mode)', 'open/4 io mode domain'],
377
+ ['open(dummy,write,S,[bogus])', 'domain_error(stream_option)', 'open/4 stream option domain'],
378
+ ['close(X,[])', 'instantiation_error', 'close/2 stream instantiation'],
379
+ ['close(no_such_stream,[X])', 'instantiation_error', 'close/2 option instantiation'],
380
+ ['close(no_such_stream,foo)', 'type_error(list)', 'close/2 options list type'],
381
+ ['close(f(a),[])', 'domain_error(stream_or_alias)', 'close/2 stream-or-alias domain'],
382
+ ['close(no_such_stream,[bogus])', 'domain_error(close_option)', 'close/2 close option domain'],
383
+ ['close(no_such_stream,[])', 'existence_error(stream)', 'close/2 missing stream'],
384
+ ['flush_output(X)', 'instantiation_error', 'flush_output/1 variable'],
385
+ ['flush_output(f(a))', 'domain_error(stream_or_alias)', 'flush_output/1 stream-or-alias domain'],
386
+ ['flush_output(no_such_stream)', 'existence_error(stream)', 'flush_output/1 missing stream'],
387
+ ['flush_output(user_input)', 'permission_error(output, stream)', 'flush_output/1 input stream'],
388
+ ['stream_property(foo,P)', 'domain_error(stream)', 'stream_property/2 stream term domain'],
389
+ ['stream_property(S,bogus)', 'domain_error(stream_property)', 'stream_property/2 property domain'],
390
+ ['at_end_of_stream(X)', 'instantiation_error', 'at_end_of_stream/1 variable'],
391
+ ['at_end_of_stream(f(a))', 'domain_error(stream_or_alias)', 'at_end_of_stream/1 stream-or-alias domain'],
392
+ ['at_end_of_stream(no_such_stream)', 'existence_error(stream)', 'at_end_of_stream/1 missing stream'],
393
+ ['set_stream_position(X,0)', 'instantiation_error', 'set_stream_position/2 stream instantiation'],
394
+ ['set_stream_position(user_input,X)', 'instantiation_error', 'set_stream_position/2 position instantiation'],
395
+ ['set_stream_position(f(a),0)', 'domain_error(stream_or_alias)', 'set_stream_position/2 stream-or-alias domain'],
396
+ ['set_stream_position(user_input,foo)', 'domain_error(stream_position)', 'set_stream_position/2 position domain'],
397
+ ['set_stream_position(no_such_stream,0)', 'existence_error(stream)', 'set_stream_position/2 missing stream'],
398
+ ['set_stream_position(user_input,0)', 'permission_error(reposition, stream)', 'set_stream_position/2 non-repositionable stream'],
399
+ ];
400
+ for (const [goal, formal, label] of errors) {
401
+ equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
402
+ }
403
+
404
+ equal(run('', { isoStrict: true, goal: 'current_input(S),stream_property(S,input)' }).stats.completed_goal_lists, 1,
405
+ 'current_input/1 successful mode');
406
+ equal(run('', { isoStrict: true, goal: 'current_output(S),stream_property(S,output)' }).stats.completed_goal_lists, 1,
407
+ 'current_output/1 successful mode');
408
+ equal(run('', { isoStrict: true, goal: 'flush_output(user_output)' }).stats.completed_goal_lists, 1,
409
+ 'flush_output/1 successful mode');
410
+ equal(run('', { isoStrict: true, goal: 'at_end_of_stream(user_input)' }).stats.completed_goal_lists, 1,
411
+ 'at_end_of_stream/1 at empty standard input');
412
+
413
+ const openErrorSolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
414
+ openErrorSolver.io.open = () => { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); };
415
+ equal(capture(() => [...openErrorSolver.solve([
416
+ parseGoalText('open(missing,read,S,[])', { isoStrict: true }),
417
+ ], new Env(), 0)]).formal, 'existence_error(source_sink)', 'open/4 missing read source');
418
+ openErrorSolver.io.open = () => { throw new Error('denied'); };
419
+ equal(capture(() => [...openErrorSolver.solve([
420
+ parseGoalText('open(denied,write,S,[])', { isoStrict: true }),
421
+ ], new Env(), 0)]).formal, 'permission_error(open, source_sink)', 'open/4 cannot open sink');
422
+
423
+ const closedSolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
424
+ const closed = closedSolver.io.add({
425
+ id: 2, alias: 'closed_stream', mode: 'read', type: 'text', content: 'a', position: 0, path: '',
426
+ reposition: true, eofAction: 'error', standard: false, pastEnd: false,
427
+ });
428
+ closedSolver.io.discard(closed);
429
+ equal([...closedSolver.solve([
430
+ parseGoalText("stream_property('$stream'(2),_)", { isoStrict: true }),
431
+ ], new Env(), 0)].length, 0, 'closed valid stream-term has no current stream property');
432
+
433
+ const positionedSolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
434
+ positionedSolver.io.add({
435
+ id: 2, alias: 'positioned', mode: 'read', type: 'text', content: 'ab', position: 0, path: '',
436
+ reposition: true, eofAction: 'error', standard: false, pastEnd: false,
437
+ });
438
+ equal([...positionedSolver.solve([
439
+ parseGoalText('set_stream_position(positioned,1)', { isoStrict: true }),
440
+ parseGoalText("get_char(positioned,'b')", { isoStrict: true }),
441
+ ], new Env(), 0)].length, 1, 'set_stream_position/2 successful mode');
442
+ });
443
+
444
+ reporter.test('closes the ISO 8.12-8.14 character, byte, and term-I/O rows', () => {
445
+ const errors = [
446
+ ['get_char(S,C)', 'instantiation_error', 'get_char/2 stream instantiation'],
447
+ ['get_char(user_input,ab)', 'type_error(in_character)', 'get_char/2 in-character type'],
448
+ ['get_char(f(a),C)', 'domain_error(stream_or_alias)', 'get_char/2 stream-or-alias domain'],
449
+ ['get_char(no_such_stream,C)', 'existence_error(stream)', 'get_char/2 missing stream'],
450
+ ['get_char(user_output,C)', 'permission_error(input, stream)', 'get_char/2 output stream'],
451
+ ['get_code(S,C)', 'instantiation_error', 'get_code/2 stream instantiation'],
452
+ ['get_code(user_input,foo)', 'type_error(integer)', 'get_code/2 integer type'],
453
+ ['get_code(f(a),C)', 'domain_error(stream_or_alias)', 'get_code/2 stream-or-alias domain'],
454
+ ['get_code(no_such_stream,C)', 'existence_error(stream)', 'get_code/2 missing stream'],
455
+ ['get_code(user_output,C)', 'permission_error(input, stream)', 'get_code/2 output stream'],
456
+ ['get_code(-2)', 'representation_error(in_character_code)', 'get_code/1 in-character-code representation'],
457
+ ['peek_char(S,C)', 'instantiation_error', 'peek_char/2 stream instantiation'],
458
+ ['peek_char(user_input,ab)', 'type_error(in_character)', 'peek_char/2 in-character type'],
459
+ ['peek_code(user_input,foo)', 'type_error(integer)', 'peek_code/2 integer type'],
460
+ ['put_char(S,a)', 'instantiation_error', 'put_char/2 stream instantiation'],
461
+ ['put_char(X)', 'instantiation_error', 'put_char/1 character instantiation'],
462
+ ['put_char(ab)', 'type_error(character)', 'put_char/1 character type'],
463
+ ['put_char(f(a),a)', 'domain_error(stream_or_alias)', 'put_char/2 stream-or-alias domain'],
464
+ ['put_char(no_such_stream,a)', 'existence_error(stream)', 'put_char/2 missing stream'],
465
+ ['put_char(user_input,a)', 'permission_error(output, stream)', 'put_char/2 input stream'],
466
+ ['put_code(S,97)', 'instantiation_error', 'put_code/2 stream instantiation'],
467
+ ['put_code(X)', 'instantiation_error', 'put_code/1 code instantiation'],
468
+ ['put_code(foo)', 'type_error(integer)', 'put_code/1 integer type'],
469
+ ['put_code(f(a),97)', 'domain_error(stream_or_alias)', 'put_code/2 stream-or-alias domain'],
470
+ ['put_code(no_such_stream,97)', 'existence_error(stream)', 'put_code/2 missing stream'],
471
+ ['put_code(user_input,97)', 'permission_error(output, stream)', 'put_code/2 input stream'],
472
+ ['put_code(1114112)', 'representation_error(character_code)', 'put_code/1 character-code representation'],
473
+ ['nl(S)', 'instantiation_error', 'nl/1 stream instantiation'],
474
+ ['nl(f(a))', 'domain_error(stream_or_alias)', 'nl/1 stream-or-alias domain'],
475
+ ['nl(no_such_stream)', 'existence_error(stream)', 'nl/1 missing stream'],
476
+ ['nl(user_input)', 'permission_error(output, stream)', 'nl/1 input stream'],
477
+ ['get_byte(S,B)', 'instantiation_error', 'get_byte/2 stream instantiation'],
478
+ ['get_byte(256)', 'type_error(in_byte)', 'get_byte/1 in-byte type'],
479
+ ['get_byte(f(a),B)', 'domain_error(stream_or_alias)', 'get_byte/2 stream-or-alias domain'],
480
+ ['get_byte(no_such_stream,B)', 'existence_error(stream)', 'get_byte/2 missing stream'],
481
+ ['get_byte(user_output,B)', 'permission_error(input, stream)', 'get_byte/2 output stream'],
482
+ ['get_byte(B)', 'permission_error(input, text_stream)', 'get_byte/1 text stream'],
483
+ ['peek_byte(S,B)', 'instantiation_error', 'peek_byte/2 stream instantiation'],
484
+ ['peek_byte(256)', 'type_error(in_byte)', 'peek_byte/1 in-byte type'],
485
+ ['put_byte(S,1)', 'instantiation_error', 'put_byte/2 stream instantiation'],
486
+ ['put_byte(X)', 'instantiation_error', 'put_byte/1 byte instantiation'],
487
+ ['put_byte(256)', 'type_error(byte)', 'put_byte/1 byte type'],
488
+ ['put_byte(f(a),1)', 'domain_error(stream_or_alias)', 'put_byte/2 stream-or-alias domain'],
489
+ ['put_byte(no_such_stream,1)', 'existence_error(stream)', 'put_byte/2 missing stream'],
490
+ ['put_byte(user_input,1)', 'permission_error(output, stream)', 'put_byte/2 input stream'],
491
+ ['put_byte(1)', 'permission_error(output, text_stream)', 'put_byte/1 text stream'],
492
+ ['read_term(S,T,[])', 'instantiation_error', 'read_term/3 stream instantiation'],
493
+ ['read_term(f(a),T,[])', 'domain_error(stream_or_alias)', 'read_term/3 stream-or-alias domain'],
494
+ ['read_term(T,foo)', 'type_error(list)', 'read_term/2 options list type'],
495
+ ['read_term(T,[bogus])', 'domain_error(read_option)', 'read_term/2 read option domain'],
496
+ ['read_term(no_such_stream,T,[])', 'existence_error(stream)', 'read_term/3 missing stream'],
497
+ ['read_term(user_output,T,[])', 'permission_error(input, stream)', 'read_term/3 output stream'],
498
+ ['write_term(S,a,[])', 'instantiation_error', 'write_term/3 stream instantiation'],
499
+ ['write_term(f(a),a,[])', 'domain_error(stream_or_alias)', 'write_term/3 stream-or-alias domain'],
500
+ ['write_term(a,foo)', 'type_error(list)', 'write_term/2 options list type'],
501
+ ['write_term(a,[bogus])', 'domain_error(write_option)', 'write_term/2 write option domain'],
502
+ ['write_term(no_such_stream,a,[])', 'existence_error(stream)', 'write_term/3 missing stream'],
503
+ ['write_term(user_input,a,[])', 'permission_error(output, stream)', 'write_term/3 input stream'],
504
+ ["op(100,xfy,',')", 'permission_error(modify, operator)', 'op/3 protects comma'],
505
+ ['current_op(a,_,_)', 'domain_error(operator_priority)', 'current_op/3 priority domain'],
506
+ ['current_op(1,1,_)', 'domain_error(operator_specifier)', 'current_op/3 specifier domain'],
507
+ ['current_op(1,fx,1)', 'type_error(atom)', 'current_op/3 operator type'],
508
+ ['char_conversion(X,a)', 'instantiation_error', 'char_conversion/2 input instantiation'],
509
+ ['char_conversion(a,X)', 'instantiation_error', 'char_conversion/2 output instantiation'],
510
+ ['char_conversion(ab,a)', 'representation_error(character)', 'char_conversion/2 input representation'],
511
+ ['char_conversion(a,ab)', 'representation_error(character)', 'char_conversion/2 output representation'],
512
+ ['current_char_conversion(ab,X)', 'type_error(character)', 'current_char_conversion/2 input type'],
513
+ ['current_char_conversion(a,ab)', 'type_error(character)', 'current_char_conversion/2 output type'],
514
+ ];
515
+ for (const [goal, formal, label] of errors) {
516
+ equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
517
+ }
518
+
519
+ const binaryInput = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
520
+ binaryInput.io.add({
521
+ id: 2, alias: 'bin_in', mode: 'read', type: 'binary', content: [65], position: 0, path: '',
522
+ reposition: false, eofAction: 'error', standard: false, pastEnd: false,
523
+ });
524
+ for (const goal of ['get_char(bin_in,C)', 'get_code(bin_in,C)', 'peek_char(bin_in,C)', 'peek_code(bin_in,C)', 'read_term(bin_in,T,[])']) {
525
+ equal(capture(() => [...binaryInput.solve([parseGoalText(goal, { isoStrict: true })], new Env(), 0)]).formal,
526
+ 'permission_error(input, binary_stream)', `${goal} binary stream`);
527
+ }
528
+ equal([...binaryInput.solve([parseGoalText('peek_byte(bin_in,65)', { isoStrict: true })], new Env(), 0)].length, 1,
529
+ 'peek_byte/2 successful binary mode');
530
+ equal([...binaryInput.solve([parseGoalText('get_byte(bin_in,65)', { isoStrict: true })], new Env(), 0)].length, 1,
531
+ 'get_byte/2 successful binary mode');
532
+
533
+ const binaryOutput = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
534
+ binaryOutput.io.add({
535
+ id: 2, alias: 'bin_out', mode: 'write', type: 'binary', content: [], position: 0, path: '',
536
+ reposition: false, eofAction: 'error', standard: false, pastEnd: false,
537
+ });
538
+ for (const goal of ['put_char(bin_out,a)', 'put_code(bin_out,97)', 'nl(bin_out)', 'write_term(bin_out,a,[])']) {
539
+ equal(capture(() => [...binaryOutput.solve([parseGoalText(goal, { isoStrict: true })], new Env(), 0)]).formal,
540
+ 'permission_error(output, binary_stream)', `${goal} binary stream`);
541
+ }
542
+ equal([...binaryOutput.solve([parseGoalText('put_byte(bin_out,65)', { isoStrict: true })], new Env(), 0)].length, 1,
543
+ 'put_byte/2 successful binary mode');
544
+
545
+ const pastInput = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
546
+ pastInput.io.add({
547
+ id: 2, alias: 'past_in', mode: 'read', type: 'text', content: '', position: 0, path: '',
548
+ reposition: false, eofAction: 'error', standard: false, pastEnd: true,
549
+ });
550
+ for (const goal of ['get_char(past_in,C)', 'get_code(past_in,C)', 'peek_char(past_in,C)', 'peek_code(past_in,C)', 'read_term(past_in,T,[])']) {
551
+ equal(capture(() => [...pastInput.solve([parseGoalText(goal, { isoStrict: true })], new Env(), 0)]).formal,
552
+ 'permission_error(input, past_end_of_stream)', `${goal} past EOF`);
553
+ }
554
+
555
+ equal(capture(() => run('', {
556
+ isoStrict: true,
557
+ goal: 'read_term(T,[])',
558
+ ioOptions: { input: "'unterminated." },
559
+ })).formal, 'syntax_error(read_term)', 'read_term/2 syntax error');
560
+
561
+ equal(run('', { isoStrict: true, goal: "char_conversion(a,b),current_char_conversion(a,b)" }).stats.completed_goal_lists, 1,
562
+ 'char_conversion/2 and current_char_conversion/2 successful mode');
563
+ equal(run('', { isoStrict: true, goal: 'op(100,xf,zz),current_op(100,xf,zz)' }).stats.completed_goal_lists, 1,
564
+ 'op/3 and current_op/3 successful mode');
565
+ });
566
+
357
567
  reporter.test('treats close force(true) as presence-based', () => {
358
568
  const closeWith = (options) => {
359
569
  const solver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
@@ -481,12 +691,12 @@ export function runIsoStrict(reporter = new TestReporter()) {
481
691
  }
482
692
  });
483
693
 
484
- reporter.test('follows ISO clause/2 private-procedure error precedence', () => {
694
+ reporter.test('keeps the selected clause/2 private-procedure overlap behavior', () => {
485
695
  equal(capture(() => run('p.\n', { isoStrict: true, goal: 'clause(atom(_),4)' })).formal,
486
696
  'permission_error(access, private_procedure)', 'private procedure before body callability');
487
697
  });
488
698
 
489
- reporter.test('validates all-solutions goals before their output lists', () => {
699
+ reporter.test('keeps the selected all-solutions Goal-before-Instances overlap behavior', () => {
490
700
  for (const predicate of ['findall', 'bagof', 'setof']) {
491
701
  equal(capture(() => run('', { isoStrict: true, goal: `${predicate}(X,Y,foo)` })).formal,
492
702
  'instantiation_error', `${predicate}/3 variable goal`);
@@ -700,6 +910,93 @@ export function runIsoStrict(reporter = new TestReporter()) {
700
910
  }
701
911
  });
702
912
 
913
+ reporter.test('closes the ISO 8.6-8.10 prescribed mode/error rows', () => {
914
+ const errors = [
915
+ ['X is Y', 'instantiation_error', 'is/2 variable expression'],
916
+ ['X is foo', 'type_error(evaluable)', 'is/2 expression evaluation error'],
917
+ ['X =:= 1', 'instantiation_error', '=:=/2 left variable'],
918
+ ['1 =:= X', 'instantiation_error', '=:=/2 right variable'],
919
+ ['X =\\= 1', 'instantiation_error', '=\\=/2 left variable'],
920
+ ['1 < X', 'instantiation_error', '</2 right variable'],
921
+ ['X =< 1', 'instantiation_error', '=</2 left variable'],
922
+ ['1 > X', 'instantiation_error', '>/2 right variable'],
923
+ ['X >= 1', 'instantiation_error', '>=/2 left variable'],
924
+ ['foo =:= 1', 'type_error(evaluable)', 'arithmetic comparison evaluation error'],
925
+ ['clause(X,_)', 'instantiation_error', 'clause/2 variable head'],
926
+ ['clause(4,_)', 'type_error(callable)', 'clause/2 head type'],
927
+ ['current_predicate(4)', 'type_error(predicate_indicator)', 'current_predicate/1 indicator type'],
928
+ ['asserta(_)', 'instantiation_error', 'asserta/1 variable head'],
929
+ ['asserta(4)', 'type_error(callable)', 'asserta/1 head type'],
930
+ ['asserta((p:-4))', 'type_error(callable)', 'asserta/1 body conversion'],
931
+ ['assertz(_)', 'instantiation_error', 'assertz/1 variable head'],
932
+ ['assertz(4)', 'type_error(callable)', 'assertz/1 head type'],
933
+ ['assertz((p:-4))', 'type_error(callable)', 'assertz/1 body conversion'],
934
+ ['retract(_)', 'instantiation_error', 'retract/1 variable head'],
935
+ ['retract(4)', 'type_error(callable)', 'retract/1 head type'],
936
+ ['retractall(_)', 'instantiation_error', 'retractall/1 variable head'],
937
+ ['retractall(4)', 'type_error(callable)', 'retractall/1 head type'],
938
+ ['abolish(X)', 'instantiation_error', 'abolish/1 variable indicator'],
939
+ ['abolish(foo/X)', 'instantiation_error', 'abolish/1 variable arity'],
940
+ ['abolish(foo)', 'type_error(predicate_indicator)', 'abolish/1 indicator type'],
941
+ ['abolish(foo/a)', 'type_error(integer)', 'abolish/1 arity type'],
942
+ ['abolish(4/1)', 'type_error(atom)', 'abolish/1 name type'],
943
+ ['abolish(foo/(-1))', 'domain_error(not_less_than_zero)', 'abolish/1 negative arity'],
944
+ ['findall(X,Y,L)', 'instantiation_error', 'findall/3 variable goal'],
945
+ ['findall(X,4,L)', 'type_error(callable)', 'findall/3 goal type'],
946
+ ['findall(X,true,foo)', 'type_error(list)', 'findall/3 Instances type'],
947
+ ['bagof(X,Y,L)', 'instantiation_error', 'bagof/3 variable iterated goal'],
948
+ ['bagof(X,4,L)', 'type_error(callable)', 'bagof/3 iterated-goal type'],
949
+ ['bagof(X,true,foo)', 'type_error(list)', 'bagof/3 Instances type'],
950
+ ['setof(X,Y,L)', 'instantiation_error', 'setof/3 variable iterated goal'],
951
+ ['setof(X,4,L)', 'type_error(callable)', 'setof/3 iterated-goal type'],
952
+ ['setof(X,true,foo)', 'type_error(list)', 'setof/3 Instances type'],
953
+ ];
954
+ for (const [goal, formal, label] of errors) {
955
+ equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
956
+ }
957
+
958
+ for (const goal of [
959
+ 'X is 1+2, X=3',
960
+ '1 =:= 1',
961
+ '1 =\\= 2',
962
+ '1 < 2',
963
+ '1 =< 1',
964
+ '2 > 1',
965
+ '2 >= 2',
966
+ 'findall(X,(X=1;X=2),[1,2])',
967
+ 'bagof(X,(X=1;X=2),[1,2])',
968
+ 'setof(X,(X=2;X=1;X=2),[1,2])',
969
+ ]) {
970
+ equal(run('', { isoStrict: true, goal }).stats.completed_goal_lists > 0, true, `${goal} successful mode`);
971
+ }
972
+ equal(run('', { isoStrict: true, goal: '4 is 1+2' }).stats.completed_goal_lists, 0, 'is/2 failed unification');
973
+ equal(run('', { isoStrict: true, goal: 'bagof(X,fail,L)' }).stats.completed_goal_lists, 0, 'bagof/3 empty solution set fails');
974
+ equal(run('', { isoStrict: true, goal: 'setof(X,fail,L)' }).stats.completed_goal_lists, 0, 'setof/3 empty solution set fails');
975
+
976
+ const current = run('p(a).\nq.\n', { isoStrict: true, goal: 'current_predicate(p/1)' });
977
+ equal(current.stats.completed_goal_lists, 1, 'current_predicate/1 finds a user procedure');
978
+ });
979
+
980
+ reporter.test('preserves 7.6.2 source-body conversion identity for clause/2 and retract/1', () => {
981
+ const source = [
982
+ ':- dynamic(foo/1).',
983
+ 'foo(X) :- X.',
984
+ ':- dynamic(bar/1).',
985
+ 'bar(X) :- (X ; true).',
986
+ ':- dynamic(baz/1).',
987
+ 'baz(X) :- (X -> true).',
988
+ '',
989
+ ].join('\n');
990
+ equal(run(source, { isoStrict: true, goal: 'clause(foo(C),call(C))' }).stats.completed_goal_lists, 1,
991
+ 'variable body converts to call/1 with head sharing');
992
+ equal(run(source, { isoStrict: true, goal: 'clause(bar(C),(call(C);true))' }).stats.completed_goal_lists, 1,
993
+ 'disjunction recursively converts a variable branch');
994
+ equal(run(source, { isoStrict: true, goal: 'clause(baz(C),(call(C)->true))' }).stats.completed_goal_lists, 1,
995
+ 'if-then recursively converts a variable branch');
996
+ equal(run(source, { isoStrict: true, goal: 'retract((foo(C):-call(C)))' }).stats.completed_goal_lists, 1,
997
+ 'retract/1 sees the converted body with shared variables');
998
+ });
999
+
703
1000
  reporter.test('closes the ISO 8.15-8.17 prescribed mode/error rows', () => {
704
1001
  const cases = [
705
1002
  ['\\+(X)', 'instantiation_error', '\+/1 variable goal'],