eyeprolog 1.3.41 → 1.3.43

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/README.md CHANGED
@@ -210,6 +210,14 @@ expansion/`phrase/2-3`; it also removes the EyeProlog `occurs_check` flag,
210
210
  Normal mode is unchanged and continues to support modules, DCGs, quads,
211
211
  libraries, proofs, cleanup-aware control, and the other documented extensions.
212
212
 
213
+ Strict mode also makes the Part 1 processor-character-set choice explicit: its
214
+ PCS is 7-bit ASCII (U+0000..U+007F), C0 controls and DEL are classified as
215
+ extended layout characters, and the collating-sequence integer of each
216
+ character is its ASCII code. Characters or character codes outside that set
217
+ raise representation errors in strict parsing and character operations. Normal
218
+ mode retains Unicode scalar character data as an implementation-specific
219
+ extension.
220
+
213
221
  The auditable processor-requirement checklist lives in
214
222
  [`test/conformance/ISO-COMPLIANCE.md`](test/conformance/ISO-COMPLIANCE.md).
215
223
  The ISO 5.4 implementation-defined/implementation-specific decision index is
@@ -221,10 +229,18 @@ newly upgraded cases run directly against the upstream Codex expectation.
221
229
  EyeProlog does not yet claim independent certification or closure of every
222
230
  normative Part 1 requirement.
223
231
 
224
- ## ISO modules and definite clause grammars
232
+ ## Module and definite clause grammar compatibility profiles
233
+
234
+ Normal EyeProlog supports the widely used `module/2`, `use_module/1-2`,
235
+ `meta_predicate/1`, and `Module:Goal` interface reflected in later WG17 module
236
+ amendment work. This is a practical module compatibility profile; EyeProlog
237
+ does **not** currently claim a clause-by-clause implementation or certification
238
+ of the complete ISO/IEC 13211-2:2000 module model.
225
239
 
226
- EyeProlog implements ISO/IEC 13211-2 modules and the grammar rules and
227
- `phrase/2-3` predicates of ISO/IEC TS 13211-3:2025. For example:
240
+ Definite clause grammar support follows the ISO/IEC TS 13211-3 grammar-rule and
241
+ `phrase/2-3` model and is exercised by the conformance corpus. As with Part 1,
242
+ that implementation evidence is not an independent certification of every
243
+ Part 3 requirement. For example:
228
244
 
229
245
  ```prolog
230
246
  sentence --> [hello], noun.
@@ -254,7 +270,7 @@ EyeProlog also adds 128 public library predicate indicators to its 129-entry ISO
254
270
  profile. **88 are implemented entirely as ordinary Prolog clauses** in focused
255
271
  modules under `src/lib/`; the remaining control predicates and finite-domain
256
272
  `library(clpz)` kernel use backtrackable host support.
257
- They are ISO/IEC 13211-2 modules loaded explicitly by purpose, such as
273
+ They are ordinary Prolog modules using EyeProlog's documented module compatibility surface, loaded explicitly by purpose, such as
258
274
  `library(lists)`, `library(lambda)`, `library(strings)`, `library(aggregate)`, or
259
275
  `library(clpz)`.
260
276
  Portable text
@@ -234,7 +234,7 @@ npm run generate
234
234
  ## Chapter 38: Language and ISO profile
235
235
 
236
236
  - [01-city.pl](chapter-38/01-city.pl)
237
- - [02-look_ahead.pl](chapter-38/02-look_ahead.pl) — ISO Part 3 definite clause grammars
237
+ - [02-look_ahead.pl](chapter-38/02-look_ahead.pl) — Part 3-oriented definite clause grammars
238
238
 
239
239
  ## Chapter 39: Built-in predicates by programming role
240
240
 
@@ -1,2 +1,2 @@
1
- % From The Art of EyeProlog, Chapter 38 — ISO Part 3 definite clause grammars.
1
+ % From The Art of EyeProlog, Chapter 38 — Part 3-oriented definite clause grammars.
2
2
  look_ahead(X), [X] --> [X].
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.41",
6
+ "version": "1.3.43",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -0,0 +1,37 @@
1
+ // ISO/IEC 13211-1 processor-character-set choices used by --iso-strict.
2
+ //
3
+ // EyeProlog's strict Part 1 profile deliberately chooses a finite PCS so every
4
+ // accepted character has one documented lexical classification and one stable
5
+ // collating-sequence integer. Normal mode keeps the broader Unicode surface as
6
+ // an implementation-specific extension.
7
+
8
+ export class CharacterRepresentationError extends Error {
9
+ constructor(formal = 'representation_error(character)') {
10
+ super(`error(${formal})`);
11
+ this.name = 'CharacterRepresentationError';
12
+ this.formal = formal;
13
+ }
14
+ }
15
+
16
+ // Strict PCS: the 128 ASCII characters U+0000..U+007F. Printable ASCII
17
+ // carries the Part 1 lexical classes; the remaining C0 controls plus DEL are
18
+ // implementation-defined extended layout characters. This lets every ISO
19
+ // octal/hexadecimal character escape in the ASCII range denote a PCS member.
20
+ // The collating-sequence integer is the Unicode/ASCII code point.
21
+ export function isStrictIsoPcsCodePoint(code) {
22
+ return Number.isInteger(code) && code >= 0 && code <= 0x7f;
23
+ }
24
+
25
+ export function isStrictIsoPcsCharacter(character) {
26
+ if (typeof character !== 'string' || Array.from(character).length !== 1) return false;
27
+ return isStrictIsoPcsCodePoint(character.codePointAt(0));
28
+ }
29
+
30
+ export function strictIsoCollatingInteger(character) {
31
+ return isStrictIsoPcsCharacter(character) ? character.codePointAt(0) : null;
32
+ }
33
+
34
+ export function assertStrictIsoPcsCharacter(character, formal = 'representation_error(character)') {
35
+ if (!isStrictIsoPcsCharacter(character)) throw new CharacterRepresentationError(formal);
36
+ return character;
37
+ }
package/src/iso.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  import { formatTermForWrite } from './write.js';
14
14
  import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
15
15
  import { INVALID_UTF8_SENTINEL } from './io.js';
16
+ import { CharacterRepresentationError, isStrictIsoPcsCharacter, isStrictIsoPcsCodePoint } from './iso-character.js';
16
17
  import {
17
18
  characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
18
19
  } from './syntax-scan.js';
@@ -662,6 +663,11 @@ function* currentPrologFlagBuiltin({ solver, goal, env }) {
662
663
  throw new PrologError('domain_error(prolog_flag)', flag);
663
664
  }
664
665
  for (const [name, definition] of solver.prologFlags) {
666
+ // ISO 7.11.1.1: when bounded=false, max_integer and min_integer have no
667
+ // current value and current_prolog_flag/2 must therefore not enumerate
668
+ // them. The definitions remain registered so attempts to change these
669
+ // non-changeable flags still receive the normal flag error handling.
670
+ if (definition.value == null) continue;
665
671
  const next = env.clone();
666
672
  if (unify(goal.args[0], atom(name), next) && unify(goal.args[1], definition.value, next)) yield next;
667
673
  }
@@ -772,7 +778,7 @@ function* currentOpBuiltin({ solver, goal, env }) {
772
778
  }
773
779
  }
774
780
 
775
- function conversionCharacter(term, env, current = false) {
781
+ function conversionCharacter(term, env, current = false, solver = null) {
776
782
  const value = deref(term, env);
777
783
  if (value.type === VAR) {
778
784
  if (current) return value;
@@ -782,18 +788,21 @@ function conversionCharacter(term, env, current = false) {
782
788
  if (current) throw new PrologError('type_error(character)', value);
783
789
  throw new PrologError('representation_error(character)');
784
790
  }
791
+ if (solver?.isoStrict && !isStrictIsoPcsCharacter(value.name)) {
792
+ throw new PrologError('representation_error(character)', value);
793
+ }
785
794
  return value;
786
795
  }
787
796
  function* charConversionBuiltin({ solver, goal, env }) {
788
- const input = conversionCharacter(goal.args[0], env);
789
- const output = conversionCharacter(goal.args[1], env);
797
+ const input = conversionCharacter(goal.args[0], env, false, solver);
798
+ const output = conversionCharacter(goal.args[1], env, false, solver);
790
799
  if (input.name === output.name) solver.charConversions.delete(input.name);
791
800
  else solver.charConversions.set(input.name, output.name);
792
801
  yield env;
793
802
  }
794
803
  function* currentCharConversionBuiltin({ solver, goal, env }) {
795
- const input = conversionCharacter(goal.args[0], env, true);
796
- const output = conversionCharacter(goal.args[1], env, true);
804
+ const input = conversionCharacter(goal.args[0], env, true, solver);
805
+ const output = conversionCharacter(goal.args[1], env, true, solver);
797
806
  for (const [from, to] of [...solver.charConversions]) {
798
807
  const next = env.clone();
799
808
  if (unify(input, atom(from), next) && unify(output, atom(to), next)) yield next;
@@ -1039,6 +1048,9 @@ function inputUnitBuiltin(name) {
1039
1048
  throw error;
1040
1049
  }
1041
1050
  if (unit == null && !peek) stream.pastEnd = true;
1051
+ if (!binary && unit != null && solver.isoStrict && !isStrictIsoPcsCharacter(unit)) {
1052
+ throw new PrologError('representation_error(character)');
1053
+ }
1042
1054
  const result = unit == null ? (binary ? numberTerm(-1) : name.endsWith('code') ? numberTerm(-1) : atom('end_of_file'))
1043
1055
  : binary ? numberTerm(unit) : name.endsWith('code') ? numberTerm(unit.codePointAt(0)) : atom(unit);
1044
1056
  const target = goal.args[goal.arity - 1];
@@ -1054,6 +1066,9 @@ function outputUnitBuiltin(name) {
1054
1066
  if (name === 'put_char') {
1055
1067
  if (stream.type !== 'text') throw new PrologError('permission_error(output, binary_stream)', streamHandle(stream.id));
1056
1068
  if (!oneChar(value)) throw new PrologError('type_error(character)', value);
1069
+ if (solver.isoStrict && !isStrictIsoPcsCharacter(value.name)) {
1070
+ throw new PrologError('representation_error(character)', value);
1071
+ }
1057
1072
  solver.io.writeUnit(stream, value.name);
1058
1073
  } else {
1059
1074
  if ((name === 'put_byte') !== (stream.type === 'binary')) {
@@ -1063,6 +1078,9 @@ function outputUnitBuiltin(name) {
1063
1078
  const code = BigInt(value.name);
1064
1079
  const max = name === 'put_byte' ? 255n : 0x10ffffn;
1065
1080
  if (code < 0n || code > max) throw new PrologError(name === 'put_byte' ? 'type_error(byte)' : 'representation_error(character_code)');
1081
+ if (name === 'put_code' && solver.isoStrict && !isStrictIsoPcsCodePoint(Number(code))) {
1082
+ throw new PrologError('representation_error(character_code)');
1083
+ }
1066
1084
  solver.io.writeUnit(stream, name === 'put_byte' ? Number(code) : String.fromCodePoint(Number(code)));
1067
1085
  }
1068
1086
  yield env;
@@ -1090,6 +1108,9 @@ function* termTextCandidates(stream, solver) {
1090
1108
  let quote = null, lineComment = false, blockComment = false;
1091
1109
  for (let i = stream.position; i < source.length; i++) {
1092
1110
  const ch = source[i], next = source[i + 1];
1111
+ if (solver.isoStrict && !isStrictIsoPcsCodePoint(ch.charCodeAt(0))) {
1112
+ throw new PrologError('representation_error(character)');
1113
+ }
1093
1114
  // Text streams preserve invalid UTF-8 bytes as an impossible Unicode
1094
1115
  // sentinel. read/1-2 and read_term/2-3 must surface the same character
1095
1116
  // representation error as get_char/1-2 instead of misclassifying the
@@ -1101,7 +1122,7 @@ function* termTextCandidates(stream, solver) {
1101
1122
  if (blockComment) { if (ch === '*' && next === '/') { blockComment = false; i++; } continue; }
1102
1123
  if (quote) {
1103
1124
  if (ch === '\\') i = quotedEscapeEnd(source, i);
1104
- else if (ch !== ' ' && /^[\u0009-\u000d]$/.test(ch)) {
1125
+ else if (ch !== ' ' && /^[\u0000-\u001f\u007f]$/.test(ch)) {
1105
1126
  // Literal layout characters are not quoted characters (6.4.2.1).
1106
1127
  // Surface the lexical error immediately even when there is no later
1107
1128
  // full stop; otherwise read/1 would misreport malformed input as EOF.
@@ -1132,7 +1153,7 @@ function hasNonLayoutRemainder(source, start) {
1132
1153
  return lastNonLayoutIndex(source, start) >= start;
1133
1154
  }
1134
1155
  function lastNonLayoutIndex(source, start = 0) {
1135
- const ignored = /[\u0009-\u000d\u0020]+|%[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\//g;
1156
+ const ignored = /[\u0000-\u0020\u007f]+|%[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\//g;
1136
1157
  ignored.lastIndex = start;
1137
1158
  let cursor = start;
1138
1159
  let last = -1;
@@ -1243,7 +1264,9 @@ function readTermFromStream(stream, solver) {
1243
1264
  stream.position = candidate.end;
1244
1265
  return scopeReadTerm(term);
1245
1266
  } catch (error) {
1246
- if (error instanceof NumberRepresentationError) throw new PrologError(error.formal);
1267
+ if (error instanceof NumberRepresentationError || error instanceof CharacterRepresentationError) {
1268
+ throw new PrologError(error.formal);
1269
+ }
1247
1270
  // A dot inside a graphic operator, such as =.., is only a possible
1248
1271
  // terminator. Keep scanning until a complete term parses.
1249
1272
  }
@@ -1505,30 +1528,35 @@ function oneChar(value) {
1505
1528
  return value.type === ATOM && characters(value.name).length === 1;
1506
1529
  }
1507
1530
 
1508
- function validCharacterCode(value) {
1531
+ function validCharacterCode(value, solver = null) {
1509
1532
  if (value.type !== NUMBER || !isDecimalInteger(value.name)) return false;
1510
1533
  const code = BigInt(value.name);
1511
- return code >= 0n && code <= 0x10ffffn && !(code >= 0xd800n && code <= 0xdfffn);
1534
+ if (code < 0n || code > 0x10ffffn || (code >= 0xd800n && code <= 0xdfffn)) return false;
1535
+ return !solver?.isoStrict || isStrictIsoPcsCodePoint(Number(code));
1512
1536
  }
1513
1537
 
1514
- function listToAtomInput(list, env, kind) {
1538
+ function listToAtomInput(list, env, kind, solver = null) {
1515
1539
  const { items, tail } = listElements(list, env);
1516
1540
  if (tail.type === VAR || items.some((item) => item.type === VAR)) throw new PrologError('instantiation_error');
1517
1541
  if (tail.type !== ATOM || tail.name !== '[]') throw new PrologError('type_error(list)', tail);
1518
1542
  if (kind === 'chars') {
1519
1543
  const invalid = items.find((item) => !oneChar(item));
1520
1544
  if (invalid) throw new PrologError('type_error(character)', invalid);
1545
+ if (solver?.isoStrict) {
1546
+ const outsidePcs = items.find((item) => !isStrictIsoPcsCharacter(item.name));
1547
+ if (outsidePcs) throw new PrologError('representation_error(character)', outsidePcs);
1548
+ }
1521
1549
  return items.map((item) => item.name).join('');
1522
1550
  }
1523
1551
  const nonInteger = items.find((item) => item.type !== NUMBER || !isDecimalInteger(item.name));
1524
1552
  if (nonInteger) throw new PrologError('type_error(integer)', nonInteger);
1525
- const invalid = items.find((item) => !validCharacterCode(item));
1553
+ const invalid = items.find((item) => !validCharacterCode(item, solver));
1526
1554
  if (invalid) throw new PrologError('representation_error(character_code)');
1527
1555
  return items.map((item) => String.fromCodePoint(Number(item.name))).join('');
1528
1556
  }
1529
1557
 
1530
1558
  function atomListBuiltin(kind) {
1531
- return function* ({ goal, env }) {
1559
+ return function* ({ solver, goal, env }) {
1532
1560
  const value = deref(goal.args[0], env);
1533
1561
  if (value.type !== VAR && value.type !== ATOM) throw new PrologError('type_error(atom)', value);
1534
1562
  const list = deref(goal.args[1], env);
@@ -1541,7 +1569,7 @@ function atomListBuiltin(kind) {
1541
1569
  }
1542
1570
  const invalid = supplied.find((item) => item.type !== VAR &&
1543
1571
  (kind === 'chars' ? !oneChar(item) :
1544
- item.type !== NUMBER || !isDecimalInteger(item.name) || !validCharacterCode(item)));
1572
+ item.type !== NUMBER || !isDecimalInteger(item.name) || !validCharacterCode(item, solver)));
1545
1573
  if (invalid) {
1546
1574
  if (kind === 'chars') throw new PrologError('type_error(character)', invalid);
1547
1575
  if (invalid.type !== NUMBER || !isDecimalInteger(invalid.name)) {
@@ -1549,26 +1577,32 @@ function atomListBuiltin(kind) {
1549
1577
  }
1550
1578
  throw new PrologError('representation_error(character_code)');
1551
1579
  }
1580
+ if (solver.isoStrict && characters(value.name).some((ch) => !isStrictIsoPcsCharacter(ch))) {
1581
+ throw new PrologError('representation_error(character)', value);
1582
+ }
1552
1583
  const items = characters(value.name).map((ch) =>
1553
1584
  kind === 'chars' ? atom(ch) : numberTerm(ch.codePointAt(0)));
1554
1585
  if (unify(goal.args[1], listFromItems(items), next)) yield next;
1555
1586
  return;
1556
1587
  }
1557
- if (unify(goal.args[0], atom(listToAtomInput(list, env, kind)), next)) yield next;
1588
+ if (unify(goal.args[0], atom(listToAtomInput(list, env, kind, solver)), next)) yield next;
1558
1589
  };
1559
1590
  }
1560
1591
  const atomCharsBuiltin = atomListBuiltin('chars');
1561
1592
  const atomCodesBuiltin = atomListBuiltin('codes');
1562
1593
 
1563
- function* charCodeBuiltin({ goal, env }) {
1594
+ function* charCodeBuiltin({ solver, goal, env }) {
1564
1595
  const char = deref(goal.args[0], env);
1565
1596
  const code = deref(goal.args[1], env);
1566
1597
  if (char.type === VAR && code.type === VAR) throw new PrologError('instantiation_error');
1567
1598
  if (char.type !== VAR && !oneChar(char)) throw new PrologError('type_error(character)', char);
1599
+ if (char.type === ATOM && solver.isoStrict && !isStrictIsoPcsCharacter(char.name)) {
1600
+ throw new PrologError('representation_error(character)', char);
1601
+ }
1568
1602
  if (code.type !== VAR && (code.type !== NUMBER || !isDecimalInteger(code.name))) {
1569
1603
  throw new PrologError('type_error(integer)', code);
1570
1604
  }
1571
- if (code.type !== VAR && !validCharacterCode(code)) throw new PrologError('representation_error(character_code)');
1605
+ if (code.type !== VAR && !validCharacterCode(code, solver)) throw new PrologError('representation_error(character_code)');
1572
1606
  const next = env.clone();
1573
1607
  if (char.type === ATOM) {
1574
1608
  if (unify(goal.args[1], numberTerm(char.name.codePointAt(0)), next)) yield next;
@@ -1578,7 +1612,7 @@ function* charCodeBuiltin({ goal, env }) {
1578
1612
  function skipNumberLayout(text, start) {
1579
1613
  let position = start;
1580
1614
  while (true) {
1581
- while (position < text.length && /[\u0009-\u000d\u0020]/.test(text[position])) {
1615
+ while (position < text.length && /[\u0000-\u0020\u007f]/.test(text[position])) {
1582
1616
  position++;
1583
1617
  }
1584
1618
  if (text[position] === '%') {
@@ -1642,7 +1676,7 @@ function parseIsoNumber(text) {
1642
1676
  // there without separating layout: `/` *can* continue the graphic token,
1643
1677
  // and the eager-consumer rule therefore keeps `-/**/1` ill-formed (the
1644
1678
  // number_chars continuation corpus case 24).
1645
- if (/[\u0009-\u000d\u0020]/.test(next) || next === '%') {
1679
+ if (/[\u0000-\u0020\u007f]/.test(next) || next === '%') {
1646
1680
  position = skipNumberLayout(text, position + 1);
1647
1681
  sign = '-';
1648
1682
  }