eyeprolog 1.2.42 → 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.42",
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",
@@ -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;
@@ -1975,6 +1975,36 @@ c4 ?- call((!;1)).
1975
1975
  assertIncludes(result.stderr, ' solve_goals_calls:', 'stderr');
1976
1976
  },
1977
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
+ },
1978
2008
  {
1979
2009
  name: '--warnings prints unstratified negation diagnostics without failing',
1980
2010
  run: () => {
@@ -3205,9 +3235,13 @@ open(X) :- candidate(X), \\+ closed(X).
3205
3235
  assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
3206
3236
  assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
3207
3237
  assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
3208
- 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');
3209
3239
  assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
3210
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');
3211
3245
  assertEqual(registeredNativeEyePrologLibraryNames().length, 40, 'public native EyeProlog builtin count');
3212
3246
  assertEqual(eyePrologPortableLibraryIndicators.length, 59, 'portable Prolog library count');
3213
3247
  assertEqual(eyePrologNativeLibraryIndicators.length, 40, 'native host library count');
@@ -4635,9 +4635,20 @@ 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
4636
  prevents calls from sharing. On the Node CLI it also reports current heap use,
4637
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 memory figures are printed even when execution ends by
4639
- raising a Prolog error, which makes `--stats` useful when reproducing bounded
4640
- memory failures such as long-running conversion stress tests.
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`.
4641
4652
 
4642
4653
  Compare statistics only between runs with the same query, data, and observable
4643
4654
  answer contract. A faster program that silently loses answers is not an
@@ -6410,7 +6421,7 @@ make the observed question explicit.
6410
6421
  | `-p`, `--proof` | Print `why/2` explanations |
6411
6422
  | `-q`, `--quads` | Run embedded quad tests and fail if any do not hold |
6412
6423
  | `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions and disable automatic tabling |
6413
- | `-s`, `--stats` | Print solver counters to stderr |
6424
+ | `-s`, `--stats` | Print final solver and memory statistics to stderr after execution |
6414
6425
  | `-v`, `--version` | Print the package version |
6415
6426
  | `-w`, `--warnings` | Print non-fatal portability warnings |
6416
6427
  | `-g`, `--goal Goal` | Solve a callable goal; may be repeated; overrides `%% goal:` comments |