eyeprolog 1.5.51 → 1.5.52

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.
@@ -32,7 +32,7 @@ spelling therefore changes this report even when no corpus file is added or remo
32
32
  | context | 11 | 0 | 0 | 0 | 11 |
33
33
  | control | 15 | 0 | 0 | 0 | 15 |
34
34
  | explicit-tabling | 6 | 0 | 0 | 0 | 6 |
35
- | iso | 173 | 219 | 0 | 0 | 392 |
35
+ | iso | 174 | 219 | 0 | 0 | 393 |
36
36
  | lists | 52 | 3 | 0 | 0 | 55 |
37
37
  | modules | 2 | 0 | 0 | 0 | 2 |
38
38
  | negation | 8 | 0 | 19 | 0 | 27 |
@@ -45,7 +45,7 @@ spelling therefore changes this report even when no corpus file is added or remo
45
45
  | terms | 26 | 3 | 0 | 0 | 29 |
46
46
  | unification | 18 | 0 | 0 | 0 | 18 |
47
47
  | variables | 16 | 7 | 0 | 0 | 23 |
48
- | **Total** | **495** | **274** | **19** | **21** | **809** |
48
+ | **Total** | **496** | **274** | **19** | **21** | **810** |
49
49
 
50
50
  ## DCG conformance clarification
51
51
 
@@ -234,13 +234,15 @@ npm run generate
234
234
 
235
235
  ## Chapter 39: Predicate reference
236
236
 
237
- - [01-answer.pl](chapter-39/01-answer.pl) — Library relations by programming role
238
- - [02-answer-2.pl](chapter-39/02-answer-2.pl)
239
- - [03-answer-3.pl](chapter-39/03-answer-3.pl)
240
- - [04-cost.pl](chapter-39/04-cost.pl)
241
- - [05-message.pl](chapter-39/05-message.pl)
242
- - [06-task.pl](chapter-39/06-task.pl)
243
- - [07-program.pl](chapter-39/07-program.pl) — Specialized library implementation notes
237
+ - [01-elk.pl](chapter-39/01-elk.pl) — Reading static procedures
238
+ - [02-solve.pl](chapter-39/02-solve.pl)
239
+ - [03-answer.pl](chapter-39/03-answer.pl) — Library relations by programming role
240
+ - [04-answer-2.pl](chapter-39/04-answer-2.pl)
241
+ - [05-answer-3.pl](chapter-39/05-answer-3.pl)
242
+ - [06-cost.pl](chapter-39/06-cost.pl)
243
+ - [07-message.pl](chapter-39/07-message.pl)
244
+ - [08-task.pl](chapter-39/08-task.pl)
245
+ - [09-program.pl](chapter-39/09-program.pl) — Specialized library implementation notes
244
246
 
245
247
  ## Chapter 40: Running EyeProlog: command line and corpus
246
248
 
@@ -0,0 +1,6 @@
1
+ % From The Art of EyeProlog, Chapter 39 — Reading static procedures.
2
+ :- public(elk/1).
3
+
4
+ elk(X) :- moose(X).
5
+
6
+ moose(bertha).
@@ -0,0 +1,10 @@
1
+ % From The Art of EyeProlog, Chapter 39.
2
+ :- set_prolog_flag(default_procedure_access, public).
3
+
4
+ solve(true) :- !.
5
+ solve((A, B)) :- !, solve(A), solve(B).
6
+ solve(H) :- clause(H, Body), solve(Body).
7
+
8
+ elk(X) :- moose(X).
9
+ moose(bertha).
10
+ grazes(X) :- elk(X).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.51",
6
+ "version": "1.5.52",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -740,7 +740,13 @@ function* clauseSolutions({ solver, goal, env }, state) {
740
740
  // the default in every mode, not only under --iso-strict: assert/1,
741
741
  // retract/1, and abolish/1 already reject static procedures unconditionally,
742
742
  // and clause/2 now agrees with them.
743
- if (isProcessorStaticProcedure(solver, head) || (group && !group.dynamic)) {
743
+ //
744
+ // 7.5.3 notes that a public/1 directive would be an extension, so a static
745
+ // procedure declared public, or any user-defined procedure when the
746
+ // default_procedure_access flag is public, stays readable here. Either way
747
+ // the procedure remains static and cannot be modified.
748
+ if (isProcessorStaticProcedure(solver, head) ||
749
+ (group && !group.dynamic && !isPublicProcedure(solver, group))) {
744
750
  throw new PrologError('permission_error(access, private_procedure)', indicator);
745
751
  }
746
752
  callableOrVariable(goal.args[1], env);
@@ -798,6 +804,13 @@ function isGrammarRuleProcedure(solver, head) {
798
804
  return !solver.isoStrict && head.name === '-->' && head.arity === 2;
799
805
  }
800
806
 
807
+ // A user-defined procedure is readable by clause/2 when it was declared public
808
+ // or when the processor grants access to every user-defined procedure.
809
+ function isPublicProcedure(solver, group) {
810
+ if (group.public) return true;
811
+ return solver.prologFlags?.get('default_procedure_access')?.value?.name === 'public';
812
+ }
813
+
801
814
  function isProcessorStaticProcedure(solver, head) {
802
815
  // Conjunction is an ISO control construct (7.5/Table 9) but is executed
803
816
  // directly by the solver rather than through the builtin registry. The
package/src/parser.js CHANGED
@@ -1130,7 +1130,7 @@ class Parser {
1130
1130
  (['char_conversion', 'set_prolog_flag'].includes(directive.name) && directive.arity === 2)
1131
1131
  );
1132
1132
  const extensionDirective = directive.type === 'compound' && (
1133
- (['use_module', 'meta_predicate', 'attribute', 'table'].includes(directive.name) && directive.arity === 1) ||
1133
+ (['use_module', 'meta_predicate', 'attribute', 'table', 'public'].includes(directive.name) && directive.arity === 1) ||
1134
1134
  (['module', 'use_module'].includes(directive.name) && directive.arity === 2)
1135
1135
  );
1136
1136
  if (this.strictIso && extensionDirective) {
package/src/program.js CHANGED
@@ -154,6 +154,10 @@ export class Program {
154
154
  this.libraryImports = [];
155
155
  this.interopPortabilityWarnings = [];
156
156
  this.dynamicPredicates = new Set();
157
+ // ISO 7.5.3 notes that a public/1 directive declaring user-defined
158
+ // procedures to be public would be an extension. Such a procedure stays
159
+ // static -- it cannot be modified -- but clause/2 may inspect it.
160
+ this.publicPredicates = new Set();
157
161
  this.multifilePredicates = new Set();
158
162
  this.discontiguousPredicates = new Set();
159
163
  this.strictIso = options.isoStrict === true;
@@ -221,6 +225,7 @@ export class Program {
221
225
  wfsDatalog: false,
222
226
  scalarFactsOnly: true,
223
227
  dynamic: this.dynamicPredicates.has(modulePredicateKey(module, name, arity)),
228
+ public: this.publicPredicates.has(modulePredicateKey(module, name, arity)),
224
229
  negationStratum: null,
225
230
  hasCut: false,
226
231
  cutReachable: null,
@@ -636,6 +641,7 @@ class ProgramBuilder {
636
641
  this.options = options;
637
642
  this.program = program ?? new Program([], { ...options, [DEFER_PROGRAM_BUILD]: true });
638
643
  this.declaredDynamicIndicators = new Map();
644
+ this.declaredPublicIndicators = new Map();
639
645
  this.declaredMultifileIndicators = new Map();
640
646
  this.declaredDiscontiguousIndicators = new Map();
641
647
  this.declaredTableIndicators = new Map();
@@ -651,7 +657,7 @@ class ProgramBuilder {
651
657
  const unit = textUnit ?? '<input>';
652
658
  let declarations = this.directiveDeclarationsByText.get(unit);
653
659
  if (!declarations) {
654
- declarations = { dynamic: new Set(), multifile: new Set(), discontiguous: new Set(), table: new Set() };
660
+ declarations = { dynamic: new Set(), public: new Set(), multifile: new Set(), discontiguous: new Set(), table: new Set() };
655
661
  this.directiveDeclarationsByText.set(unit, declarations);
656
662
  }
657
663
  return declarations[kind];
@@ -717,6 +723,13 @@ class ProgramBuilder {
717
723
  local.add(key);
718
724
  targetSet.add(key);
719
725
  declaredMap.set(`${textUnit}\u0000${key}`, { ...indicator, key, module, textUnit });
726
+ if (kind === 'public') {
727
+ // Unlike dynamic/1, a public declaration says nothing about existence:
728
+ // it only grants clause/2 access. Groups created later pick the flag up
729
+ // from program.publicPredicates in makeGroup.
730
+ const existing = program.groups.get(key);
731
+ if (existing) existing.public = true;
732
+ }
720
733
  if (kind === 'dynamic') {
721
734
  // A dynamic declaration creates the procedure immediately, not only
722
735
  // when ProgramBuilder.finish() runs. Compile-time source-expansion
@@ -830,6 +843,10 @@ class ProgramBuilder {
830
843
  const module = clause.module ?? 'user';
831
844
  const textUnit = clause.textUnit ?? '<input>';
832
845
  this.addProcedureDirective(clause, 'dynamic', program.dynamicPredicates, this.declaredDynamicIndicators);
846
+ // public/1 is an extension, so the strict Part 1 profile does not offer it.
847
+ if (!program.strictIso) {
848
+ this.addProcedureDirective(clause, 'public', program.publicPredicates, this.declaredPublicIndicators);
849
+ }
833
850
  this.addProcedureDirective(clause, 'multifile', program.multifilePredicates, this.declaredMultifileIndicators);
834
851
  this.addProcedureDirective(clause, 'discontiguous', program.discontiguousPredicates, this.declaredDiscontiguousIndicators);
835
852
  this.addProcedureDirective(clause, 'table', program.tabledPredicates, this.declaredTableIndicators);
package/src/solver.js CHANGED
@@ -1263,8 +1263,17 @@ function defaultPrologFlags(unknown = 'error', strictIso = false) {
1263
1263
  ['unknown', { value: compound(unknown, []), allowed: ['error', 'fail', 'warning'], changeable: true }],
1264
1264
  ['double_quotes', { value: compound('chars', []), allowed: ['chars', 'codes', 'atom'], changeable: true }],
1265
1265
  ['occurs_check', { value: compound('true', []), allowed: ['true', 'error'], changeable: true }],
1266
+ // ISO 7.5.2 makes every user-defined procedure static, and therefore
1267
+ // private, unless declared otherwise. Setting this flag to public grants
1268
+ // clause/2 access to all of them at once, which meta-interpreters need
1269
+ // without having to enumerate every predicate in a public/1 directive.
1270
+ // The procedures stay static: assert/1 and retract/1 still refuse them.
1271
+ ['default_procedure_access', { value: compound('private', []), allowed: ['private', 'public'], changeable: true }],
1266
1272
  ]);
1267
- if (strictIso) flags.delete('occurs_check');
1273
+ if (strictIso) {
1274
+ flags.delete('occurs_check');
1275
+ flags.delete('default_procedure_access');
1276
+ }
1268
1277
  return flags;
1269
1278
  }
1270
1279
 
@@ -69,7 +69,7 @@ Status values are:
69
69
  | 7.11.2.5 | Default `double_quotes` | `chars`. | **defined** — `src/solver.js`, parser flag state. |
70
70
  | 7.12.1 | Second argument of `error/2` | The default context term is the atom `eyeprolog`. A few implementation-specific diagnostics may deliberately supply a more specific context term. | **defined** — `formalErrorTerm()` in `src/iso.js`. |
71
71
  | 7.12.2(f) | Implementation-defined representation limits | Character and character-code operations are limited to Unicode scalar values; surrogates and values above U+10FFFF are representation errors. Arity/integer values are modeled as unbounded but may hit host/resource limits. Float input overflow uses the implementation-specific `max_float`/`min_float` representation names documented by the STC-oriented tests. | **defined** — parser/ISO numeric and character guards. |
72
- | 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` flag. With `bounded=false`, `max_integer` and `min_integer` have no current or selectable value and their `current_prolog_flag/2` queries fail. Valid alternative values of fixed standard flags are distinguished from invalid values so `set_prolog_flag/2` reports permission versus domain errors as prescribed. | **defined** — strict registry/flag filtering in `src/solver.js`; strict flag tests. |
72
+ | 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` and `default_procedure_access` flags. With `bounded=false`, `max_integer` and `min_integer` have no current or selectable value and their `current_prolog_flag/2` queries fail. Valid alternative values of fixed standard flags are distinguished from invalid values so `set_prolog_flag/2` reports permission versus domain errors as prescribed. | **defined** — strict registry/flag filtering in `src/solver.js`; strict flag tests. |
73
73
  | 8.17.3 | Other effects of `halt/0` | Terminates EyeProlog execution and returns host/process status `0`; it produces no Prolog solution. | **defined** — `HaltSignal`, `haltBuiltin()`, CLI/runner handling. |
74
74
  | 8.17.4 | Meaning/effects of `halt(Status)` | Integer `Status` is converted to the host process/runner halt code; it produces no Prolog solution. | **defined** — `haltBuiltin()`, `src/execute.js`, `src/cli.js`. |
75
75
  | 9.1.4.1 | Floating-point rounding function `rndF` | Floating values and operations use ECMAScript `Number` (IEEE-754 binary64) and the host's specified binary64 arithmetic/conversions. | **defined** — `src/iso-arithmetic.js`, `src/number-value.js`. |
@@ -121,6 +121,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
121
121
  | 5.5.6 Side effects | Normal mode adds `statistics/0-2`, cleanup/library state, and optional proof/statistics host instrumentation. | The strict registry excludes Prolog-visible statistics/cleanup/library adapters; ordinary Part 1 I/O/database/flag/operator side effects remain. Host proof/statistics collection observes execution through the embedding API rather than adding a strict Prolog goal effect. **covered** — strict registry/execution regression. |
122
122
  | 5.5.7 Control constructs | `tnot/1`, `wfs_truth/2`, and normal-profile execution optimizations | `tnot/1`, `wfs_truth/2`, and the normal-profile explicit `table` declaration are absent. |
123
123
  | 5.5.8 Flags | `occurs_check` | Absent in strict mode. |
124
+ | 5.5.8 Flags, 7.5.3 NOTE | `public/1` directive, `default_procedure_access` flag | Absent in strict mode. The 7.5.3 NOTE anticipates a `public/1` extension declaring user-defined procedures public; the flag generalizes it to all of them at once, which meta-interpreters need without annotating the interpreted program. Read access only: a public procedure stays static, so `assert`/`retract` still report `permission_error(modify, static_procedure)`, and built-ins stay private. |
124
125
  | 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and bundled-library autoloaded predicates | Strict registry contains only the Part 1 + Corrigenda core registry. |
125
126
  | 5.5.10 Evaluable functors | Normal mode additionally accepts the EyeProlog evaluable atom `e`; the remaining arithmetic functors accepted by strict mode are the Part 1 + Corrigenda set. | Strict mode rejects `e/0` as non-evaluable and retains the Corrigendum arithmetic additions. **covered** — strict extension-boundary regression plus `src/iso-arithmetic.js`. |
126
127
  | 5.5.11 Reserved atoms | None | None. |
@@ -37,7 +37,7 @@ and preparation error cases.
37
37
  | --- | --- | --- |
38
38
  | 7.5.1 initial database and clause order | covered | Prepared clauses retain textual preparation order. The implementation-defined initial database/standard-procedure choices are documented separately. |
39
39
  | 7.5.2 static and dynamic procedures | covered | Source procedures are static unless declared dynamic; asserting a previously absent predicate creates a dynamic procedure; modification of protected/static procedures is rejected. |
40
- | 7.5.3 private and public procedures | covered | User static procedures are private to `clause/2` in every execution mode, not only under `--iso-strict`; dynamic procedures are public; standardized built-ins/control forms remain static/private. Corpus: `iso/clause_static_and_dynamic_access`, `error/iso/clause_static_user_procedure`. |
40
+ | 7.5.3 private and public procedures | covered | User static procedures are private to `clause/2` in every execution mode, not only under `--iso-strict`; dynamic procedures are public; standardized built-ins/control forms remain static/private. The NOTE's suggested `public/1` directive is provided as an extension in normal mode, together with a `default_procedure_access` flag that declares every user-defined procedure public; both grant read access only and leave the procedures static. Corpus: `iso/clause_static_and_dynamic_access`, `iso/public_procedure_access`, `error/iso/clause_static_user_procedure`. |
41
41
  | 7.5.4 database update visibility | covered | Dynamic calls use the logical-update view: an activation continues over the clause set visible when it began, while later activations see successful assertions/retractions. `retractall/1` retains an empty dynamic procedure and `abolish/1` removes it. |
42
42
 
43
43
  The strict test `closes ISO 7.5-7.7 database, conversion, and execution rows`
@@ -140,7 +140,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
140
140
  Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
141
141
  are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
142
142
 
143
- The corpus has 392 cases in `iso/` and 809 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
143
+ The corpus has 393 cases in `iso/` and 810 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
144
144
 
145
145
  ## Updating expected output
146
146
 
@@ -0,0 +1,52 @@
1
+ % ISO 7.5.3 notes that "an additional directive public/1 that specifies some
2
+ % user-defined procedures to be public would be an extension". EyeProlog
3
+ % provides that directive, plus a default_procedure_access flag that grants
4
+ % clause/2 access to every user-defined procedure at once, which a
5
+ % meta-interpreter needs without annotating the program it interprets.
6
+ %
7
+ % Both are read access only. A public procedure is still static, so assert/1
8
+ % and retract/1 continue to report permission_error(modify, static_procedure),
9
+ % and built-in procedures stay private either way.
10
+ % https://github.com/eyereasoner/eyeprolog/issues/96
11
+
12
+ :- public(elk/1).
13
+ elk(X) :- moose(X).
14
+
15
+ moose(bertha).
16
+
17
+ %% goal: declared_public(X0)
18
+
19
+ declared_public(Body) :-
20
+ clause(elk(bertha), Body).
21
+
22
+ %% goal: undeclared_sibling(X0)
23
+
24
+ % moose/1 carries no declaration, so it stays private.
25
+ undeclared_sibling(Culprit) :-
26
+ catch(clause(moose(_), _), error(Formal, _), true),
27
+ Formal = permission_error(access, private_procedure, Culprit).
28
+
29
+ %% goal: public_is_not_dynamic(X0)
30
+
31
+ public_is_not_dynamic(Formal) :-
32
+ catch(assertz(elk(clara)), error(Formal, _), true).
33
+
34
+ %% goal: flag_default(X0)
35
+
36
+ flag_default(Access) :-
37
+ current_prolog_flag(default_procedure_access, Access).
38
+
39
+ %% goal: flag_opens_every_procedure(X0)
40
+
41
+ flag_opens_every_procedure(Body) :-
42
+ set_prolog_flag(default_procedure_access, public),
43
+ clause(moose(bertha), Body),
44
+ set_prolog_flag(default_procedure_access, private).
45
+
46
+ %% goal: builtins_stay_private(X0)
47
+
48
+ builtins_stay_private(Culprit) :-
49
+ set_prolog_flag(default_procedure_access, public),
50
+ catch(clause(atom(_), _), error(Formal, _), true),
51
+ set_prolog_flag(default_procedure_access, private),
52
+ Formal = permission_error(access, private_procedure, Culprit).
@@ -1,3 +1,3 @@
1
1
  answer(7, red, caught(eyeprolog), first, caught(eyeprolog)).
2
2
  answer(7, red, caught(eyeprolog), second, caught(eyeprolog)).
3
- flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true)]).
3
+ flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true), pair(default_procedure_access, private)]).
@@ -0,0 +1,6 @@
1
+ declared_public(moose(bertha)).
2
+ undeclared_sibling(moose / 1).
3
+ public_is_not_dynamic(permission_error(modify, static_procedure, elk / 1)).
4
+ flag_default(private).
5
+ flag_opens_every_procedure(true).
6
+ builtins_stay_private(atom / 1).
@@ -4987,6 +4987,112 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
4987
4987
  assertEqual(termToString(caught.culprit), '/(atom_length, 2)', 'culprit predicate indicator');
4988
4988
  },
4989
4989
  },
4990
+ {
4991
+ // ISO 7.5.3 NOTE: public/1 as an extension.
4992
+ name: 'a public/1 directive grants clause/2 access to a static procedure',
4993
+ run: () => {
4994
+ const program = Program.parse(':- public(elk/1).\nelk(X) :- moose(X).\nmoose(bertha).\n');
4995
+ const solver = new Solver(program, {});
4996
+ const answers = [...solver.solve([parseGoalText('clause(elk(_), _)')], new Env(), 0)];
4997
+ assertEqual(answers.length, 1, 'declared public procedure is readable');
4998
+ assertEqual(program.findGroup('elk', 1).dynamic, false, 'public does not imply dynamic');
4999
+ },
5000
+ },
5001
+ {
5002
+ name: 'a public/1 declaration still refuses database modification',
5003
+ run: () => {
5004
+ const solver = new Solver(Program.parse(':- public(elk/1).\nelk(bertha).\n'), {});
5005
+ for (const goal of ['assertz(elk(clara))', 'retract(elk(bertha))']) {
5006
+ let caught = null;
5007
+ try {
5008
+ [...solver.solve([parseGoalText(goal)], new Env(), 0)];
5009
+ } catch (error) {
5010
+ caught = error;
5011
+ }
5012
+ assertEqual(caught?.formal, 'permission_error(modify, static_procedure)', `${goal} is refused`);
5013
+ }
5014
+ },
5015
+ },
5016
+ {
5017
+ name: 'public/1 applies to clauses that precede the directive',
5018
+ run: () => {
5019
+ const program = Program.parse('elk(bertha).\n:- public(elk/1).\n');
5020
+ const solver = new Solver(program, {});
5021
+ const answers = [...solver.solve([parseGoalText('clause(elk(_), _)')], new Env(), 0)];
5022
+ assertEqual(answers.length, 1, 'earlier clauses become readable');
5023
+ },
5024
+ },
5025
+ {
5026
+ name: 'public/1 leaves undeclared procedures private',
5027
+ run: () => {
5028
+ const solver = new Solver(Program.parse(':- public(elk/1).\nelk(X) :- moose(X).\nmoose(bertha).\n'), {});
5029
+ let caught = null;
5030
+ try {
5031
+ [...solver.solve([parseGoalText('clause(moose(_), _)')], new Env(), 0)];
5032
+ } catch (error) {
5033
+ caught = error;
5034
+ }
5035
+ assertEqual(caught?.formal, 'permission_error(access, private_procedure)', 'sibling stays private');
5036
+ },
5037
+ },
5038
+ {
5039
+ // The meta-interpreter case: no annotation on the interpreted program.
5040
+ name: 'default_procedure_access public opens every user-defined procedure',
5041
+ run: () => {
5042
+ const source = ':- set_prolog_flag(default_procedure_access, public).\n' +
5043
+ 'solve(true) :- !.\n' +
5044
+ 'solve((A, B)) :- !, solve(A), solve(B).\n' +
5045
+ 'solve(H) :- clause(H, Body), solve(Body).\n' +
5046
+ 'elk(X) :- moose(X).\nmoose(bertha).\ngrazes(X) :- elk(X).\n';
5047
+ const solver = new Solver(Program.parse(source), {});
5048
+ const goal = parseGoalText('solve(grazes(W))');
5049
+ const answers = [...solver.solve([goal], new Env(), 0)];
5050
+ assertEqual(answers.length, 1, 'meta-interpreter answer count');
5051
+ assertEqual(termToString(copyResolved(goal.args[0], answers[0])), 'grazes(bertha)', 'meta-interpreter answer');
5052
+ },
5053
+ },
5054
+ {
5055
+ name: 'default_procedure_access public keeps procedures static and built-ins private',
5056
+ run: () => {
5057
+ const source = ':- set_prolog_flag(default_procedure_access, public).\nmoose(bertha).\n';
5058
+ const solver = new Solver(Program.parse(source), {});
5059
+ for (const [goal, formal] of [
5060
+ ['retract(moose(bertha))', 'permission_error(modify, static_procedure)'],
5061
+ ['clause(atom(_), _)', 'permission_error(access, private_procedure)'],
5062
+ ]) {
5063
+ let caught = null;
5064
+ try {
5065
+ [...solver.solve([parseGoalText(goal)], new Env(), 0)];
5066
+ } catch (error) {
5067
+ caught = error;
5068
+ }
5069
+ assertEqual(caught?.formal, formal, `${goal} is refused`);
5070
+ }
5071
+ },
5072
+ },
5073
+ {
5074
+ // public/1 and the flag are extensions, so the strict profile omits both.
5075
+ name: 'strict ISO core mode offers neither public/1 nor the access flag',
5076
+ run: () => {
5077
+ let caught = null;
5078
+ try {
5079
+ Program.parse(':- public(elk/1).\nelk(bertha).\n', { isoStrict: true });
5080
+ } catch (error) {
5081
+ caught = error;
5082
+ }
5083
+ if (!caught) throw new Error('public/1 was accepted in strict ISO core mode');
5084
+ // 8.17.4.3: an unknown flag name is a domain error, so the flag being
5085
+ // absent from the strict profile is observable that way.
5086
+ const solver = new Solver(Program.parse('elk(bertha).\n', { isoStrict: true }), { isoStrict: true });
5087
+ let flagError = null;
5088
+ try {
5089
+ [...solver.solve([parseGoalText('current_prolog_flag(default_procedure_access, _)', { isoStrict: true })], new Env(), 0)];
5090
+ } catch (error) {
5091
+ flagError = error;
5092
+ }
5093
+ assertEqual(flagError?.formal, 'domain_error(prolog_flag)', 'flag is absent in strict ISO core mode');
5094
+ },
5095
+ },
4990
5096
  {
4991
5097
  // https://github.com/eyereasoner/eyeprolog/issues/96
4992
5098
  name: 'clause/2 keeps static procedures private in the default mode',
@@ -6151,7 +6151,7 @@ partial list.
6151
6151
 
6152
6152
  #### Dynamic database and procedure information
6153
6153
 
6154
- - **`clause(+Head,?Body)`** — Enumerates fresh copies of source clauses matching the callable `Head`; facts have body `true`. Only *public* procedures can be inspected: a procedure defined by a Prolog text is static unless a *dynamic/1* directive declares it, and a procedure first created by *assertz/1* or *asserta/1* is dynamic. Access to a static user procedure, or to a built-in, raises *permission_error(access,private_procedure)*. This applies in every execution mode, not only under `--iso-strict`, and matches the *permission_error(modify,static_procedure)* that *assertz/1* and *retract/1* already raise for the same procedures.
6154
+ - **`clause(+Head,?Body)`** — Enumerates fresh copies of source clauses matching the callable `Head`; facts have body `true`. Only *public* procedures can be inspected: a procedure defined by a Prolog text is static unless a *dynamic/1* directive declares it, and a procedure first created by *assertz/1* or *asserta/1* is dynamic. Access to a static user procedure, or to a built-in, raises *permission_error(access,private_procedure)*. This applies in every execution mode, not only under `--iso-strict`, and matches the *permission_error(modify,static_procedure)* that *assertz/1* and *retract/1* already raise for the same procedures. Normal mode additionally accepts a *public/1* directive and a `default_procedure_access` flag that open static procedures to inspection; see below.
6155
6155
  - **`asserta(+Clause)`, `assertz(+Clause)`** — Insert a copied fact or rule at the beginning or end of a predicate declared *dynamic/1*. Static and built-in procedures cannot be modified.
6156
6156
  - **`retract(+Clause)`** — Removes matching dynamic clauses one at a time on backtracking. A call sees the logical update view captured when it began. A fact pattern matches facts only.
6157
6157
  - **`retractall(+Head)`** — Removes every matching clause from a dynamic procedure, succeeds when none match, and keeps the empty dynamic procedure known.
@@ -6205,6 +6205,58 @@ consults another file or imports a module instead of being reset by the host
6205
6205
  rebuild of the program. Programs that intentionally treat an undefined
6206
6206
  predicate as failure must opt in with `set_prolog_flag(unknown, fail)`; bundled
6207
6207
  examples and non-ISO corpus cases that depend on that policy do so explicitly.
6208
+ ### Reading static procedures
6209
+
6210
+ A procedure defined by a Prolog text is static, so `clause/2` refuses it
6211
+ (ISO 7.5.2, 7.5.3, 8.8.1.3). Declaring the procedure `dynamic` lifts the
6212
+ restriction, but it also makes the procedure modifiable, and for a
6213
+ meta-interpreter it means annotating a program you may not want to edit and
6214
+ enumerating every predicate you intend to read.
6215
+
6216
+ ISO 7.5.3 has a NOTE observing that a `public/1` directive declaring
6217
+ user-defined procedures to be public would be an extension. Normal EyeProlog
6218
+ provides it:
6219
+
6220
+ ```eyeprolog
6221
+ :- public(elk/1).
6222
+
6223
+ elk(X) :- moose(X).
6224
+
6225
+ moose(bertha).
6226
+ ```
6227
+
6228
+ `clause(elk(bertha), Body)` now succeeds with `Body = moose(bertha)`, while
6229
+ `moose/1` carries no declaration and stays private. A public procedure is still
6230
+ *static*: `assertz(elk(clara))` continues to raise
6231
+ *permission_error(modify,static_procedure)*. The directive grants read access
6232
+ only.
6233
+
6234
+ To open every user-defined procedure at once, set the
6235
+ `default_procedure_access` flag to `public`:
6236
+
6237
+ ```eyeprolog
6238
+ :- set_prolog_flag(default_procedure_access, public).
6239
+
6240
+ solve(true) :- !.
6241
+ solve((A, B)) :- !, solve(A), solve(B).
6242
+ solve(H) :- clause(H, Body), solve(Body).
6243
+
6244
+ elk(X) :- moose(X).
6245
+ moose(bertha).
6246
+ grazes(X) :- elk(X).
6247
+ ```
6248
+
6249
+ `solve(grazes(W))` yields `W = bertha` without a single declaration on the
6250
+ interpreted program. The flag changes access, not mutability or existence:
6251
+ procedures remain static, and built-in procedures remain private, so
6252
+ `clause(atom(_), _)` still raises *permission_error(access,private_procedure)*.
6253
+ The supported values are `private` (the default, matching ISO) and `public`.
6254
+
6255
+ Both the directive and the flag are extensions, so strict ISO core mode offers
6256
+ neither: `public/1` is rejected as an implementation-specific directive, and
6257
+ `current_prolog_flag(default_procedure_access, _)` raises
6258
+ *domain_error(prolog_flag)*.
6259
+
6208
6260
  The `occurs_check` flag is an EyeProlog diagnostic
6209
6261
  extension rather than an ISO-defined core flag: it is absent in strict mode,
6210
6262
  while normal mode keeps its `true` default and optional `error` diagnostic for
@@ -10370,7 +10422,7 @@ must preserve the same observable outcome. Additional normal-mode syntax may
10370
10422
  accept texts outside the strict grammar, but it may not reinterpret an accepted
10371
10423
  standard case.
10372
10424
 
10373
- The file-based conformance corpus contains 809 cases, including 392 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
10425
+ The file-based conformance corpus contains 810 cases, including 393 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
10374
10426
  Separate exact-output suites check 210 normal examples and 61 proof examples; all executable chapter programs are parsed and their declared goals are executed. The eight-case
10375
10427
  playground contract suite imports the production worker, sends real reasoning
10376
10428
  requests through its message protocol, and crawls the served module graph for