eyeprolog 1.5.54 → 1.5.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/README.md CHANGED
@@ -73,7 +73,6 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
73
73
  - [ISO conformance review](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
74
74
  - [Latest Neumerkel conformity](test/conformance/NEUMERKEL-LATEST.md) — tracked result from the current live upstream inventory
75
75
  - [Conformance report](conformance-report.md) — generated executable conformance status, local corpus summary, and known deviations
76
- - [Acknowledgements](ACKNOWLEDGEMENTS.md) — funding, standards consulted, and credits for vendored conformance corpora
77
76
  - [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
78
77
  ## RDF, Prolog, and symbiotic knowledge graphs
79
78
 
@@ -70,3 +70,24 @@ runs that corpus live through the Neumerkel gate; the offline regression
70
70
  suite runs all 58 vendored quads. Neither expectations nor error matching
71
71
  are relaxed for quads 41-44.
72
72
 
73
+
74
+ ## Integer flag choice under `bounded=false`
75
+
76
+ EyeProlog reports `bounded=false` and uses arbitrary-precision integers.
77
+ It therefore associates no current value with `max_integer` or
78
+ `min_integer`, so `current_prolog_flag/2` does not enumerate them and
79
+ fails when either is named. Both flags stay registered, so
80
+ `set_prolog_flag/2` still reaches the normal non-changeable-flag errors.
81
+
82
+ This is an implementation choice, not a requirement of Part 1. Clause
83
+ 7.11.1.1 defines the `bounded` flag and does not govern
84
+ `current_prolog_flag/2` outcomes, while 7.11.1.2 and 7.11.1.3 give both
85
+ flags an implementation-defined default value unconditionally; the
86
+ `bounded` condition constrains what that value *means*, not whether the
87
+ flag exists. Two alternative readings are equally defensible: expose
88
+ implementation-defined values so the flags enumerate, or treat them as
89
+ unsupported and raise `domain_error(prolog_flag, Flag)` per 8.17.2.3 b.
90
+ EyeProlog prefers silence over inventing a largest integer that its
91
+ arithmetic does not have. The vendored Prologue corpus records the
92
+ resulting single divergence rather than patching the upstream fixture.
93
+
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.54",
6
+ "version": "1.5.55",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -28,7 +28,6 @@
28
28
  "files": [
29
29
  "LICENSE.md",
30
30
  "README.md",
31
- "ACKNOWLEDGEMENTS.md",
32
31
  "index.js",
33
32
  "index.d.ts",
34
33
  "playground.html",
package/src/datalog.js CHANGED
@@ -32,7 +32,7 @@ function sameScalar(left, right) {
32
32
  return scalarKey(left) === scalarKey(right);
33
33
  }
34
34
 
35
- export class DatalogRelation {
35
+ class DatalogRelation {
36
36
  constructor(arity) {
37
37
  this.arity = arity;
38
38
  this.rows = [];
@@ -66,7 +66,7 @@ function sourceTermList(term, env) {
66
66
  return null;
67
67
  }
68
68
 
69
- export function systemExpandTerm(term, module = 'user') {
69
+ function systemExpandTerm(term, module = 'user') {
70
70
  const resolved = deref(term, new Env());
71
71
  if (resolved.type === COMPOUND && resolved.name === '-->' && resolved.arity === 2) {
72
72
  const expanded = expandDcgRuleClause({ head: resolved, body: [] }, module);
@@ -23,13 +23,3 @@ export function isStrictIsoPcsCharacter(character) {
23
23
  if (typeof character !== 'string' || Array.from(character).length !== 1) return false;
24
24
  return isStrictIsoPcsCodePoint(character.codePointAt(0));
25
25
  }
26
-
27
- // EyeProlog chooses the Unicode scalar value as the collating-sequence integer.
28
- export function strictIsoCollatingInteger(character) {
29
- return isStrictIsoPcsCharacter(character) ? character.codePointAt(0) : null;
30
- }
31
-
32
- export function assertStrictIsoPcsCharacter(character, formal = 'representation_error(character)') {
33
- if (!isStrictIsoPcsCharacter(character)) throw new CharacterRepresentationError(formal);
34
- return character;
35
- }
package/src/iso.js CHANGED
@@ -35,7 +35,7 @@ class ThrownTerm extends Error {
35
35
  const succeed = function* ({ env }) { yield env; };
36
36
  const fail = function* () {};
37
37
 
38
- export const isoBuiltins = {
38
+ const isoBuiltins = {
39
39
  register(registry) {
40
40
  registry.add('true', 0, succeed, { deterministic: true });
41
41
  registry.add('fail', 0, fail, { deterministic: true });
@@ -956,10 +956,15 @@ function* currentPrologFlagSolutions({ solver, goal, env }, state) {
956
956
  .filter(([name, definition]) => definition.value != null && (flag.type === VAR || flag.name === name));
957
957
  for (let index = 0; index < definitions.length; index++) {
958
958
  const [name, definition] = definitions[index];
959
- // ISO 7.11.1.1: when bounded=false, max_integer and min_integer have no
960
- // current value and current_prolog_flag/2 must therefore not enumerate
961
- // them. The definitions remain registered so attempts to change these
962
- // non-changeable flags still receive the normal flag error handling.
959
+ // Implementation choice: with bounded=false there is no largest integer to
960
+ // report, so max_integer and min_integer carry no current value and are not
961
+ // enumerated. ISO 7.11.1.1 defines the bounded flag and does not govern
962
+ // current_prolog_flag/2; 7.11.1.2 and 7.11.1.3 give both flags an
963
+ // implementation-defined default unconditionally, so the alternative
964
+ // reading (expose values, or raise domain_error(prolog_flag)) is equally
965
+ // available. See conformance-report.md. The definitions remain registered
966
+ // so attempts to change these non-changeable flags still receive the
967
+ // normal flag error handling.
963
968
  const next = env.clone();
964
969
  if (unify(goal.args[0], atom(name), next) && unify(goal.args[1], definition.value, next)) {
965
970
  state.pending = index + 1 < definitions.length;
@@ -1802,15 +1807,6 @@ function parseReadTermText(text, solver) {
1802
1807
  });
1803
1808
  }
1804
1809
 
1805
- export function isCompleteReadTermText(text, solver) {
1806
- try {
1807
- parseReadTermText(text, solver);
1808
- return true;
1809
- } catch (_) {
1810
- return false;
1811
- }
1812
- }
1813
-
1814
1810
  function readTermFromStream(stream, solver) {
1815
1811
  if (stream.pastEnd && stream.eofAction === 'error') {
1816
1812
  throw new PrologError('permission_error(input, past_end_of_stream)', streamHandle(stream.id));
@@ -16,13 +16,13 @@ export function componentHasNegativeEdge(start, deps, negativeEdges) {
16
16
  return negativeEdges.some(([from, to]) => component.has(from) && component.has(to));
17
17
  }
18
18
 
19
- export function compactClauseIsDirectRecursive(clause, group) {
19
+ function compactClauseIsDirectRecursive(clause, group) {
20
20
  return isCompactBinaryClause(clause) && clause.bodyName === group.name && group.arity === 2;
21
21
  }
22
22
 
23
23
 
24
24
 
25
- export function clauseIsDirectRecursive(clause, group) {
25
+ function clauseIsDirectRecursive(clause, group) {
26
26
  if (isCompactBinaryClause(clause)) return compactClauseIsDirectRecursive(clause, group);
27
27
  return clause.body.some((goal) =>
28
28
  goal.type === COMPOUND && goal.name === group.name && goal.arity === group.arity
@@ -86,7 +86,7 @@ function reachableIndexesTransposed(target, deps, candidates) {
86
86
  }
87
87
 
88
88
 
89
- export function isFiniteDatalogArgument(term) {
89
+ function isFiniteDatalogArgument(term) {
90
90
  return term?.type === VAR || term?.type === ATOM || term?.type === 'string' || term?.type === 'number';
91
91
  }
92
92
 
@@ -289,7 +289,7 @@ export function isFiniteWfsDatalogGroup(program, group, cache = new Map(), visit
289
289
  return finite;
290
290
  }
291
291
 
292
- export function collectVariables(term, output) {
292
+ function collectVariables(term, output) {
293
293
  if (!term) return;
294
294
  if (term.type === VAR) {
295
295
  output.add(term.name);
@@ -420,12 +420,12 @@ export function isPortableBetweenGenerator(group) {
420
420
  group.clauses.every((clause) => clause.eyePrologLibraryPortable === true);
421
421
  }
422
422
 
423
- export function termContainsVariable(term, name) {
423
+ function termContainsVariable(term, name) {
424
424
  if (term.type === 'var') return term.name === name;
425
425
  return term.args.some((arg) => termContainsVariable(arg, name));
426
426
  }
427
427
 
428
- export function sameClauseTerm(left, right) {
428
+ function sameClauseTerm(left, right) {
429
429
  if (left.type !== right.type || left.name !== right.name || left.args.length !== right.args.length) return false;
430
430
  return left.args.every((arg, index) => sameClauseTerm(arg, right.args[index]));
431
431
  }
@@ -446,7 +446,7 @@ export function directGoalDependencyKey(goal) {
446
446
  }
447
447
 
448
448
  // ISO 13211-1, 7.1.6.3: `V1^V2^...^Goal` has the iterated goal Goal.
449
- export function iteratedGoal(goal) {
449
+ function iteratedGoal(goal) {
450
450
  let current = goal;
451
451
  while (current?.type === COMPOUND && current.name === '^' && current.arity === 2) {
452
452
  current = current.args[1];
@@ -68,14 +68,6 @@ export function compactHeadArgName(clause, index) {
68
68
  return index === 0 ? clause.head0Name : clause.head1Name;
69
69
  }
70
70
 
71
- export function compactBodyArgType(clause, index) {
72
- return index === 0 ? clause.body0Type : clause.body1Type;
73
- }
74
-
75
- export function compactBodyArgName(clause, index) {
76
- return index === 0 ? clause.body0Name : clause.body1Name;
77
- }
78
-
79
71
  export function clauseBodyLength(clause) {
80
72
  return isCompactBinaryClause(clause) ? (clause.bodyName == null ? 0 : 1) : clause.body.length;
81
73
  }
package/src/program.js CHANGED
@@ -1940,7 +1940,3 @@ function predicateIndicator(name, arity) {
1940
1940
  export function makeProgram(source, options = {}) {
1941
1941
  return Program.parse(source, options);
1942
1942
  }
1943
-
1944
- export function parseSourceClauses(source, options = {}) {
1945
- return parseClauses(source, options);
1946
- }
@@ -58,7 +58,9 @@ concrete mismatches that are now part of the closed Part 1 review:
58
58
 
59
59
  - `bounded=false` no longer exposes implementation-specific `unbounded` values
60
60
  for `max_integer` or `min_integer`; the corresponding
61
- `current_prolog_flag/2` queries fail as specified by 7.11.1.1;
61
+ `current_prolog_flag/2` queries fail. Part 1 does not require that outcome
62
+ (7.11.1.1 defines the `bounded` flag and does not govern
63
+ `current_prolog_flag/2`), so it is recorded as an implementation choice;
62
64
  - preparation-time `char_conversion/2` now converts later unquoted source text
63
65
  when the `char_conversion` flag is `on`, leaves quoted characters unchanged,
64
66
  and feeds the same mapping into execution-time term input.
@@ -32,7 +32,9 @@ included before its examples are run.
32
32
 
33
33
  The upstream Prologue snapshot contains one `max_integer` quad that accepts an
34
34
  implementation-specific `Max = unbounded` result. EyeProlog deliberately does
35
- not patch that vendored fixture: with its ISO `bounded=false` choice, Part 1
36
- 7.11.1.1 requires `current_prolog_flag(max_integer, _)` to fail. The regression
37
- gate therefore records this single standards-driven divergence explicitly while
38
- requiring the other 32 quads to pass.
35
+ not patch that vendored fixture: with `bounded=false` it reports no value for
36
+ `max_integer`, so `current_prolog_flag(max_integer, _)` fails. Part 1 does not
37
+ mandate that outcome -- 7.11.1.1 defines the `bounded` flag and says nothing
38
+ about `current_prolog_flag/2` -- so this is an implementation choice, not a
39
+ standards requirement. The regression gate records the single divergence
40
+ explicitly while requiring the other 32 quads to pass.
@@ -120,6 +120,7 @@ export function formatConformanceReport(report = buildConformanceReport()) {
120
120
  lines.push(`| **Total** | **${report.total.positive}** | **${report.total.errors}** | **${report.total.warnings}** | **${report.total.proofs}** | **${report.total.total}** |`);
121
121
 
122
122
  lines.push(...dcgConformanceSection());
123
+ lines.push(...integerFlagChoiceSection());
123
124
 
124
125
  if (report.corpusIssues.length > 0) {
125
126
  lines.push('', '## Corpus issues', '');
@@ -163,6 +164,34 @@ function dcgConformanceSection() {
163
164
  ];
164
165
  }
165
166
 
167
+ // Record the unbounded-integer flag choice as an explicit implementation
168
+ // decision rather than leaving it implicit in the source comments.
169
+ function integerFlagChoiceSection() {
170
+ return [
171
+ '',
172
+ '## Integer flag choice under `bounded=false`',
173
+ '',
174
+ 'EyeProlog reports `bounded=false` and uses arbitrary-precision integers.',
175
+ 'It therefore associates no current value with `max_integer` or',
176
+ '`min_integer`, so `current_prolog_flag/2` does not enumerate them and',
177
+ 'fails when either is named. Both flags stay registered, so',
178
+ '`set_prolog_flag/2` still reaches the normal non-changeable-flag errors.',
179
+ '',
180
+ 'This is an implementation choice, not a requirement of Part 1. Clause',
181
+ '7.11.1.1 defines the `bounded` flag and does not govern',
182
+ '`current_prolog_flag/2` outcomes, while 7.11.1.2 and 7.11.1.3 give both',
183
+ 'flags an implementation-defined default value unconditionally; the',
184
+ '`bounded` condition constrains what that value *means*, not whether the',
185
+ 'flag exists. Two alternative readings are equally defensible: expose',
186
+ 'implementation-defined values so the flags enumerate, or treat them as',
187
+ 'unsupported and raise `domain_error(prolog_flag, Flag)` per 8.17.2.3 b.',
188
+ 'EyeProlog prefers silence over inventing a largest integer that its',
189
+ 'arithmetic does not have. The vendored Prologue corpus records the',
190
+ 'resulting single divergence rather than patching the upstream fixture.',
191
+ '',
192
+ ];
193
+ }
194
+
166
195
  function executeGate(name, runSuite) {
167
196
  const failures = [];
168
197
  const reporter = {
@@ -95,6 +95,38 @@ export function runIsoStrict(reporter = new TestReporter()) {
95
95
  }
96
96
  });
97
97
 
98
+ // ISO 7.10.5 and 8.14.2: write_term/2 with quoted(true) shall emit text that
99
+ // reads back as the same term under the current operator table. Individual
100
+ // spellings are pinned elsewhere; this is the general property, over a corpus
101
+ // chosen to stress operator priority, prefix minus, and quoted atoms.
102
+ reporter.test('writeq output reads back as the identical term', () => {
103
+ const corpus = [
104
+ '-(1)', '- (-(1))', '-(a)', '- - 1', '1 - -1', 'f(-1)', '-(1)^2', '2^ -1',
105
+ '1*(2+3)', '(1*2)+3', '2** -3', 'a* -1', 'a- (-1)', '1 rem 2', 'a mod b',
106
+ '[a|b]', '[[]]', '[]', '[-]', '[-,+]', '[a,b,c]', '{}', '{a,b}',
107
+ "f(',')", "','(a,b)", 'f(;)', 'f(!)', 'f(-)', 'f(+)', 'f(*)',
108
+ 'f(a,(b,c))', 'f((a,b))', 'f(g(h(1)))', '(a:-b,c)', '(a;b;c)', '(a->b;c)',
109
+ '*(1,2)', '+(1)', 'a=..b', "'ABC'", 'abc', "''", "' '", "'/*'", "'%'",
110
+ '1.0', '-1.0', '0.0', '1.0e-10', '1.5e300',
111
+ String.raw`'\n'`, String.raw`'\t'`, String.raw`'don''t'`,
112
+ String.raw`\+ a`, String.raw`f('|')`,
113
+ ];
114
+ for (const text of corpus) {
115
+ let written = '';
116
+ run('', {
117
+ isoStrict: true,
118
+ goal: `writeq(${text})`,
119
+ ioOptions: { write: (chunk) => { written += chunk; } },
120
+ });
121
+ const reread = run('', {
122
+ isoStrict: true,
123
+ goal: `read_term(X,[]), X == (${text})`,
124
+ ioOptions: { input: `${written}.` },
125
+ });
126
+ equal(reread.stats.completed_goal_lists, 1, `writeq round-trip ${text} -> ${written}`);
127
+ }
128
+ });
129
+
98
130
  reporter.test('keeps Corrigendum 2 core predicates and excludes Part 3 phrase', () => {
99
131
  const registry = createStrictIsoRegistry();
100
132
  equal(Boolean(registry.get('subsumes_term', 2)), true, 'subsumes_term/2');
@@ -2325,10 +2325,11 @@ c4 ?- call((!;1)).
2325
2325
  program.quads = maxIntegerQuads;
2326
2326
  const result = publicApi.runQuads(program);
2327
2327
  // The upstream working-draft quad accepts either integer overflow or
2328
- // Max=unbounded. ISO/IEC 13211-1 7.11.1.1 instead says that when
2329
- // bounded=false, current_prolog_flag(max_integer, N) fails. Preserve
2330
- // the upstream fixture unchanged and make that one deliberate
2331
- // standards-driven divergence explicit in the regression gate.
2328
+ // Max=unbounded. EyeProlog reports no value for max_integer when
2329
+ // bounded=false, so current_prolog_flag(max_integer, N) fails. Part 1
2330
+ // does not mandate that outcome, so this is an implementation choice
2331
+ // rather than a standards requirement. Preserve the upstream fixture
2332
+ // unchanged and make the one deliberate divergence explicit here.
2332
2333
  assertEqual(result.total, 1, 'quad total');
2333
2334
  assertEqual(result.passed, 0, 'quad passed');
2334
2335
  assertEqual(result.failed, 1, 'quad failed');
@@ -9070,7 +9071,9 @@ function declaredDefaultExportNames() {
9070
9071
  function missingDocumentedPackageScripts() {
9071
9072
  const docs = documentationFiles();
9072
9073
  const missing = [];
9073
- const nativeCommands = new Set(['exec', 'install', 'link']);
9074
+ // Native npm subcommands are not package scripts, so documenting them must
9075
+ // not require a matching entry in package.json.
9076
+ const nativeCommands = new Set(['exec', 'install', 'link', 'pack', 'publish', 'version', 'ci']);
9074
9077
  for (const file of docs) {
9075
9078
  const text = fs.readFileSync(file, 'utf8');
9076
9079
  for (const line of text.split('\n')) {
@@ -6194,8 +6194,15 @@ silently changing a static program.
6194
6194
  - **`occurs_check`** — **Default:** `true`; **Allowed:** `true`, `error`; **Mutable:** yes.
6195
6195
 
6196
6196
  Because `bounded=false`, `current_prolog_flag(max_integer, _)` and
6197
- `current_prolog_flag(min_integer, _)` fail as required by ISO 7.11.1.1;
6198
- EyeProlog does not expose an `unbounded` sentinel as either flag value.
6197
+ `current_prolog_flag(min_integer, _)` fail, and EyeProlog does not expose an
6198
+ `unbounded` sentinel as either flag value. This is a deliberate implementation
6199
+ choice rather than a requirement: ISO 7.11.1.1 defines the `bounded` flag and
6200
+ does not govern `current_prolog_flag/2` outcomes, and 7.11.1.2 and 7.11.1.3
6201
+ give `max_integer` and `min_integer` an implementation-defined default value
6202
+ unconditionally, making the `bounded` condition a constraint on what the value
6203
+ *means* rather than on whether the flag exists. A processor with unbounded
6204
+ integers has no largest integer to report, so EyeProlog declines to invent one.
6205
+ See `conformance-report.md` for the alternative reading.
6199
6206
  Preparation-time `char_conversion/2` mappings affect later unquoted source text
6200
6207
  and also initialize the execution-time conversion mapping; setting the
6201
6208
  `char_conversion` flag to `off` disables conversion for following source text.
@@ -1,36 +0,0 @@
1
- # Acknowledgements
2
-
3
- This work was carried out at [IDLab](https://idlab.ugent.be/), Ghent University
4
- – imec, within the [KNoWS](https://knows.idlab.ugent.be/) (Knowledge on Web
5
- Scale) research team, as part of the **Koreografeye** project.
6
-
7
- ## Standards
8
-
9
- Conformance work on this repository targets the following standards.
10
- Licensed standards documents are not redistributed here:
11
-
12
- - ISO/IEC 13211-1:1995, *Information technology — Programming languages —
13
- Prolog — Part 1: General core*
14
- - ISO/IEC 13211-1:1995/Cor.1:2007
15
- - ISO/IEC 13211-1:1995/Cor.2:2012
16
- - ISO/IEC 13211-1:1995/Cor.3:2017
17
- - ISO/IEC 13211-2:2000, *Part 2: Modules*, and its 2013 amendment
18
- - ISO/IEC TS 13211-3:2025, *Part 3: Definite clause grammar rules*
19
-
20
- ## External conformance corpora
21
-
22
- The test suite executes third-party material that is vendored unmodified and
23
- credited in place:
24
-
25
- - Ulrich Neumerkel's ISO Prolog conformity corpora (TU Wien), including the
26
- `phrase_quad.pl` and `variable_names_quad.pl` snapshots under
27
- `test/fixtures/`.
28
- - The WG17 syntax conformity-testing matrix.
29
- - Test cases adapted from the Logtalk ISO conformance suite, marked as such in
30
- the corpus files that use them.
31
-
32
- ## Citation
33
-
34
- When referring to this work in a publication, please acknowledge the project as
35
- described above. A suggested sentence is available in the paper template used by
36
- the KNoWS team.