eyeprolog 1.5.50 → 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 +18 -20
- package/examples/book/chapter-03/09-program.pl +1 -1
- 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 +39 -10
- package/src/solver.js +10 -1
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +2 -1
- package/test/conformance/ISO-PROLOG-TEXT-EXECUTION-MATRIX.md +2 -2
- package/test/conformance/README.md +1 -1
- package/test/conformance/cases/iso/public_procedure_access.pl +52 -0
- package/test/conformance/errors/iso/consult_redefines_builtin.pl +11 -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/conformance/expected-errors/iso/consult_redefines_builtin.txt +1 -0
- package/test/run-regression.mjs +193 -5
- package/the-art-of-eyeprolog.md +62 -5
- package/tools/extract-book-examples.mjs +28 -0
- package/examples/book/chapter-08/01-findall.pl +0 -8
- package/examples/book/chapter-09/01-functor.pl +0 -4
- package/examples/book/chapter-24/03-reachable.pl +0 -6
- package/examples/book/chapter-28/01-program.pl +0 -2
- /package/examples/book/chapter-08/{02-outgoing_costs.pl → 01-outgoing_costs.pl} +0 -0
- /package/examples/book/chapter-08/{03-best_route.pl → 02-best_route.pl} +0 -0
- /package/examples/book/chapter-09/{02-normalized.pl → 01-normalized.pl} +0 -0
- /package/examples/book/chapter-09/{03-message.pl → 02-message.pl} +0 -0
- /package/examples/book/chapter-24/{04-best_plan.pl → 03-best_plan.pl} +0 -0
- /package/examples/book/chapter-28/{02-triangle.pl → 01-triangle.pl} +0 -0
- /package/examples/book/chapter-28/{03-rectangle.pl → 02-rectangle.pl} +0 -0
- /package/examples/book/chapter-28/{04-integer_rectangle.pl → 03-integer_rectangle.pl} +0 -0
- /package/examples/book/chapter-28/{05-preserves_combine.pl → 04-preserves_combine.pl} +0 -0
- /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
|
@@ -61,15 +61,13 @@ npm run generate
|
|
|
61
61
|
|
|
62
62
|
## Chapter 8: Collecting and choosing answers
|
|
63
63
|
|
|
64
|
-
- [01-
|
|
65
|
-
- [02-
|
|
66
|
-
- [03-best_route.pl](chapter-08/03-best_route.pl)
|
|
64
|
+
- [01-outgoing_costs.pl](chapter-08/01-outgoing_costs.pl)
|
|
65
|
+
- [02-best_route.pl](chapter-08/02-best_route.pl)
|
|
67
66
|
|
|
68
67
|
## Chapter 9: Structured data, text, and contexts
|
|
69
68
|
|
|
70
|
-
- [01-
|
|
71
|
-
- [02-
|
|
72
|
-
- [03-message.pl](chapter-09/03-message.pl)
|
|
69
|
+
- [01-normalized.pl](chapter-09/01-normalized.pl)
|
|
70
|
+
- [02-message.pl](chapter-09/02-message.pl)
|
|
73
71
|
|
|
74
72
|
## Chapter 10: From puzzles to models
|
|
75
73
|
|
|
@@ -148,8 +146,7 @@ npm run generate
|
|
|
148
146
|
|
|
149
147
|
- [01-worker.pl](chapter-24/01-worker.pl) — Generate, constrain, describe
|
|
150
148
|
- [02-simple_path.pl](chapter-24/02-simple_path.pl) — Search over states
|
|
151
|
-
- [03-
|
|
152
|
-
- [04-best_plan.pl](chapter-24/04-best_plan.pl) — Optimization is search plus an order
|
|
149
|
+
- [03-best_plan.pl](chapter-24/03-best_plan.pl) — Optimization is search plus an order
|
|
153
150
|
|
|
154
151
|
## Chapter 25: Case study: an auditable decision service
|
|
155
152
|
|
|
@@ -174,11 +171,10 @@ npm run generate
|
|
|
174
171
|
|
|
175
172
|
## Chapter 28: Algebra, symmetry, and representation
|
|
176
173
|
|
|
177
|
-
- [01-
|
|
178
|
-
- [02-
|
|
179
|
-
- [03-
|
|
180
|
-
- [04-
|
|
181
|
-
- [05-preserves_combine.pl](chapter-28/05-preserves_combine.pl) — Composition, homomorphism, and reusable laws
|
|
174
|
+
- [01-triangle.pl](chapter-28/01-triangle.pl) — Symmetry reduces search
|
|
175
|
+
- [02-rectangle.pl](chapter-28/02-rectangle.pl) — Relations reveal inverse problems
|
|
176
|
+
- [03-integer_rectangle.pl](chapter-28/03-integer_rectangle.pl)
|
|
177
|
+
- [04-preserves_combine.pl](chapter-28/04-preserves_combine.pl) — Composition, homomorphism, and reusable laws
|
|
182
178
|
|
|
183
179
|
## Chapter 29: Search as experimental mathematics
|
|
184
180
|
|
|
@@ -238,13 +234,15 @@ npm run generate
|
|
|
238
234
|
|
|
239
235
|
## Chapter 39: Predicate reference
|
|
240
236
|
|
|
241
|
-
- [01-
|
|
242
|
-
- [02-
|
|
243
|
-
- [03-answer
|
|
244
|
-
- [04-
|
|
245
|
-
- [05-
|
|
246
|
-
- [06-
|
|
247
|
-
- [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
|
|
248
246
|
|
|
249
247
|
## Chapter 40: Running EyeProlog: command line and corpus
|
|
250
248
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
% From The Art of EyeProlog, Chapter 3 — Equality, unification, and the occurs check.
|
|
2
|
-
|
|
2
|
+
cyclic_unification :- X = wrapper(X).
|
|
@@ -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);
|
|
@@ -1879,16 +1896,28 @@ function assertDynamicIndicatorIsDefinable(indicator, strictIso = false) {
|
|
|
1879
1896
|
|
|
1880
1897
|
function assertPredicateIsDefinable(name, arity, strictIso = false) {
|
|
1881
1898
|
// false/0 is standardized as a static built-in by Corrigendum 2 and cannot
|
|
1882
|
-
// be redefined in either profile.
|
|
1883
|
-
//
|
|
1884
|
-
//
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1899
|
+
// be redefined in either profile.
|
|
1900
|
+
//
|
|
1901
|
+
// ISO 7.4.3 requires that the predicate indicator of the head of a clause in
|
|
1902
|
+
// a Prolog text is not that of a built-in predicate or a control construct.
|
|
1903
|
+
// That subclause otherwise defers to assertz/1 and waives only the static
|
|
1904
|
+
// procedure error, so the built-in restriction is a separate requirement and
|
|
1905
|
+
// is not profile-dependent. The strict ISO registry holds exactly the Part 1
|
|
1906
|
+
// built-ins and control constructs, so it is consulted in every mode.
|
|
1907
|
+
//
|
|
1908
|
+
// EyeProlog's library and extension predicates are deliberately absent from
|
|
1909
|
+
// that registry and stay redefinable, which preserves the source
|
|
1910
|
+
// compatibility that user programs rely on.
|
|
1911
|
+
//
|
|
1912
|
+
// (,)/2 is a control construct (table 9) but is recognized by the parser
|
|
1913
|
+
// rather than the registry, so it is named here. (:-)/1-2 come from the STC
|
|
1914
|
+
// #56 working draft rather than published text and stay strict-only.
|
|
1915
|
+
const controlConstruct = name === ',' && arity === 2;
|
|
1916
|
+
const draftSyntaxProcedure = strictIso && name === ':-' && (arity === 1 || arity === 2);
|
|
1889
1917
|
if ((name === 'false' && arity === 0) ||
|
|
1890
|
-
|
|
1891
|
-
|
|
1918
|
+
getStrictIsoRegistry().get(name, arity) ||
|
|
1919
|
+
controlConstruct ||
|
|
1920
|
+
draftSyntaxProcedure) {
|
|
1892
1921
|
throw staticProcedureModificationError(name, arity);
|
|
1893
1922
|
}
|
|
1894
1923
|
}
|
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. |
|
|
@@ -23,7 +23,7 @@ chosen behavior is also indexed by `ISO-IMPLEMENTATION-DEFINED.md`.
|
|
|
23
23
|
| 7.4.2.7 `include/1` | covered | The included text is prepared at the directive position and shares operator/character/flag preparation state with its parent. |
|
|
24
24
|
| 7.4.2.8 `ensure_loaded/1` | covered | A source is prepared at most once in the current load graph, including repeated references and self/top-level references. |
|
|
25
25
|
| 7.4.2.9 `set_prolog_flag/2` | covered | Preparation-time flag changes affect subsequent text and are replayed into execution state; strict flag names/values/changeability remain governed by the closed 7.11 audit. |
|
|
26
|
-
| 7.4.3 source clauses | covered | Source heads/bodies are validated like program clauses, standardized static/control procedures are protected, declarations can create empty procedures, and body conversion follows 7.6.2 while preserving head/body variable identity. |
|
|
26
|
+
| 7.4.3 source clauses | covered | Source heads/bodies are validated like program clauses, standardized static/control procedures are protected, declarations can create empty procedures, and body conversion follows 7.6.2 while preserving head/body variable identity. The built-in restriction applies in every execution mode, so consulting a clause for a built-in predicate or control construct reports the same `permission_error(modify, static_procedure)` as `assert`ing one; EyeProlog's own library and extension predicates are not standard built-ins and stay redefinable. Corpus: `error/iso/consult_redefines_builtin`. |
|
|
27
27
|
|
|
28
28
|
The strict release test `closes ISO 7.4 Prolog-text preparation and directive
|
|
29
29
|
rows` exercises the cross-text operator/character/flag state, include and
|
|
@@ -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).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
% ISO 7.4.3: a clause of a clause-term in a Prolog text must satisfy the same
|
|
2
|
+
% constraints as a successful assertz/1, waiving only the static-procedure
|
|
3
|
+
% error, and the predicate indicator of its head must not be that of a built-in
|
|
4
|
+
% predicate or a control construct. Consulting a definition of clause/2 is
|
|
5
|
+
% therefore a permission error, exactly as asserta/1 already reported.
|
|
6
|
+
% https://github.com/eyereasoner/eyeprolog/issues/97
|
|
7
|
+
%% goal: answer
|
|
8
|
+
|
|
9
|
+
clause(elk(1), 2).
|
|
10
|
+
|
|
11
|
+
answer.
|
|
@@ -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 @@
|
|
|
1
|
+
error(permission_error(modify, static_procedure), /(clause, 2))
|
package/test/run-regression.mjs
CHANGED
|
@@ -4907,6 +4907,192 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
|
|
|
4907
4907
|
assertEqual(answers.length, 2, 'cut did not prune the second clause');
|
|
4908
4908
|
},
|
|
4909
4909
|
},
|
|
4910
|
+
{
|
|
4911
|
+
// https://github.com/eyereasoner/eyeprolog/issues/97
|
|
4912
|
+
name: 'a Prolog text cannot supply clauses for a built-in predicate',
|
|
4913
|
+
run: () => {
|
|
4914
|
+
let caught = null;
|
|
4915
|
+
try {
|
|
4916
|
+
Program.parse('clause(elk(1), 2).\n');
|
|
4917
|
+
} catch (error) {
|
|
4918
|
+
caught = error;
|
|
4919
|
+
}
|
|
4920
|
+
if (!caught) throw new Error('a clause for clause/2 was accepted from a Prolog text');
|
|
4921
|
+
assertEqual(caught.formal, 'permission_error(modify, static_procedure)', 'formal error');
|
|
4922
|
+
assertEqual(termToString(caught.culprit), '/(clause, 2)', 'culprit predicate indicator');
|
|
4923
|
+
},
|
|
4924
|
+
},
|
|
4925
|
+
{
|
|
4926
|
+
name: 'consult and asserta/1 agree on redefining a built-in',
|
|
4927
|
+
run: () => {
|
|
4928
|
+
let textFormal = null;
|
|
4929
|
+
try {
|
|
4930
|
+
Program.parse('clause(elk(1), 2).\n');
|
|
4931
|
+
} catch (error) {
|
|
4932
|
+
textFormal = error.formal;
|
|
4933
|
+
}
|
|
4934
|
+
const solver = new Solver(Program.parse('run :- asserta(clause(elk(1), 2)).\n'), {});
|
|
4935
|
+
let assertFormal = null;
|
|
4936
|
+
try {
|
|
4937
|
+
[...solver.solve([parseGoalText('run')], new Env(), 0)];
|
|
4938
|
+
} catch (error) {
|
|
4939
|
+
assertFormal = error.formal;
|
|
4940
|
+
}
|
|
4941
|
+
assertEqual(textFormal, 'permission_error(modify, static_procedure)', 'consult path');
|
|
4942
|
+
assertEqual(assertFormal, textFormal, 'assert path agrees with consult');
|
|
4943
|
+
},
|
|
4944
|
+
},
|
|
4945
|
+
{
|
|
4946
|
+
// Control constructs are covered as well as built-in predicates.
|
|
4947
|
+
name: 'a Prolog text cannot supply clauses for a control construct',
|
|
4948
|
+
run: () => {
|
|
4949
|
+
for (const [source, indicator] of [
|
|
4950
|
+
["';'(a, b).\n", "/(';', 2)"],
|
|
4951
|
+
["','(a, b).\n", "/(',', 2)"],
|
|
4952
|
+
['!.\n', '/(!, 0)'],
|
|
4953
|
+
]) {
|
|
4954
|
+
let caught = null;
|
|
4955
|
+
try {
|
|
4956
|
+
Program.parse(source);
|
|
4957
|
+
} catch (error) {
|
|
4958
|
+
caught = error;
|
|
4959
|
+
}
|
|
4960
|
+
assertEqual(caught?.formal, 'permission_error(modify, static_procedure)', `formal error for ${indicator}`);
|
|
4961
|
+
assertEqual(termToString(caught.culprit), indicator, `culprit for ${indicator}`);
|
|
4962
|
+
}
|
|
4963
|
+
},
|
|
4964
|
+
},
|
|
4965
|
+
{
|
|
4966
|
+
// EyeProlog's library and extension predicates are not standard
|
|
4967
|
+
// built-ins, so user programs may still define their own versions.
|
|
4968
|
+
name: 'a Prolog text may still redefine library predicates',
|
|
4969
|
+
run: () => {
|
|
4970
|
+
const program = Program.parse('member(X, [X]).\nappend([], L, L).\n');
|
|
4971
|
+
assertEqual(program.findGroup('member', 2) != null, true, 'member/2 defined by the text');
|
|
4972
|
+
assertEqual(program.findGroup('append', 3) != null, true, 'append/3 defined by the text');
|
|
4973
|
+
},
|
|
4974
|
+
},
|
|
4975
|
+
{
|
|
4976
|
+
// A dynamic/1 directive names a procedure the text is about to define,
|
|
4977
|
+
// so it is subject to the same restriction.
|
|
4978
|
+
name: 'a dynamic/1 directive cannot name a built-in predicate',
|
|
4979
|
+
run: () => {
|
|
4980
|
+
let caught = null;
|
|
4981
|
+
try {
|
|
4982
|
+
Program.parse(':- dynamic(atom_length/2).\n');
|
|
4983
|
+
} catch (error) {
|
|
4984
|
+
caught = error;
|
|
4985
|
+
}
|
|
4986
|
+
assertEqual(caught?.formal, 'permission_error(modify, static_procedure)', 'formal error');
|
|
4987
|
+
assertEqual(termToString(caught.culprit), '/(atom_length, 2)', 'culprit predicate indicator');
|
|
4988
|
+
},
|
|
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
|
+
},
|
|
4910
5096
|
{
|
|
4911
5097
|
// https://github.com/eyereasoner/eyeprolog/issues/96
|
|
4912
5098
|
name: 'clause/2 keeps static procedures private in the default mode',
|
|
@@ -8170,14 +8356,16 @@ function whiteBoxCases() {
|
|
|
8170
8356
|
assertEqual(termToString(candidates.primary[0].head, new Env(), true), 'row(a0, b0, c0, first)', 'first head');
|
|
8171
8357
|
assertEqual(termToString(candidates.primary[1].head, new Env(), true), 'row(a0, X, c0, wildcard)', 'wildcard head');
|
|
8172
8358
|
|
|
8359
|
+
// A user-defined name: ISO 7.4.3 forbids a Prolog text from defining
|
|
8360
|
+
// clauses for a built-in predicate such as open/3.
|
|
8173
8361
|
const variableHeavy = Program.parse(Array.from(
|
|
8174
8362
|
{ length: 12 },
|
|
8175
|
-
(_, index) => `
|
|
8363
|
+
(_, index) => `gate(X${index}, Y${index}, value${index}).`,
|
|
8176
8364
|
).join('\n'));
|
|
8177
|
-
const
|
|
8178
|
-
selectClauseCandidates(
|
|
8179
|
-
assertEqual(
|
|
8180
|
-
assertEqual(
|
|
8365
|
+
const gateGroup = variableHeavy.findGroup('gate', 3);
|
|
8366
|
+
selectClauseCandidates(gateGroup, parseGoalText('gate(a, b, Result)'), new Env());
|
|
8367
|
+
assertEqual(gateGroup.demandIndexes.size, 0, 'poor wide index discarded');
|
|
8368
|
+
assertEqual(gateGroup.rejectedDemandIndexes.has('0,1'), true, 'poor call mode remembered');
|
|
8181
8369
|
},
|
|
8182
8370
|
},
|
|
8183
8371
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -770,13 +770,18 @@ Equality in the pure Herbrand reading is syntactic identity after substitution.
|
|
|
770
770
|
Operationally, unification discovers a substitution that makes terms
|
|
771
771
|
identical. EyeProlog performs an occurs check whenever unification would bind a
|
|
772
772
|
variable. It therefore uses finite-tree unification and rejects a binding when
|
|
773
|
-
the variable occurs anywhere in the proposed value. For example, this
|
|
774
|
-
fails rather than constructing a cyclic term:
|
|
773
|
+
the variable occurs anywhere in the proposed value. For example, the body of this
|
|
774
|
+
clause fails rather than constructing a cyclic term:
|
|
775
775
|
|
|
776
776
|
```eyeprolog
|
|
777
|
-
|
|
777
|
+
cyclic_unification :- X = wrapper(X).
|
|
778
778
|
```
|
|
779
779
|
|
|
780
|
+
The goal has to be written inside a clause of its own: ISO 7.4.3 forbids a
|
|
781
|
+
Prolog text from supplying clauses for a built-in predicate, so a bare
|
|
782
|
+
`(X = wrapper(X)).` in a program file is a clause for `(=)/2` and is rejected
|
|
783
|
+
with *permission_error(modify, static_procedure, (=)/2)*.
|
|
784
|
+
|
|
780
785
|
ISO classifies unifications whose outcome depends on an occurs check as
|
|
781
786
|
subject-to-occurs-check (STO). EyeProlog's default remains the sound finite-tree
|
|
782
787
|
behavior above. For diagnosis, EyeProlog additionally provides the
|
|
@@ -6146,7 +6151,7 @@ partial list.
|
|
|
6146
6151
|
|
|
6147
6152
|
#### Dynamic database and procedure information
|
|
6148
6153
|
|
|
6149
|
-
- **`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.
|
|
6150
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.
|
|
6151
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.
|
|
6152
6157
|
- **`retractall(+Head)`** — Removes every matching clause from a dynamic procedure, succeeds when none match, and keeps the empty dynamic procedure known.
|
|
@@ -6200,6 +6205,58 @@ consults another file or imports a module instead of being reset by the host
|
|
|
6200
6205
|
rebuild of the program. Programs that intentionally treat an undefined
|
|
6201
6206
|
predicate as failure must opt in with `set_prolog_flag(unknown, fail)`; bundled
|
|
6202
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
|
+
|
|
6203
6260
|
The `occurs_check` flag is an EyeProlog diagnostic
|
|
6204
6261
|
extension rather than an ISO-defined core flag: it is absent in strict mode,
|
|
6205
6262
|
while normal mode keeps its `true` default and optional `error` diagnostic for
|
|
@@ -10365,7 +10422,7 @@ must preserve the same observable outcome. Additional normal-mode syntax may
|
|
|
10365
10422
|
accept texts outside the strict grammar, but it may not reinterpret an accepted
|
|
10366
10423
|
standard case.
|
|
10367
10424
|
|
|
10368
|
-
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.
|
|
10369
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
|
|
10370
10427
|
playground contract suite imports the production worker, sends real reasoning
|
|
10371
10428
|
requests through its message protocol, and crawls the served module graph for
|
|
@@ -4,6 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
+
import { Program, getStrictIsoRegistry } from '../src/index.js';
|
|
8
|
+
|
|
7
9
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
10
|
const bookPath = path.join(root, 'the-art-of-eyeprolog.md');
|
|
9
11
|
const outputRoot = path.join(root, 'examples', 'book');
|
|
@@ -36,6 +38,11 @@ for (const match of book.matchAll(/^(## \d+\. ([^\n]+)|# ([^\n]+)|```eyeprolog\n
|
|
|
36
38
|
if (!chapter) continue;
|
|
37
39
|
const source = match[4].trim();
|
|
38
40
|
if (!source.endsWith('.')) continue;
|
|
41
|
+
// The book also uses eyeprolog blocks for template listings and single
|
|
42
|
+
// illustrative goals. Those read as clauses for a built-in predicate, which
|
|
43
|
+
// ISO 7.4.3 forbids a Prolog text from defining, so they are not programs
|
|
44
|
+
// and are not extracted as such.
|
|
45
|
+
if (definesBuiltIn(source)) continue;
|
|
39
46
|
|
|
40
47
|
const firstClause = source
|
|
41
48
|
.split('\n')
|
|
@@ -46,6 +53,27 @@ for (const match of book.matchAll(/^(## \d+\. ([^\n]+)|# ([^\n]+)|```eyeprolog\n
|
|
|
46
53
|
chapter.examples.push({ predicate, section, source });
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
// True when a code block supplies clauses for a standard built-in predicate or
|
|
57
|
+
// control construct, which marks it as a signature listing or goal fragment
|
|
58
|
+
// rather than an extractable program.
|
|
59
|
+
function definesBuiltIn(source) {
|
|
60
|
+
try {
|
|
61
|
+
const program = Program.parse(source);
|
|
62
|
+
const registry = getStrictIsoRegistry();
|
|
63
|
+
for (const group of program.groups.values()) {
|
|
64
|
+
if (group.name === ',' && group.arity === 2) return true;
|
|
65
|
+
if (registry.get(group.name, group.arity)) return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Preparation itself rejects a clause head that is a built-in, so that
|
|
70
|
+
// error is the signal we are looking for. Any other parse failure is left
|
|
71
|
+
// to the book-example test, which reports it instead of silently dropping
|
|
72
|
+
// the block here.
|
|
73
|
+
return error?.formal === 'permission_error(modify, static_procedure)';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
49
77
|
const files = new Map();
|
|
50
78
|
const readme = [
|
|
51
79
|
'# The Art of EyeProlog — inline examples',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|