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.
- package/conformance-report.md +2 -2
- package/examples/book/README.md +9 -7
- package/examples/book/chapter-39/01-elk.pl +6 -0
- package/examples/book/chapter-39/02-solve.pl +10 -0
- package/package.json +1 -1
- package/src/iso.js +14 -1
- package/src/parser.js +1 -1
- package/src/program.js +18 -1
- package/src/solver.js +10 -1
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +2 -1
- package/test/conformance/ISO-PROLOG-TEXT-EXECUTION-MATRIX.md +1 -1
- package/test/conformance/README.md +1 -1
- package/test/conformance/cases/iso/public_procedure_access.pl +52 -0
- package/test/conformance/expected/iso/exceptions_and_flags.pl +1 -1
- package/test/conformance/expected/iso/public_procedure_access.pl +6 -0
- package/test/run-regression.mjs +106 -0
- package/the-art-of-eyeprolog.md +54 -2
- /package/examples/book/chapter-39/{01-answer.pl → 03-answer.pl} +0 -0
- /package/examples/book/chapter-39/{02-answer-2.pl → 04-answer-2.pl} +0 -0
- /package/examples/book/chapter-39/{03-answer-3.pl → 05-answer-3.pl} +0 -0
- /package/examples/book/chapter-39/{04-cost.pl → 06-cost.pl} +0 -0
- /package/examples/book/chapter-39/{05-message.pl → 07-message.pl} +0 -0
- /package/examples/book/chapter-39/{06-task.pl → 08-task.pl} +0 -0
- /package/examples/book/chapter-39/{07-program.pl → 09-program.pl} +0 -0
package/conformance-report.md
CHANGED
|
@@ -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 |
|
|
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** | **
|
|
48
|
+
| **Total** | **496** | **274** | **19** | **21** | **810** |
|
|
49
49
|
|
|
50
50
|
## DCG conformance clarification
|
|
51
51
|
|
package/examples/book/README.md
CHANGED
|
@@ -234,13 +234,15 @@ npm run generate
|
|
|
234
234
|
|
|
235
235
|
## Chapter 39: Predicate reference
|
|
236
236
|
|
|
237
|
-
- [01-
|
|
238
|
-
- [02-
|
|
239
|
-
- [03-answer
|
|
240
|
-
- [04-
|
|
241
|
-
- [05-
|
|
242
|
-
- [06-
|
|
243
|
-
- [07-
|
|
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,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
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
|
-
|
|
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)
|
|
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`
|
|
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
|
|
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)]).
|
package/test/run-regression.mjs
CHANGED
|
@@ -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',
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|