eyeprolog 1.2.3 → 1.2.4

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.
Files changed (29) hide show
  1. package/examples/access-control-policy.pl +2 -0
  2. package/examples/d3-group.pl +2 -0
  3. package/examples/fast-fourier-transform.pl +1 -0
  4. package/examples/map-four-color-search.pl +2 -0
  5. package/examples/partial-evaluator.pl +2 -0
  6. package/examples/weighted-interval-scheduling.pl +2 -0
  7. package/package.json +1 -1
  8. package/src/repl.js +32 -9
  9. package/src/solver.js +1 -1
  10. package/test/conformance/ISO-COMPLIANCE.md +1 -1
  11. package/test/conformance/ISO-MATRIX.md +3 -3
  12. package/test/conformance/cases/aggregation/057_countall_empty_and_nonempty.pl +2 -0
  13. package/test/conformance/cases/aggregation/075_aggregation_edges.pl +2 -0
  14. package/test/conformance/cases/aggregation/aggregate_max_no_answers_fails.pl +2 -0
  15. package/test/conformance/cases/aggregation/countall_zero.pl +2 -0
  16. package/test/conformance/cases/aggregation/findall_empty_bag.pl +2 -0
  17. package/test/conformance/cases/aggregation/sumall_empty_zero.pl +2 -0
  18. package/test/conformance/cases/control/074_forall_edges.pl +2 -0
  19. package/test/conformance/cases/control/extra_not_unbound_unknown_goal_succeeds.pl +2 -0
  20. package/test/conformance/cases/control/extra_once_empty_fails.pl +2 -0
  21. package/test/conformance/cases/control/forall_empty_generator_true.pl +2 -0
  22. package/test/conformance/cases/negation/not_unknown_goal_succeeds.pl +2 -0
  23. package/test/conformance/warnings/negation/extra_stratified_multiple_negations_quiet.pl +2 -0
  24. package/test/conformance/warnings/negation/extra_warning_preserves_empty_stdout.pl +2 -0
  25. package/test/conformance/warnings/negation/negative_unknown_group_quiet.pl +2 -0
  26. package/test/conformance/warnings/negation/stratified_negative_unknown_after_positive_quiet.pl +2 -0
  27. package/test/conformance/warnings/negation/stratified_quiet.pl +2 -0
  28. package/test/run-regression.mjs +48 -1
  29. package/the-art-of-eyeprolog.md +14 -9
@@ -53,3 +53,5 @@ status(test1, policy_passed) :-
53
53
 
54
54
  reason(test1, "all required claims are present, one allowed claim is present, and no forbidden claim is present") :-
55
55
  passes_policy(test1, policy_x).
56
+
57
+ :- set_prolog_flag(unknown, fail).
@@ -102,3 +102,5 @@ subgroupCount(d3_group, Count) :-
102
102
  length(Groups, Count).
103
103
 
104
104
  reason(d3_group, "findall enumerates candidate subgroups and sort gives canonical subgroup order").
105
+
106
+ :- set_prolog_flag(unknown, fail).
@@ -53,3 +53,4 @@ node(n(A1, N1), [n(A, N)|T]-[n(A1, N1), n(A, N)|T]) :-
53
53
  % query
54
54
  %% goal: fft([0, 1, 2, 3, 4, 5, 6, 7], _)
55
55
 
56
+ :- set_prolog_flag(unknown, fail).
@@ -66,3 +66,5 @@ four_color_answer(european_union, Coloring) :-
66
66
  place_order(Places),
67
67
  place_pairs(Places, Coloring),
68
68
  once(color_places(Coloring)).
69
+
70
+ :- set_prolog_flag(unknown, fail).
@@ -72,3 +72,5 @@ residual_program(Name, Residual) :- program(Name, Expr, Env), pe(Env, Expr, Resi
72
72
 
73
73
  partialEvalAnswer(residual(Name), Residual) :- residual_program(Name, Residual).
74
74
  partialEvalAnswer(note, "static inputs are folded while dynamic variables remain as residual code") :- residual_program(poly_y, _).
75
+
76
+ :- set_prolog_flag(unknown, fail).
@@ -82,3 +82,5 @@ weighted_interval_answer(chosen_interval, interval(I, Start, Finish, Value)) :-
82
82
  chosen_from(1, I),
83
83
  interval(I, Start, Finish, Value).
84
84
  weighted_interval_answer(candidate_count, Count) :- countall(interval(_i, _start, _finish, _value), Count).
85
+
86
+ :- set_prolog_flag(unknown, fail).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.3",
6
+ "version": "1.2.4",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/repl.js CHANGED
@@ -34,7 +34,7 @@ export async function runRepl(engine, options = {}) {
34
34
  const goal = parseGoal(engine, state, text);
35
35
  if (!options.isoStrict && isUseModuleGoal(goal)) {
36
36
  sources.push({ text: `:- ${text}.\n`, filename: '<repl>' });
37
- state = makeState(engine, sources, output, options);
37
+ state = makeState(engine, sources, output, options, state);
38
38
  state.solver.runInitializations();
39
39
  output.write(' true.\n');
40
40
  continue;
@@ -42,17 +42,22 @@ export async function runRepl(engine, options = {}) {
42
42
  const consultFiles = options.isoStrict ? null : consultDesignations(engine, goal);
43
43
  if (consultFiles != null) {
44
44
  for (const filename of consultFiles) sources.push(await readSource(filename));
45
- state = makeState(engine, sources, output, options);
45
+ state = makeState(engine, sources, output, options, state);
46
46
  state.solver.runInitializations();
47
47
  output.write(' true.\n');
48
48
  continue;
49
49
  }
50
50
 
51
- await prepareInteractiveTermInput(state, goal, reader);
52
- const result = await solveQuery(engine, state, goal, reader, output);
53
- if (result?.halted) {
54
- exitCode = result.code;
55
- break;
51
+ const flagsBefore = snapshotFlagValues(state.solver);
52
+ try {
53
+ await prepareInteractiveTermInput(state, goal, reader);
54
+ const result = await solveQuery(engine, state, goal, reader, output);
55
+ if (result?.halted) {
56
+ exitCode = result.code;
57
+ break;
58
+ }
59
+ } finally {
60
+ rememberFlagOverrides(state, flagsBefore);
56
61
  }
57
62
  } catch (error) {
58
63
  if (error?.name === 'HaltSignal') {
@@ -143,7 +148,7 @@ class LineReader {
143
148
  }
144
149
  }
145
150
 
146
- function makeState(engine, sources, output, options = {}) {
151
+ function makeState(engine, sources, output, options = {}, previousState = null) {
147
152
  const strictIso = options.isoStrict === true;
148
153
  const program = engine.Program.parseSources(sources, { strictIso, sourceMetadata: strictIso });
149
154
  const solver = new engine.Solver(program, {
@@ -151,7 +156,25 @@ function makeState(engine, sources, output, options = {}) {
151
156
  isoStrict: strictIso,
152
157
  ioOptions: { write: (text) => output.write(String(text)) },
153
158
  });
154
- return { program: solver.program, solver, strictIso };
159
+ const flagOverrides = new Map(previousState?.flagOverrides ?? []);
160
+ for (const [name, value] of flagOverrides) {
161
+ const definition = solver.prologFlags.get(name);
162
+ if (definition?.changeable && definition.allowed.includes(value)) {
163
+ definition.value = engine.atom(value);
164
+ }
165
+ }
166
+ return { program: solver.program, solver, strictIso, flagOverrides };
167
+ }
168
+
169
+ function snapshotFlagValues(solver) {
170
+ return new Map([...solver.prologFlags].map(([name, definition]) => [name, definition.value?.name]));
171
+ }
172
+
173
+ function rememberFlagOverrides(state, before) {
174
+ for (const [name, definition] of state.solver.prologFlags) {
175
+ const value = definition.value?.name;
176
+ if (before.get(name) !== value) state.flagOverrides.set(name, value);
177
+ }
155
178
  }
156
179
 
157
180
  async function readQuery(reader) {
package/src/solver.js CHANGED
@@ -49,7 +49,7 @@ export class Solver {
49
49
  this.inferenceLimitExceeded = false;
50
50
  this.solutionLimit = options.solutionLimit ?? 10000000;
51
51
  this.solutionsSeen = 0;
52
- this.prologFlags = options.prologFlags ?? defaultPrologFlags(this.registry?.eyePrologLibrary ? 'fail' : 'error', this.isoStrict);
52
+ this.prologFlags = options.prologFlags ?? defaultPrologFlags('error', this.isoStrict);
53
53
  if (this.isoStrict) {
54
54
  for (const name of [...this.prologFlags.keys()]) {
55
55
  if (!ISO_CORE_FLAG_NAMES.has(name)) this.prologFlags.delete(name);
@@ -38,7 +38,7 @@ error-ordering alternative to an individual executable assertion.
38
38
  | 7.8 — control constructs and exceptions | audit | call, cut, conjunction, disjunction, if-then, catch/throw, renamed-copy tests. |
39
39
  | 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Exceptional-value/error-precedence matrix remains to be exhaustively enumerated. |
40
40
  | 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, Corrigendum 3 writer cases. Full option cross-product remains open. |
41
- | 7.11 — flags | audit | Required Part 1 flags implemented. Strict mode excludes the EyeProlog `occurs_check` extension and uses ISO `unknown=error`. |
41
+ | 7.11 — flags | audit | Required Part 1 flags implemented. Normal and strict modes use the ISO `unknown=error` default; strict mode excludes the EyeProlog `occurs_check` extension. |
42
42
  | 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax families and focused error cases. Complete prescribed-error ordering remains open. |
43
43
  | 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md; Corrigendum 2 additions (`subsumes_term/2`, `term_variables/2`, `call/2..8`, `false/0`) are in the strict registry. Mode/error matrix is not yet one-row-per-standard-row. |
44
44
  | Clause 9 — evaluable functors | audit | Integer/float/rounding/transcendental/bitwise suites and corrigendum cases. Host floating-point representation choices remain documented implementation-defined behavior. |
@@ -31,9 +31,9 @@ integer powers (Cor.3).
31
31
  Implementation-defined choices are documented in *The Art of EyeProlog*:
32
32
  integers and arity are unbounded by the Prolog model (subject to host memory),
33
33
  ordinary unification performs an occurs check, `double_quotes` defaults to
34
- `chars`, strict core mode uses ISO `unknown=error` while normal EyeProlog
35
- defaults `unknown` to `fail`, `//` rounds toward zero, floating-point operations
36
- use finite ECMAScript numbers, and character codes use Unicode scalar values.
34
+ `chars`, both strict core mode and normal EyeProlog use the ISO
35
+ `unknown=error` default, `//` rounds toward zero, floating-point operations use
36
+ finite ECMAScript numbers, and character codes use Unicode scalar values.
37
37
 
38
38
  This is an executable conformance matrix, not a certification issued by an
39
39
  independent standards body. Release gating runs the ISO cases and the dedicated
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  item(a).
2
4
  item(b).
3
5
  answer(counts, counts(C, Z)) :- countall(item(X), C), countall(missing(X), Z).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Reference 9.1: aggregation handles empty result sets, structured templates, and ordered best answers.
2
4
  %% goal: answer(X0, X1)
3
5
 
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(ok) :- \+ aggregate_max(Key, Value, missing(Value), BestKey, BestValue).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(N) :- countall(missing(X), N).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(Bag) :- findall(X, missing(X), Bag).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(N) :- sumall(X, missing(X), N).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Reference 9.1: forall/2 succeeds for every generated binding, including the empty generator case.
2
4
  %% goal: answer(X0, X1)
3
5
 
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(not_unbound_unknown_goal_succeeds) :- \+ missing(X).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(once_empty_fails) :- once(missing).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(ok) :- \+ empty_counterexample.
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Negation succeeds when its inner goal has no solution.
2
4
  %% goal: answer(X0)
3
5
 
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  p(a).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  p(a) :- \+ q(a).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  answer(ok) :- \+ missing(ok).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  %% goal: answer(X0)
2
4
 
3
5
  item(a).
@@ -1,3 +1,5 @@
1
+ :- set_prolog_flag(unknown, fail).
2
+
1
3
  % Stratified negation emits no portability warning.
2
4
  %% goal: answer(X0)
3
5
 
@@ -716,6 +716,36 @@ c4 ?- call((!;1)).
716
716
  assertEqual(result.stderr, '', 'stderr');
717
717
  },
718
718
  },
719
+ {
720
+ name: 'REPL preserves runtime unknown flag across consultation',
721
+ run: () => {
722
+ const filename = path.join(tmp, `repl-empty-${++tmpCounter}.pl`);
723
+ fs.writeFileSync(filename, '');
724
+ const result = runCli([], {
725
+ input:
726
+ 'current_prolog_flag(unknown, V).\n' +
727
+ 'set_prolog_flag(unknown, fail).\n' +
728
+ `[${sourceAtom(filename)}].\n` +
729
+ 'current_prolog_flag(unknown, V).\n' +
730
+ 'set_prolog_flag(unknown, error).\n' +
731
+ `[${sourceAtom(filename)}].\n` +
732
+ 'missing_after_consult.\n' +
733
+ 'halt.\n',
734
+ });
735
+ assertEqual(result.status, 0, 'exit status');
736
+ assertEqual(result.stdout,
737
+ '?- V = error.\n' +
738
+ '?- true.\n' +
739
+ '?- true.\n' +
740
+ '?- V = fail.\n' +
741
+ '?- true.\n' +
742
+ '?- true.\n' +
743
+ '?- error(existence_error(procedure, missing_after_consult / 0), eyeprolog).\n' +
744
+ '?- ',
745
+ 'stdout');
746
+ assertEqual(result.stderr, '', 'stderr');
747
+ },
748
+ },
719
749
  {
720
750
  name: 'REPL use_module imports Part 2 library predicates',
721
751
  run: () => {
@@ -723,7 +753,11 @@ c4 ?- call((!;1)).
723
753
  input: 'append(X, Y, [1, 2, 3, 4]).\nuse_module(library(lists)).\nappend(X, Y, [1, 2, 3, 4]).\n\nhalt.\n',
724
754
  });
725
755
  assertEqual(result.status, 0, 'exit status');
726
- assertEqual(result.stdout, '?- false.\n?- true.\n?- X = [], Y = [1, 2, 3, 4]\n; ... .\n?- ', 'stdout');
756
+ assertEqual(result.stdout,
757
+ '?- error(existence_error(procedure, append / 3), eyeprolog).\n' +
758
+ '?- true.\n' +
759
+ '?- X = [], Y = [1, 2, 3, 4]\n; ... .\n?- ',
760
+ 'stdout');
727
761
  assertEqual(result.stderr, '', 'stderr');
728
762
  },
729
763
  },
@@ -1120,6 +1154,19 @@ c4 ?- call((!;1)).
1120
1154
  assertEqual(infix.arity, 2, 'quad query arity');
1121
1155
  },
1122
1156
  },
1157
+ {
1158
+ name: 'normal EyeProlog uses the ISO unknown=error default',
1159
+ run: () => {
1160
+ const program = Program.parse('');
1161
+ const solver = new Solver(program, { registry: getEyePrologRegistry() });
1162
+ assertEqual(solver.prologFlags.get('unknown')?.value?.name, 'error', 'normal unknown default');
1163
+ assertEqual(
1164
+ run('', { goal: 'current_prolog_flag(unknown, V)' }).stdout,
1165
+ 'current_prolog_flag(unknown, error).\n',
1166
+ 'public runner unknown default',
1167
+ );
1168
+ },
1169
+ },
1123
1170
  {
1124
1171
  name: 'strict ISO core mode exposes only the Part 1 registry and flag surface',
1125
1172
  run: () => {
@@ -5667,18 +5667,23 @@ silently changing a static program.
5667
5667
  | `max_integer` | `unbounded` | `unbounded` | no |
5668
5668
  | `min_integer` | `unbounded` | `unbounded` | no |
5669
5669
  | `max_arity` | `unbounded` | `unbounded` | no |
5670
- | `unknown` | `fail` | `error`, `fail`, `warning` | yes |
5670
+ | `unknown` | `error` | `error`, `fail`, `warning` | yes |
5671
5671
  | `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
5672
5672
  | `occurs_check` | `true` | `true`, `error` | yes |
5673
5673
 
5674
- Strict ISO core mode defaults `unknown` to `error`; the normal EyeProlog
5675
- environment defaults it to `fail`. The `occurs_check` flag is an EyeProlog
5676
- diagnostic extension rather than an ISO-defined core flag: it is absent in
5677
- strict mode, while normal mode keeps its `true` default and optional `error`
5678
- diagnostic for STO attempts. Operator and flag directives are processed per
5679
- program rather than changing global JavaScript state. The `double_quotes`
5680
- setting affects subsequent source text, included files, command-line and API
5681
- goal text, and terms read by `read_term/*`:
5674
+ Both normal EyeProlog and strict ISO core mode use the ISO `unknown=error`
5675
+ default. Interactive `set_prolog_flag/2` changes are retained when the REPL
5676
+ consults another file or imports a module instead of being reset by the host
5677
+ rebuild of the program. Programs that intentionally treat an undefined
5678
+ predicate as failure must opt in with `set_prolog_flag(unknown, fail)`; bundled
5679
+ examples and non-ISO corpus cases that depend on that policy do so explicitly.
5680
+ The `occurs_check` flag is an EyeProlog diagnostic
5681
+ extension rather than an ISO-defined core flag: it is absent in strict mode,
5682
+ while normal mode keeps its `true` default and optional `error` diagnostic for
5683
+ STO attempts. Operator and flag directives are processed per program rather
5684
+ than changing global JavaScript state. The `double_quotes` setting affects
5685
+ subsequent source text, included files, command-line and API goal text, and
5686
+ terms read by `read_term/*`:
5682
5687
 
5683
5688
  ```text
5684
5689
  % Default: a list of one-character atoms.