eyeprolog 1.3.47 → 1.3.49
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-arithmetic.js +80 -4
- package/src/iso.js +74 -24
- package/test/conformance/ISO-COMPLIANCE.md +22 -6
- package/test/conformance/ISO-MATRIX.md +6 -6
- package/test/run-iso-strict.mjs +78 -0
package/package.json
CHANGED
package/src/iso-arithmetic.js
CHANGED
|
@@ -20,10 +20,57 @@ function evaluate(term, env, options = {}) {
|
|
|
20
20
|
throw new PrologError('type_error(evaluable)', compound('/', [atom(term.name), numberTerm(0)]));
|
|
21
21
|
}
|
|
22
22
|
if (term.type !== COMPOUND) throw new PrologError('type_error(evaluable)', term);
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
let args;
|
|
25
|
+
if (options.isoStrict === true) {
|
|
26
|
+
// ISO 7.9.2(b) gives a direct variable argument precedence over the other
|
|
27
|
+
// expression errors of the same compound expression. Check the direct
|
|
28
|
+
// arguments before selecting an implementation-dependent evaluation order.
|
|
29
|
+
const operands = term.args.map((arg) => deref(arg, env));
|
|
30
|
+
if (operands.some((arg) => arg.type === VAR)) throw new PrologError('instantiation_error');
|
|
31
|
+
args = operands.map((arg) => evaluateOperand(term, arg, env, options));
|
|
32
|
+
} else {
|
|
33
|
+
// Preserve the normal EyeProlog profile's established left-to-right
|
|
34
|
+
// evaluator and diagnostics; the stricter prescribed errors belong to
|
|
35
|
+
// the explicit ISO profile.
|
|
36
|
+
args = term.args.map((arg) => evaluate(arg, env, options));
|
|
37
|
+
}
|
|
38
|
+
return evaluateOperation(term, args, options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function evaluatesAtomDirectly(term, options) {
|
|
42
|
+
return term.type === ATOM && (term.name === 'pi' || (term.name === 'e' && options.isoStrict !== true));
|
|
25
43
|
}
|
|
26
|
-
|
|
44
|
+
|
|
45
|
+
function directOperandType(term) {
|
|
46
|
+
const name = term.name;
|
|
47
|
+
const arity = term.arity;
|
|
48
|
+
if ((arity === 1 && name === '\\') ||
|
|
49
|
+
(arity === 2 && ['>>', '<<', '/\\', '\\/', 'xor'].includes(name))) return 'integer';
|
|
50
|
+
|
|
51
|
+
if ((arity === 1 && ['+', '-', 'abs', 'sign', 'float', 'truncate', 'round', 'ceiling', 'floor',
|
|
52
|
+
'float_integer_part', 'float_fractional_part', 'sin', 'cos', 'atan', 'asin', 'acos', 'tan',
|
|
53
|
+
'exp', 'log', 'sqrt'].includes(name)) ||
|
|
54
|
+
(arity === 2 && ['+', '-', '*', '/', '//', 'div', 'mod', 'rem', '**', '^', 'max', 'min', 'atan2'].includes(name))) {
|
|
55
|
+
return 'number';
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function evaluateOperand(parent, operand, env, options) {
|
|
61
|
+
// The arithmetic-functor error clauses use the offending operand as the
|
|
62
|
+
// culprit when an atomic operand is not numeric (e.g. +(foo,77),
|
|
63
|
+
// truncate(foo), and the bitwise families). A nested compound still goes
|
|
64
|
+
// through ordinary expression evaluation so an unknown nested F/N retains
|
|
65
|
+
// type_error(evaluable,F/N).
|
|
66
|
+
if (operand.type === ATOM && !evaluatesAtomDirectly(operand, options)) {
|
|
67
|
+
const expected = directOperandType(parent);
|
|
68
|
+
if (expected) throw new PrologError(`type_error(${expected})`, operand);
|
|
69
|
+
}
|
|
70
|
+
return evaluate(operand, env, options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function evaluateOperation(term, args, options = {}) {
|
|
27
74
|
const name = term.name;
|
|
28
75
|
const arity = term.arity;
|
|
29
76
|
if (arity === 1 && (name === '+' || name === '-')) {
|
|
@@ -38,6 +85,12 @@ function evaluateOperation(term, args) {
|
|
|
38
85
|
if (arity === 1 && ['abs', 'sign', 'float', 'truncate', 'round', 'ceiling', 'floor',
|
|
39
86
|
'float_integer_part', 'float_fractional_part',
|
|
40
87
|
'sin', 'cos', 'atan', 'asin', 'acos', 'tan', 'exp', 'log', 'sqrt'].includes(name)) {
|
|
88
|
+
// The four float-to-integer rounding functors have only F->I operators
|
|
89
|
+
// (9.1.1/9.1.6). Corrigendum 1/2 therefore requires type_error(float,...)
|
|
90
|
+
// when their evaluated argument has integer type.
|
|
91
|
+
if (options.isoStrict === true && ['truncate', 'round', 'ceiling', 'floor'].includes(name) && args[0].integer) {
|
|
92
|
+
throw new PrologError('type_error(float)', numericTerm(args[0]));
|
|
93
|
+
}
|
|
41
94
|
const a = Number(args[0].value);
|
|
42
95
|
if (name === 'abs' && args[0].integer) return { integer: true, value: args[0].value < 0n ? -args[0].value : args[0].value };
|
|
43
96
|
if (name === 'sign' && args[0].integer) return { integer: true, value: args[0].value < 0n ? -1n : args[0].value > 0n ? 1n : 0n };
|
|
@@ -105,6 +158,11 @@ function evaluateOperation(term, args) {
|
|
|
105
158
|
if (x === 0 && y === 0) throw new PrologError('evaluation_error(undefined)');
|
|
106
159
|
value = Math.atan2(x, y);
|
|
107
160
|
}
|
|
161
|
+
else if ((name === '**' || name === '^') && x === 0 && y < 0) {
|
|
162
|
+
// 9.3.1.3(d) and Cor.2 9.3.10.3(d): zero to a negative power is
|
|
163
|
+
// undefined, not floating-point overflow.
|
|
164
|
+
throw new PrologError('evaluation_error(undefined)');
|
|
165
|
+
}
|
|
108
166
|
else if (name === '+') value = x + y;
|
|
109
167
|
else if (name === '-') value = x - y;
|
|
110
168
|
else if (name === '*') value = x * y;
|
|
@@ -152,6 +210,22 @@ export function compareArithmeticValues(left, right) {
|
|
|
152
210
|
if (right.integer) return -compareIntegerToFloat(b, a);
|
|
153
211
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
154
212
|
}
|
|
213
|
+
|
|
214
|
+
function compareStrictIsoArithmeticValues(left, right) {
|
|
215
|
+
if (left.integer === right.integer) return compareArithmeticValues(left, right);
|
|
216
|
+
|
|
217
|
+
// ISO/IEC 13211-1:1995 8.7 specifies mixed arithmetic comparisons by
|
|
218
|
+
// floatI->F conversion of the integer operand. That conversion can round
|
|
219
|
+
// an otherwise exact integer and can itself overflow. Normal EyeProlog
|
|
220
|
+
// deliberately keeps its later exact cross-type comparison extension; the
|
|
221
|
+
// strict profile follows the Part 1 operation table.
|
|
222
|
+
const leftValue = left.integer ? Number(left.value) : left.value;
|
|
223
|
+
const rightValue = right.integer ? Number(right.value) : right.value;
|
|
224
|
+
if (!Number.isFinite(leftValue) || !Number.isFinite(rightValue)) {
|
|
225
|
+
throw new PrologError('evaluation_error(float_overflow)');
|
|
226
|
+
}
|
|
227
|
+
return leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0;
|
|
228
|
+
}
|
|
155
229
|
export function* isBuiltin({ solver, goal, env }) {
|
|
156
230
|
const result = arithmeticValueTerm(evaluateArithmetic(goal.args[1], env, { isoStrict: solver?.isoStrict === true }));
|
|
157
231
|
const next = env.clone();
|
|
@@ -162,7 +236,9 @@ export function arithmeticComparison(test) {
|
|
|
162
236
|
const options = { isoStrict: solver?.isoStrict === true };
|
|
163
237
|
const left = evaluateArithmetic(goal.args[0], env, options);
|
|
164
238
|
const right = evaluateArithmetic(goal.args[1], env, options);
|
|
165
|
-
const cmp =
|
|
239
|
+
const cmp = solver?.isoStrict === true
|
|
240
|
+
? compareStrictIsoArithmeticValues(left, right)
|
|
241
|
+
: compareArithmeticValues(left, right);
|
|
166
242
|
if (test(cmp)) yield env;
|
|
167
243
|
};
|
|
168
244
|
}
|
package/src/iso.js
CHANGED
|
@@ -415,11 +415,19 @@ function* functorBuiltin({ goal, env }) {
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
function* argBuiltin({ goal, env }) {
|
|
418
|
-
const
|
|
418
|
+
const indexTerm = deref(goal.args[0], env);
|
|
419
419
|
const term = deref(goal.args[1], env);
|
|
420
|
+
// ISO 8.5.2.3 prescribes both instantiation checks before either type
|
|
421
|
+
// check. In particular arg(a, X, _) is an instantiation error because
|
|
422
|
+
// the second argument is still a variable.
|
|
423
|
+
if (indexTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
420
424
|
if (term.type === VAR) throw new PrologError('instantiation_error');
|
|
425
|
+
if (indexTerm.type !== NUMBER || !isDecimalInteger(indexTerm.name)) {
|
|
426
|
+
throw new PrologError('type_error(integer)', indexTerm);
|
|
427
|
+
}
|
|
421
428
|
if (term.type !== COMPOUND) throw new PrologError('type_error(compound)', term);
|
|
422
|
-
|
|
429
|
+
const index = BigInt(indexTerm.name);
|
|
430
|
+
if (index < 0n) throw new PrologError('domain_error(not_less_than_zero)', indexTerm);
|
|
423
431
|
if (index === 0n || index > BigInt(term.arity)) return;
|
|
424
432
|
const next = env.clone();
|
|
425
433
|
if (unify(goal.args[2], term.args[Number(index) - 1], next)) yield next;
|
|
@@ -537,7 +545,7 @@ function* clauseBuiltin({ solver, goal, env }) {
|
|
|
537
545
|
const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
538
546
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
539
547
|
// ISO 8.8.1.3 places private-procedure access before Body callability.
|
|
540
|
-
if (
|
|
548
|
+
if (isProcessorStaticProcedure(solver, head) ||
|
|
541
549
|
(solver.isoStrict && group && !group.dynamic)) {
|
|
542
550
|
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
543
551
|
}
|
|
@@ -581,9 +589,19 @@ function isGrammarRuleProcedure(solver, head) {
|
|
|
581
589
|
return !solver.isoStrict && head.name === '-->' && head.arity === 2;
|
|
582
590
|
}
|
|
583
591
|
|
|
592
|
+
function isProcessorStaticProcedure(solver, head) {
|
|
593
|
+
// Conjunction is an ISO control construct (7.5/Table 9) but is executed
|
|
594
|
+
// directly by the solver rather than through the builtin registry. Keep it
|
|
595
|
+
// in the same protected static/private family as the registered Part 1
|
|
596
|
+
// control constructs for runtime database operations.
|
|
597
|
+
return Boolean(solver.registry.get(head.name, head.arity)) ||
|
|
598
|
+
isGrammarRuleProcedure(solver, head) ||
|
|
599
|
+
(solver.isoStrict && head.name === ',' && head.arity === 2);
|
|
600
|
+
}
|
|
601
|
+
|
|
584
602
|
function assertModifiable(solver, head, module = 'user') {
|
|
585
603
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? module);
|
|
586
|
-
if (
|
|
604
|
+
if (isProcessorStaticProcedure(solver, head) || (group && !group.dynamic)) {
|
|
587
605
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
|
|
588
606
|
}
|
|
589
607
|
}
|
|
@@ -610,7 +628,7 @@ function* retractBuiltin({ solver, goal, env }) {
|
|
|
610
628
|
const parts = clauseParts(goal.args[0], env);
|
|
611
629
|
requireClauseHead(parts.head);
|
|
612
630
|
const group = solver.program.findGroup(parts.head.name, parts.head.arity, parts.head.module ?? goal.module ?? 'user');
|
|
613
|
-
if (
|
|
631
|
+
if (isProcessorStaticProcedure(solver, parts.head) || (group && !group.dynamic)) {
|
|
614
632
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(parts.head));
|
|
615
633
|
}
|
|
616
634
|
if (!group) return;
|
|
@@ -633,7 +651,7 @@ function* retractAllBuiltin({ solver, goal, env }) {
|
|
|
633
651
|
const head = deref(goal.args[0], env);
|
|
634
652
|
requireClauseHead(head);
|
|
635
653
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
636
|
-
if (
|
|
654
|
+
if (isProcessorStaticProcedure(solver, head) || (group && !group.dynamic)) {
|
|
637
655
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
|
|
638
656
|
}
|
|
639
657
|
if (group) {
|
|
@@ -667,7 +685,7 @@ function* abolishBuiltin({ solver, goal, env }) {
|
|
|
667
685
|
const target = predicateIndicatorParts(goal.args[0], env);
|
|
668
686
|
const module = goal.module ?? 'user';
|
|
669
687
|
const group = solver.program.findGroup(target.name, target.arity, module);
|
|
670
|
-
if (
|
|
688
|
+
if (isProcessorStaticProcedure(solver, target) || (group && !group.dynamic)) {
|
|
671
689
|
throw new PrologError('permission_error(modify, static_procedure)', target.indicator);
|
|
672
690
|
}
|
|
673
691
|
solver.program.abolishDynamicGroup(target.name, target.arity, module);
|
|
@@ -815,8 +833,14 @@ function conversionCharacter(term, env, current = false, solver = null) {
|
|
|
815
833
|
return value;
|
|
816
834
|
}
|
|
817
835
|
function* charConversionBuiltin({ solver, goal, env }) {
|
|
818
|
-
|
|
819
|
-
|
|
836
|
+
// ISO 8.14.5.3 checks both arguments for instantiation before testing
|
|
837
|
+
// either character representation.
|
|
838
|
+
const inputTerm = deref(goal.args[0], env);
|
|
839
|
+
const outputTerm = deref(goal.args[1], env);
|
|
840
|
+
if (inputTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
841
|
+
if (outputTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
842
|
+
const input = conversionCharacter(inputTerm, env, false, solver);
|
|
843
|
+
const output = conversionCharacter(outputTerm, env, false, solver);
|
|
820
844
|
if (input.name === output.name) solver.charConversions.delete(input.name);
|
|
821
845
|
else solver.charConversions.set(input.name, output.name);
|
|
822
846
|
yield env;
|
|
@@ -970,7 +994,7 @@ function* openBuiltin({ solver, goal, env }) {
|
|
|
970
994
|
if (!['read', 'write', 'append'].includes(mode.name)) throw new PrologError('domain_error(io_mode)', mode);
|
|
971
995
|
const options = goal.arity === 3 ? {} : openOptions(optionTerm, env);
|
|
972
996
|
if (options.alias && solver.io.resolve(options.alias)) {
|
|
973
|
-
throw new PrologError('permission_error(open, source_sink)', atom(options.alias));
|
|
997
|
+
throw new PrologError('permission_error(open, source_sink)', compound('alias', [atom(options.alias)]));
|
|
974
998
|
}
|
|
975
999
|
let stream;
|
|
976
1000
|
try {
|
|
@@ -1689,11 +1713,16 @@ function* atomLengthBuiltin({ goal, env }) {
|
|
|
1689
1713
|
}
|
|
1690
1714
|
|
|
1691
1715
|
function* atomConcatBuiltin({ goal, env }) {
|
|
1692
|
-
const first =
|
|
1693
|
-
const second =
|
|
1694
|
-
const whole =
|
|
1716
|
+
const first = deref(goal.args[0], env);
|
|
1717
|
+
const second = deref(goal.args[1], env);
|
|
1718
|
+
const whole = deref(goal.args[2], env);
|
|
1719
|
+
// ISO 8.16.2.3 puts the two under-instantiation modes ahead of all atom
|
|
1720
|
+
// type diagnostics.
|
|
1695
1721
|
if (first.type === VAR && whole.type === VAR) throw new PrologError('instantiation_error');
|
|
1696
1722
|
if (second.type === VAR && whole.type === VAR) throw new PrologError('instantiation_error');
|
|
1723
|
+
if (first.type !== VAR && first.type !== ATOM) throw new PrologError('type_error(atom)', first);
|
|
1724
|
+
if (second.type !== VAR && second.type !== ATOM) throw new PrologError('type_error(atom)', second);
|
|
1725
|
+
if (whole.type !== VAR && whole.type !== ATOM) throw new PrologError('type_error(atom)', whole);
|
|
1697
1726
|
|
|
1698
1727
|
const candidates = [];
|
|
1699
1728
|
if (whole.type === ATOM && first.type === VAR && second.type === VAR) {
|
|
@@ -1713,23 +1742,33 @@ function* atomConcatBuiltin({ goal, env }) {
|
|
|
1713
1742
|
}
|
|
1714
1743
|
}
|
|
1715
1744
|
|
|
1716
|
-
function
|
|
1745
|
+
function optionalInteger(term, env) {
|
|
1717
1746
|
const value = deref(term, env);
|
|
1718
1747
|
if (value.type === VAR) return null;
|
|
1719
1748
|
if (value.type !== NUMBER || !isDecimalInteger(value.name)) throw new PrologError('type_error(integer)', value);
|
|
1720
|
-
|
|
1721
|
-
if (integer < 0n) throw new PrologError('domain_error(not_less_than_zero)', value);
|
|
1722
|
-
return integer;
|
|
1749
|
+
return BigInt(value.name);
|
|
1723
1750
|
}
|
|
1724
1751
|
|
|
1725
1752
|
function* subAtomBuiltin({ goal, env }) {
|
|
1726
1753
|
const source = deref(goal.args[0], env);
|
|
1727
1754
|
if (source.type === VAR) throw new PrologError('instantiation_error');
|
|
1728
1755
|
if (source.type !== ATOM) throw new PrologError('type_error(atom)', source);
|
|
1729
|
-
const sub =
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1756
|
+
const sub = deref(goal.args[4], env);
|
|
1757
|
+
if (sub.type !== VAR && sub.type !== ATOM) throw new PrologError('type_error(atom)', sub);
|
|
1758
|
+
// ISO 8.16.3.3 prescribes all type checks before the non-negative domain
|
|
1759
|
+
// checks, in Before/Length/After order.
|
|
1760
|
+
const before = optionalInteger(goal.args[1], env);
|
|
1761
|
+
const length = optionalInteger(goal.args[2], env);
|
|
1762
|
+
const after = optionalInteger(goal.args[3], env);
|
|
1763
|
+
if (before != null && before < 0n) {
|
|
1764
|
+
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[1], env));
|
|
1765
|
+
}
|
|
1766
|
+
if (length != null && length < 0n) {
|
|
1767
|
+
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[2], env));
|
|
1768
|
+
}
|
|
1769
|
+
if (after != null && after < 0n) {
|
|
1770
|
+
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[3], env));
|
|
1771
|
+
}
|
|
1733
1772
|
const chars = characters(source.name);
|
|
1734
1773
|
for (let start = 0; start <= chars.length; start++) {
|
|
1735
1774
|
if (before != null && before !== BigInt(start)) continue;
|
|
@@ -1770,9 +1809,14 @@ function validCharacterCode(value, solver = null) {
|
|
|
1770
1809
|
}
|
|
1771
1810
|
|
|
1772
1811
|
function listToAtomInput(list, env, kind, solver = null) {
|
|
1812
|
+
const whole = deref(list, env);
|
|
1773
1813
|
const { items, tail } = listElements(list, env);
|
|
1774
|
-
|
|
1775
|
-
|
|
1814
|
+
// Corrigendum 2 distinguishes a partial list from an improper one: only
|
|
1815
|
+
// the former precedes the complete-list type check. Variables in a proper
|
|
1816
|
+
// list prefix are checked after that list-shape diagnostic.
|
|
1817
|
+
if (tail.type === VAR) throw new PrologError('instantiation_error');
|
|
1818
|
+
if (tail.type !== ATOM || tail.name !== '[]') throw new PrologError('type_error(list)', whole);
|
|
1819
|
+
if (items.some((item) => item.type === VAR)) throw new PrologError('instantiation_error');
|
|
1776
1820
|
if (kind === 'chars') {
|
|
1777
1821
|
const invalid = items.find((item) => !oneChar(item));
|
|
1778
1822
|
if (invalid) throw new PrologError('type_error(character)', invalid);
|
|
@@ -1968,7 +2012,14 @@ function numberListText(list, env, kind, valueIsBound, solver = null) {
|
|
|
1968
2012
|
const whole = deref(list, env);
|
|
1969
2013
|
const { items, tail } = listElements(list, env);
|
|
1970
2014
|
const proper = tail.type === ATOM && tail.name === '[]';
|
|
2015
|
+
// Corrigendum 2 orders conversion-mode instantiation errors specially:
|
|
2016
|
+
// a partial list is diagnosed before its fixed prefix, while an improper
|
|
2017
|
+
// non-list is diagnosed before a variable element in that prefix.
|
|
2018
|
+
if (!valueIsBound && tail.type === VAR) throw new PrologError('instantiation_error');
|
|
1971
2019
|
if (tail.type !== VAR && !proper) throw new PrologError('type_error(list)', whole);
|
|
2020
|
+
if (!valueIsBound && items.some((item) => item.type === VAR)) {
|
|
2021
|
+
throw new PrologError('instantiation_error');
|
|
2022
|
+
}
|
|
1972
2023
|
|
|
1973
2024
|
const invalid = items.find((item) => item.type !== VAR &&
|
|
1974
2025
|
(kind === 'chars' ? !oneChar(item) : !validCharacterCode(item, solver)));
|
|
@@ -1981,7 +2032,6 @@ function numberListText(list, env, kind, valueIsBound, solver = null) {
|
|
|
1981
2032
|
}
|
|
1982
2033
|
|
|
1983
2034
|
const hasVariable = tail.type === VAR || items.some((item) => item.type === VAR);
|
|
1984
|
-
if (!valueIsBound && hasVariable) throw new PrologError('instantiation_error');
|
|
1985
2035
|
if (hasVariable) return null;
|
|
1986
2036
|
return items.map((item) => kind === 'chars'
|
|
1987
2037
|
? item.name
|
|
@@ -33,15 +33,15 @@ error-ordering alternative to an individual executable assertion.
|
|
|
33
33
|
| Clause 6 — tokens, terms, lists, operators, quoted text | audit | Complete vendored WG17 syntax matrix, `lexical_and_curly_terms`, `scryer_lexical_terms`, operator suites, syntax-error cases, quoted-layout/escape error cases, writer/read-back regressions, and strict ASCII PCS/collation boundary tests. The implementation-defined 6.5/6.6 character-model decisions are now closed; wider shall-by-shall lexical mapping remains open. |
|
|
34
34
|
| 7.1-7.3 — term types, term order, unification | audit | Standard-order, identity, finite-tree and occurs-check suites, Corrigendum 2 term predicates, plus strict checks for the required `variable < float < integer < atom < compound` type order, PCS-based atom collation, and the finite `max_arity` representation boundary used by term construction. |
|
|
35
35
|
| 7.4 — Prolog text and directives | audit | All Part 1 directive indicators are parsed; include/ensure-loaded/operator/flag/character-conversion behavior has executable coverage. Preparation-time `char_conversion/2` affects later unquoted source text and respects `char_conversion=off`. Strict preparation now enforces declaration-before-clause ordering for `dynamic/1`, `multifile/1`, and `discontiguous/1`, cross-text `multifile/1`, discontiguous clause grouping, empty declared procedures, include textual-replacement behavior, and one-time initialization per prepared program. The remaining shall-by-shall mapping is still open. |
|
|
36
|
-
| 7.5-7.6 — database and term/clause conversion | audit | Dynamic database and logical-update-view suites. Strict mode restores Part 1 private-static/public-dynamic `clause/2` access; focused strict checks now cover `current_predicate/1`, `clause/2`, `asserta/1`, `assertz/1`, `retract/1`, Corrigendum 2 `retractall/1`, and `abolish/1` errors plus empty-procedure lifetime
|
|
36
|
+
| 7.5-7.6 — database and term/clause conversion | audit | Dynamic database and logical-update-view suites. Strict mode restores Part 1 private-static/public-dynamic `clause/2` access; focused strict checks now cover `current_predicate/1`, `clause/2`, `asserta/1`, `assertz/1`, `retract/1`, Corrigendum 2 `retractall/1`, and `abolish/1` errors plus empty-procedure lifetime, including runtime protection of the solver-native conjunction control construct `','/2`. The remaining clause-conversion shall-by-shall mapping is still open. |
|
|
37
37
|
| 7.7 — execution and backtracking | audit | Control/search suites. Strict mode disables EyeProlog automatic tabling, cycle guards, and recursive numeric shortcuts so core execution uses ordinary clause selection/backtracking. |
|
|
38
38
|
| 7.8 — control constructs and exceptions | audit | call, cut, conjunction, disjunction (including failed branches after callee-local cuts), if-then, catch/throw, renamed-copy tests. |
|
|
39
|
-
| 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Exceptional-value/error-precedence
|
|
40
|
-
| 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, and Corrigendum 3 writer cases. The audit now also covers write-mode creation/truncation, append creation, repositioned overwrite, flushing, EOF actions, close/current-stream handling, strict stream-position terms, stream-property validation, stream-term requirements,
|
|
39
|
+
| 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Strict mode now also pins direct-variable precedence, arithmetic operand type errors, float-only rounding conversions, zero-to-negative-power undefined errors, and the Part 1 mixed integer/float comparison conversion rule. Exceptional-value/error-precedence mapping remains to be exhaustively enumerated. |
|
|
40
|
+
| 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, and Corrigendum 3 writer cases. The audit now also covers write-mode creation/truncation, append creation, repositioned overwrite, flushing, EOF actions, close/current-stream handling, strict stream-position terms, stream-property validation, stream-term requirements, text-vs-binary permission errors, and the prescribed `alias(A)` culprit for `open/4` alias collisions. The full option/mode cross-product is still being mapped. |
|
|
41
41
|
| 7.11 — flags | covered | The complete Part 1 flag set, selected defaults, standard value domains, changeability, `current_prolog_flag/2`, and `set_prolog_flag/2` error behavior have dedicated strict tests. EyeProlog selects `bounded=false` and `integer_rounding_function=toward_zero`; valid alternative values of those fixed flags reach `permission_error(modify,flag,...)`, while `max_integer` and `min_integer` have no current value. Strict mode excludes the EyeProlog `occurs_check` extension. |
|
|
42
|
-
| 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax families and focused error cases. Exact ISO 8.14.1-8.14.4 error precedence is covered for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3`; the continuing audit has corrected additional 8.11-8.13 stream/character/byte errors, Corrigendum 2 `keysort/2` errors, and arithmetic culprit reporting. A complete one-row-per-prescribed-error ordering map remains open. |
|
|
43
|
-
| 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md; Corrigendum 2 additions (`subsumes_term/2`, `term_variables/2`, `call/2..8`, `false/0`) are in the strict registry. The audit now includes corrected `keysort/2` variable/non-pair errors, stricter 8.11-8.13 stream and character/byte modes/errors, the 8.14 option/error-order subfamily,
|
|
44
|
-
| Clause 9 — evaluable functors | audit | Integer/float/rounding/transcendental/bitwise suites and corrigendum cases. Strict mode excludes the EyeProlog-only evaluable atom `e`, retains Corrigendum 2 arithmetic additions,
|
|
42
|
+
| 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax families and focused error cases. Exact ISO 8.14.1-8.14.4 error precedence is covered for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3`; the continuing audit has corrected additional 8.11-8.13 stream/character/byte errors (including the complete `alias(A)` culprit for `open/4` alias collisions), Corrigendum 2 `keysort/2` errors, atomic-conversion list-shape precedence, `arg/3`, `atom_concat/3`, `sub_atom/5`, `char_conversion/2`, runtime control-construct database protection, and arithmetic culprit/precedence reporting. A complete one-row-per-prescribed-error ordering map remains open. |
|
|
43
|
+
| 8.2-8.17 — built-in predicates | audit | Predicate-family coverage is mapped in ISO-MATRIX.md; Corrigendum 2 additions (`subsumes_term/2`, `term_variables/2`, `call/2..8`, `false/0`) are in the strict registry. The audit now includes corrected `keysort/2` variable/non-pair errors, stricter 8.11-8.13 stream and character/byte modes/errors, the 8.14 option/error-order subfamily, closed 8.17 flags, and additional prescribed precedence for `arg/3`, `atom_concat/3`, `sub_atom/5`, number/atom list conversions, and `char_conversion/2`. The remaining mode/error matrix is not yet one-row-per-standard-row. |
|
|
44
|
+
| Clause 9 — evaluable functors | audit | Integer/float/rounding/transcendental/bitwise suites and corrigendum cases. Strict mode excludes the EyeProlog-only evaluable atom `e`, retains Corrigendum 2 arithmetic additions, reports unknown zero-arity evaluables with the required `F/0` culprit shape, enforces the Part 1 numeric/integer operand errors and float-only rounding modes, and uses the Part 1 integer-to-float conversion rule for mixed arithmetic comparisons. Normal mode retains EyeProlog's exact mixed-type comparison extension. Host floating-point representation choices remain documented implementation-defined behavior; exhaustive exceptional-value precedence remains open. |
|
|
45
45
|
| Corrigendum 1 | covered | Double-quoted atom/operator-priority corrections have dedicated cases. |
|
|
46
46
|
| Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases. |
|
|
47
47
|
| Corrigendum 3 | covered | Writer options, `variable_names/1`, canonical list output and negative-power corrections have dedicated cases. |
|
|
@@ -119,6 +119,22 @@ argument as the culprit of `type_error(list, List)`, as required by the
|
|
|
119
119
|
Corrigendum 2 replacement error clauses. Dedicated strict-core regressions pin
|
|
120
120
|
both behaviors.
|
|
121
121
|
|
|
122
|
+
|
|
123
|
+
The following Part 1 pass tightens more of the still-open prescribed-error and
|
|
124
|
+
arithmetic matrix. `arg/3`, `atom_concat/3`, `sub_atom/5`, `number_chars/2`,
|
|
125
|
+
`number_codes/2`, and `char_conversion/2` now follow the standard ordering when
|
|
126
|
+
multiple error conditions are simultaneously present. Corrigendum 2 partial
|
|
127
|
+
versus improper list distinctions are applied consistently to atomic and
|
|
128
|
+
number conversion. Clause 9 strict evaluation now gives the prescribed
|
|
129
|
+
number/integer operand diagnostics, checks direct-variable instantiation first,
|
|
130
|
+
enforces the float-only input modes of `floor/1`, `truncate/1`, `round/1`, and
|
|
131
|
+
`ceiling/1`, and reports zero raised to a negative power as undefined. Finally,
|
|
132
|
+
strict mixed integer/float arithmetic comparisons perform the Part 1
|
|
133
|
+
integer-to-float conversion (including `float_overflow`); normal EyeProlog keeps
|
|
134
|
+
its exact cross-type comparison as an extension. Dedicated strict regressions
|
|
135
|
+
cover these distinctions without changing the normal-profile arithmetic error
|
|
136
|
+
contract.
|
|
137
|
+
|
|
122
138
|
## Strict-core boundary
|
|
123
139
|
|
|
124
140
|
`isoStrict: true` is intentionally a **Part 1 + Corrigenda 1-3** mode. It does
|
|
@@ -11,14 +11,14 @@ compliance audit and the remaining work before a full conformance claim.
|
|
|
11
11
|
| Clause 6 lexical and term syntax | tokenizer, operator parser, lists, curly terms, quotes, numeric syntax, comments, strict ASCII PCS/collation | `scryer_lexical_terms`, `lexical_and_curly_terms`, `double_quoted_lists`, `corrigendum1_double_quote_operator`, `wg17_syntax_high_risk`, `wg17_invalid_octal_escape`, `wg17_unterminated_quoted_token`, `wg17_literal_newline_in_quote`, `wg17_non_iso_escape`, strict PCS/collation tests in `run-iso-strict.mjs`, syntax error cases |
|
|
12
12
|
| Clause 7 term order and unification | finite-tree unification, identity, standard order, errors | `unification_control_information`, `swipl_occurs_check`, `term_modes_and_ordering`, `logtalk_compare_standard_order` |
|
|
13
13
|
| Clause 7 control and exceptions | call, cut, conjunction, disjunction, if-then-else, catch and throw | `cut_control`, `control_and_terms`, `exceptions_and_flags`, `corrigenda_catch_callability`, `throw_copies_ball` |
|
|
14
|
-
| 8.2-8.5 term predicates | unification, Corrigendum 2 tests, comparison, sorting, creation and decomposition | `corrigenda_term_predicates`, `corrigenda_sort_keysort`, `logtalk_arg_unification`, `logtalk_univ`, associated error cases, Corrigendum 2 `keysort/2` variable/non-pair errors, and strict `functor/3` / `=../2` error-order plus max-arity checks |
|
|
14
|
+
| 8.2-8.5 term predicates | unification, Corrigendum 2 tests, comparison, sorting, creation and decomposition | `corrigenda_term_predicates`, `corrigenda_sort_keysort`, `logtalk_arg_unification`, `logtalk_univ`, associated error cases, Corrigendum 2 `keysort/2` variable/non-pair errors, and strict `arg/3`, `functor/3` / `=../2` error-order plus max-arity checks |
|
|
15
15
|
| 8.6-8.7 arithmetic predicates | `is/2` and all six arithmetic comparisons | `arithmetic`, comparison cases, isolated evaluation errors |
|
|
16
|
-
| 8.8-8.10 database and solutions | logical update view, dynamic mutation, all-solutions grouping | `dynamic_database`, `trealla_logical_update_view`, `corrigenda_retractall`, `grouped_solutions_and_clauses`, plus strict clause/database error and all-solutions precedence checks |
|
|
17
|
-
| 8.11-8.14 streams and term I/O | text/binary streams, properties, units, read/write options and operators | `streams_and_term_io`, `operators`, Corrigendum 3 option cases, stream error cases, strict 8.14.1-8.14.4 option/error-order tests, Corrigendum 3 variable metadata/write naming checks, and issue #65 checks for stream-term/property validation, text-vs-binary permissions, EOF/flush/position,
|
|
16
|
+
| 8.8-8.10 database and solutions | logical update view, dynamic mutation, all-solutions grouping | `dynamic_database`, `trealla_logical_update_view`, `corrigenda_retractall`, `grouped_solutions_and_clauses`, plus strict clause/database error and all-solutions precedence checks, including runtime static/private protection for the solver-native conjunction control construct |
|
|
17
|
+
| 8.11-8.14 streams and term I/O | text/binary streams, properties, units, read/write options and operators | `streams_and_term_io`, `operators`, Corrigendum 3 option cases, stream error cases, strict 8.14.1-8.14.4 option/error-order tests, Corrigendum 3 variable metadata/write naming checks, and issue #65 checks for stream-term/property validation, text-vs-binary permissions, EOF/flush/position, open/close semantics, and the exact `alias(A)` collision culprit |
|
|
18
18
|
| 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin`, plus strict Corrigendum 2 resulting-goal `max_arity` error coverage |
|
|
19
|
-
| 8.16 atomic processing | atoms, characters, codes and number conversion with prescribed errors | `atomic_term_processing`, focused forward/reverse cases, parenthesized-number rejection, Logtalk-derived error cases,
|
|
20
|
-
| 8.17 flags and hooks | complete required Part 1 flag set, selected defaults, standard value domains/changeability, halt and character conversion | `exceptions_and_flags`, `remaining_builtins_and_directives`, flag error cases, and the strict
|
|
21
|
-
| Clause 9 evaluable functors | integer, float, rounding, transcendental and bitwise operations | `arithmetic`, `corrigenda_arithmetic`, `corrigenda_atan2_zero`, `corrigenda_integer_negative_power`, strict exclusion of the normal-mode `e` evaluable extension
|
|
19
|
+
| 8.16 atomic processing | atoms, characters, codes and number conversion with prescribed errors | `atomic_term_processing`, focused forward/reverse cases, parenthesized-number rejection, Logtalk-derived error cases, strict Corrigendum 2 partial/improper-list precedence for atom/number conversions, and prescribed `atom_concat/3` / `sub_atom/5` ordering checks |
|
|
20
|
+
| 8.17 flags and hooks | complete required Part 1 flag set, selected defaults, standard value domains/changeability, halt and character conversion | `exceptions_and_flags`, `remaining_builtins_and_directives`, flag error cases, strict `char_conversion/2` precedence, and the complete strict flag audit in `run-iso-strict.mjs` |
|
|
21
|
+
| Clause 9 evaluable functors | integer, float, rounding, transcendental and bitwise operations | `arithmetic`, `corrigenda_arithmetic`, `corrigenda_atan2_zero`, `corrigenda_integer_negative_power`, strict exclusion of the normal-mode `e` evaluable extension, zero-arity unknown-evaluable checks, prescribed operand/type and negative-power errors, float-only rounding modes, and Part 1 mixed integer/float comparison conversion |
|
|
22
22
|
| Module compatibility profile (related to ISO/IEC 13211-2 and later WG17 amendment work) | module declarations, exports, imports, qualification, meta-predicate context | `modules/qualified_call`, `modules/selective_library_import`, `dcg_module_nonterminal_indicator` |
|
|
23
23
|
| Part 3-oriented DCG compatibility profile | `-->`, terminal and partial sequences, grammar control constructs, semicontexts, nonterminal indicators, modules, `phrase/2-3`, steadfastness and errors | `dcg_terminals_and_remainder`, `dcg_control_constructs`, `dcg_partial_sequences`, `dcg_phrase_steadfastness`, `dcg_dynamic_nonterminal_indicator`, `logtalk_dcg_phrase_identity`, `logtalk_dcg_semicontexts`, DCG error and precedence cases |
|
|
24
24
|
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -183,6 +183,15 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
183
183
|
includes(ignoredNonVariable.stdout, 'x', 'non-variable right side is permitted and ignored');
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
reporter.test('reports the complete alias option in open/4 alias collisions', () => {
|
|
187
|
+
const error = capture(() => run('', {
|
|
188
|
+
isoStrict: true,
|
|
189
|
+
goal: 'open(dummy,write,_,[alias(user_input)])',
|
|
190
|
+
}));
|
|
191
|
+
equal(error.formal, 'permission_error(open, source_sink)', 'formal error');
|
|
192
|
+
includes(error.message, 'alias(user_input)', 'alias option culprit');
|
|
193
|
+
});
|
|
194
|
+
|
|
186
195
|
reporter.test('follows the ISO 8.14.1.3 read_term/3 error order', () => {
|
|
187
196
|
equal(capture(() => run('', { isoStrict: true, goal: 'read_term(f(a),_,[X])' })).formal,
|
|
188
197
|
'instantiation_error', 'partial/variable option before stream domain');
|
|
@@ -243,6 +252,60 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
243
252
|
'type_error(atom)', 'operator type');
|
|
244
253
|
});
|
|
245
254
|
|
|
255
|
+
reporter.test('follows remaining Part 1 built-in error precedence', () => {
|
|
256
|
+
const cases = [
|
|
257
|
+
['arg(a,X,_)', 'instantiation_error', 'arg/3 second-argument instantiation before index type'],
|
|
258
|
+
['atom_concat(1,X,Y)', 'instantiation_error', 'atom_concat/3 second/whole under-instantiation before first type'],
|
|
259
|
+
['atom_concat(X,1,Y)', 'instantiation_error', 'atom_concat/3 first/whole under-instantiation before second type'],
|
|
260
|
+
['sub_atom(a,-1,bad,_,_)', 'type_error(integer)', 'sub_atom/5 integer type before non-negative domain'],
|
|
261
|
+
['number_chars(N,[1|T])', 'instantiation_error', 'number_chars/2 partial list before element type'],
|
|
262
|
+
['number_chars(N,[X,1])', 'instantiation_error', 'number_chars/2 variable element before later element type'],
|
|
263
|
+
['number_codes(N,[foo|T])', 'instantiation_error', 'number_codes/2 partial list before element type'],
|
|
264
|
+
['number_codes(N,[X,foo])', 'instantiation_error', 'number_codes/2 variable element before later element type'],
|
|
265
|
+
['atom_chars(A,[X|foo])', 'type_error(list)', 'atom_chars/2 improper list before prefix variable'],
|
|
266
|
+
['atom_codes(A,[X|foo])', 'type_error(list)', 'atom_codes/2 improper list before prefix variable'],
|
|
267
|
+
['char_conversion(foo,X)', 'instantiation_error', 'char_conversion/2 output instantiation before input representation'],
|
|
268
|
+
];
|
|
269
|
+
for (const [goal, formal, label] of cases) {
|
|
270
|
+
equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
reporter.test('follows Part 1 arithmetic type and exceptional errors', () => {
|
|
275
|
+
const cases = [
|
|
276
|
+
["X is '+'(foo,77)", 'type_error(number)', 'simple arithmetic atomic operand'],
|
|
277
|
+
['X is mod(foo,77)', 'type_error(number)', 'integer arithmetic non-number operand'],
|
|
278
|
+
['X is mod(7.5,2)', 'type_error(integer)', 'integer arithmetic numeric type'],
|
|
279
|
+
['X is truncate(foo)', 'type_error(number)', 'rounding non-number operand'],
|
|
280
|
+
['X is sin(foo)', 'type_error(number)', 'transcendental non-number operand'],
|
|
281
|
+
['X is foo+Y', 'instantiation_error', 'direct variable before another operand error'],
|
|
282
|
+
['X is floor(7)', 'type_error(float)', 'floor integer operand'],
|
|
283
|
+
['X is truncate(7)', 'type_error(float)', 'truncate integer operand'],
|
|
284
|
+
['X is round(7)', 'type_error(float)', 'round integer operand'],
|
|
285
|
+
['X is ceiling(7)', 'type_error(float)', 'ceiling integer operand'],
|
|
286
|
+
['X is 0 ** -1', 'evaluation_error(undefined)', 'power zero negative exponent'],
|
|
287
|
+
['X is 0.0 ^ -1', 'evaluation_error(undefined)', 'Corrigendum 2 power zero negative exponent'],
|
|
288
|
+
];
|
|
289
|
+
for (const [goal, formal, label] of cases) {
|
|
290
|
+
equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
reporter.test('uses the Part 1 mixed arithmetic comparison operations', () => {
|
|
295
|
+
// 8.7 converts the integer operand to float in mixed comparisons.
|
|
296
|
+
equal(run('', { isoStrict: true, goal: '18014398509481985 =:= 18014398509481984.0' }).stats.completed_goal_lists,
|
|
297
|
+
1, 'mixed equality after float conversion');
|
|
298
|
+
equal(run('', { isoStrict: true, goal: '\\+ (18014398509481985 > 18014398509481984.0)' }).stats.completed_goal_lists,
|
|
299
|
+
1, 'mixed ordering after float conversion');
|
|
300
|
+
const huge = `1${'0'.repeat(400)}`;
|
|
301
|
+
equal(capture(() => run('', { isoStrict: true, goal: `${huge} > 1.0` })).formal,
|
|
302
|
+
'evaluation_error(float_overflow)', 'mixed integer-to-float overflow');
|
|
303
|
+
|
|
304
|
+
// The normal profile retains EyeProlog's exact cross-type extension.
|
|
305
|
+
equal(run('', { goal: '9007199254740993 > 9007199254740992.0' }).stats.completed_goal_lists,
|
|
306
|
+
1, 'normal exact mixed ordering extension');
|
|
307
|
+
});
|
|
308
|
+
|
|
246
309
|
reporter.test('follows ISO term-construction error types and precedence', () => {
|
|
247
310
|
equal(capture(() => run('', { isoStrict: true, goal: 'functor(_,foo(a),1)' })).formal,
|
|
248
311
|
'type_error(atomic)', 'functor compound name');
|
|
@@ -322,6 +385,21 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
322
385
|
equal(answers.length, 1, 'answer count');
|
|
323
386
|
});
|
|
324
387
|
|
|
388
|
+
reporter.test('protects conjunction as an ISO static/private control construct at runtime', () => {
|
|
389
|
+
for (const goal of [
|
|
390
|
+
"asserta(','(a,b))",
|
|
391
|
+
"assertz(','(a,b))",
|
|
392
|
+
"retract(','(a,b))",
|
|
393
|
+
"retractall(','(a,b))",
|
|
394
|
+
"abolish('/'(',',2))",
|
|
395
|
+
]) {
|
|
396
|
+
equal(capture(() => run('', { isoStrict: true, goal })).formal,
|
|
397
|
+
'permission_error(modify, static_procedure)', goal);
|
|
398
|
+
}
|
|
399
|
+
equal(capture(() => run('', { isoStrict: true, goal: "clause(','(a,b),_)" })).formal,
|
|
400
|
+
'permission_error(access, private_procedure)', 'clause/2 conjunction access');
|
|
401
|
+
});
|
|
402
|
+
|
|
325
403
|
reporter.test('covers ISO database predicate errors and empty-procedure lifetime', () => {
|
|
326
404
|
const errors = [
|
|
327
405
|
['current_predicate(4)', '', 'type_error(predicate_indicator)'],
|