eyeprolog 1.3.100 → 1.4.0

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.100",
6
+ "version": "1.4.0",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/write.js CHANGED
@@ -28,6 +28,11 @@ function quotedControlEscape(ch) {
28
28
  function atomNeedsQuotes(name) {
29
29
  if (!name) return true;
30
30
  if (name === '[]' || name === '{}') return false;
31
+ // `;` is a solo-character name token in ISO 6.4.2/6.5.3, so unlike most
32
+ // solo characters it is a valid atom without quotes. Keep `|` distinct:
33
+ // Corrigendum 2 makes the bar token equivalent to atom '|' only while it
34
+ // is being used as an operator; as an ordinary atom/functor it is quoted.
35
+ if (name === ';') return false;
31
36
  // A lone full stop is the end token, not a graphic atom. Longer
32
37
  // graphic tokens may contain dots and are valid unquoted writeq/1 output
33
38
  // (WG17 #371-373: ./*, .*, ...*). Only a token beginning with /* would
@@ -164,6 +169,10 @@ function writeNumberedVariable(index) {
164
169
  }
165
170
 
166
171
  function operatorName(name) {
172
+ // Corrigendum 2 adds a dedicated bar token to operator syntax. The atom
173
+ // itself still needs quoting in functional notation, but operator-form
174
+ // output must use the unquoted `|` token (WG17 #181/#290).
175
+ if (name === '|') return '|';
167
176
  if (name === '.' || name.startsWith('/*')) return quoteAtom(name);
168
177
  if (/^[a-z][A-Za-z0-9_]*$/.test(name)) return name;
169
178
  if (/^[!#$&*+\-./<=>?@^~\\;:]+$/.test(name)) return name;
@@ -274,6 +283,10 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
274
283
  if (resolved.type === STRING) return writeString(resolved.name);
275
284
  if (resolved.type === ATOM) {
276
285
  if (!options.quoted) return resolved.name;
286
+ // Corrigendum 2's bare bar token exists only in operator syntax. It is
287
+ // not an atom token, so an ordinary atom `|` must remain quoted even when
288
+ // `|` is currently declared as an operator (for example as f('|')).
289
+ if (resolved.name === '|') return quoteAtom(resolved.name);
277
290
  // Top-level bindings are already delimited by their answer punctuation.
278
291
  // Keep valid dotted graphic tokens readable there without weakening the
279
292
  // ISO writeq/1 policy tested by WG17 #308.
@@ -38,13 +38,13 @@ relevant implementation-dependent overlap choice to an explicit audit outcome.
38
38
  | 7.7 — execution and backtracking | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes ordinary clause selection/backtracking, source/database order, empty-versus-unknown procedures, logical-update visibility, and strict dispatch. EyeProlog automatic tabling, cycle guards, and recursive numeric shortcuts remain disabled in the strict profile. |
39
39
  | 7.8 — control constructs and exceptions | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes `true/0`, `fail/0`, `call/1`, cut, conjunction, disjunction, if-then(-else), and `catch/3`/`throw/1`, including Corrigendum 2 catchability of errors arising from the protected goal and Corrigendum 3 term-to-body conversion. The 8.15 built-in additions remain independently row-audited. |
40
40
  | 7.9 — expression evaluation | covered | [ISO-EVALUABLE-FUNCTOR-MATRIX.md](ISO-EVALUABLE-FUNCTOR-MATRIX.md) closes recursive evaluation, direct-variable precedence, corrected non-evaluable `F/N` errors (including STC #69), operand template/type rules, mixed arithmetic-comparison conversion, exceptional values, and finite-host resource normalization. The audit also fixes float-only `float_integer_part/1` and `float_fractional_part/1` so even huge valid integers reach `type_error(float,...)` before any I->F overflow. |
41
- | 7.10 — input/output concepts | covered | Higher-level stream semantics are pinned for non-atom ground stream terms, alias lifetime, current/target stream selection and fallback, write truncation, append positioning, contradictory stream options, exact binary round-tripping, flushing, standard-stream close behavior, and repositioned overwrite. The complete 8.11-8.14 built-in mode/error family remains row-audited in [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md), while all implementation-defined stream choices are indexed in [ISO-IMPLEMENTATION-DEFINED.md](ISO-IMPLEMENTATION-DEFINED.md). |
41
+ | 7.10 — input/output concepts | covered | Higher-level stream semantics are pinned for non-atom ground stream terms, alias lifetime, current/target stream selection and fallback, write truncation, append positioning, contradictory stream options, exact binary round-tripping, flushing, standard-stream close behavior, and repositioned overwrite. Term-output spelling is also checked lexically where ISO distinguishes token forms: Corrigendum 2 bar operators are written with bare `|`, ordinary atom/functor `|` remains quoted, and the semicolon name token is emitted bare by canonical output. The complete 8.11-8.14 built-in mode/error family remains row-audited in [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md), while all implementation-defined stream choices are indexed in [ISO-IMPLEMENTATION-DEFINED.md](ISO-IMPLEMENTATION-DEFINED.md). |
42
42
  | 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. |
43
43
  | 7.12 — errors | covered | The strict error-envelope gate exercises instantiation, type, domain, existence, permission, representation, evaluation, resource, syntax, and system errors through `error(Error, Context)`. Built-in rows provide the individual prescribed conditions, implementation-defined representation/context choices are documented, and simultaneous-error regressions pin deterministic EyeProlog choices without treating textual table order as normative. |
44
44
  | 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. |
45
45
  | Clause 9 — evaluable functors | covered | [ISO-EVALUABLE-FUNCTOR-MATRIX.md](ISO-EVALUABLE-FUNCTOR-MATRIX.md) records the published Part 1 + Corrigenda arithmetic templates and exceptional branches across 9.1, 9.3, and 9.4. It pins the unbounded integer model, `rndI`/`rndF`/`resultF` choices, float-only conversion rules, I->F overflow, explicit `exp/1` and power underflow, Corrigendum 2 additions and mixed-type `max/2`/`min/2` implementation-dependent choice, Corrigendum 3 negative-power correction, signed bitwise/shift choices, and resource-error translation. Post-N289 STC #75 remains a separately tracked draft proposal rather than a silent change to the published baseline. |
46
46
  | Corrigendum 1 | covered | Double-quoted atom/operator-priority corrections have dedicated cases. |
47
- | Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases. |
47
+ | Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases, including the distinct bare `|` operator token required for term output. |
48
48
  | Corrigendum 3 | covered | Writer options, `variable_names/1`, canonical list output and negative-power corrections have dedicated cases. |
49
49
 
50
50
  ## Conformance corrections and audit closure
@@ -7,7 +7,10 @@ This ledger counts an upstream case when its WG17 identifier, query, and
7
7
  expected ISO disposition are stored in the offline executable matrix. Every
8
8
  case is executed against the upstream Codex expectation. Reviewed exact
9
9
  EyeProlog outcomes are additional regression locks and can never override the
10
- upstream assertion.
10
+ upstream assertion. Writer expectations also preserve ISO-significant token
11
+ spelling: in particular, a bare bar used as an operator is not treated as
12
+ equivalent to a quoted atom, and a bare semicolon name token is not silently
13
+ accepted in quoted form.
11
14
 
12
15
  ## Current standing
13
16
 
@@ -3894,7 +3894,7 @@
3894
3894
  "variables": "[]"
3895
3895
  },
3896
3896
  {
3897
- "output": "a-->b,c'|'d",
3897
+ "output": "a-->b,c|d",
3898
3898
  "variables": "[]"
3899
3899
  }
3900
3900
  ]
@@ -3914,7 +3914,7 @@
3914
3914
  "variables": "[]"
3915
3915
  },
3916
3916
  {
3917
- "output": "'|'(a,';'(b,c))",
3917
+ "output": "'|'(a,;(b,c))",
3918
3918
  "variables": "[]"
3919
3919
  }
3920
3920
  ]
@@ -3934,7 +3934,7 @@
3934
3934
  "variables": "[]"
3935
3935
  },
3936
3936
  {
3937
- "output": "'|'(';'(a,b),c)",
3937
+ "output": "'|'(;(a,b),c)",
3938
3938
  "variables": "[]"
3939
3939
  }
3940
3940
  ]
@@ -3965,7 +3965,7 @@
3965
3965
  "variables": "[]"
3966
3966
  },
3967
3967
  {
3968
- "output": "[(a'|'b)]",
3968
+ "output": "[(a|b)]",
3969
3969
  "variables": "[]"
3970
3970
  }
3971
3971
  ]
@@ -5173,7 +5173,7 @@
5173
5173
  "type": "success",
5174
5174
  "stages": [
5175
5175
  {
5176
- "output": "';'(a)",
5176
+ "output": ";(a)",
5177
5177
  "variables": "[]"
5178
5178
  }
5179
5179
  ]
@@ -5189,7 +5189,7 @@
5189
5189
  "type": "success",
5190
5190
  "stages": [
5191
5191
  {
5192
- "output": "';'(a)",
5192
+ "output": ";(a)",
5193
5193
  "variables": "[]"
5194
5194
  }
5195
5195
  ]
@@ -5205,7 +5205,7 @@
5205
5205
  "type": "success",
5206
5206
  "stages": [
5207
5207
  {
5208
- "output": "';'(a,b)",
5208
+ "output": ";(a,b)",
5209
5209
  "variables": "[]"
5210
5210
  }
5211
5211
  ]
@@ -4698,6 +4698,34 @@ function documentationSyncCases() {
4698
4698
  true,
4699
4699
  'non-semantic internal operator spacing remains flexible',
4700
4700
  );
4701
+ const barWriter = {
4702
+ id: 181,
4703
+ query: "writeq((a-->b,c|d)).",
4704
+ input: "op(1105,xfy,'|').\nwriteq((a-->b,c|d)).",
4705
+ };
4706
+ assertEqual(
4707
+ matchesUpstreamExpectation(
4708
+ 'a-->b,c|d',
4709
+ { type: 'success', stages: [{ output: "a-->b,c'|'d", variables: '[]' }] },
4710
+ barWriter,
4711
+ ),
4712
+ false,
4713
+ 'quoted bar is not accepted in operator-form writer output',
4714
+ );
4715
+ const semicolonWriter = {
4716
+ id: 331,
4717
+ query: 'write_canonical(;(a,b)).',
4718
+ input: 'write_canonical(;(a,b)).',
4719
+ };
4720
+ assertEqual(
4721
+ matchesUpstreamExpectation(
4722
+ ';(a,b)',
4723
+ { type: 'success', stages: [{ output: "';'(a,b)", variables: '[]' }] },
4724
+ semicolonWriter,
4725
+ ),
4726
+ false,
4727
+ 'quoted semicolon is not accepted when the ISO name token is bare',
4728
+ );
4701
4729
  const repeated = {
4702
4730
  id: 227, input: 'write_canonical(B+B).',
4703
4731
  outcome: { type: 'success', stages: [{ output: '+(_A,_A)', variables: "['B' = B]" }] },
package/test/run-wg17.mjs CHANGED
@@ -252,6 +252,48 @@ function termEquivalent(expected, actual, item) {
252
252
  }
253
253
  }
254
254
 
255
+ function soloTokenQuoteSignature(text) {
256
+ const signature = [];
257
+ let quote = null;
258
+ const source = presentationText(text);
259
+ for (let index = 0; index < source.length; index++) {
260
+ const ch = source[index];
261
+ if (quote != null) {
262
+ if (ch === '\\') {
263
+ index++;
264
+ continue;
265
+ }
266
+ if (ch === quote) {
267
+ if (source[index + 1] === quote) {
268
+ index++;
269
+ continue;
270
+ }
271
+ quote = null;
272
+ continue;
273
+ }
274
+ if (ch === '|' || ch === ';' || ch === ',') signature.push(`${ch}:quoted`);
275
+ continue;
276
+ }
277
+ if (ch === "'") {
278
+ quote = ch;
279
+ continue;
280
+ }
281
+ if (ch === '|' || ch === ';' || ch === ',') signature.push(`${ch}:bare`);
282
+ }
283
+ return signature;
284
+ }
285
+
286
+ function preservesWriterSoloTokenSpelling(expected, actual, item) {
287
+ // For writer conformity cases, semantic term equivalence is deliberately
288
+ // not enough: ISO distinguishes the bare bar operator token from atom '|',
289
+ // and semicolon is itself a valid unquoted name token. This prevents the
290
+ // fallback parser comparison from accepting regressions such as `a'|'b` or
291
+ // canonical `';'(a,b)` when the WG17 expected spelling is `a|b` / `;(a,b)`.
292
+ if (!/\bwrite(?:q|_canonical|_term)?\s*\(/.test(item?.query ?? '')) return true;
293
+ return JSON.stringify(soloTokenQuoteSignature(expected)) ===
294
+ JSON.stringify(soloTokenQuoteSignature(actual));
295
+ }
296
+
255
297
  function abbreviatedErrorMatches(expected, actual) {
256
298
  if (actual.type !== 'error') return false;
257
299
  const formal = actual.formal ?? '';
@@ -284,6 +326,7 @@ function textExpectationMatches(expectedText, actual, item, example = false) {
284
326
  // boundary, notably Cor.3 prefix-operator cases such as `- (1^2)`. Do not
285
327
  // erase that boundary before the semantic/layout-tolerant fallbacks below.
286
328
  if (!preservesLeadingOperatorParenLayout(left, right)) continue;
329
+ if (!preservesWriterSoloTokenSpelling(left, right, item)) continue;
287
330
  if (stripLayoutOutsideQuotes(left) === stripLayoutOutsideQuotes(right)) return true;
288
331
  if (termEquivalent(left, right, item)) return true;
289
332
  if (example) {
@@ -95,7 +95,10 @@ This ledger counts an upstream case when its WG17 identifier, query, and
95
95
  expected ISO disposition are stored in the offline executable matrix. Every
96
96
  case is executed against the upstream Codex expectation. Reviewed exact
97
97
  EyeProlog outcomes are additional regression locks and can never override the
98
- upstream assertion.
98
+ upstream assertion. Writer expectations also preserve ISO-significant token
99
+ spelling: in particular, a bare bar used as an operator is not treated as
100
+ equivalent to a quoted atom, and a bare semicolon name token is not silently
101
+ accepted in quoted form.
99
102
 
100
103
  ## Current standing
101
104