eyeprolog 1.2.41 → 1.2.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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.41",
6
+ "version": "1.2.43",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/cli.js CHANGED
@@ -4,6 +4,7 @@ import fs from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import process from 'node:process';
6
6
  import { goalsFromSource } from './goal-metadata.js';
7
+ import { memoryStatistics } from './platform.js';
7
8
 
8
9
  let engineModule = null;
9
10
  let explanationModule = null;
@@ -230,9 +231,9 @@ async function runDefault(engine, program, options) {
230
231
  } catch (error) {
231
232
  if (error?.name !== 'HaltSignal') throw error;
232
233
  process.exitCode = error.code;
234
+ } finally {
235
+ if (options.stats) printStats(solver.stats);
233
236
  }
234
-
235
- if (options.stats) printStats(solver.stats);
236
237
  }
237
238
 
238
239
  function writeExplanation(explanation, program, resolved, registry) {
@@ -259,7 +260,7 @@ Options:
259
260
  -h, --help Show this help text and exit.
260
261
  -p, --proof Enable proof explanations.
261
262
  -q, --quads Run embedded quad tests and fail if any do not hold.
262
- -s, --stats Print solver statistics to stderr after execution.
263
+ -s, --stats Print solver and memory statistics to stderr after execution.
263
264
  --iso-strict Use ISO/IEC 13211-1 core + Corrigenda 1-3 only;
264
265
  reject EyeProlog language extensions and disable automatic tabling.
265
266
  -v, --version Show the package version and exit.
@@ -294,7 +295,7 @@ function printWarnings(program) {
294
295
 
295
296
  function printStats(stats) {
296
297
  process.stderr.write('eyeprolog stats:\n');
297
- for (const [key, value] of Object.entries(stats)) {
298
+ for (const [key, value] of Object.entries({ ...stats, ...memoryStatistics() })) {
298
299
  process.stderr.write(` ${key}: ${value}\n`);
299
300
  }
300
301
  }
package/src/iso.js CHANGED
@@ -1715,21 +1715,22 @@ function numberListBuiltin(kind) {
1715
1715
  const list = deref(goal.args[1], env);
1716
1716
  if (value.type === VAR && list.type === VAR) throw new PrologError('instantiation_error');
1717
1717
  const text = numberListText(list, env, kind, value.type === NUMBER);
1718
- const next = env.clone();
1719
1718
  if (value.type === NUMBER) {
1720
1719
  if (text != null) {
1721
1720
  const parsed = parseIsoNumber(text);
1722
1721
  if (parsed == null) throw numberSyntaxError;
1723
- if (sameNumber(value, parsed)) yield next;
1722
+ if (sameNumber(value, parsed)) yield env.clone();
1724
1723
  return;
1725
1724
  }
1726
1725
  const items = characters(canonicalNumberText(value)).map((ch) =>
1727
1726
  kind === 'chars' ? atom(ch) : numberTerm(ch.codePointAt(0)));
1727
+ const next = env.clone();
1728
1728
  if (unify(goal.args[1], listFromItems(items), next)) yield next;
1729
1729
  return;
1730
1730
  }
1731
1731
  const parsed = parseIsoNumber(text);
1732
1732
  if (parsed == null) throw numberSyntaxError;
1733
+ const next = env.clone();
1733
1734
  if (unify(goal.args[0], parsed, next)) yield next;
1734
1735
  };
1735
1736
  }
@@ -1979,36 +1980,59 @@ function* phraseBuiltin({ solver, goal, env }) {
1979
1980
  solver.absorbStatsFrom(child);
1980
1981
  }
1981
1982
  }
1982
- export function formalErrorTerm(error) {
1983
- const context = error.contextTerm ?? atom('eyeprolog');
1984
- if (error.formalTerm != null) return compound('error', [error.formalTerm, context]);
1985
- const parse = (text) => {
1986
- const open = text.indexOf('(');
1987
- if (open === -1) return atom(text);
1988
- const name = text.slice(0, open);
1989
- const inner = text.slice(open + 1, -1);
1990
- const args = [];
1991
- let start = 0;
1992
- let depth = 0;
1993
- for (let i = 0; i <= inner.length; i++) {
1994
- const ch = inner[i];
1995
- if (ch === '(') depth++;
1996
- else if (ch === ')') depth--;
1997
- else if ((ch === ',' || i === inner.length) && depth === 0) {
1998
- args.push(parse(inner.slice(start, i).trim()));
1999
- start = i + 1;
2000
- }
1983
+ const defaultErrorContext = atom('eyeprolog');
1984
+
1985
+ function parseFormalErrorTerm(text) {
1986
+ const open = text.indexOf('(');
1987
+ if (open === -1) return atom(text);
1988
+ const name = text.slice(0, open);
1989
+ const inner = text.slice(open + 1, -1);
1990
+ const args = [];
1991
+ let start = 0;
1992
+ let depth = 0;
1993
+ for (let i = 0; i <= inner.length; i++) {
1994
+ const ch = inner[i];
1995
+ if (ch === '(') depth++;
1996
+ else if (ch === ')') depth--;
1997
+ else if ((ch === ',' || i === inner.length) && depth === 0) {
1998
+ args.push(parseFormalErrorTerm(inner.slice(start, i).trim()));
1999
+ start = i + 1;
2001
2000
  }
2002
- return compound(name, args);
2003
- };
2004
- let formal = parse(error.formal);
2001
+ }
2002
+ return compound(name, args);
2003
+ }
2004
+
2005
+ function hasDefaultGroundErrorShape(error) {
2006
+ return error.formalTerm == null && error.culprit == null && error.contextTerm == null;
2007
+ }
2008
+
2009
+ export function formalErrorTerm(error) {
2010
+ // Reused processor errors can occur hundreds of thousands of times in a
2011
+ // caught failure loop. Cache their immutable ground error/2 term on the
2012
+ // error object itself instead of rebuilding the same tree on every catch.
2013
+ if (hasDefaultGroundErrorShape(error) && error._groundErrorTerm != null) {
2014
+ return error._groundErrorTerm;
2015
+ }
2016
+
2017
+ const context = error.contextTerm ?? defaultErrorContext;
2018
+ let formal = error.formalTerm ?? parseFormalErrorTerm(error.formal);
2005
2019
  if (error.culprit != null) {
2006
2020
  if (formal.type === COMPOUND) formal = compound(formal.name, [...formal.args, error.culprit]);
2007
2021
  else if (formal.type === ATOM && formal.name === 'uninstantiation_error') {
2008
2022
  formal = compound(formal.name, [error.culprit]);
2009
2023
  }
2010
2024
  }
2011
- return compound('error', [formal, context]);
2025
+ const term = compound('error', [formal, context]);
2026
+ if (hasDefaultGroundErrorShape(error)) error._groundErrorTerm = term;
2027
+ return term;
2028
+ }
2029
+
2030
+ function prologErrorBall(error) {
2031
+ const term = formalErrorTerm(error);
2032
+ // Ground terms are immutable from unification's perspective: bindings are
2033
+ // recorded only in the catcher Env, so copying them is unnecessary.
2034
+ if (hasDefaultGroundErrorShape(error) || termIsGround(term)) return term;
2035
+ return freshCopy(term, new Env());
2012
2036
  }
2013
2037
  function* catchBuiltin({ solver, goal, env }) {
2014
2038
  const child = solver.cloneForInnerGoal();
@@ -2020,7 +2044,7 @@ function* catchBuiltin({ solver, goal, env }) {
2020
2044
  const ball = error instanceof ThrownTerm
2021
2045
  ? error.term
2022
2046
  : error instanceof PrologError
2023
- ? freshCopy(formalErrorTerm(error), new Env())
2047
+ ? prologErrorBall(error)
2024
2048
  : null;
2025
2049
  if (ball == null) throw error;
2026
2050
  const recovered = env.clone();
package/src/platform.js CHANGED
@@ -23,6 +23,13 @@ export function currentWorkingDirectory() {
23
23
  return isNode && typeof process.cwd === 'function' ? process.cwd() : '/';
24
24
  }
25
25
 
26
+ function oldGenerationUsedSize() {
27
+ if (!isNode || typeof v8?.getHeapSpaceStatistics !== 'function') return null;
28
+ return v8.getHeapSpaceStatistics()
29
+ .filter(({ space_name: name }) => name !== 'read_only_space' && !name.startsWith('new_'))
30
+ .reduce((total, { space_used_size: size }) => total + size, 0);
31
+ }
32
+
26
33
  export function usedHeapSize() {
27
34
  if (isNode && typeof process.memoryUsage === 'function') {
28
35
  // --max-old-space-size constrains V8's old generation, not the complete
@@ -30,11 +37,9 @@ export function usedHeapSize() {
30
37
  // with total heap use makes bursts of collectible new-space objects look
31
38
  // like retained memory. Measure the corresponding non-young spaces when
32
39
  // an old-space limit was supplied.
33
- if (configuredOldSpaceLimit != null && typeof v8?.getHeapSpaceStatistics === 'function') {
34
- const spaces = v8.getHeapSpaceStatistics();
35
- return spaces
36
- .filter(({ space_name: name }) => name !== 'read_only_space' && !name.startsWith('new_'))
37
- .reduce((total, { space_used_size: size }) => total + size, 0);
40
+ if (configuredOldSpaceLimit != null) {
41
+ const oldGeneration = oldGenerationUsedSize();
42
+ if (oldGeneration != null) return oldGeneration;
38
43
  }
39
44
  return process.memoryUsage().heapUsed;
40
45
  }
@@ -42,6 +47,31 @@ export function usedHeapSize() {
42
47
  return Number.isFinite(memory?.usedJSHeapSize) ? memory.usedJSHeapSize : null;
43
48
  }
44
49
 
50
+ export function memoryStatistics() {
51
+ const stats = {};
52
+ if (isNode && typeof process.memoryUsage === 'function') {
53
+ const memory = process.memoryUsage();
54
+ stats.memory_heap_used_bytes = memory.heapUsed;
55
+ const oldGeneration = oldGenerationUsedSize();
56
+ if (oldGeneration != null) stats.memory_old_generation_used_bytes = oldGeneration;
57
+ stats.memory_guard_used_bytes = configuredOldSpaceLimit != null && oldGeneration != null
58
+ ? oldGeneration
59
+ : memory.heapUsed;
60
+ stats.memory_rss_bytes = memory.rss;
61
+ } else {
62
+ const memory = globalThis.performance?.memory;
63
+ if (Number.isFinite(memory?.usedJSHeapSize)) {
64
+ stats.memory_heap_used_bytes = memory.usedJSHeapSize;
65
+ stats.memory_guard_used_bytes = memory.usedJSHeapSize;
66
+ }
67
+ }
68
+ const softLimit = softHeapLimit();
69
+ const hardLimit = hardHeapLimit();
70
+ if (Number.isFinite(softLimit)) stats.memory_soft_limit_bytes = softLimit;
71
+ if (Number.isFinite(hardLimit)) stats.memory_hard_limit_bytes = hardLimit;
72
+ return stats;
73
+ }
74
+
45
75
  export function softHeapLimit() {
46
76
  const limit = hardHeapLimit();
47
77
  // Leave ample room for the generator stack to unwind and for the top level
@@ -2,9 +2,10 @@
2
2
  // The sources are registered here so library(Name) works in Node and browsers;
3
3
  // unlike the former autoloader, no clauses are added unless use_module/1 or
4
4
  // use_module/2 requests their module.
5
- import { createDefaultRegistry, eyePrologLibraryBuiltins } from './iso.js';
5
+ import { PrologError, createDefaultRegistry, eyePrologLibraryBuiltins } from './iso.js';
6
6
  import { clpzBuiltins } from './clpz.js';
7
- import { fs, isNode } from './platform.js';
7
+ import { fs, isNode, memoryStatistics } from './platform.js';
8
+ import { ATOM, VAR, atom, deref, numberTerm, unify } from './term.js';
8
9
 
9
10
  const moduleFiles = Object.freeze({
10
11
  aggregate: 'aggregate.pl',
@@ -73,8 +74,44 @@ export const eyePrologLibraryIndicators = Object.freeze([
73
74
  ...eyePrologNativeLibraryIndicators,
74
75
  ]);
75
76
 
77
+ function runtimeStatistics(solver) {
78
+ return { ...solver.stats, ...memoryStatistics() };
79
+ }
80
+
81
+ function* statisticsBuiltin({ solver, env }) {
82
+ const stream = solver.io.resolve(solver.io.currentOutput);
83
+ if (stream?.type !== 'text') throw new PrologError('permission_error(output, binary_stream)');
84
+ solver.io.writeUnit(stream, 'eyeprolog stats:\n');
85
+ for (const [key, value] of Object.entries(runtimeStatistics(solver))) {
86
+ solver.io.writeUnit(stream, ` ${key}: ${value}\n`);
87
+ }
88
+ yield env;
89
+ }
90
+
91
+ function* statisticsValueBuiltin({ solver, goal, env }) {
92
+ const snapshot = runtimeStatistics(solver);
93
+ const key = deref(goal.args[0], env);
94
+ const entries = key.type === VAR
95
+ ? Object.entries(snapshot)
96
+ : key.type === ATOM && Object.hasOwn(snapshot, key.name)
97
+ ? [[key.name, snapshot[key.name]]]
98
+ : null;
99
+
100
+ if (entries == null) {
101
+ if (key.type !== ATOM) throw new PrologError('type_error(atom)', key);
102
+ return;
103
+ }
104
+
105
+ for (const [name, value] of entries) {
106
+ const next = env.clone();
107
+ if (unify(goal.args[0], atom(name), next) && unify(goal.args[1], numberTerm(value), next)) yield next;
108
+ }
109
+ }
110
+
76
111
  export function createEyePrologRegistry() {
77
112
  const registry = createDefaultRegistry();
113
+ registry.add('statistics', 0, statisticsBuiltin, { deterministic: true });
114
+ registry.add('statistics', 2, statisticsValueBuiltin);
78
115
  eyePrologLibraryBuiltins.register(registry);
79
116
  clpzBuiltins.register(registry);
80
117
  registry.eyePrologLibrary = true;
@@ -39,6 +39,7 @@ import {
39
39
  parseProgramText,
40
40
  } from '../src/index.js';
41
41
  import { ISO_OPERATOR_DEFINITIONS, parseGoalText, parseNumberTokenText } from '../src/parser.js';
42
+ import { PrologError, formalErrorTerm } from '../src/iso.js';
42
43
  import { compareTerms } from '../src/term.js';
43
44
  import { formatTermForWrite } from '../src/write.js';
44
45
  import { selectClauseCandidates } from '../src/program.js';
@@ -1939,13 +1940,29 @@ c4 ?- call((!;1)).
1939
1940
 
1940
1941
 
1941
1942
  {
1942
- name: '--stats prints solver statistics to stderr',
1943
+ name: '--stats prints solver and memory statistics to stderr',
1943
1944
  run: () => {
1944
1945
  const result = runCli(['--stats', '-'], { input: '%% goal: q(X, Y)\np(a, b).\nq(X, Y) :- p(X, Y).\n' });
1945
1946
  assertEqual(result.status, 0, 'exit status');
1946
1947
  assertEqual(result.stdout, 'q(a, b).\n', 'stdout');
1947
1948
  assertIncludes(result.stderr, 'eyeprolog stats:\n', 'stderr');
1948
1949
  assertIncludes(result.stderr, ' solve_goals_calls:', 'stderr');
1950
+ assertIncludes(result.stderr, ' memory_heap_used_bytes:', 'stderr');
1951
+ assertIncludes(result.stderr, ' memory_old_generation_used_bytes:', 'stderr');
1952
+ assertIncludes(result.stderr, ' memory_guard_used_bytes:', 'stderr');
1953
+ assertIncludes(result.stderr, ' memory_rss_bytes:', 'stderr');
1954
+ assertIncludes(result.stderr, ' memory_soft_limit_bytes:', 'stderr');
1955
+ assertIncludes(result.stderr, ' memory_hard_limit_bytes:', 'stderr');
1956
+ },
1957
+ },
1958
+ {
1959
+ name: '--stats is still printed when a query raises an error',
1960
+ run: () => {
1961
+ const result = runCli(['--stats', '-'], { input: "%% goal: number_chars(N, ['x'])\n" });
1962
+ assertEqual(result.status, 1, 'exit status');
1963
+ assertIncludes(result.stderr, 'eyeprolog stats:\n', 'stderr');
1964
+ assertIncludes(result.stderr, ' memory_heap_used_bytes:', 'stderr');
1965
+ assertIncludes(result.stderr, 'eyeprolog: error(syntax_error(number))', 'stderr');
1949
1966
  },
1950
1967
  },
1951
1968
  {
@@ -1958,6 +1975,36 @@ c4 ?- call((!;1)).
1958
1975
  assertIncludes(result.stderr, ' solve_goals_calls:', 'stderr');
1959
1976
  },
1960
1977
  },
1978
+ {
1979
+ name: 'statistics/0 prints snapshots during execution',
1980
+ run: () => {
1981
+ const input = '%% goal: live\nlive :- statistics, statistics.\n';
1982
+ const result = runCli(['-'], { input });
1983
+ assertEqual(result.status, 0, 'exit status');
1984
+ assertEqual((result.stdout.match(/eyeprolog stats:/g) ?? []).length, 2, 'in-run snapshot count');
1985
+ assertIncludes(result.stdout, ' memory_guard_used_bytes:', 'stdout');
1986
+ assertIncludes(result.stdout, 'live.\n', 'stdout');
1987
+ assertEqual(result.stderr, '', 'stderr');
1988
+ },
1989
+ },
1990
+ {
1991
+ name: 'statistics/2 exposes current counters and memory values',
1992
+ run: () => {
1993
+ const input = '%% goal: live(Used)\nlive(Used) :- statistics(memory_guard_used_bytes, Used).\n';
1994
+ const result = runCli(['-'], { input });
1995
+ assertEqual(result.status, 0, 'exit status');
1996
+ assertEqual(/^live\(\d+\)\.\n$/.test(result.stdout), true, 'numeric memory statistic');
1997
+ assertEqual(result.stderr, '', 'stderr');
1998
+ },
1999
+ },
2000
+ {
2001
+ name: 'statistics predicates are excluded from strict ISO mode',
2002
+ run: () => {
2003
+ const result = runCli(['--iso-strict', '-'], { input: '%% goal: statistics\n' });
2004
+ assertEqual(result.status, 1, 'exit status');
2005
+ assertIncludes(result.stderr, 'existence_error(procedure)', 'stderr');
2006
+ },
2007
+ },
1961
2008
  {
1962
2009
  name: '--warnings prints unstratified negation diagnostics without failing',
1963
2010
  run: () => {
@@ -2959,6 +3006,15 @@ open(X) :- candidate(X), \\+ closed(X).
2959
3006
  assertEqual(result.stdout, '300000', 'fresh-variable answer count');
2960
3007
  },
2961
3008
  },
3009
+ {
3010
+ name: 'ground Prolog error terms are reused across catches',
3011
+ run: () => {
3012
+ const error = new PrologError('syntax_error(number)');
3013
+ const first = formalErrorTerm(error);
3014
+ const second = formalErrorTerm(error);
3015
+ assertEqual(first === second, true, 'reused ground error term');
3016
+ },
3017
+ },
2962
3018
  {
2963
3019
  name: 'caught number syntax errors do not exhaust memory on distinct inputs',
2964
3020
  run: () => {
@@ -2979,22 +3035,22 @@ open(X) :- candidate(X), \\+ closed(X).
2979
3035
  const goal = parseGoalText('trial(Chars)');
2980
3036
  let count = 0;
2981
3037
  for (const _ of solver.solve([goal], new Env(), 0)) {
2982
- if (++count === 50000) break;
3038
+ if (++count === 250000) break;
2983
3039
  }
2984
- if (count !== 50000) throw new Error('unexpected answer count: ' + count);
3040
+ if (count !== 250000) throw new Error('unexpected answer count: ' + count);
2985
3041
  process.stdout.write(String(count));
2986
3042
  `;
2987
3043
  const result = spawnSync(process.execPath, [
2988
- // A smaller heap preserves the original false-exhaustion signal
2989
- // without requiring half a million transient conversion attempts.
3044
+ // Run well past the roughly 126,000-answer failure reported in #28
3045
+ // while keeping the host heap deliberately constrained.
2990
3046
  '--max-old-space-size=32',
2991
3047
  '--input-type=module',
2992
3048
  '--eval',
2993
3049
  script,
2994
- ], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
3050
+ ], { cwd: packageRoot, encoding: 'utf8', timeout: 30000 });
2995
3051
  if (result.error) throw result.error;
2996
3052
  assertEqual(result.status, 0, `bounded-heap child status; stderr=${result.stderr}`);
2997
- assertEqual(result.stdout, '50000', 'distinct number syntax attempts');
3053
+ assertEqual(result.stdout, '250000', 'distinct number syntax attempts');
2998
3054
  },
2999
3055
  },
3000
3056
  {
@@ -3179,9 +3235,13 @@ open(X) :- candidate(X), \\+ closed(X).
3179
3235
  assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
3180
3236
  assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
3181
3237
  assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
3182
- assertEqual(library.defs.size, 153, 'EyeProlog registry contains ISO definitions and private library adapters');
3238
+ assertEqual(library.defs.size, 155, 'EyeProlog registry contains ISO definitions, observability extensions, and private library adapters');
3183
3239
  assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
3184
3240
  assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
3241
+ assertEqual(registry.get('statistics', 0), null, 'statistics/0 is absent from the ISO registry');
3242
+ assertEqual(registry.get('statistics', 2), null, 'statistics/2 is absent from the ISO registry');
3243
+ assertEqual(Boolean(library.get('statistics', 0)), true, 'statistics/0 is an EyeProlog observability extension');
3244
+ assertEqual(Boolean(library.get('statistics', 2)), true, 'statistics/2 is an EyeProlog observability extension');
3185
3245
  assertEqual(registeredNativeEyePrologLibraryNames().length, 40, 'public native EyeProlog builtin count');
3186
3246
  assertEqual(eyePrologPortableLibraryIndicators.length, 59, 'portable Prolog library count');
3187
3247
  assertEqual(eyePrologNativeLibraryIndicators.length, 40, 'native host library count');
@@ -4633,7 +4633,22 @@ testing is a powerful guard during program transformation.
4633
4633
  `--stats` reports work, not meaning. A high solution count may be necessary or
4634
4634
  may indicate a generator that should be constrained. Many table hits may show
4635
4635
  effective reuse; many distinct table entries may reveal an argument that
4636
- prevents calls from sharing.
4636
+ prevents calls from sharing. On the Node CLI it also reports current heap use,
4637
+ non-young/old-generation use, the amount currently compared with the memory
4638
+ guard, resident-set size, and the soft and hard memory ceilings in bytes. These
4639
+ memory figures are printed even when execution ends by raising a Prolog error.
4640
+
4641
+ `--stats` is an end-of-run summary. For a deliberately non-terminating or very
4642
+ long computation, call the EyeProlog extension `statistics/0` at the points
4643
+ where a live snapshot is useful. For example, a long-running loop can include
4644
+ `statistics` as one of its goals, as in `loop :- work, statistics, loop.`
4645
+
4646
+ `statistics/0` writes the current solver counters and memory figures
4647
+ immediately to the current output stream. `statistics/2` makes an individual
4648
+ value available to the program, for example
4649
+ `statistics(memory_guard_used_bytes, Used)`. With an unbound first argument it
4650
+ enumerates the available statistic keys and values. These predicates are
4651
+ EyeProlog observability extensions and are not available under `--iso-strict`.
4637
4652
 
4638
4653
  Compare statistics only between runs with the same query, data, and observable
4639
4654
  answer contract. A faster program that silently loses answers is not an
@@ -6406,7 +6421,7 @@ make the observed question explicit.
6406
6421
  | `-p`, `--proof` | Print `why/2` explanations |
6407
6422
  | `-q`, `--quads` | Run embedded quad tests and fail if any do not hold |
6408
6423
  | `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions and disable automatic tabling |
6409
- | `-s`, `--stats` | Print solver counters to stderr |
6424
+ | `-s`, `--stats` | Print final solver and memory statistics to stderr after execution |
6410
6425
  | `-v`, `--version` | Print the package version |
6411
6426
  | `-w`, `--warnings` | Print non-fatal portability warnings |
6412
6427
  | `-g`, `--goal Goal` | Solve a callable goal; may be repeated; overrides `%% goal:` comments |