eyeprolog 1.3.46 → 1.3.48
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 +63 -19
- package/test/conformance/ISO-COMPLIANCE.md +29 -4
- package/test/conformance/ISO-MATRIX.md +5 -5
- package/test/run-iso-strict.mjs +70 -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;
|
|
@@ -815,8 +823,14 @@ function conversionCharacter(term, env, current = false, solver = null) {
|
|
|
815
823
|
return value;
|
|
816
824
|
}
|
|
817
825
|
function* charConversionBuiltin({ solver, goal, env }) {
|
|
818
|
-
|
|
819
|
-
|
|
826
|
+
// ISO 8.14.5.3 checks both arguments for instantiation before testing
|
|
827
|
+
// either character representation.
|
|
828
|
+
const inputTerm = deref(goal.args[0], env);
|
|
829
|
+
const outputTerm = deref(goal.args[1], env);
|
|
830
|
+
if (inputTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
831
|
+
if (outputTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
832
|
+
const input = conversionCharacter(inputTerm, env, false, solver);
|
|
833
|
+
const output = conversionCharacter(outputTerm, env, false, solver);
|
|
820
834
|
if (input.name === output.name) solver.charConversions.delete(input.name);
|
|
821
835
|
else solver.charConversions.set(input.name, output.name);
|
|
822
836
|
yield env;
|
|
@@ -1689,11 +1703,16 @@ function* atomLengthBuiltin({ goal, env }) {
|
|
|
1689
1703
|
}
|
|
1690
1704
|
|
|
1691
1705
|
function* atomConcatBuiltin({ goal, env }) {
|
|
1692
|
-
const first =
|
|
1693
|
-
const second =
|
|
1694
|
-
const whole =
|
|
1706
|
+
const first = deref(goal.args[0], env);
|
|
1707
|
+
const second = deref(goal.args[1], env);
|
|
1708
|
+
const whole = deref(goal.args[2], env);
|
|
1709
|
+
// ISO 8.16.2.3 puts the two under-instantiation modes ahead of all atom
|
|
1710
|
+
// type diagnostics.
|
|
1695
1711
|
if (first.type === VAR && whole.type === VAR) throw new PrologError('instantiation_error');
|
|
1696
1712
|
if (second.type === VAR && whole.type === VAR) throw new PrologError('instantiation_error');
|
|
1713
|
+
if (first.type !== VAR && first.type !== ATOM) throw new PrologError('type_error(atom)', first);
|
|
1714
|
+
if (second.type !== VAR && second.type !== ATOM) throw new PrologError('type_error(atom)', second);
|
|
1715
|
+
if (whole.type !== VAR && whole.type !== ATOM) throw new PrologError('type_error(atom)', whole);
|
|
1697
1716
|
|
|
1698
1717
|
const candidates = [];
|
|
1699
1718
|
if (whole.type === ATOM && first.type === VAR && second.type === VAR) {
|
|
@@ -1713,23 +1732,33 @@ function* atomConcatBuiltin({ goal, env }) {
|
|
|
1713
1732
|
}
|
|
1714
1733
|
}
|
|
1715
1734
|
|
|
1716
|
-
function
|
|
1735
|
+
function optionalInteger(term, env) {
|
|
1717
1736
|
const value = deref(term, env);
|
|
1718
1737
|
if (value.type === VAR) return null;
|
|
1719
1738
|
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;
|
|
1739
|
+
return BigInt(value.name);
|
|
1723
1740
|
}
|
|
1724
1741
|
|
|
1725
1742
|
function* subAtomBuiltin({ goal, env }) {
|
|
1726
1743
|
const source = deref(goal.args[0], env);
|
|
1727
1744
|
if (source.type === VAR) throw new PrologError('instantiation_error');
|
|
1728
1745
|
if (source.type !== ATOM) throw new PrologError('type_error(atom)', source);
|
|
1729
|
-
const sub =
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1746
|
+
const sub = deref(goal.args[4], env);
|
|
1747
|
+
if (sub.type !== VAR && sub.type !== ATOM) throw new PrologError('type_error(atom)', sub);
|
|
1748
|
+
// ISO 8.16.3.3 prescribes all type checks before the non-negative domain
|
|
1749
|
+
// checks, in Before/Length/After order.
|
|
1750
|
+
const before = optionalInteger(goal.args[1], env);
|
|
1751
|
+
const length = optionalInteger(goal.args[2], env);
|
|
1752
|
+
const after = optionalInteger(goal.args[3], env);
|
|
1753
|
+
if (before != null && before < 0n) {
|
|
1754
|
+
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[1], env));
|
|
1755
|
+
}
|
|
1756
|
+
if (length != null && length < 0n) {
|
|
1757
|
+
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[2], env));
|
|
1758
|
+
}
|
|
1759
|
+
if (after != null && after < 0n) {
|
|
1760
|
+
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[3], env));
|
|
1761
|
+
}
|
|
1733
1762
|
const chars = characters(source.name);
|
|
1734
1763
|
for (let start = 0; start <= chars.length; start++) {
|
|
1735
1764
|
if (before != null && before !== BigInt(start)) continue;
|
|
@@ -1770,9 +1799,14 @@ function validCharacterCode(value, solver = null) {
|
|
|
1770
1799
|
}
|
|
1771
1800
|
|
|
1772
1801
|
function listToAtomInput(list, env, kind, solver = null) {
|
|
1802
|
+
const whole = deref(list, env);
|
|
1773
1803
|
const { items, tail } = listElements(list, env);
|
|
1774
|
-
|
|
1775
|
-
|
|
1804
|
+
// Corrigendum 2 distinguishes a partial list from an improper one: only
|
|
1805
|
+
// the former precedes the complete-list type check. Variables in a proper
|
|
1806
|
+
// list prefix are checked after that list-shape diagnostic.
|
|
1807
|
+
if (tail.type === VAR) throw new PrologError('instantiation_error');
|
|
1808
|
+
if (tail.type !== ATOM || tail.name !== '[]') throw new PrologError('type_error(list)', whole);
|
|
1809
|
+
if (items.some((item) => item.type === VAR)) throw new PrologError('instantiation_error');
|
|
1776
1810
|
if (kind === 'chars') {
|
|
1777
1811
|
const invalid = items.find((item) => !oneChar(item));
|
|
1778
1812
|
if (invalid) throw new PrologError('type_error(character)', invalid);
|
|
@@ -1968,7 +2002,14 @@ function numberListText(list, env, kind, valueIsBound, solver = null) {
|
|
|
1968
2002
|
const whole = deref(list, env);
|
|
1969
2003
|
const { items, tail } = listElements(list, env);
|
|
1970
2004
|
const proper = tail.type === ATOM && tail.name === '[]';
|
|
2005
|
+
// Corrigendum 2 orders conversion-mode instantiation errors specially:
|
|
2006
|
+
// a partial list is diagnosed before its fixed prefix, while an improper
|
|
2007
|
+
// non-list is diagnosed before a variable element in that prefix.
|
|
2008
|
+
if (!valueIsBound && tail.type === VAR) throw new PrologError('instantiation_error');
|
|
1971
2009
|
if (tail.type !== VAR && !proper) throw new PrologError('type_error(list)', whole);
|
|
2010
|
+
if (!valueIsBound && items.some((item) => item.type === VAR)) {
|
|
2011
|
+
throw new PrologError('instantiation_error');
|
|
2012
|
+
}
|
|
1972
2013
|
|
|
1973
2014
|
const invalid = items.find((item) => item.type !== VAR &&
|
|
1974
2015
|
(kind === 'chars' ? !oneChar(item) : !validCharacterCode(item, solver)));
|
|
@@ -1981,7 +2022,6 @@ function numberListText(list, env, kind, valueIsBound, solver = null) {
|
|
|
1981
2022
|
}
|
|
1982
2023
|
|
|
1983
2024
|
const hasVariable = tail.type === VAR || items.some((item) => item.type === VAR);
|
|
1984
|
-
if (!valueIsBound && hasVariable) throw new PrologError('instantiation_error');
|
|
1985
2025
|
if (hasVariable) return null;
|
|
1986
2026
|
return items.map((item) => kind === 'chars'
|
|
1987
2027
|
? item.name
|
|
@@ -2228,7 +2268,11 @@ function* callBuiltin({ solver, goal, env }) {
|
|
|
2228
2268
|
function* callClosureBuiltin({ solver, goal, env }) {
|
|
2229
2269
|
const closure = callable(goal.args[0], env);
|
|
2230
2270
|
const existing = closure.type === COMPOUND ? closure.args : [];
|
|
2231
|
-
const
|
|
2271
|
+
const extra = goal.args.slice(1);
|
|
2272
|
+
if (existing.length + extra.length > ISO_MAX_ARITY) {
|
|
2273
|
+
throw new PrologError('representation_error(max_arity)');
|
|
2274
|
+
}
|
|
2275
|
+
const invoked = compound(closure.name, [...existing, ...extra]);
|
|
2232
2276
|
if (closure.module != null) invoked.module = closure.module;
|
|
2233
2277
|
const child = solver.cloneForInnerGoal();
|
|
2234
2278
|
try {
|
|
@@ -36,12 +36,12 @@ error-ordering alternative to an individual executable assertion.
|
|
|
36
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. 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
|
|
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
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, and text-vs-binary permission errors. 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, Corrigendum 2 `keysort/2` errors, atomic-conversion list-shape precedence, `arg/3`, `atom_concat/3`, `sub_atom/5`, `char_conversion/2`, 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. |
|
|
@@ -110,6 +110,31 @@ EyeProlog-only evaluable atom `e`, while the Corrigendum arithmetic additions
|
|
|
110
110
|
remain available. These corrections narrow the remaining `audit` rows but do
|
|
111
111
|
not by themselves close the full shall-by-shall/error-order exit criteria.
|
|
112
112
|
|
|
113
|
+
The next Corrigendum 2 pass closes two smaller prescribed-error gaps.
|
|
114
|
+
`call/2..8` now raises `representation_error(max_arity)` when appending its
|
|
115
|
+
extra arguments would make the resulting goal exceed the declared `max_arity`,
|
|
116
|
+
rather than falling through to a procedure-existence error. Reverse
|
|
117
|
+
`atom_chars/2` and `atom_codes/2` now report the complete improper `List`
|
|
118
|
+
argument as the culprit of `type_error(list, List)`, as required by the
|
|
119
|
+
Corrigendum 2 replacement error clauses. Dedicated strict-core regressions pin
|
|
120
|
+
both behaviors.
|
|
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
|
+
|
|
113
138
|
## Strict-core boundary
|
|
114
139
|
|
|
115
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
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
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, and open/close semantics |
|
|
18
|
-
| 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin` |
|
|
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
|
|
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, 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
|
@@ -243,6 +243,60 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
243
243
|
'type_error(atom)', 'operator type');
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
+
reporter.test('follows remaining Part 1 built-in error precedence', () => {
|
|
247
|
+
const cases = [
|
|
248
|
+
['arg(a,X,_)', 'instantiation_error', 'arg/3 second-argument instantiation before index type'],
|
|
249
|
+
['atom_concat(1,X,Y)', 'instantiation_error', 'atom_concat/3 second/whole under-instantiation before first type'],
|
|
250
|
+
['atom_concat(X,1,Y)', 'instantiation_error', 'atom_concat/3 first/whole under-instantiation before second type'],
|
|
251
|
+
['sub_atom(a,-1,bad,_,_)', 'type_error(integer)', 'sub_atom/5 integer type before non-negative domain'],
|
|
252
|
+
['number_chars(N,[1|T])', 'instantiation_error', 'number_chars/2 partial list before element type'],
|
|
253
|
+
['number_chars(N,[X,1])', 'instantiation_error', 'number_chars/2 variable element before later element type'],
|
|
254
|
+
['number_codes(N,[foo|T])', 'instantiation_error', 'number_codes/2 partial list before element type'],
|
|
255
|
+
['number_codes(N,[X,foo])', 'instantiation_error', 'number_codes/2 variable element before later element type'],
|
|
256
|
+
['atom_chars(A,[X|foo])', 'type_error(list)', 'atom_chars/2 improper list before prefix variable'],
|
|
257
|
+
['atom_codes(A,[X|foo])', 'type_error(list)', 'atom_codes/2 improper list before prefix variable'],
|
|
258
|
+
['char_conversion(foo,X)', 'instantiation_error', 'char_conversion/2 output instantiation before input representation'],
|
|
259
|
+
];
|
|
260
|
+
for (const [goal, formal, label] of cases) {
|
|
261
|
+
equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
reporter.test('follows Part 1 arithmetic type and exceptional errors', () => {
|
|
266
|
+
const cases = [
|
|
267
|
+
["X is '+'(foo,77)", 'type_error(number)', 'simple arithmetic atomic operand'],
|
|
268
|
+
['X is mod(foo,77)', 'type_error(number)', 'integer arithmetic non-number operand'],
|
|
269
|
+
['X is mod(7.5,2)', 'type_error(integer)', 'integer arithmetic numeric type'],
|
|
270
|
+
['X is truncate(foo)', 'type_error(number)', 'rounding non-number operand'],
|
|
271
|
+
['X is sin(foo)', 'type_error(number)', 'transcendental non-number operand'],
|
|
272
|
+
['X is foo+Y', 'instantiation_error', 'direct variable before another operand error'],
|
|
273
|
+
['X is floor(7)', 'type_error(float)', 'floor integer operand'],
|
|
274
|
+
['X is truncate(7)', 'type_error(float)', 'truncate integer operand'],
|
|
275
|
+
['X is round(7)', 'type_error(float)', 'round integer operand'],
|
|
276
|
+
['X is ceiling(7)', 'type_error(float)', 'ceiling integer operand'],
|
|
277
|
+
['X is 0 ** -1', 'evaluation_error(undefined)', 'power zero negative exponent'],
|
|
278
|
+
['X is 0.0 ^ -1', 'evaluation_error(undefined)', 'Corrigendum 2 power zero negative exponent'],
|
|
279
|
+
];
|
|
280
|
+
for (const [goal, formal, label] of cases) {
|
|
281
|
+
equal(capture(() => run('', { isoStrict: true, goal })).formal, formal, label);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
reporter.test('uses the Part 1 mixed arithmetic comparison operations', () => {
|
|
286
|
+
// 8.7 converts the integer operand to float in mixed comparisons.
|
|
287
|
+
equal(run('', { isoStrict: true, goal: '18014398509481985 =:= 18014398509481984.0' }).stats.completed_goal_lists,
|
|
288
|
+
1, 'mixed equality after float conversion');
|
|
289
|
+
equal(run('', { isoStrict: true, goal: '\\+ (18014398509481985 > 18014398509481984.0)' }).stats.completed_goal_lists,
|
|
290
|
+
1, 'mixed ordering after float conversion');
|
|
291
|
+
const huge = `1${'0'.repeat(400)}`;
|
|
292
|
+
equal(capture(() => run('', { isoStrict: true, goal: `${huge} > 1.0` })).formal,
|
|
293
|
+
'evaluation_error(float_overflow)', 'mixed integer-to-float overflow');
|
|
294
|
+
|
|
295
|
+
// The normal profile retains EyeProlog's exact cross-type extension.
|
|
296
|
+
equal(run('', { goal: '9007199254740993 > 9007199254740992.0' }).stats.completed_goal_lists,
|
|
297
|
+
1, 'normal exact mixed ordering extension');
|
|
298
|
+
});
|
|
299
|
+
|
|
246
300
|
reporter.test('follows ISO term-construction error types and precedence', () => {
|
|
247
301
|
equal(capture(() => run('', { isoStrict: true, goal: 'functor(_,foo(a),1)' })).formal,
|
|
248
302
|
'type_error(atomic)', 'functor compound name');
|
|
@@ -252,6 +306,22 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
252
306
|
'type_error(list)', '=../2 fixed non-list');
|
|
253
307
|
});
|
|
254
308
|
|
|
309
|
+
reporter.test('enforces Corrigendum 2 call/N max-arity errors', () => {
|
|
310
|
+
equal(capture(() => run('', {
|
|
311
|
+
isoStrict: true,
|
|
312
|
+
goal: 'functor(T,f,65535), call(T,x)',
|
|
313
|
+
})).formal, 'representation_error(max_arity)', 'call/2 resulting arity');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
reporter.test('reports the complete List culprit for Corrigendum 2 atomic conversions', () => {
|
|
317
|
+
for (const goal of ['atom_chars(A,[a|foo])', 'atom_codes(A,[97|foo])']) {
|
|
318
|
+
const error = capture(() => run('', { isoStrict: true, goal }));
|
|
319
|
+
equal(error.formal, 'type_error(list)', `${goal} formal`);
|
|
320
|
+
includes(error.message, '[', `${goal} complete list culprit`);
|
|
321
|
+
if (error.message.endsWith(', foo)')) throw new Error(`${goal}: reported only the improper tail`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
255
325
|
reporter.test('follows ISO clause/2 private-procedure error precedence', () => {
|
|
256
326
|
equal(capture(() => run('p.\n', { isoStrict: true, goal: 'clause(atom(_),4)' })).formal,
|
|
257
327
|
'permission_error(access, private_procedure)', 'private procedure before body callability');
|