eyeprolog 1.1.28 → 1.2.0
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/README.md +21 -0
- package/conformance-report.md +2 -2
- package/index.d.ts +9 -0
- package/package.json +2 -1
- package/src/cli.js +31 -2
- package/src/index.js +14 -3
- package/src/iso.js +34 -7
- package/src/parser.js +47 -6
- package/src/program.js +36 -18
- package/src/repl.js +37 -14
- package/src/solver.js +22 -5
- package/src/write.js +6 -1
- package/test/conformance/ISO-COMPLIANCE.md +98 -0
- package/test/conformance/ISO-MATRIX.md +9 -7
- package/test/conformance/README.md +13 -5
- package/test/conformance/cases/iso/wg17_syntax_high_risk.pl +26 -0
- package/test/conformance/expected/iso/wg17_syntax_high_risk.pl +13 -0
- package/test/run-all.mjs +2 -0
- package/test/run-iso-strict.mjs +113 -0
- package/test/run-regression.mjs +126 -0
- package/the-art-of-eyeprolog.md +82 -37
package/README.md
CHANGED
|
@@ -79,6 +79,27 @@ member_test ?- member(X, [prolog, logic]).
|
|
|
79
79
|
; X = logic.
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
## Strict ISO/IEC 13211-1 core
|
|
83
|
+
|
|
84
|
+
For portability and conformance work, run the Part 1 core with Technical
|
|
85
|
+
Corrigenda 1–3 in strict mode:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
eyeprolog --iso-strict
|
|
89
|
+
eyeprolog --iso-strict --goal 'p(X)' program.pl
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The equivalent JavaScript option is `isoStrict: true`. Strict mode rejects
|
|
93
|
+
EyeProlog language extensions, Part 2 module directives, and Part 3 grammar-rule
|
|
94
|
+
expansion/`phrase/2-3`; it also removes the EyeProlog `occurs_check` flag and
|
|
95
|
+
disables automatic tabling. Normal mode is unchanged and continues to support
|
|
96
|
+
modules, DCGs, quads, libraries, proofs, and the other documented extensions.
|
|
97
|
+
|
|
98
|
+
The auditable processor-requirement checklist lives in
|
|
99
|
+
[`test/conformance/ISO-COMPLIANCE.md`](test/conformance/ISO-COMPLIANCE.md).
|
|
100
|
+
EyeProlog does not yet claim independent certification or closure of every
|
|
101
|
+
normative Part 1 requirement.
|
|
102
|
+
|
|
82
103
|
## ISO modules and definite clause grammars
|
|
83
104
|
|
|
84
105
|
EyeProlog implements ISO/IEC 13211-2 modules and the grammar rules and
|
package/conformance-report.md
CHANGED
|
@@ -11,7 +11,7 @@ This report summarizes the file-based conformance corpus under `test/conformance
|
|
|
11
11
|
| builtins | 11 | 0 | 0 | 0 | 11 |
|
|
12
12
|
| context | 11 | 0 | 0 | 0 | 11 |
|
|
13
13
|
| control | 15 | 0 | 0 | 0 | 15 |
|
|
14
|
-
| iso |
|
|
14
|
+
| iso | 167 | 209 | 0 | 0 | 376 |
|
|
15
15
|
| lists | 52 | 3 | 0 | 0 | 55 |
|
|
16
16
|
| modules | 2 | 0 | 0 | 0 | 2 |
|
|
17
17
|
| negation | 8 | 0 | 19 | 0 | 27 |
|
|
@@ -23,4 +23,4 @@ This report summarizes the file-based conformance corpus under `test/conformance
|
|
|
23
23
|
| terms | 26 | 3 | 0 | 0 | 29 |
|
|
24
24
|
| unification | 18 | 0 | 0 | 0 | 18 |
|
|
25
25
|
| variables | 16 | 9 | 0 | 0 | 25 |
|
|
26
|
-
| **Total** | **
|
|
26
|
+
| **Total** | **482** | **260** | **19** | **21** | **782** |
|
package/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface EyePrologRunOptions {
|
|
|
17
17
|
sourceMetadata?: boolean;
|
|
18
18
|
strictNegation?: boolean;
|
|
19
19
|
analyzeNegation?: boolean;
|
|
20
|
+
/** Restrict parsing and execution to ISO/IEC 13211-1:1995 plus Corrigenda 1-3. */
|
|
21
|
+
isoStrict?: boolean;
|
|
20
22
|
/** Initial ISO interpretation of double-quoted list notation. Defaults to chars. */
|
|
21
23
|
doubleQuotes?: 'chars' | 'codes' | 'atom';
|
|
22
24
|
ioOptions?: {
|
|
@@ -120,6 +122,7 @@ export class Program {
|
|
|
120
122
|
moduleImports: Map<string, Map<string, string>>;
|
|
121
123
|
quads: EyePrologQuad[];
|
|
122
124
|
doubleQuotes: 'chars' | 'codes' | 'atom';
|
|
125
|
+
strictIso: boolean;
|
|
123
126
|
negationDependencies: Array<{ from: string; to: string; negative: boolean }>;
|
|
124
127
|
negationStratificationErrors: Array<{ from: string; to: string }>;
|
|
125
128
|
stratifiedNegation: boolean;
|
|
@@ -155,12 +158,14 @@ export class BuiltinRegistry {
|
|
|
155
158
|
eyePrologLibrary?: boolean;
|
|
156
159
|
add(name: string, arity: number, handler: BuiltinHandler, options?: Partial<BuiltinDefinition>): this;
|
|
157
160
|
get(name: string, arity: number): BuiltinDefinition | null;
|
|
161
|
+
remove(name: string, arity: number): this;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
export class Solver {
|
|
161
165
|
constructor(program: Program, options?: EyePrologRunOptions);
|
|
162
166
|
program: Program;
|
|
163
167
|
registry: BuiltinRegistry;
|
|
168
|
+
isoStrict: boolean;
|
|
164
169
|
maxDepth: number;
|
|
165
170
|
depthLimitExceeded: boolean;
|
|
166
171
|
maxInferences: number;
|
|
@@ -219,8 +224,10 @@ export function parseClauses(source: string, options?: EyePrologRunOptions): Arr
|
|
|
219
224
|
export function parseProgramText(source: string, options?: EyePrologRunOptions): Array<EyePrologClause | EyePrologQuad>;
|
|
220
225
|
export function parseGoalText(source: string, options?: EyePrologRunOptions): EyePrologTerm;
|
|
221
226
|
export function createDefaultRegistry(): BuiltinRegistry;
|
|
227
|
+
export function createStrictIsoRegistry(): BuiltinRegistry;
|
|
222
228
|
export function createEyePrologRegistry(): BuiltinRegistry;
|
|
223
229
|
export function getDefaultRegistry(): BuiltinRegistry;
|
|
230
|
+
export function getStrictIsoRegistry(): BuiltinRegistry;
|
|
224
231
|
export function getEyePrologRegistry(): BuiltinRegistry;
|
|
225
232
|
export const standardLibrarySources: ReadonlyMap<string, { filename: string; source: string }>;
|
|
226
233
|
export const eyePrologLibraryIndicators: readonly string[];
|
|
@@ -291,8 +298,10 @@ declare const eyeprolog: {
|
|
|
291
298
|
parseGoalText: typeof parseGoalText;
|
|
292
299
|
parseProgramText: typeof parseProgramText;
|
|
293
300
|
createDefaultRegistry: typeof createDefaultRegistry;
|
|
301
|
+
createStrictIsoRegistry: typeof createStrictIsoRegistry;
|
|
294
302
|
createEyePrologRegistry: typeof createEyePrologRegistry;
|
|
295
303
|
getDefaultRegistry: typeof getDefaultRegistry;
|
|
304
|
+
getStrictIsoRegistry: typeof getStrictIsoRegistry;
|
|
296
305
|
getEyePrologRegistry: typeof getEyePrologRegistry;
|
|
297
306
|
standardLibrarySources: typeof standardLibrarySources;
|
|
298
307
|
eyePrologLibraryIndicators: typeof eyePrologLibraryIndicators;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.2.0",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"generate": "node tools/extract-book-examples.mjs",
|
|
50
50
|
"test:eyeprolog": "node test/run-all.mjs",
|
|
51
51
|
"test:conformance": "node test/run-conformance.mjs",
|
|
52
|
+
"test:iso-strict": "node test/run-iso-strict.mjs",
|
|
52
53
|
"test:examples": "node test/run-examples.mjs",
|
|
53
54
|
"test:regression": "node test/run-regression.mjs",
|
|
54
55
|
"test:playground": "node test/run-playground.mjs",
|
package/src/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ export async function main(argv) {
|
|
|
26
26
|
proof: false,
|
|
27
27
|
quads: false,
|
|
28
28
|
stats: false,
|
|
29
|
+
isoStrict: false,
|
|
29
30
|
version: false,
|
|
30
31
|
warnings: false,
|
|
31
32
|
goals: [],
|
|
@@ -47,6 +48,8 @@ export async function main(argv) {
|
|
|
47
48
|
options.quads = true;
|
|
48
49
|
} else if (!endOptions && (arg === '--stats' || arg === '-s')) {
|
|
49
50
|
options.stats = true;
|
|
51
|
+
} else if (!endOptions && arg === '--iso-strict') {
|
|
52
|
+
options.isoStrict = true;
|
|
50
53
|
} else if (!endOptions && (arg === '--version' || arg === '-v')) {
|
|
51
54
|
options.version = true;
|
|
52
55
|
} else if (!endOptions && (arg === '--warnings' || arg === '-w')) {
|
|
@@ -81,6 +84,24 @@ export async function main(argv) {
|
|
|
81
84
|
return;
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
if (options.isoStrict && options.quads) {
|
|
88
|
+
throw new Error('--iso-strict cannot be combined with --quads');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (options.isoStrict && options.files.length === 0 && options.goals.length === 0 &&
|
|
92
|
+
!options.proof && !options.stats && !options.warnings) {
|
|
93
|
+
const engine = await loadEngine();
|
|
94
|
+
const { runRepl } = await import('./repl.js');
|
|
95
|
+
const exitCode = await runRepl(engine, {
|
|
96
|
+
input: process.stdin,
|
|
97
|
+
output: process.stdout,
|
|
98
|
+
errorOutput: process.stderr,
|
|
99
|
+
isoStrict: true,
|
|
100
|
+
});
|
|
101
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
84
105
|
if (options.files.length === 0) {
|
|
85
106
|
options.files.push('-');
|
|
86
107
|
}
|
|
@@ -121,7 +142,10 @@ export async function main(argv) {
|
|
|
121
142
|
}
|
|
122
143
|
|
|
123
144
|
const engine = await loadEngine();
|
|
124
|
-
let program = engine.Program.parseSources(sourceParts, {
|
|
145
|
+
let program = engine.Program.parseSources(sourceParts, {
|
|
146
|
+
sourceMetadata: options.proof || options.isoStrict,
|
|
147
|
+
isoStrict: options.isoStrict,
|
|
148
|
+
});
|
|
125
149
|
|
|
126
150
|
if (options.warnings) printWarnings(program);
|
|
127
151
|
|
|
@@ -156,15 +180,18 @@ async function loadExplanation() {
|
|
|
156
180
|
}
|
|
157
181
|
|
|
158
182
|
async function runDefault(engine, program, options) {
|
|
159
|
-
const registry = engine.getEyePrologRegistry();
|
|
183
|
+
const registry = options.isoStrict ? engine.getStrictIsoRegistry() : engine.getEyePrologRegistry();
|
|
160
184
|
const solver = new engine.Solver(program, {
|
|
161
185
|
registry,
|
|
186
|
+
isoStrict: options.isoStrict,
|
|
162
187
|
ioOptions: { write: (text) => process.stdout.write(String(text)) },
|
|
163
188
|
});
|
|
164
189
|
program = solver.program;
|
|
165
190
|
const goals = options.goals.map((text) => {
|
|
166
191
|
const goal = engine.parseGoalText(text, {
|
|
167
192
|
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
193
|
+
operatorDefinitions: [...program.operators.values()],
|
|
194
|
+
isoStrict: options.isoStrict,
|
|
168
195
|
});
|
|
169
196
|
if (goal.type === 'var') throw new engine.PrologError('instantiation_error');
|
|
170
197
|
if (goal.type !== 'atom' && goal.type !== 'compound') throw new engine.PrologError('type_error(callable)', goal);
|
|
@@ -233,6 +260,8 @@ Options:
|
|
|
233
260
|
-p, --proof Enable proof explanations.
|
|
234
261
|
-q, --quads Run embedded quad tests and fail if any do not hold.
|
|
235
262
|
-s, --stats Print solver statistics to stderr after execution.
|
|
263
|
+
--iso-strict Use ISO/IEC 13211-1 core + Corrigenda 1-3 only;
|
|
264
|
+
reject EyeProlog language extensions and disable automatic tabling.
|
|
236
265
|
-v, --version Show the package version and exit.
|
|
237
266
|
-w, --warnings Print non-fatal portability warnings to stderr.
|
|
238
267
|
-g, --goal goal Solve goal and print its ground answers; may be repeated.
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,9 @@ export * from './term.js';
|
|
|
7
7
|
export {
|
|
8
8
|
BuiltinRegistry,
|
|
9
9
|
createDefaultRegistry,
|
|
10
|
+
createStrictIsoRegistry,
|
|
10
11
|
getDefaultRegistry,
|
|
12
|
+
getStrictIsoRegistry,
|
|
11
13
|
HaltSignal,
|
|
12
14
|
PrologError,
|
|
13
15
|
} from './iso.js';
|
|
@@ -26,16 +28,23 @@ import { ATOM, COMPOUND, VAR, Env, copyResolved, termIsGround } from './term.js'
|
|
|
26
28
|
import { Program } from './program.js';
|
|
27
29
|
import { Solver } from './solver.js';
|
|
28
30
|
import { whyNoProof, whyProof } from './explain.js';
|
|
29
|
-
import { HaltSignal, PrologError } from './iso.js';
|
|
31
|
+
import { HaltSignal, PrologError, getStrictIsoRegistry } from './iso.js';
|
|
30
32
|
import { getEyePrologRegistry } from './standard-library.js';
|
|
31
33
|
import { parseGoalText } from './parser.js';
|
|
32
34
|
import { formatTermForWrite } from './write.js';
|
|
33
35
|
|
|
34
36
|
export function run(source, options = {}) {
|
|
35
37
|
const includeWhy = options.proof === true || options.why === true || options.explain === true;
|
|
36
|
-
const
|
|
38
|
+
const requestedStrictIso = options.isoStrict === true;
|
|
39
|
+
if (source instanceof Program && requestedStrictIso && source.strictIso !== true) {
|
|
40
|
+
throw new Error('strict ISO mode requires a Program parsed with isoStrict: true');
|
|
41
|
+
}
|
|
42
|
+
const parseOptions = { ...options, sourceMetadata: includeWhy || requestedStrictIso };
|
|
37
43
|
let program = source instanceof Program ? source : Program.parse(source, parseOptions);
|
|
38
|
-
const
|
|
44
|
+
const strictIso = requestedStrictIso || program.strictIso === true;
|
|
45
|
+
const runOptions = strictIso
|
|
46
|
+
? { ...options, isoStrict: true, registry: getStrictIsoRegistry() }
|
|
47
|
+
: options.registry ? options : { ...options, registry: getEyePrologRegistry() };
|
|
39
48
|
const output = [];
|
|
40
49
|
const solver = new Solver(program, {
|
|
41
50
|
...runOptions,
|
|
@@ -91,6 +100,8 @@ function normalizeGoals(options, solver) {
|
|
|
91
100
|
const goal = typeof requestedGoal === 'string'
|
|
92
101
|
? parseGoalText(requestedGoal, {
|
|
93
102
|
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
103
|
+
operatorDefinitions: [...solver.program.operators.values()],
|
|
104
|
+
isoStrict: solver.isoStrict,
|
|
94
105
|
})
|
|
95
106
|
: requestedGoal;
|
|
96
107
|
if (goal.type === VAR) throw new PrologError('instantiation_error');
|
package/src/iso.js
CHANGED
|
@@ -508,11 +508,18 @@ function* clauseBuiltin({ solver, goal, env }) {
|
|
|
508
508
|
if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
|
|
509
509
|
callableOrVariable(goal.args[1], env);
|
|
510
510
|
const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
511
|
-
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head)) {
|
|
511
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head)) {
|
|
512
512
|
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
513
513
|
}
|
|
514
514
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
515
515
|
if (!group) return;
|
|
516
|
+
// ISO 7.5.3 makes dynamic procedures public and static user-defined
|
|
517
|
+
// procedures private by default. EyeProlog's normal profile keeps static
|
|
518
|
+
// clauses inspectable for proof tooling; strict core mode restores the ISO
|
|
519
|
+
// access rule used by clause/2.
|
|
520
|
+
if (solver.isoStrict && !group.dynamic) {
|
|
521
|
+
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
522
|
+
}
|
|
516
523
|
for (const clause of group.clauses) {
|
|
517
524
|
const pair = compound('$clause', [clause.head, clauseBodyTerm(clause.body)]);
|
|
518
525
|
const copied = freshCopy(pair, new Env());
|
|
@@ -547,13 +554,13 @@ function procedureIndicator(head) {
|
|
|
547
554
|
return compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
548
555
|
}
|
|
549
556
|
|
|
550
|
-
function isGrammarRuleProcedure(head) {
|
|
551
|
-
return head.name === '-->' && head.arity === 2;
|
|
557
|
+
function isGrammarRuleProcedure(solver, head) {
|
|
558
|
+
return !solver.isoStrict && head.name === '-->' && head.arity === 2;
|
|
552
559
|
}
|
|
553
560
|
|
|
554
561
|
function assertModifiable(solver, head, module = 'user') {
|
|
555
562
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? module);
|
|
556
|
-
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head) || (group && !group.dynamic)) {
|
|
563
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head) || (group && !group.dynamic)) {
|
|
557
564
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
|
|
558
565
|
}
|
|
559
566
|
}
|
|
@@ -580,7 +587,7 @@ function* retractBuiltin({ solver, goal, env }) {
|
|
|
580
587
|
const parts = clauseParts(goal.args[0], env);
|
|
581
588
|
requireClauseHead(parts.head);
|
|
582
589
|
const group = solver.program.findGroup(parts.head.name, parts.head.arity, parts.head.module ?? goal.module ?? 'user');
|
|
583
|
-
if (solver.registry.get(parts.head.name, parts.head.arity) || isGrammarRuleProcedure(parts.head) || (group && !group.dynamic)) {
|
|
590
|
+
if (solver.registry.get(parts.head.name, parts.head.arity) || isGrammarRuleProcedure(solver, parts.head) || (group && !group.dynamic)) {
|
|
584
591
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(parts.head));
|
|
585
592
|
}
|
|
586
593
|
if (!group) return;
|
|
@@ -603,7 +610,7 @@ function* retractAllBuiltin({ solver, goal, env }) {
|
|
|
603
610
|
const head = deref(goal.args[0], env);
|
|
604
611
|
requireClauseHead(head);
|
|
605
612
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
606
|
-
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head) || (group && !group.dynamic)) {
|
|
613
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head) || (group && !group.dynamic)) {
|
|
607
614
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
|
|
608
615
|
}
|
|
609
616
|
if (group) {
|
|
@@ -637,7 +644,7 @@ function* abolishBuiltin({ solver, goal, env }) {
|
|
|
637
644
|
const target = predicateIndicatorParts(goal.args[0], env);
|
|
638
645
|
const module = goal.module ?? 'user';
|
|
639
646
|
const group = solver.program.findGroup(target.name, target.arity, module);
|
|
640
|
-
if (solver.registry.get(target.name, target.arity) || isGrammarRuleProcedure(target) || (group && !group.dynamic)) {
|
|
647
|
+
if (solver.registry.get(target.name, target.arity) || isGrammarRuleProcedure(solver, target) || (group && !group.dynamic)) {
|
|
641
648
|
throw new PrologError('permission_error(modify, static_procedure)', target.indicator);
|
|
642
649
|
}
|
|
643
650
|
solver.program.abolishDynamicGroup(target.name, target.arity, module);
|
|
@@ -2057,6 +2064,11 @@ export class BuiltinRegistry {
|
|
|
2057
2064
|
get(name, arity) {
|
|
2058
2065
|
return this.defs.get(`${name}/${arity}`) ?? null;
|
|
2059
2066
|
}
|
|
2067
|
+
|
|
2068
|
+
remove(name, arity) {
|
|
2069
|
+
this.defs.delete(`${name}/${arity}`);
|
|
2070
|
+
return this;
|
|
2071
|
+
}
|
|
2060
2072
|
}
|
|
2061
2073
|
|
|
2062
2074
|
export function createDefaultRegistry() {
|
|
@@ -2065,9 +2077,24 @@ export function createDefaultRegistry() {
|
|
|
2065
2077
|
return registry;
|
|
2066
2078
|
}
|
|
2067
2079
|
|
|
2080
|
+
// ISO/IEC 13211-1:1995 + Corrigenda 1-3 only. phrase/2-3 and grammar-rule
|
|
2081
|
+
// expansion belong to the separate grammar-rule specification, while the
|
|
2082
|
+
// EyeProlog standard-library/CLP(Z) adapters are registered elsewhere.
|
|
2083
|
+
export function createStrictIsoRegistry() {
|
|
2084
|
+
return createDefaultRegistry()
|
|
2085
|
+
.remove('phrase', 2)
|
|
2086
|
+
.remove('phrase', 3);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2068
2089
|
let defaultRegistry = null;
|
|
2090
|
+
let strictIsoRegistry = null;
|
|
2069
2091
|
|
|
2070
2092
|
export function getDefaultRegistry() {
|
|
2071
2093
|
if (defaultRegistry == null) defaultRegistry = createDefaultRegistry();
|
|
2072
2094
|
return defaultRegistry;
|
|
2073
2095
|
}
|
|
2096
|
+
|
|
2097
|
+
export function getStrictIsoRegistry() {
|
|
2098
|
+
if (strictIsoRegistry == null) strictIsoRegistry = createStrictIsoRegistry();
|
|
2099
|
+
return strictIsoRegistry;
|
|
2100
|
+
}
|
package/src/parser.js
CHANGED
|
@@ -139,12 +139,16 @@ function defineParserOperator(state, priority, specifier, name) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export function createParserOperatorState(definitions = [], includeDefaults = true) {
|
|
142
|
+
export function createParserOperatorState(definitions = [], includeDefaults = true, options = {}) {
|
|
143
143
|
const state = {
|
|
144
144
|
infixOperators: includeDefaults ? new Map(INFIX_OPERATORS) : new Map(),
|
|
145
145
|
prefixOperators: includeDefaults ? new Map(PREFIX_OPERATORS) : new Map(),
|
|
146
146
|
postfixOperators: new Map(),
|
|
147
147
|
};
|
|
148
|
+
// The infix ?-/2 form is an EyeProlog quad extension. ISO 13211-1
|
|
149
|
+
// predefines only the 1200 fx ?- operator; strict core mode starts from that
|
|
150
|
+
// table and still permits an explicit op/3 directive to add an infix form.
|
|
151
|
+
if (options.isoStrict === true) state.infixOperators.delete('?-');
|
|
148
152
|
for (const definition of definitions) {
|
|
149
153
|
const [priority, specifier, name] = Array.isArray(definition)
|
|
150
154
|
? definition
|
|
@@ -162,6 +166,7 @@ class Parser {
|
|
|
162
166
|
this.line = 1;
|
|
163
167
|
this.anonymous = 0;
|
|
164
168
|
this.sourceMetadata = options.sourceMetadata !== false;
|
|
169
|
+
this.strictIso = options.isoStrict === true;
|
|
165
170
|
this.parserFlagState = options.parserFlagState ?? {
|
|
166
171
|
doubleQuotes: options.doubleQuotes ?? 'chars',
|
|
167
172
|
};
|
|
@@ -171,6 +176,7 @@ class Parser {
|
|
|
171
176
|
const operatorState = options.operatorState ?? createParserOperatorState(
|
|
172
177
|
options.operatorDefinitions ?? [],
|
|
173
178
|
options.includeDefaultOperators !== false,
|
|
179
|
+
{ isoStrict: this.strictIso },
|
|
174
180
|
);
|
|
175
181
|
this.infixOperators = operatorState.infixOperators;
|
|
176
182
|
this.prefixOperators = operatorState.prefixOperators;
|
|
@@ -685,6 +691,19 @@ class Parser {
|
|
|
685
691
|
while (this.token.type !== TOK.EOF) {
|
|
686
692
|
const line = this.token.line;
|
|
687
693
|
if (this.operatorTokenName() === '?-') {
|
|
694
|
+
if (this.strictIso) {
|
|
695
|
+
// In Part 1, ?- is the predefined 1200 fx operator. A strict-core
|
|
696
|
+
// source therefore reads it as an ordinary term rather than giving
|
|
697
|
+
// it EyeProlog's top-level quad meaning.
|
|
698
|
+
const head = this.parseTerm(0, true);
|
|
699
|
+
this.expect(TOK.DOT, '.');
|
|
700
|
+
this.advance();
|
|
701
|
+
const clause = { head, body: [] };
|
|
702
|
+
clauseNumber++;
|
|
703
|
+
if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauseNumber };
|
|
704
|
+
accept(clause);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
688
707
|
this.advance();
|
|
689
708
|
this.parseQuad(null, line, accept);
|
|
690
709
|
continue;
|
|
@@ -692,13 +711,19 @@ class Parser {
|
|
|
692
711
|
if (this.token.type === TOK.IF) {
|
|
693
712
|
this.advance();
|
|
694
713
|
const directive = this.parseTerm();
|
|
695
|
-
const
|
|
696
|
-
(['dynamic', 'multifile', 'discontiguous', 'initialization', 'include', 'ensure_loaded'
|
|
697
|
-
|
|
698
|
-
(['char_conversion', 'set_prolog_flag', 'module', 'use_module'].includes(directive.name) && directive.arity === 2)
|
|
714
|
+
const coreDirective = directive.type === 'compound' && (
|
|
715
|
+
(['dynamic', 'multifile', 'discontiguous', 'initialization', 'include', 'ensure_loaded'].includes(directive.name) && directive.arity === 1) ||
|
|
716
|
+
(['char_conversion', 'set_prolog_flag'].includes(directive.name) && directive.arity === 2)
|
|
699
717
|
);
|
|
718
|
+
const extensionDirective = directive.type === 'compound' && (
|
|
719
|
+
(['use_module', 'meta_predicate'].includes(directive.name) && directive.arity === 1) ||
|
|
720
|
+
(['module', 'use_module'].includes(directive.name) && directive.arity === 2)
|
|
721
|
+
);
|
|
722
|
+
if (this.strictIso && extensionDirective) {
|
|
723
|
+
throw new Error(`parse line ${line}: implementation-specific directive ${directive.name}/${directive.arity} is not available in strict ISO core mode`);
|
|
724
|
+
}
|
|
700
725
|
const operator = this.applyOperatorDirective(directive, line);
|
|
701
|
-
if (!
|
|
726
|
+
if (!coreDirective && !extensionDirective && !operator) {
|
|
702
727
|
throw new Error(`parse line ${line}: bad term`);
|
|
703
728
|
}
|
|
704
729
|
this.expect(TOK.DOT, '.');
|
|
@@ -716,6 +741,22 @@ class Parser {
|
|
|
716
741
|
}
|
|
717
742
|
let head = this.parseTerm(3);
|
|
718
743
|
if (this.operatorTokenName() === '?-') {
|
|
744
|
+
if (this.strictIso) {
|
|
745
|
+
// There is no predefined infix ?-/2 in strict core mode. If a
|
|
746
|
+
// conforming source explicitly introduced one with op/3, read it as
|
|
747
|
+
// an ordinary operator term rather than as a quad label.
|
|
748
|
+
const info = this.infixOperators.get('?-');
|
|
749
|
+
if (!info) throw new Error(`parse line ${line}: expected ., got ?-`);
|
|
750
|
+
this.advance();
|
|
751
|
+
const right = this.parseTerm(info.associativity === 'right' ? info.precedence : info.precedence + 1, true);
|
|
752
|
+
const clause = { head: compound('?-', [head, right]), body: [] };
|
|
753
|
+
this.expect(TOK.DOT, '.');
|
|
754
|
+
this.advance();
|
|
755
|
+
clauseNumber++;
|
|
756
|
+
if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauseNumber };
|
|
757
|
+
accept(clause);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
719
760
|
this.advance();
|
|
720
761
|
this.parseQuad(head, line, accept);
|
|
721
762
|
continue;
|
package/src/program.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parseClausesInto,
|
|
11
11
|
tryParseClausesFastInto,
|
|
12
12
|
} from './parser.js';
|
|
13
|
-
import { PrologError } from './iso.js';
|
|
13
|
+
import { PrologError, getStrictIsoRegistry } from './iso.js';
|
|
14
14
|
import { currentWorkingDirectory, fs, path } from './platform.js';
|
|
15
15
|
import { standardLibrarySources } from './standard-library.js';
|
|
16
16
|
import { expandDcgRuleClause } from './dcg.js';
|
|
@@ -101,8 +101,12 @@ export class Program {
|
|
|
101
101
|
this.moduleImports = new Map();
|
|
102
102
|
this.moduleMetaPredicates = new Map();
|
|
103
103
|
this.dynamicPredicates = new Set();
|
|
104
|
+
this.strictIso = options.isoStrict === true;
|
|
104
105
|
this.operators = new Map();
|
|
105
|
-
|
|
106
|
+
const predefinedOperatorSets = this.strictIso
|
|
107
|
+
? [ISO_OPERATOR_DEFINITIONS]
|
|
108
|
+
: [ISO_OPERATOR_DEFINITIONS, QUAD_OPERATOR_DEFINITIONS];
|
|
109
|
+
for (const definitions of predefinedOperatorSets) {
|
|
106
110
|
for (const [priority, specifier, name] of definitions) {
|
|
107
111
|
this.defineOperator(priority, specifier, name);
|
|
108
112
|
}
|
|
@@ -162,7 +166,7 @@ export class Program {
|
|
|
162
166
|
}
|
|
163
167
|
_indexClause(clause, initialBuild) {
|
|
164
168
|
const head = clause.head;
|
|
165
|
-
if (!initialBuild) assertHeadIsDefinable(head);
|
|
169
|
+
if (!initialBuild) assertHeadIsDefinable(head, this.strictIso);
|
|
166
170
|
if (head.type !== ATOM && head.type !== COMPOUND) return;
|
|
167
171
|
const module = clause.module ?? 'user';
|
|
168
172
|
const key = modulePredicateKey(module, head.name, head.arity);
|
|
@@ -229,7 +233,7 @@ export class Program {
|
|
|
229
233
|
if (group) group.metaArgumentPositions = positions;
|
|
230
234
|
}
|
|
231
235
|
ensureDynamicGroup(name, arity, module = 'user') {
|
|
232
|
-
assertPredicateIsDefinable(name, arity);
|
|
236
|
+
assertPredicateIsDefinable(name, arity, this.strictIso);
|
|
233
237
|
const key = modulePredicateKey(module, name, arity);
|
|
234
238
|
let group = this.groups.get(key);
|
|
235
239
|
if (!group) {
|
|
@@ -280,7 +284,7 @@ export class Program {
|
|
|
280
284
|
noteMutation(reanalyze = false) {
|
|
281
285
|
this._revisionState.value++;
|
|
282
286
|
this._negationAnalysis = null;
|
|
283
|
-
if (reanalyze) this.markRecursivePredicates();
|
|
287
|
+
if (reanalyze && !this.strictIso) this.markRecursivePredicates();
|
|
284
288
|
}
|
|
285
289
|
markRecursivePredicates() {
|
|
286
290
|
// Recursion analysis drives automatic tabling and is always part of program setup.
|
|
@@ -467,7 +471,7 @@ export class Program {
|
|
|
467
471
|
class ProgramBuilder {
|
|
468
472
|
constructor(options = {}, program = null) {
|
|
469
473
|
this.options = options;
|
|
470
|
-
this.program = program ?? new Program([], { [DEFER_PROGRAM_BUILD]: true });
|
|
474
|
+
this.program = program ?? new Program([], { ...options, [DEFER_PROGRAM_BUILD]: true });
|
|
471
475
|
this.declaredDynamicIndicators = new Map();
|
|
472
476
|
this.lastGroupKey = null;
|
|
473
477
|
this.lastGroup = null;
|
|
@@ -491,7 +495,7 @@ class ProgramBuilder {
|
|
|
491
495
|
program.clauses.push(clause);
|
|
492
496
|
|
|
493
497
|
if (isCompactBinaryClause(clause)) {
|
|
494
|
-
assertPredicateIsDefinable(clause.headName, 2);
|
|
498
|
+
assertPredicateIsDefinable(clause.headName, 2, program.strictIso);
|
|
495
499
|
const module = clause.module ?? 'user';
|
|
496
500
|
const key = modulePredicateKey(module, clause.headName, 2);
|
|
497
501
|
let group = key === lastGroupKey ? lastGroup : program.groups.get(key);
|
|
@@ -512,7 +516,7 @@ class ProgramBuilder {
|
|
|
512
516
|
}
|
|
513
517
|
|
|
514
518
|
if (!isDirectiveClause(clause)) {
|
|
515
|
-
assertHeadIsDefinable(clause.head);
|
|
519
|
+
assertHeadIsDefinable(clause.head, program.strictIso);
|
|
516
520
|
const head = clause.head;
|
|
517
521
|
if (head.type !== ATOM && head.type !== COMPOUND) continue;
|
|
518
522
|
const module = clause.module ?? 'user';
|
|
@@ -546,7 +550,7 @@ class ProgramBuilder {
|
|
|
546
550
|
const program = this.program;
|
|
547
551
|
const module = clause.module ?? 'user';
|
|
548
552
|
for (const indicator of dynamicDirectiveIndicators(clause)) {
|
|
549
|
-
assertDynamicIndicatorIsDefinable(indicator);
|
|
553
|
+
assertDynamicIndicatorIsDefinable(indicator, program.strictIso);
|
|
550
554
|
const key = modulePredicateKey(module, indicator.name, indicator.arity);
|
|
551
555
|
program.dynamicPredicates.add(key);
|
|
552
556
|
this.declaredDynamicIndicators.set(key, { ...indicator, key, module });
|
|
@@ -597,7 +601,11 @@ class ProgramBuilder {
|
|
|
597
601
|
|
|
598
602
|
// Static indexes are built while clauses stream into the builder. Dynamic
|
|
599
603
|
// updates still rebuild only the affected predicate group.
|
|
600
|
-
|
|
604
|
+
// Strict ISO core mode follows ordinary ISO clause selection rather than
|
|
605
|
+
// EyeProlog's automatic recursion guards, numeric recursion shortcuts, or
|
|
606
|
+
// tabled fixed points. Leaving the recursion-planning fields at their
|
|
607
|
+
// neutral defaults preserves the standard depth-first execution model.
|
|
608
|
+
if (!program.strictIso) program.markRecursivePredicates();
|
|
601
609
|
if (this.options.analyzeNegation === true || this.options.strictNegation === true) {
|
|
602
610
|
program.analyzeNegationStratification();
|
|
603
611
|
}
|
|
@@ -629,7 +637,7 @@ function buildProgramFromSources(sources, options) {
|
|
|
629
637
|
function loadSourcesIntoBuilder(builder, sources, options, fast) {
|
|
630
638
|
const ensured = new Set();
|
|
631
639
|
const loadedModules = new Set();
|
|
632
|
-
const operatorState = createParserOperatorState();
|
|
640
|
+
const operatorState = createParserOperatorState([], true, { isoStrict: options.isoStrict === true });
|
|
633
641
|
const parserFlagState = { doubleQuotes: options.doubleQuotes ?? 'chars' };
|
|
634
642
|
const prepared = sources.map((source) => ({
|
|
635
643
|
source,
|
|
@@ -686,7 +694,10 @@ function loadSourceIntoBuilder(builder, source, options, ensured, loadedModules,
|
|
|
686
694
|
builder.addClauses([clause]);
|
|
687
695
|
return;
|
|
688
696
|
}
|
|
689
|
-
|
|
697
|
+
// Grammar-rule expansion belongs to ISO/IEC TS 13211-3 rather than the
|
|
698
|
+
// Part 1 strict-core language. In strict core mode -->/2 remains the
|
|
699
|
+
// ordinary predefined operator term from Table 7 and is not rewritten.
|
|
700
|
+
const grammarClause = builder.program.strictIso ? null : expandDcgRuleClause(clause, context.module);
|
|
690
701
|
if (grammarClause) clause = grammarClause;
|
|
691
702
|
const moduleDeclaration = moduleDirective(clause);
|
|
692
703
|
if (moduleDeclaration) {
|
|
@@ -911,16 +922,23 @@ function operatorDirective(clause) {
|
|
|
911
922
|
};
|
|
912
923
|
}
|
|
913
924
|
|
|
914
|
-
function assertHeadIsDefinable(head) {
|
|
915
|
-
if (head.type === ATOM
|
|
925
|
+
function assertHeadIsDefinable(head, strictIso = false) {
|
|
926
|
+
if (head.type === ATOM || head.type === COMPOUND) {
|
|
927
|
+
assertPredicateIsDefinable(head.name, head.arity, strictIso);
|
|
928
|
+
}
|
|
916
929
|
}
|
|
917
930
|
|
|
918
|
-
function assertDynamicIndicatorIsDefinable(indicator) {
|
|
919
|
-
assertPredicateIsDefinable(indicator.name, indicator.arity);
|
|
931
|
+
function assertDynamicIndicatorIsDefinable(indicator, strictIso = false) {
|
|
932
|
+
assertPredicateIsDefinable(indicator.name, indicator.arity, strictIso);
|
|
920
933
|
}
|
|
921
934
|
|
|
922
|
-
function assertPredicateIsDefinable(name, arity) {
|
|
923
|
-
|
|
935
|
+
function assertPredicateIsDefinable(name, arity, strictIso = false) {
|
|
936
|
+
// false/0 is standardized as a static built-in by Corrigendum 2 and cannot
|
|
937
|
+
// be redefined in either profile. Strict core mode extends the same ISO
|
|
938
|
+
// rule to every Part-1 built-in/control construct; the normal EyeProlog
|
|
939
|
+
// profile keeps its historical source-compatibility behavior.
|
|
940
|
+
if ((name === 'false' && arity === 0) ||
|
|
941
|
+
(strictIso && (getStrictIsoRegistry().get(name, arity) || (name === ',' && arity === 2)))) {
|
|
924
942
|
throw staticProcedureModificationError(name, arity);
|
|
925
943
|
}
|
|
926
944
|
}
|