eyeprolog 1.3.55 → 1.3.57
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/package.json +1 -1
- package/src/iso.js +59 -41
- package/src/program.js +31 -2
- package/src/solver.js +22 -1
- package/test/conformance/ISO-BUILTIN-MODE-ERROR-MATRIX.md +378 -0
- package/test/conformance/ISO-COMPLIANCE.md +73 -20
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/conformance/ISO-MATRIX.md +7 -7
- package/test/conformance/ISO-PROCESSOR-REQUIREMENTS.md +55 -0
- package/test/conformance/README.md +9 -7
- package/test/run-iso-strict.mjs +476 -9
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -393,9 +393,9 @@ function* functorBuiltin({ goal, env }) {
|
|
|
393
393
|
|
|
394
394
|
const name = deref(goal.args[1], env);
|
|
395
395
|
const arityTerm = deref(goal.args[2], env);
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
396
|
+
// Keep EyeProlog's deterministic construction-mode diagnostics aligned with
|
|
397
|
+
// the individual 8.5.1.3 conditions. Under 7.12, a call satisfying several
|
|
398
|
+
// error conditions at once does not acquire a globally mandated table order.
|
|
399
399
|
if (name.type === VAR) throw new PrologError('instantiation_error');
|
|
400
400
|
if (arityTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
401
401
|
if (name.type === COMPOUND) throw new PrologError('type_error(atomic)', name);
|
|
@@ -562,7 +562,9 @@ function* clauseBuiltin({ solver, goal, env }) {
|
|
|
562
562
|
if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
|
|
563
563
|
const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
564
564
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
565
|
-
//
|
|
565
|
+
// EyeProlog selects private-procedure access before Body callability when both
|
|
566
|
+
// conditions hold. ISO 7.12 permits an implementation-dependent choice when
|
|
567
|
+
// more than one error condition is simultaneously satisfied.
|
|
566
568
|
if (isProcessorStaticProcedure(solver, head) ||
|
|
567
569
|
(solver.isoStrict && group && !group.dynamic)) {
|
|
568
570
|
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
@@ -590,12 +592,22 @@ function requireClauseHead(head) {
|
|
|
590
592
|
if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
|
|
591
593
|
}
|
|
592
594
|
|
|
593
|
-
function
|
|
595
|
+
export function convertClauseBodyTerm(term, culprit = term) {
|
|
594
596
|
if (term.type === VAR) return compound('call', [term]);
|
|
595
|
-
|
|
596
|
-
|
|
597
|
+
// ISO 7.6.2 converts each argument of conjunction, disjunction, and if-then
|
|
598
|
+
// recursively when a term is converted to a clause body. This matters for
|
|
599
|
+
// variables (which become call(Var)) and for invalid nested terms, which
|
|
600
|
+
// must be rejected while assert[az]/1 performs the conversion rather than
|
|
601
|
+
// being stored and failing only if that branch is later executed.
|
|
602
|
+
if (term.type === COMPOUND && [',', ';', '->'].includes(term.name) && term.arity === 2) {
|
|
603
|
+
return compound(term.name, [
|
|
604
|
+
convertClauseBodyTerm(term.args[0], culprit),
|
|
605
|
+
convertClauseBodyTerm(term.args[1], culprit),
|
|
606
|
+
]);
|
|
607
|
+
}
|
|
608
|
+
if (term.type !== ATOM && term.type !== COMPOUND) {
|
|
609
|
+
throw new PrologError('type_error(callable)', culprit);
|
|
597
610
|
}
|
|
598
|
-
if (term.type !== ATOM && term.type !== COMPOUND) throw new PrologError('type_error(callable)', term);
|
|
599
611
|
return term;
|
|
600
612
|
}
|
|
601
613
|
|
|
@@ -633,7 +645,7 @@ function assertBuiltin(atStart) {
|
|
|
633
645
|
return function* ({ solver, goal, env }) {
|
|
634
646
|
const parts = clauseParts(goal.args[0], env);
|
|
635
647
|
requireClauseHead(parts.head);
|
|
636
|
-
const body =
|
|
648
|
+
const body = convertClauseBodyTerm(parts.body);
|
|
637
649
|
assertModifiable(solver, parts.head, goal.module ?? 'user');
|
|
638
650
|
const copied = freshCopy(compound('$clause', [parts.head, body]), env);
|
|
639
651
|
solver.program.insertDynamicClause({
|
|
@@ -772,9 +784,9 @@ function* opBuiltin({ solver, goal, env }) {
|
|
|
772
784
|
const priorityTerm = deref(goal.args[0], env);
|
|
773
785
|
const specifierTerm = deref(goal.args[1], env);
|
|
774
786
|
|
|
775
|
-
//
|
|
776
|
-
//
|
|
777
|
-
//
|
|
787
|
+
// Keep EyeProlog's selected 8.14.3.3 overlap order deterministic. Each
|
|
788
|
+
// individual condition remains observable; ISO 7.12 leaves the choice
|
|
789
|
+
// implementation dependent when several error conditions hold at once.
|
|
778
790
|
if (priorityTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
779
791
|
if (specifierTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
780
792
|
const operator = opListPreflight(goal.args[2], env);
|
|
@@ -933,12 +945,11 @@ function optionList(term, env) {
|
|
|
933
945
|
return items.map((item) => deref(item, env));
|
|
934
946
|
}
|
|
935
947
|
|
|
936
|
-
// read_term/3 and write_term/3
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
-
// validation so those predicates can follow the standard order exactly.
|
|
948
|
+
// Keep read_term/3 and write_term/3 diagnostics deterministic and make every
|
|
949
|
+
// 8.14 error condition independently testable. ISO 7.12 leaves the choice
|
|
950
|
+
// implementation dependent when several error conditions hold simultaneously,
|
|
951
|
+
// so this preflight order is an EyeProlog processor choice rather than a claim
|
|
952
|
+
// that the textual order of an error table is globally mandatory.
|
|
942
953
|
function preflightOptionList(term, env) {
|
|
943
954
|
const value = deref(term, env);
|
|
944
955
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
@@ -1007,9 +1018,9 @@ function* openBuiltin({ solver, goal, env }) {
|
|
|
1007
1018
|
const streamTarget = deref(goal.args[2], env);
|
|
1008
1019
|
const optionTerm = goal.arity === 3 ? emptyList() : goal.args[3];
|
|
1009
1020
|
|
|
1010
|
-
// ISO 8.11.5.3 + Corrigenda 2/3.
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1021
|
+
// ISO 8.11.5.3 + Corrigenda 2/3. Keep each required condition observable
|
|
1022
|
+
// on its own. When several conditions hold, the order below is EyeProlog's
|
|
1023
|
+
// deterministic 7.12 processor choice unless a procedural rule constrains it.
|
|
1013
1024
|
if (source.type === VAR) throw new PrologError('instantiation_error');
|
|
1014
1025
|
if (mode.type === VAR) throw new PrologError('instantiation_error');
|
|
1015
1026
|
const preflight = preflightOptionList(optionTerm, env);
|
|
@@ -1173,16 +1184,21 @@ function isStreamPropertyPattern(value) {
|
|
|
1173
1184
|
function* streamPropertyBuiltin({ solver, goal, env }) {
|
|
1174
1185
|
const reference = deref(goal.args[0], env);
|
|
1175
1186
|
const propertyPattern = deref(goal.args[1], env);
|
|
1187
|
+
let fixedStreamId = null;
|
|
1176
1188
|
if (reference.type !== VAR) {
|
|
1177
|
-
|
|
1178
|
-
|
|
1189
|
+
// 8.11.8.1 enumerates (Stream, Property) pairs for currently open
|
|
1190
|
+
// streams. A ground term with the implementation's stream-term shape is
|
|
1191
|
+
// still a stream-term even when its stream is no longer open; in that
|
|
1192
|
+
// case the enumeration simply has no matching pair. domain_error(stream)
|
|
1193
|
+
// is reserved for a term which is not a stream-term at all.
|
|
1194
|
+
fixedStreamId = streamTermReference(goal.args[0], env);
|
|
1179
1195
|
}
|
|
1180
1196
|
if (!isStreamPropertyPattern(propertyPattern)) {
|
|
1181
1197
|
throw new PrologError('domain_error(stream_property)', propertyPattern);
|
|
1182
1198
|
}
|
|
1183
1199
|
const streams = reference.type === VAR
|
|
1184
1200
|
? [...solver.io.streams.values()]
|
|
1185
|
-
: [solver.io.resolve(
|
|
1201
|
+
: [solver.io.resolve(fixedStreamId)].filter(Boolean);
|
|
1186
1202
|
for (const stream of streams) {
|
|
1187
1203
|
for (const property of streamProperties(stream)) {
|
|
1188
1204
|
const next = env.clone();
|
|
@@ -1222,11 +1238,10 @@ function preflightInputUnitTarget(name, target, env, solver) {
|
|
|
1222
1238
|
throw new PrologError('type_error(in_character)', value);
|
|
1223
1239
|
}
|
|
1224
1240
|
} else if (name.endsWith('code')) {
|
|
1225
|
-
//
|
|
1226
|
-
//
|
|
1227
|
-
//
|
|
1228
|
-
//
|
|
1229
|
-
// itself has been inspected below.
|
|
1241
|
+
// Keep the selected 8.12 overlap behavior stable: integer type validation
|
|
1242
|
+
// is done here, while the in-character-code repertoire check is deferred
|
|
1243
|
+
// until after stream/entity diagnostics. ISO 7.12 permits this processor
|
|
1244
|
+
// choice when those error conditions overlap.
|
|
1230
1245
|
if (value.type !== VAR && (value.type !== NUMBER || !isDecimalInteger(value.name))) {
|
|
1231
1246
|
throw new PrologError('type_error(integer)', value);
|
|
1232
1247
|
}
|
|
@@ -1280,10 +1295,9 @@ function inputUnitBuiltin(name) {
|
|
|
1280
1295
|
if (!binary && unit != null && solver.isoStrict && !isStrictIsoPcsCharacter(unit)) {
|
|
1281
1296
|
throw new PrologError('representation_error(character)');
|
|
1282
1297
|
}
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1285
|
-
//
|
|
1286
|
-
// output-stream, binary-stream, past-EOF, or invalid-character error.
|
|
1298
|
+
// EyeProlog deliberately defers this representation check until after
|
|
1299
|
+
// stream and input-entity diagnostics. ISO 7.12 permits that deterministic
|
|
1300
|
+
// choice when several 8.12 error conditions hold simultaneously.
|
|
1287
1301
|
const targetValue = deref(target, env);
|
|
1288
1302
|
if (name.endsWith('code') && targetValue.type !== VAR &&
|
|
1289
1303
|
!validStrictInputCharacterCode(targetValue, solver)) {
|
|
@@ -1586,9 +1600,9 @@ function* readTermBuiltin({ solver, goal, env }) {
|
|
|
1586
1600
|
const optionTerm = goal.args[goal.arity - 1];
|
|
1587
1601
|
const streamTerm = goal.arity === 3 ? deref(goal.args[0], env) : null;
|
|
1588
1602
|
|
|
1589
|
-
//
|
|
1590
|
-
//
|
|
1591
|
-
//
|
|
1603
|
+
// EyeProlog's selected 8.14.1.3 overlap order. Each listed error condition
|
|
1604
|
+
// is covered independently; 7.12 makes simultaneous-error selection
|
|
1605
|
+
// implementation dependent unless another procedural requirement constrains it.
|
|
1592
1606
|
if (streamTerm?.type === VAR) throw new PrologError('instantiation_error');
|
|
1593
1607
|
const preflight = preflightOptionList(optionTerm, env);
|
|
1594
1608
|
if (goal.arity === 3) streamReference(goal.args[0], env);
|
|
@@ -1702,9 +1716,9 @@ function* writeTermBuiltin({ solver, goal, env }) {
|
|
|
1702
1716
|
const optionTerm = goal.args[goal.arity - 1];
|
|
1703
1717
|
const streamTerm = goal.arity === 3 ? deref(goal.args[0], env) : null;
|
|
1704
1718
|
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
//
|
|
1719
|
+
// EyeProlog's selected 8.14.2.3 overlap order. Each listed error condition
|
|
1720
|
+
// is covered independently; 7.12 makes simultaneous-error selection
|
|
1721
|
+
// implementation dependent unless another procedural requirement constrains it.
|
|
1708
1722
|
if (streamTerm?.type === VAR) throw new PrologError('instantiation_error');
|
|
1709
1723
|
const preflight = preflightOptionList(optionTerm, env);
|
|
1710
1724
|
const optionItems = requireProperOptionList(preflight);
|
|
@@ -2177,7 +2191,9 @@ function flatFindallTemplate(template) {
|
|
|
2177
2191
|
|
|
2178
2192
|
function* findallBuiltin({ solver, goal, env }) {
|
|
2179
2193
|
const [template, innerGoal, bag] = goal.args;
|
|
2180
|
-
//
|
|
2194
|
+
// EyeProlog validates Goal before Instances when both are erroneous. ISO 7.12
|
|
2195
|
+
// makes simultaneous-error selection implementation dependent; this order is
|
|
2196
|
+
// a documented/tested processor choice, not a blanket table-order rule.
|
|
2181
2197
|
const invoked = callable(innerGoal, env);
|
|
2182
2198
|
assertListOrPartial(bag, env);
|
|
2183
2199
|
const collector = solver.cloneForInnerGoal(10000000);
|
|
@@ -2242,7 +2258,9 @@ function sortedUnique(items) {
|
|
|
2242
2258
|
|
|
2243
2259
|
function allSolutionsBuiltin(asSet) {
|
|
2244
2260
|
return function* ({ solver, goal, env }) {
|
|
2245
|
-
//
|
|
2261
|
+
// EyeProlog validates the iterated goal before Instances. ISO 7.12 makes
|
|
2262
|
+
// simultaneous-error selection implementation dependent; keep this stable as
|
|
2263
|
+
// a processor choice rather than describing it as mandatory table ordering.
|
|
2246
2264
|
const { iterated, quantified } = bagGoalParts(goal.args[1], env);
|
|
2247
2265
|
assertListOrPartial(goal.args[2], env);
|
|
2248
2266
|
const free = freeVariables(iterated, goal.args[0], quantified, env);
|
package/src/program.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Program representation and clause indexing.
|
|
2
2
|
// Indexes are deliberately conservative: they speed up common scalar arguments but never replace unification as the final check.
|
|
3
|
-
import { ATOM, COMPOUND, VAR, Env, atom, compound, deref, flattenConjunction, isScalar, numberTerm, properListItems, termToString, variable } from './term.js';
|
|
3
|
+
import { ATOM, COMPOUND, STRING, VAR, Env, atom, compound, deref, flattenConjunction, isScalar, numberTerm, properListItems, termToString, variable } from './term.js';
|
|
4
4
|
import { formatTermForWrite } from './write.js';
|
|
5
5
|
import {
|
|
6
6
|
ISO_OPERATOR_DEFINITIONS,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
parseGoalText,
|
|
13
13
|
tryParseClausesFastInto,
|
|
14
14
|
} from './parser.js';
|
|
15
|
-
import { PrologError, getStrictIsoRegistry } from './iso.js';
|
|
15
|
+
import { PrologError, convertClauseBodyTerm, getStrictIsoRegistry } from './iso.js';
|
|
16
16
|
import { currentWorkingDirectory, fs, path } from './platform.js';
|
|
17
17
|
import {
|
|
18
18
|
standardLibrarySources,
|
|
@@ -33,6 +33,21 @@ import {
|
|
|
33
33
|
const DEFER_PROGRAM_BUILD = Symbol('deferProgramBuild');
|
|
34
34
|
const FAST_PARSE_ABORT = Symbol('fastParseAbort');
|
|
35
35
|
const PROGRAM_BUILD_BATCH_SIZE = 16384;
|
|
36
|
+
|
|
37
|
+
function termContainsStringExtension(term, seen = new Set()) {
|
|
38
|
+
if (term == null || typeof term !== 'object' || seen.has(term)) return false;
|
|
39
|
+
seen.add(term);
|
|
40
|
+
if (term.type === STRING) return true;
|
|
41
|
+
if (term.type === COMPOUND) return term.args.some((arg) => termContainsStringExtension(arg, seen));
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function clauseContainsStringExtension(clause) {
|
|
46
|
+
if (clause == null) return false;
|
|
47
|
+
if (clause.head0Type === STRING || clause.head1Type === STRING) return true;
|
|
48
|
+
if (termContainsStringExtension(clause.head) || termContainsStringExtension(clause.query)) return true;
|
|
49
|
+
return Array.isArray(clause.body) && clause.body.some((goal) => termContainsStringExtension(goal));
|
|
50
|
+
}
|
|
36
51
|
export class Program {
|
|
37
52
|
constructor(clauses = [], options = {}) {
|
|
38
53
|
this.clauses = [];
|
|
@@ -574,6 +589,12 @@ class ProgramBuilder {
|
|
|
574
589
|
let lastGroup = this.lastGroup;
|
|
575
590
|
|
|
576
591
|
for (const clause of clauses) {
|
|
592
|
+
if (program.strictIso && clauseContainsStringExtension(clause)) {
|
|
593
|
+
// Programmatic clauses are part of the public embedding surface. The
|
|
594
|
+
// normal profile permits its host string term extension, but the Part 1
|
|
595
|
+
// strict profile must reject that implementation-specific sixth type.
|
|
596
|
+
throw new PrologError('representation_error(term)');
|
|
597
|
+
}
|
|
577
598
|
if (clause?.kind === 'quad') {
|
|
578
599
|
const module = clause.module ?? 'user';
|
|
579
600
|
annotateGoalModule(clause.query, module);
|
|
@@ -606,6 +627,14 @@ class ProgramBuilder {
|
|
|
606
627
|
}
|
|
607
628
|
|
|
608
629
|
if (!isDirectiveClause(clause)) {
|
|
630
|
+
if (program.strictIso && Array.isArray(clause.body) && clause.body.length > 0) {
|
|
631
|
+
// ISO 7.6.2 applies when source text is prepared as well as when a
|
|
632
|
+
// clause term is asserted dynamically. Keep the original variable
|
|
633
|
+
// objects while wrapping variable goals in call/1 so sharing with
|
|
634
|
+
// the head (and recursively through ;/2 and ->/2) is preserved for
|
|
635
|
+
// later clause/2 and retract/1 conversion back to terms.
|
|
636
|
+
clause.body = clause.body.map((goal) => convertClauseBodyTerm(goal));
|
|
637
|
+
}
|
|
609
638
|
assertHeadIsDefinable(clause.head, program.strictIso);
|
|
610
639
|
const head = clause.head;
|
|
611
640
|
if (head.type !== ATOM && head.type !== COMPOUND) continue;
|
package/src/solver.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Depth-first EyeProlog solver with builtin dispatch, memoization, and guarded recursion handling.
|
|
2
2
|
// Most semantic decisions still flow through unification; optimizations only select candidates earlier.
|
|
3
3
|
import {
|
|
4
|
-
ATOM, COMPOUND, NUMBER, VAR, Env, compactListLength, compactVariableList, compound, cons, copyResolved, deref, emptyList,
|
|
4
|
+
ATOM, COMPOUND, NUMBER, STRING, VAR, Env, compactListLength, compactVariableList, compound, cons, copyResolved, deref, emptyList,
|
|
5
5
|
flattenConjunction, freshTerm, isCons, isDecimalInteger, isEmptyList,
|
|
6
6
|
numberTerm, numberTextFromDouble, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
@@ -45,6 +45,26 @@ function raiseOccursCheckError(left, right, env) {
|
|
|
45
45
|
throw error;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
|
|
49
|
+
function rejectStrictIsoStringTerms(terms, env) {
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
const visit = (term) => {
|
|
52
|
+
const resolved = deref(term, env);
|
|
53
|
+
if (resolved == null || seen.has(resolved)) return;
|
|
54
|
+
seen.add(resolved);
|
|
55
|
+
if (resolved.type === STRING) {
|
|
56
|
+
// `stringTerm()` is a normal-profile host/API term extension. It has no
|
|
57
|
+
// Part 1 abstract/token syntax, so strict execution must reject it rather
|
|
58
|
+
// than silently introducing a sixth term type through the embedding API.
|
|
59
|
+
throw new PrologError('representation_error(term)');
|
|
60
|
+
}
|
|
61
|
+
if (resolved.type === COMPOUND) {
|
|
62
|
+
for (const arg of resolved.args) visit(arg);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
for (const term of terms) visit(term);
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
export class Solver {
|
|
49
69
|
constructor(program, options = {}) {
|
|
50
70
|
this.program = program;
|
|
@@ -301,6 +321,7 @@ export class Solver {
|
|
|
301
321
|
*solve(goals, env = new Env(), depth = 0) {
|
|
302
322
|
if (!Array.isArray(goals)) goals = [goals];
|
|
303
323
|
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
324
|
+
if (this.isoStrict) rejectStrictIsoStringTerms(goals, env);
|
|
304
325
|
|
|
305
326
|
const writeVariableState = this.writeVariableState;
|
|
306
327
|
if (writeVariableState.depth === 0) {
|