eyeprolog 1.3.54 → 1.3.55

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.54",
6
+ "version": "1.3.55",
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
@@ -1057,7 +1057,10 @@ function* closeBuiltin({ solver, goal, env }) {
1057
1057
  if (!value || value.type !== ATOM || !['true', 'false'].includes(value.name)) {
1058
1058
  throw new PrologError('domain_error(close_option)', option);
1059
1059
  }
1060
- forceClose = value.name === 'true'; // rightmost contradictory option applies
1060
+ // 8.11.6.1(a) is presence-based: if there is a force(true) option,
1061
+ // resource/system close errors are ignored. A later force(false) does
1062
+ // not cancel an earlier force(true).
1063
+ forceClose ||= value.name === 'true';
1061
1064
  }
1062
1065
  const stream = requireStream(solver, goal.args[0], env);
1063
1066
  if (!stream.standard) {
@@ -1219,12 +1222,14 @@ function preflightInputUnitTarget(name, target, env, solver) {
1219
1222
  throw new PrologError('type_error(in_character)', value);
1220
1223
  }
1221
1224
  } else if (name.endsWith('code')) {
1225
+ // ISO 8.12.1.3/8.12.2.3 puts the integer type check before stream
1226
+ // diagnostics, but the integer-is-not-an-in-character-code
1227
+ // representation error comes after the stream existence/mode/type/EOF
1228
+ // conditions. Defer that range/repertoire check until the input entity
1229
+ // itself has been inspected below.
1222
1230
  if (value.type !== VAR && (value.type !== NUMBER || !isDecimalInteger(value.name))) {
1223
1231
  throw new PrologError('type_error(integer)', value);
1224
1232
  }
1225
- if (value.type !== VAR && !validStrictInputCharacterCode(value, solver)) {
1226
- throw new PrologError('representation_error(in_character_code)');
1227
- }
1228
1233
  } else {
1229
1234
  if (value.type !== VAR) {
1230
1235
  if (value.type !== NUMBER || !isDecimalInteger(value.name)) throw new PrologError('type_error(in_byte)', value);
@@ -1275,6 +1280,15 @@ function inputUnitBuiltin(name) {
1275
1280
  if (!binary && unit != null && solver.isoStrict && !isStrictIsoPcsCharacter(unit)) {
1276
1281
  throw new PrologError('representation_error(character)');
1277
1282
  }
1283
+ // For get_code/peek_code the published error table places an invalid
1284
+ // in-character-code target after stream and input-entity errors. Doing
1285
+ // this here prevents a fixed bad Code from masking (for example) an
1286
+ // output-stream, binary-stream, past-EOF, or invalid-character error.
1287
+ const targetValue = deref(target, env);
1288
+ if (name.endsWith('code') && targetValue.type !== VAR &&
1289
+ !validStrictInputCharacterCode(targetValue, solver)) {
1290
+ throw new PrologError('representation_error(in_character_code)');
1291
+ }
1278
1292
  const result = unit == null
1279
1293
  ? (binary ? numberTerm(-1) : name.endsWith('code') ? numberTerm(-1) : atom('end_of_file'))
1280
1294
  : binary ? numberTerm(unit) : name.endsWith('code') ? numberTerm(unit.codePointAt(0)) : atom(unit);
@@ -37,10 +37,10 @@ error-ordering alternative to an individual executable assertion.
37
37
  | 7.7 — execution and backtracking | audit | Control/search suites. Strict mode disables EyeProlog automatic tabling, cycle guards, and recursive numeric shortcuts so core execution uses ordinary clause selection/backtracking. |
38
38
  | 7.8 — control constructs and exceptions | audit | call, cut, conjunction, disjunction (including failed branches after callee-local cuts), if-then, catch/throw, renamed-copy tests. |
39
39
  | 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. 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. |
40
- | 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 full option/mode cross-product is still being mapped. |
40
+ | 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. |
41
41
  | 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. |
42
- | 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax/resource families and focused error cases. Exact ISO 8.14.1-8.14.4 error precedence is covered for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3`; the continuing audit has corrected additional 8.11-8.13 stream/character/byte errors (including the complete `alias(A)` culprit for `open/4` alias collisions), Corrigendum 2 `keysort/2` errors, atomic-conversion list-shape precedence, `arg/3`, `atom_concat/3`, `sub_atom/5`, `char_conversion/2`, runtime control-construct/database protection, arithmetic culprit/precedence reporting, and host-resource normalization for unbounded term/integer operations. A complete one-row-per-prescribed-error ordering map remains open. |
43
- | 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md; Corrigendum 2 additions (`subsumes_term/2`, `term_variables/2`, `call/2..8`, `false/0`) are in the strict registry. The audit now includes corrected `keysort/2` variable/non-pair errors, stricter 8.11-8.13 stream and character/byte modes/errors, the 8.14 option/error-order subfamily, closed 8.17 flags, additional prescribed precedence for `arg/3`, `atom_concat/3`, `sub_atom/5`, number/atom list conversions, and `char_conversion/2`, plus finite-host resource handling for unbounded `functor/3` construction. The remaining mode/error matrix is not yet one-row-per-standard-row. |
42
+ | 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax/resource families and focused error cases. Exact ISO 8.14.1-8.14.4 error precedence is covered for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3`; the continuing audit has corrected additional 8.11-8.13 stream/character/byte errors (including the complete `alias(A)` culprit for `open/4` alias collisions), Corrigendum 2 `keysort/2` errors, atomic-conversion list-shape precedence, `arg/3`, `atom_concat/3`, `sub_atom/5`, `char_conversion/2`, runtime control-construct/database protection, arithmetic culprit/precedence reporting, input-code representation ordering, presence-based close forcing, and host-resource normalization for unbounded term/integer operations. A complete one-row-per-prescribed-error ordering map remains open. |
43
+ | 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md; Corrigendum 2 additions (`subsumes_term/2`, `term_variables/2`, `call/2..8`, `false/0`) are in the strict registry. The audit now includes corrected `keysort/2` variable/non-pair errors, stricter 8.11-8.13 stream and character/byte modes/errors, the 8.14 option/error-order subfamily, closed 8.17 flags, additional prescribed precedence for `arg/3`, `atom_concat/3`, `sub_atom/5`, number/atom list conversions, and `char_conversion/2`, plus finite-host resource handling for unbounded `functor/3` construction and the 8.11/8.12 close/input-code overlap cases. The remaining mode/error matrix is not yet one-row-per-standard-row. |
44
44
  | 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. |
45
45
  | Corrigendum 1 | covered | Double-quoted atom/operator-priority corrections have dedicated cases. |
46
46
  | Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases. |
@@ -151,6 +151,17 @@ exception conditions before any later I->F overflow, including Corrigendum 3's
151
151
  correction to the `**/2` operand wording and Corrigendum 2's distinct rule that
152
152
  `^/2` may accept an integer-valued float exponent.
153
153
 
154
+
155
+ A subsequent 8.11/8.12 pass closes two stream-option/error-overlap details.
156
+ For `get_code/1-2` and `peek_code/1-2`, a fixed integer which is not an
157
+ in-character code is now diagnosed only after the earlier stream
158
+ existence/direction/text-vs-binary/past-EOF and input-entity conditions, so it
159
+ cannot mask the stream error prescribed by the published table. `close/2` now
160
+ also treats `force(true)` as a presence-based option exactly as 8.11.6.1(a)
161
+ describes: a later `force(false)` does not cancel an earlier `force(true)` when
162
+ a Resource Error or System Error occurs during closure. Focused strict tests
163
+ cover both overlap families.
164
+
154
165
  ## Post-Corrigendum STC cross-check
155
166
 
156
167
  The public WG17 STC draft is tracked as useful defect-discovery material, but it
@@ -14,7 +14,7 @@ compliance audit and the remaining work before a full conformance claim.
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, Corrigendum 2 `keysort/2` variable/non-pair errors, and strict `arg/3`, `functor/3` / `=../2` error-order plus unbounded-arity/resource checks |
15
15
  | 8.6-8.7 arithmetic predicates | `is/2` and all six arithmetic comparisons | `arithmetic`, comparison cases, isolated evaluation errors |
16
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, including runtime static/private protection for the solver-native conjunction control construct 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, and the exact `alias(A)` collision culprit |
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 |
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 strict Corrigendum 2 resulting-goal `max_arity` error coverage |
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 error cases, strict Corrigendum 2 partial/improper-list precedence for atom/number conversions, and prescribed `atom_concat/3` / `sub_atom/5` ordering checks |
20
20
  | 8.17 flags and hooks | complete required Part 1 flag set, selected defaults, standard value domains/changeability, halt and character conversion | `exceptions_and_flags`, `remaining_builtins_and_directives`, flag error cases, strict `char_conversion/2` precedence, and the complete strict flag audit in `run-iso-strict.mjs` |
@@ -38,9 +38,10 @@ regressions.
38
38
  | [#68](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/stc#68) | division examples | Existing arithmetic coverage evaluates signed integer `/` through the floating operation; no implementation-defined signed-division result is used. |
39
39
  | [#69](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/stc#69) | arithmetic example culprit | **Found a #65 gap.** Strict expression evaluation now applies 7.9.2(c) to an atomic subexpression such as `foo`: it reports `type_error(evaluable,foo/0)` rather than the misleading `type_error(number,foo)` shown by the old 9.1.7 example. |
40
40
  | [#70](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/stc#70) | optional `max_procedure_arity` | Reviewed after issue #66 corrected the earlier #71 pointer. EyeProlog has no declared procedure-arity limit smaller than its `max_arity=unbounded` term model, so the implementation-defined optional flag is intentionally absent. |
41
- | [#73](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/stc#73) | float-reading limits discussed from issue #54 | Positive/negative literal and `number_chars/2` overflow are kept as explicit draft cases; underflow is tested separately. See the note below. |
41
+ | [#72](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/stc#72) | tentative non-ground stream-term instances | Reviewed as a tentative post-2026 proposal. EyeProlog does not make this draft wording normative in `--iso-strict`; the published Part 1 stream-term/domain rules remain the baseline until WG17 settles the proposal. |
42
+ | [issue #54](https://github.com/eyereasoner/eyeprolog/issues/54) | float-reading limits (historically discussed as a later STC item) | Positive/negative literal and `number_chars/2` overflow are kept as explicit draft-facing cases; underflow is tested separately. The current public STC page ends at #72, so this is no longer labelled as a current `stc#73` entry. See the note below. |
42
43
 
43
- ## Float-reading note for #73
44
+ ## Float-reading note from issue #54
44
45
 
45
46
  EyeProlog currently has a finite-double numeric profile. The draft-facing cases
46
47
  record the behavior discussed in issue #54:
@@ -297,6 +297,60 @@ export function runIsoStrict(reporter = new TestReporter()) {
297
297
  }
298
298
  });
299
299
 
300
+ reporter.test('orders input-code representation errors after stream diagnostics', () => {
301
+ // ISO 8.12.1.3/8.12.2.3: Code's integer type is checked early, but an
302
+ // integer outside the in-character-code domain is the final listed error.
303
+ // It must therefore not mask output/binary/past-EOF/entity errors.
304
+ for (const predicate of ['get_code', 'peek_code']) {
305
+ equal(capture(() => run('', { isoStrict: true, goal: `${predicate}(user_output,-2)` })).formal,
306
+ 'permission_error(input, stream)', `${predicate}/2 output stream before bad code`);
307
+ }
308
+
309
+ const program = Program.parse('', { isoStrict: true });
310
+ const binarySolver = new Solver(program, { isoStrict: true });
311
+ binarySolver.io.add({
312
+ id: 2, alias: 'bin_in', mode: 'read', type: 'binary', content: [], position: 0, path: '',
313
+ reposition: false, eofAction: 'error', standard: false, pastEnd: false,
314
+ });
315
+ equal(capture(() => [...binarySolver.solve([parseGoalText('get_code(bin_in,-2)', { isoStrict: true })], new Env(), 0)]).formal,
316
+ 'permission_error(input, binary_stream)', 'binary stream before bad code');
317
+
318
+ const eofSolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
319
+ eofSolver.io.add({
320
+ id: 2, alias: 'past_in', mode: 'read', type: 'text', content: '', position: 0, path: '',
321
+ reposition: false, eofAction: 'error', standard: false, pastEnd: true,
322
+ });
323
+ equal(capture(() => [...eofSolver.solve([parseGoalText('peek_code(past_in,-2)', { isoStrict: true })], new Env(), 0)]).formal,
324
+ 'permission_error(input, past_end_of_stream)', 'past EOF before bad code');
325
+
326
+ const badEntitySolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
327
+ badEntitySolver.io.add({
328
+ id: 2, alias: 'bad_utf8', mode: 'read', type: 'text', content: '\udc00', position: 0, path: '',
329
+ reposition: false, eofAction: 'error', standard: false, pastEnd: false, strictUtf8: true,
330
+ });
331
+ equal(capture(() => [...badEntitySolver.solve([parseGoalText('peek_code(bad_utf8,-2)', { isoStrict: true })], new Env(), 0)]).formal,
332
+ 'representation_error(character)', 'invalid input entity before bad code');
333
+
334
+ equal(capture(() => run('', { isoStrict: true, goal: 'get_code(-2)' })).formal,
335
+ 'representation_error(in_character_code)', 'bad code once no earlier stream error applies');
336
+ });
337
+
338
+ reporter.test('treats close force(true) as presence-based', () => {
339
+ const closeWith = (options) => {
340
+ const solver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
341
+ solver.io.add({
342
+ id: 2, alias: 'tmp_out', mode: 'write', type: 'text', content: '', position: 0, path: '',
343
+ reposition: false, eofAction: 'error', standard: false, pastEnd: false,
344
+ });
345
+ solver.io.close = () => { throw new Error('simulated close failure'); };
346
+ const answers = [...solver.solve([parseGoalText(`close(tmp_out,${options})`, { isoStrict: true })], new Env(), 0)];
347
+ equal(answers.length, 1, `${options} succeeds under force(true)`);
348
+ equal(Boolean(solver.io.resolve('tmp_out')), false, `${options} discards stream`);
349
+ };
350
+ closeWith('[force(true),force(false)]');
351
+ closeWith('[force(false),force(true)]');
352
+ });
353
+
300
354
  reporter.test('follows Part 1 arithmetic type and exceptional errors', () => {
301
355
  const cases = [
302
356
  ["X is '+'(foo,77)", 'type_error(evaluable)', 'STC #69 simple arithmetic atom is foo/0'],