eyeprolog 1.3.42 → 1.3.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/package.json +1 -1
- package/src/iso-character.js +37 -0
- package/src/iso.js +163 -75
- package/src/parser.js +12 -2
- package/src/solver.js +4 -4
- package/src/syntax-scan.js +1 -1
- package/test/conformance/ISO-COMPLIANCE.md +27 -10
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +14 -14
- package/test/conformance/ISO-MATRIX.md +3 -3
- package/test/conformance/README.md +7 -2
- package/test/conformance/expected-errors/iso/swipl_current_op_float_priority.txt +1 -1
- package/test/conformance/expected-errors/iso/swipl_current_op_priority_type.txt +1 -1
- package/test/conformance/expected-errors/iso/swipl_current_op_specifier_type.txt +1 -1
- package/test/run-iso-strict.mjs +157 -0
- package/the-art-of-eyeprolog.md +37 -18
- package/why-eyeprolog.md +4 -1
package/README.md
CHANGED
|
@@ -210,6 +210,21 @@ expansion/`phrase/2-3`; it also removes the EyeProlog `occurs_check` flag,
|
|
|
210
210
|
Normal mode is unchanged and continues to support modules, DCGs, quads,
|
|
211
211
|
libraries, proofs, cleanup-aware control, and the other documented extensions.
|
|
212
212
|
|
|
213
|
+
Strict mode also makes the Part 1 processor-character-set choice explicit: its
|
|
214
|
+
PCS is 7-bit ASCII (U+0000..U+007F), C0 controls and DEL are classified as
|
|
215
|
+
extended layout characters, and the collating-sequence integer of each
|
|
216
|
+
character is its ASCII code. Characters or character codes outside that set
|
|
217
|
+
raise representation errors in strict parsing and character operations. Normal
|
|
218
|
+
mode retains Unicode scalar character data as an implementation-specific
|
|
219
|
+
extension.
|
|
220
|
+
|
|
221
|
+
Strict term I/O likewise keeps the standardized option boundary: `read_term/2-3`
|
|
222
|
+
and `write_term/2-3` accept the Part 1 plus Corrigendum 3 options, while the
|
|
223
|
+
normal-profile `write_term(...,[double_quotes(...)])` option remains an
|
|
224
|
+
EyeProlog extension. The strict gate also checks the prescribed ISO error
|
|
225
|
+
precedence for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3`, and
|
|
226
|
+
the complete Part 1 flag defaults/value domains/changeability rules.
|
|
227
|
+
|
|
213
228
|
The auditable processor-requirement checklist lives in
|
|
214
229
|
[`test/conformance/ISO-COMPLIANCE.md`](test/conformance/ISO-COMPLIANCE.md).
|
|
215
230
|
The ISO 5.4 implementation-defined/implementation-specific decision index is
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// ISO/IEC 13211-1 processor-character-set choices used by --iso-strict.
|
|
2
|
+
//
|
|
3
|
+
// EyeProlog's strict Part 1 profile deliberately chooses a finite PCS so every
|
|
4
|
+
// accepted character has one documented lexical classification and one stable
|
|
5
|
+
// collating-sequence integer. Normal mode keeps the broader Unicode surface as
|
|
6
|
+
// an implementation-specific extension.
|
|
7
|
+
|
|
8
|
+
export class CharacterRepresentationError extends Error {
|
|
9
|
+
constructor(formal = 'representation_error(character)') {
|
|
10
|
+
super(`error(${formal})`);
|
|
11
|
+
this.name = 'CharacterRepresentationError';
|
|
12
|
+
this.formal = formal;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Strict PCS: the 128 ASCII characters U+0000..U+007F. Printable ASCII
|
|
17
|
+
// carries the Part 1 lexical classes; the remaining C0 controls plus DEL are
|
|
18
|
+
// implementation-defined extended layout characters. This lets every ISO
|
|
19
|
+
// octal/hexadecimal character escape in the ASCII range denote a PCS member.
|
|
20
|
+
// The collating-sequence integer is the Unicode/ASCII code point.
|
|
21
|
+
export function isStrictIsoPcsCodePoint(code) {
|
|
22
|
+
return Number.isInteger(code) && code >= 0 && code <= 0x7f;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isStrictIsoPcsCharacter(character) {
|
|
26
|
+
if (typeof character !== 'string' || Array.from(character).length !== 1) return false;
|
|
27
|
+
return isStrictIsoPcsCodePoint(character.codePointAt(0));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function strictIsoCollatingInteger(character) {
|
|
31
|
+
return isStrictIsoPcsCharacter(character) ? character.codePointAt(0) : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assertStrictIsoPcsCharacter(character, formal = 'representation_error(character)') {
|
|
35
|
+
if (!isStrictIsoPcsCharacter(character)) throw new CharacterRepresentationError(formal);
|
|
36
|
+
return character;
|
|
37
|
+
}
|
package/src/iso.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { formatTermForWrite } from './write.js';
|
|
14
14
|
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
|
|
15
15
|
import { INVALID_UTF8_SENTINEL } from './io.js';
|
|
16
|
+
import { CharacterRepresentationError, isStrictIsoPcsCharacter, isStrictIsoPcsCodePoint } from './iso-character.js';
|
|
16
17
|
import {
|
|
17
18
|
characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
|
|
18
19
|
} from './syntax-scan.js';
|
|
@@ -689,44 +690,49 @@ function* setPrologFlagBuiltin({ solver, goal, env }) {
|
|
|
689
690
|
|
|
690
691
|
const operatorSpecifiers = new Set(['fx', 'fy', 'xf', 'yf', 'xfx', 'xfy', 'yfx']);
|
|
691
692
|
|
|
692
|
-
function
|
|
693
|
+
function opListPreflight(term, env) {
|
|
693
694
|
const value = deref(term, env);
|
|
694
695
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
695
|
-
if (value.type
|
|
696
|
-
const
|
|
697
|
-
if (
|
|
698
|
-
|
|
696
|
+
if (value.type === ATOM) return { value, items: [value], proper: true, atom: true };
|
|
697
|
+
const { items, tail } = listElements(value, env);
|
|
698
|
+
if (tail.type === VAR || items.some((item) => item.type === VAR)) {
|
|
699
|
+
throw new PrologError('instantiation_error');
|
|
700
|
+
}
|
|
701
|
+
const proper = tail.type === ATOM && tail.name === '[]';
|
|
702
|
+
return { value, items, proper, atom: false };
|
|
699
703
|
}
|
|
700
704
|
|
|
701
|
-
function
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
if (value.type !== ATOM) throw new PrologError('type_error(atom)', value);
|
|
705
|
-
if (!operatorSpecifiers.has(value.name)) throw new PrologError('domain_error(operator_specifier)', value);
|
|
706
|
-
return value.name;
|
|
707
|
-
}
|
|
705
|
+
function* opBuiltin({ solver, goal, env }) {
|
|
706
|
+
const priorityTerm = deref(goal.args[0], env);
|
|
707
|
+
const specifierTerm = deref(goal.args[1], env);
|
|
708
708
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
709
|
+
// ISO 8.14.3.3 prescribes the ordering below. In particular, all three
|
|
710
|
+
// instantiation cases precede type/domain errors, and Operator list-shape
|
|
711
|
+
// errors precede priority/specifier domain errors.
|
|
712
|
+
if (priorityTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
713
|
+
if (specifierTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
714
|
+
const operator = opListPreflight(goal.args[2], env);
|
|
715
|
+
|
|
716
|
+
if (priorityTerm.type !== NUMBER || !isDecimalInteger(priorityTerm.name)) {
|
|
717
|
+
throw new PrologError('type_error(integer)', priorityTerm);
|
|
717
718
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (
|
|
719
|
+
if (specifierTerm.type !== ATOM) throw new PrologError('type_error(atom)', specifierTerm);
|
|
720
|
+
if (!operator.atom && !operator.proper) throw new PrologError('type_error(list)', operator.value);
|
|
721
|
+
for (const item of operator.items) {
|
|
722
|
+
if (item.type !== ATOM) throw new PrologError('type_error(atom)', item);
|
|
722
723
|
}
|
|
723
|
-
return items.map((item) => deref(item, env));
|
|
724
|
-
}
|
|
725
724
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
725
|
+
const priorityBig = BigInt(priorityTerm.name);
|
|
726
|
+
if (priorityBig < 0n || priorityBig > 1200n) {
|
|
727
|
+
throw new PrologError('domain_error(operator_priority)', priorityTerm);
|
|
728
|
+
}
|
|
729
|
+
if (!operatorSpecifiers.has(specifierTerm.name)) {
|
|
730
|
+
throw new PrologError('domain_error(operator_specifier)', specifierTerm);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const priority = Number(priorityBig);
|
|
734
|
+
const specifier = specifierTerm.name;
|
|
735
|
+
for (const name of operator.items) {
|
|
730
736
|
if (name.name === ',') {
|
|
731
737
|
throw new PrologError('permission_error(modify, operator)', name);
|
|
732
738
|
}
|
|
@@ -754,19 +760,16 @@ function* currentOpBuiltin({ solver, goal, env }) {
|
|
|
754
760
|
const priority = deref(goal.args[0], env);
|
|
755
761
|
const specifier = deref(goal.args[1], env);
|
|
756
762
|
const name = deref(goal.args[2], env);
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
763
|
+
// ISO 8.14.4.3 defines the first two argument errors by domain, not by
|
|
764
|
+
// their underlying atom/integer representation type.
|
|
765
|
+
if (priority.type !== VAR &&
|
|
766
|
+
(priority.type !== NUMBER || !isDecimalInteger(priority.name) ||
|
|
767
|
+
BigInt(priority.name) < 0n || BigInt(priority.name) > 1200n)) {
|
|
768
|
+
throw new PrologError('domain_error(operator_priority)', priority);
|
|
764
769
|
}
|
|
765
|
-
if (specifier.type !== VAR
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
throw new PrologError('domain_error(operator_specifier)', specifier);
|
|
769
|
-
}
|
|
770
|
+
if (specifier.type !== VAR &&
|
|
771
|
+
(specifier.type !== ATOM || !operatorSpecifiers.has(specifier.name))) {
|
|
772
|
+
throw new PrologError('domain_error(operator_specifier)', specifier);
|
|
770
773
|
}
|
|
771
774
|
if (name.type !== VAR && name.type !== ATOM) throw new PrologError('type_error(atom)', name);
|
|
772
775
|
for (const definition of solver.program.operators.values()) {
|
|
@@ -777,7 +780,7 @@ function* currentOpBuiltin({ solver, goal, env }) {
|
|
|
777
780
|
}
|
|
778
781
|
}
|
|
779
782
|
|
|
780
|
-
function conversionCharacter(term, env, current = false) {
|
|
783
|
+
function conversionCharacter(term, env, current = false, solver = null) {
|
|
781
784
|
const value = deref(term, env);
|
|
782
785
|
if (value.type === VAR) {
|
|
783
786
|
if (current) return value;
|
|
@@ -787,18 +790,21 @@ function conversionCharacter(term, env, current = false) {
|
|
|
787
790
|
if (current) throw new PrologError('type_error(character)', value);
|
|
788
791
|
throw new PrologError('representation_error(character)');
|
|
789
792
|
}
|
|
793
|
+
if (solver?.isoStrict && !isStrictIsoPcsCharacter(value.name)) {
|
|
794
|
+
throw new PrologError('representation_error(character)', value);
|
|
795
|
+
}
|
|
790
796
|
return value;
|
|
791
797
|
}
|
|
792
798
|
function* charConversionBuiltin({ solver, goal, env }) {
|
|
793
|
-
const input = conversionCharacter(goal.args[0], env);
|
|
794
|
-
const output = conversionCharacter(goal.args[1], env);
|
|
799
|
+
const input = conversionCharacter(goal.args[0], env, false, solver);
|
|
800
|
+
const output = conversionCharacter(goal.args[1], env, false, solver);
|
|
795
801
|
if (input.name === output.name) solver.charConversions.delete(input.name);
|
|
796
802
|
else solver.charConversions.set(input.name, output.name);
|
|
797
803
|
yield env;
|
|
798
804
|
}
|
|
799
805
|
function* currentCharConversionBuiltin({ solver, goal, env }) {
|
|
800
|
-
const input = conversionCharacter(goal.args[0], env, true);
|
|
801
|
-
const output = conversionCharacter(goal.args[1], env, true);
|
|
806
|
+
const input = conversionCharacter(goal.args[0], env, true, solver);
|
|
807
|
+
const output = conversionCharacter(goal.args[1], env, true, solver);
|
|
802
808
|
for (const [from, to] of [...solver.charConversions]) {
|
|
803
809
|
const next = env.clone();
|
|
804
810
|
if (unify(input, atom(from), next) && unify(output, atom(to), next)) yield next;
|
|
@@ -845,6 +851,28 @@ function optionList(term, env) {
|
|
|
845
851
|
return items.map((item) => deref(item, env));
|
|
846
852
|
}
|
|
847
853
|
|
|
854
|
+
// read_term/3 and write_term/3 have prescribed error ordering in ISO 8.14.
|
|
855
|
+
// In particular, a partial option list (or a variable option element) is
|
|
856
|
+
// diagnosed before several stream errors, while the relative order of a
|
|
857
|
+
// non-list Options term and an invalid stream-or-alias differs between the
|
|
858
|
+
// read and write predicates. Split list-shape inspection from full option
|
|
859
|
+
// validation so those predicates can follow the standard order exactly.
|
|
860
|
+
function preflightOptionList(term, env) {
|
|
861
|
+
const value = deref(term, env);
|
|
862
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
863
|
+
const { items, tail } = listElements(value, env);
|
|
864
|
+
if (tail.type === VAR || items.some((item) => item.type === VAR)) {
|
|
865
|
+
throw new PrologError('instantiation_error');
|
|
866
|
+
}
|
|
867
|
+
const proper = tail.type === ATOM && tail.name === '[]';
|
|
868
|
+
return { value, items, proper };
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function requireProperOptionList(preflight) {
|
|
872
|
+
if (!preflight.proper) throw new PrologError('type_error(list)', preflight.value);
|
|
873
|
+
return preflight.items;
|
|
874
|
+
}
|
|
875
|
+
|
|
848
876
|
function optionAtom(option, name) {
|
|
849
877
|
if (option.type !== COMPOUND || option.name !== name || option.arity !== 1) return null;
|
|
850
878
|
return option.args[0];
|
|
@@ -1044,6 +1072,9 @@ function inputUnitBuiltin(name) {
|
|
|
1044
1072
|
throw error;
|
|
1045
1073
|
}
|
|
1046
1074
|
if (unit == null && !peek) stream.pastEnd = true;
|
|
1075
|
+
if (!binary && unit != null && solver.isoStrict && !isStrictIsoPcsCharacter(unit)) {
|
|
1076
|
+
throw new PrologError('representation_error(character)');
|
|
1077
|
+
}
|
|
1047
1078
|
const result = unit == null ? (binary ? numberTerm(-1) : name.endsWith('code') ? numberTerm(-1) : atom('end_of_file'))
|
|
1048
1079
|
: binary ? numberTerm(unit) : name.endsWith('code') ? numberTerm(unit.codePointAt(0)) : atom(unit);
|
|
1049
1080
|
const target = goal.args[goal.arity - 1];
|
|
@@ -1059,6 +1090,9 @@ function outputUnitBuiltin(name) {
|
|
|
1059
1090
|
if (name === 'put_char') {
|
|
1060
1091
|
if (stream.type !== 'text') throw new PrologError('permission_error(output, binary_stream)', streamHandle(stream.id));
|
|
1061
1092
|
if (!oneChar(value)) throw new PrologError('type_error(character)', value);
|
|
1093
|
+
if (solver.isoStrict && !isStrictIsoPcsCharacter(value.name)) {
|
|
1094
|
+
throw new PrologError('representation_error(character)', value);
|
|
1095
|
+
}
|
|
1062
1096
|
solver.io.writeUnit(stream, value.name);
|
|
1063
1097
|
} else {
|
|
1064
1098
|
if ((name === 'put_byte') !== (stream.type === 'binary')) {
|
|
@@ -1068,6 +1102,9 @@ function outputUnitBuiltin(name) {
|
|
|
1068
1102
|
const code = BigInt(value.name);
|
|
1069
1103
|
const max = name === 'put_byte' ? 255n : 0x10ffffn;
|
|
1070
1104
|
if (code < 0n || code > max) throw new PrologError(name === 'put_byte' ? 'type_error(byte)' : 'representation_error(character_code)');
|
|
1105
|
+
if (name === 'put_code' && solver.isoStrict && !isStrictIsoPcsCodePoint(Number(code))) {
|
|
1106
|
+
throw new PrologError('representation_error(character_code)');
|
|
1107
|
+
}
|
|
1071
1108
|
solver.io.writeUnit(stream, name === 'put_byte' ? Number(code) : String.fromCodePoint(Number(code)));
|
|
1072
1109
|
}
|
|
1073
1110
|
yield env;
|
|
@@ -1095,6 +1132,9 @@ function* termTextCandidates(stream, solver) {
|
|
|
1095
1132
|
let quote = null, lineComment = false, blockComment = false;
|
|
1096
1133
|
for (let i = stream.position; i < source.length; i++) {
|
|
1097
1134
|
const ch = source[i], next = source[i + 1];
|
|
1135
|
+
if (solver.isoStrict && !isStrictIsoPcsCodePoint(ch.charCodeAt(0))) {
|
|
1136
|
+
throw new PrologError('representation_error(character)');
|
|
1137
|
+
}
|
|
1098
1138
|
// Text streams preserve invalid UTF-8 bytes as an impossible Unicode
|
|
1099
1139
|
// sentinel. read/1-2 and read_term/2-3 must surface the same character
|
|
1100
1140
|
// representation error as get_char/1-2 instead of misclassifying the
|
|
@@ -1106,7 +1146,7 @@ function* termTextCandidates(stream, solver) {
|
|
|
1106
1146
|
if (blockComment) { if (ch === '*' && next === '/') { blockComment = false; i++; } continue; }
|
|
1107
1147
|
if (quote) {
|
|
1108
1148
|
if (ch === '\\') i = quotedEscapeEnd(source, i);
|
|
1109
|
-
else if (ch !== ' ' && /^[\
|
|
1149
|
+
else if (ch !== ' ' && /^[\u0000-\u001f\u007f]$/.test(ch)) {
|
|
1110
1150
|
// Literal layout characters are not quoted characters (6.4.2.1).
|
|
1111
1151
|
// Surface the lexical error immediately even when there is no later
|
|
1112
1152
|
// full stop; otherwise read/1 would misreport malformed input as EOF.
|
|
@@ -1137,7 +1177,7 @@ function hasNonLayoutRemainder(source, start) {
|
|
|
1137
1177
|
return lastNonLayoutIndex(source, start) >= start;
|
|
1138
1178
|
}
|
|
1139
1179
|
function lastNonLayoutIndex(source, start = 0) {
|
|
1140
|
-
const ignored = /[\
|
|
1180
|
+
const ignored = /[\u0000-\u0020\u007f]+|%[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\//g;
|
|
1141
1181
|
ignored.lastIndex = start;
|
|
1142
1182
|
let cursor = start;
|
|
1143
1183
|
let last = -1;
|
|
@@ -1248,7 +1288,9 @@ function readTermFromStream(stream, solver) {
|
|
|
1248
1288
|
stream.position = candidate.end;
|
|
1249
1289
|
return scopeReadTerm(term);
|
|
1250
1290
|
} catch (error) {
|
|
1251
|
-
if (error instanceof NumberRepresentationError
|
|
1291
|
+
if (error instanceof NumberRepresentationError || error instanceof CharacterRepresentationError) {
|
|
1292
|
+
throw new PrologError(error.formal);
|
|
1293
|
+
}
|
|
1252
1294
|
// A dot inside a graphic operator, such as =.., is only a possible
|
|
1253
1295
|
// terminator. Keep scanning until a complete term parses.
|
|
1254
1296
|
}
|
|
@@ -1288,17 +1330,38 @@ function* readBuiltin({ solver, goal, env }) {
|
|
|
1288
1330
|
const { term } = readTermFromStream(stream, solver);
|
|
1289
1331
|
if (unify(goal.args[goal.arity - 1], term, next)) yield next;
|
|
1290
1332
|
}
|
|
1333
|
+
function validateReadOptions(options) {
|
|
1334
|
+
for (const option of options) {
|
|
1335
|
+
if (option.type !== COMPOUND || option.arity !== 1 ||
|
|
1336
|
+
!['variables', 'variable_names', 'singletons'].includes(option.name)) {
|
|
1337
|
+
throw new PrologError('domain_error(read_option)', option);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1291
1342
|
function* readTermBuiltin({ solver, goal, env }) {
|
|
1292
|
-
const
|
|
1343
|
+
const optionTerm = goal.args[goal.arity - 1];
|
|
1344
|
+
const streamTerm = goal.arity === 3 ? deref(goal.args[0], env) : null;
|
|
1345
|
+
|
|
1346
|
+
// ISO 8.14.1.3 error order:
|
|
1347
|
+
// a) variable stream; b) partial/options-variable; c) bad stream-or-alias;
|
|
1348
|
+
// d) non-list options; e) invalid read option; then stream existence/mode.
|
|
1349
|
+
if (streamTerm?.type === VAR) throw new PrologError('instantiation_error');
|
|
1350
|
+
const preflight = preflightOptionList(optionTerm, env);
|
|
1351
|
+
if (goal.arity === 3) streamReference(goal.args[0], env);
|
|
1352
|
+
const options = requireProperOptionList(preflight);
|
|
1353
|
+
validateReadOptions(options);
|
|
1354
|
+
|
|
1355
|
+
const stream = goal.arity === 2
|
|
1356
|
+
? solver.io.resolve(solver.io.currentInput)
|
|
1357
|
+
: requireStream(solver, goal.args[0], env, 'read');
|
|
1293
1358
|
if (stream.type !== 'text') throw new PrologError('permission_error(input, binary_stream)', streamHandle(stream.id));
|
|
1294
|
-
|
|
1359
|
+
|
|
1295
1360
|
const target = goal.args[goal.arity - 2];
|
|
1296
1361
|
const { term, variables } = readTermFromStream(stream, solver);
|
|
1297
1362
|
const next = env.clone();
|
|
1298
1363
|
if (!unify(target, term, next)) return;
|
|
1299
1364
|
for (const option of options) {
|
|
1300
|
-
if (option.type === VAR) throw new PrologError('instantiation_error');
|
|
1301
|
-
if (option.type !== COMPOUND || option.arity !== 1) throw new PrologError('domain_error(read_option)', option);
|
|
1302
1365
|
let value;
|
|
1303
1366
|
if (option.name === 'variables') {
|
|
1304
1367
|
value = listFromItems(variables.map((item) => item.term));
|
|
@@ -1306,12 +1369,10 @@ function* readTermBuiltin({ solver, goal, env }) {
|
|
|
1306
1369
|
value = listFromItems(variables
|
|
1307
1370
|
.filter((item) => !item.anonymous)
|
|
1308
1371
|
.map((item) => compound('=', [atom(item.sourceName), item.term])));
|
|
1309
|
-
} else
|
|
1372
|
+
} else {
|
|
1310
1373
|
value = listFromItems(variables
|
|
1311
1374
|
.filter((item) => !item.anonymous && item.count === 1)
|
|
1312
1375
|
.map((item) => compound('=', [atom(item.sourceName), item.term])));
|
|
1313
|
-
} else {
|
|
1314
|
-
throw new PrologError('domain_error(read_option)', option);
|
|
1315
1376
|
}
|
|
1316
1377
|
if (!unify(option.args[0], value, next)) return;
|
|
1317
1378
|
}
|
|
@@ -1360,23 +1421,26 @@ function writeVariableNames(value, env, option) {
|
|
|
1360
1421
|
return names;
|
|
1361
1422
|
}
|
|
1362
1423
|
|
|
1363
|
-
function
|
|
1424
|
+
function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = null) {
|
|
1364
1425
|
const result = defaultTermWriteOptions(mode);
|
|
1365
|
-
for (const option of
|
|
1366
|
-
if (option.type === VAR) throw new PrologError('instantiation_error');
|
|
1426
|
+
for (const option of options) {
|
|
1367
1427
|
if (option.type !== COMPOUND || option.arity !== 1) {
|
|
1368
1428
|
throw new PrologError('domain_error(write_option)', option);
|
|
1369
1429
|
}
|
|
1370
1430
|
if (option.name === 'quoted') result.quoted = writeOptionBoolean(option.args[0], env, option);
|
|
1371
1431
|
else if (option.name === 'ignore_ops') result.ignoreOps = writeOptionBoolean(option.args[0], env, option);
|
|
1372
1432
|
else if (option.name === 'numbervars') result.numbervars = writeOptionBoolean(option.args[0], env, option);
|
|
1373
|
-
else if (option.name === 'double_quotes') result.doubleQuotes = writeOptionBoolean(option.args[0], env, option);
|
|
1374
1433
|
else if (option.name === 'variable_names') result.variableNames = writeVariableNames(option.args[0], env, option);
|
|
1375
|
-
else
|
|
1434
|
+
else if (option.name === 'double_quotes' && !solver?.isoStrict) {
|
|
1435
|
+
result.doubleQuotes = writeOptionBoolean(option.args[0], env, option);
|
|
1436
|
+
} else {
|
|
1437
|
+
throw new PrologError('domain_error(write_option)', option);
|
|
1438
|
+
}
|
|
1376
1439
|
}
|
|
1377
1440
|
return result;
|
|
1378
1441
|
}
|
|
1379
1442
|
|
|
1443
|
+
|
|
1380
1444
|
function writeBuiltin(mode) {
|
|
1381
1445
|
return function* ({ solver, goal, env }) {
|
|
1382
1446
|
const stream = outputStreamFor(solver, goal, env);
|
|
@@ -1392,9 +1456,22 @@ function writeBuiltin(mode) {
|
|
|
1392
1456
|
};
|
|
1393
1457
|
}
|
|
1394
1458
|
function* writeTermBuiltin({ solver, goal, env }) {
|
|
1395
|
-
const
|
|
1459
|
+
const optionTerm = goal.args[goal.arity - 1];
|
|
1460
|
+
const streamTerm = goal.arity === 3 ? deref(goal.args[0], env) : null;
|
|
1461
|
+
|
|
1462
|
+
// ISO 8.14.2.3 error order:
|
|
1463
|
+
// a) variable stream; b) partial/options-variable; c) non-list options;
|
|
1464
|
+
// d) bad stream-or-alias; e) invalid write option; then stream errors.
|
|
1465
|
+
if (streamTerm?.type === VAR) throw new PrologError('instantiation_error');
|
|
1466
|
+
const preflight = preflightOptionList(optionTerm, env);
|
|
1467
|
+
const optionItems = requireProperOptionList(preflight);
|
|
1468
|
+
if (goal.arity === 3) streamReference(goal.args[0], env);
|
|
1469
|
+
const options = termWriteOptionsFromItems(optionItems, env, 'write_term', solver);
|
|
1470
|
+
|
|
1471
|
+
const stream = goal.arity === 2
|
|
1472
|
+
? solver.io.resolve(solver.io.currentOutput)
|
|
1473
|
+
: requireStream(solver, goal.args[0], env, 'write');
|
|
1396
1474
|
if (stream.type !== 'text') throw new PrologError('permission_error(output, binary_stream)', streamHandle(stream.id));
|
|
1397
|
-
const options = termWriteOptions(goal.args[goal.arity - 1], env);
|
|
1398
1475
|
if (options.doubleQuotes === true) {
|
|
1399
1476
|
options.doubleQuotes = solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars';
|
|
1400
1477
|
} else {
|
|
@@ -1510,30 +1587,35 @@ function oneChar(value) {
|
|
|
1510
1587
|
return value.type === ATOM && characters(value.name).length === 1;
|
|
1511
1588
|
}
|
|
1512
1589
|
|
|
1513
|
-
function validCharacterCode(value) {
|
|
1590
|
+
function validCharacterCode(value, solver = null) {
|
|
1514
1591
|
if (value.type !== NUMBER || !isDecimalInteger(value.name)) return false;
|
|
1515
1592
|
const code = BigInt(value.name);
|
|
1516
|
-
|
|
1593
|
+
if (code < 0n || code > 0x10ffffn || (code >= 0xd800n && code <= 0xdfffn)) return false;
|
|
1594
|
+
return !solver?.isoStrict || isStrictIsoPcsCodePoint(Number(code));
|
|
1517
1595
|
}
|
|
1518
1596
|
|
|
1519
|
-
function listToAtomInput(list, env, kind) {
|
|
1597
|
+
function listToAtomInput(list, env, kind, solver = null) {
|
|
1520
1598
|
const { items, tail } = listElements(list, env);
|
|
1521
1599
|
if (tail.type === VAR || items.some((item) => item.type === VAR)) throw new PrologError('instantiation_error');
|
|
1522
1600
|
if (tail.type !== ATOM || tail.name !== '[]') throw new PrologError('type_error(list)', tail);
|
|
1523
1601
|
if (kind === 'chars') {
|
|
1524
1602
|
const invalid = items.find((item) => !oneChar(item));
|
|
1525
1603
|
if (invalid) throw new PrologError('type_error(character)', invalid);
|
|
1604
|
+
if (solver?.isoStrict) {
|
|
1605
|
+
const outsidePcs = items.find((item) => !isStrictIsoPcsCharacter(item.name));
|
|
1606
|
+
if (outsidePcs) throw new PrologError('representation_error(character)', outsidePcs);
|
|
1607
|
+
}
|
|
1526
1608
|
return items.map((item) => item.name).join('');
|
|
1527
1609
|
}
|
|
1528
1610
|
const nonInteger = items.find((item) => item.type !== NUMBER || !isDecimalInteger(item.name));
|
|
1529
1611
|
if (nonInteger) throw new PrologError('type_error(integer)', nonInteger);
|
|
1530
|
-
const invalid = items.find((item) => !validCharacterCode(item));
|
|
1612
|
+
const invalid = items.find((item) => !validCharacterCode(item, solver));
|
|
1531
1613
|
if (invalid) throw new PrologError('representation_error(character_code)');
|
|
1532
1614
|
return items.map((item) => String.fromCodePoint(Number(item.name))).join('');
|
|
1533
1615
|
}
|
|
1534
1616
|
|
|
1535
1617
|
function atomListBuiltin(kind) {
|
|
1536
|
-
return function* ({ goal, env }) {
|
|
1618
|
+
return function* ({ solver, goal, env }) {
|
|
1537
1619
|
const value = deref(goal.args[0], env);
|
|
1538
1620
|
if (value.type !== VAR && value.type !== ATOM) throw new PrologError('type_error(atom)', value);
|
|
1539
1621
|
const list = deref(goal.args[1], env);
|
|
@@ -1546,7 +1628,7 @@ function atomListBuiltin(kind) {
|
|
|
1546
1628
|
}
|
|
1547
1629
|
const invalid = supplied.find((item) => item.type !== VAR &&
|
|
1548
1630
|
(kind === 'chars' ? !oneChar(item) :
|
|
1549
|
-
item.type !== NUMBER || !isDecimalInteger(item.name) || !validCharacterCode(item)));
|
|
1631
|
+
item.type !== NUMBER || !isDecimalInteger(item.name) || !validCharacterCode(item, solver)));
|
|
1550
1632
|
if (invalid) {
|
|
1551
1633
|
if (kind === 'chars') throw new PrologError('type_error(character)', invalid);
|
|
1552
1634
|
if (invalid.type !== NUMBER || !isDecimalInteger(invalid.name)) {
|
|
@@ -1554,26 +1636,32 @@ function atomListBuiltin(kind) {
|
|
|
1554
1636
|
}
|
|
1555
1637
|
throw new PrologError('representation_error(character_code)');
|
|
1556
1638
|
}
|
|
1639
|
+
if (solver.isoStrict && characters(value.name).some((ch) => !isStrictIsoPcsCharacter(ch))) {
|
|
1640
|
+
throw new PrologError('representation_error(character)', value);
|
|
1641
|
+
}
|
|
1557
1642
|
const items = characters(value.name).map((ch) =>
|
|
1558
1643
|
kind === 'chars' ? atom(ch) : numberTerm(ch.codePointAt(0)));
|
|
1559
1644
|
if (unify(goal.args[1], listFromItems(items), next)) yield next;
|
|
1560
1645
|
return;
|
|
1561
1646
|
}
|
|
1562
|
-
if (unify(goal.args[0], atom(listToAtomInput(list, env, kind)), next)) yield next;
|
|
1647
|
+
if (unify(goal.args[0], atom(listToAtomInput(list, env, kind, solver)), next)) yield next;
|
|
1563
1648
|
};
|
|
1564
1649
|
}
|
|
1565
1650
|
const atomCharsBuiltin = atomListBuiltin('chars');
|
|
1566
1651
|
const atomCodesBuiltin = atomListBuiltin('codes');
|
|
1567
1652
|
|
|
1568
|
-
function* charCodeBuiltin({ goal, env }) {
|
|
1653
|
+
function* charCodeBuiltin({ solver, goal, env }) {
|
|
1569
1654
|
const char = deref(goal.args[0], env);
|
|
1570
1655
|
const code = deref(goal.args[1], env);
|
|
1571
1656
|
if (char.type === VAR && code.type === VAR) throw new PrologError('instantiation_error');
|
|
1572
1657
|
if (char.type !== VAR && !oneChar(char)) throw new PrologError('type_error(character)', char);
|
|
1658
|
+
if (char.type === ATOM && solver.isoStrict && !isStrictIsoPcsCharacter(char.name)) {
|
|
1659
|
+
throw new PrologError('representation_error(character)', char);
|
|
1660
|
+
}
|
|
1573
1661
|
if (code.type !== VAR && (code.type !== NUMBER || !isDecimalInteger(code.name))) {
|
|
1574
1662
|
throw new PrologError('type_error(integer)', code);
|
|
1575
1663
|
}
|
|
1576
|
-
if (code.type !== VAR && !validCharacterCode(code)) throw new PrologError('representation_error(character_code)');
|
|
1664
|
+
if (code.type !== VAR && !validCharacterCode(code, solver)) throw new PrologError('representation_error(character_code)');
|
|
1577
1665
|
const next = env.clone();
|
|
1578
1666
|
if (char.type === ATOM) {
|
|
1579
1667
|
if (unify(goal.args[1], numberTerm(char.name.codePointAt(0)), next)) yield next;
|
|
@@ -1583,7 +1671,7 @@ function* charCodeBuiltin({ goal, env }) {
|
|
|
1583
1671
|
function skipNumberLayout(text, start) {
|
|
1584
1672
|
let position = start;
|
|
1585
1673
|
while (true) {
|
|
1586
|
-
while (position < text.length && /[\
|
|
1674
|
+
while (position < text.length && /[\u0000-\u0020\u007f]/.test(text[position])) {
|
|
1587
1675
|
position++;
|
|
1588
1676
|
}
|
|
1589
1677
|
if (text[position] === '%') {
|
|
@@ -1647,7 +1735,7 @@ function parseIsoNumber(text) {
|
|
|
1647
1735
|
// there without separating layout: `/` *can* continue the graphic token,
|
|
1648
1736
|
// and the eager-consumer rule therefore keeps `-/**/1` ill-formed (the
|
|
1649
1737
|
// number_chars continuation corpus case 24).
|
|
1650
|
-
if (/[\
|
|
1738
|
+
if (/[\u0000-\u0020\u007f]/.test(next) || next === '%') {
|
|
1651
1739
|
position = skipNumberLayout(text, position + 1);
|
|
1652
1740
|
sign = '-';
|
|
1653
1741
|
}
|
package/src/parser.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// It preserves the compact Prolog-like syntax while producing Term objects for the solver.
|
|
3
3
|
import { ATOM, COMPOUND, atom, compound, cons, emptyList, numberTerm, variable } from './term.js';
|
|
4
4
|
import { continuesGraphicToken, isTerminatingFullStop } from './syntax-scan.js';
|
|
5
|
+
import { CharacterRepresentationError, isStrictIsoPcsCodePoint } from './iso-character.js';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
export class NumberRepresentationError extends Error {
|
|
@@ -33,7 +34,10 @@ const TOK = {
|
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
function isWhitespaceCode(code) {
|
|
36
|
-
|
|
37
|
+
// EyeProlog classifies ASCII C0 controls and DEL as layout characters. In
|
|
38
|
+
// strict mode these are the implementation-defined extended-layout members
|
|
39
|
+
// of the ASCII processor character set.
|
|
40
|
+
return (code >= 0 && code <= 32) || code === 127;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
function isDigitCode(code) {
|
|
@@ -232,7 +236,11 @@ class Parser {
|
|
|
232
236
|
return this.parserFlagState.charConversions.get(character) ?? character;
|
|
233
237
|
}
|
|
234
238
|
rawPeek(offset = 0) {
|
|
235
|
-
|
|
239
|
+
const ch = this.source[this.pos + offset] ?? '';
|
|
240
|
+
if (this.strictIso && ch && !isStrictIsoPcsCodePoint(ch.charCodeAt(0))) {
|
|
241
|
+
throw new CharacterRepresentationError();
|
|
242
|
+
}
|
|
243
|
+
return ch;
|
|
236
244
|
}
|
|
237
245
|
rawTake() {
|
|
238
246
|
const ch = this.rawPeek();
|
|
@@ -410,6 +418,7 @@ class Parser {
|
|
|
410
418
|
if (code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
|
|
411
419
|
throw new Error(`parse line ${line}: character escape out of range`);
|
|
412
420
|
}
|
|
421
|
+
if (this.strictIso && !isStrictIsoPcsCodePoint(code)) throw new CharacterRepresentationError();
|
|
413
422
|
return String.fromCodePoint(code);
|
|
414
423
|
}
|
|
415
424
|
if (/^[0-7]$/.test(escaped)) {
|
|
@@ -420,6 +429,7 @@ class Parser {
|
|
|
420
429
|
if (code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
|
|
421
430
|
throw new Error(`parse line ${line}: character escape out of range`);
|
|
422
431
|
}
|
|
432
|
+
if (this.strictIso && !isStrictIsoPcsCodePoint(code)) throw new CharacterRepresentationError();
|
|
423
433
|
return String.fromCodePoint(code);
|
|
424
434
|
}
|
|
425
435
|
// A backslash followed by a decimal digit is numeric-escape syntax, but
|
package/src/solver.js
CHANGED
|
@@ -951,12 +951,12 @@ const ISO_CORE_FLAG_NAMES = new Set([
|
|
|
951
951
|
|
|
952
952
|
function defaultPrologFlags(unknown = 'error', strictIso = false) {
|
|
953
953
|
const flags = new Map([
|
|
954
|
-
['bounded', { value: compound('false', []), allowed: ['false'], changeable: false }],
|
|
955
|
-
['integer_rounding_function', { value: compound('toward_zero', []), allowed: ['toward_zero'], changeable: false }],
|
|
954
|
+
['bounded', { value: compound('false', []), allowed: ['true', 'false'], changeable: false }],
|
|
955
|
+
['integer_rounding_function', { value: compound('toward_zero', []), allowed: ['down', 'toward_zero'], changeable: false }],
|
|
956
956
|
['char_conversion', { value: compound('on', []), allowed: ['on', 'off'], changeable: true }],
|
|
957
957
|
['debug', { value: compound('off', []), allowed: ['on', 'off'], changeable: true }],
|
|
958
|
-
['max_integer', { value: null, allowed: [
|
|
959
|
-
['min_integer', { value: null, allowed: [
|
|
958
|
+
['max_integer', { value: null, allowed: [], changeable: false }],
|
|
959
|
+
['min_integer', { value: null, allowed: [], changeable: false }],
|
|
960
960
|
['max_arity', { value: compound('unbounded', []), allowed: ['unbounded'], changeable: false }],
|
|
961
961
|
['unknown', { value: compound(unknown, []), allowed: ['error', 'fail', 'warning'], changeable: true }],
|
|
962
962
|
['double_quotes', { value: compound('chars', []), allowed: ['chars', 'codes', 'atom'], changeable: true }],
|
package/src/syntax-scan.js
CHANGED
|
@@ -46,7 +46,7 @@ export function isTerminatingFullStop(source, index, convert = null) {
|
|
|
46
46
|
// token character accepted by continuesGraphicToken().
|
|
47
47
|
if (continuesGraphicToken(source, index, convert)) return false;
|
|
48
48
|
if (next === '' || next === '%' || next === '\n' || next === '\r') return true;
|
|
49
|
-
if (/^[\
|
|
49
|
+
if (/^[\u0000-\u0020\u007f]$/.test(next)) return true;
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -30,17 +30,17 @@ error-ordering alternative to an individual executable assertion.
|
|
|
30
30
|
|
|
31
31
|
| Standard area | Status | Current evidence |
|
|
32
32
|
| --- | --- | --- |
|
|
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,
|
|
34
|
-
| 7.1-7.3 — term types, term order, unification | audit | Standard-order, identity, finite-tree and occurs-check suites, Corrigendum 2 term predicates. |
|
|
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
|
+
| 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 and PCS-based atom collation. |
|
|
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` now affects later unquoted source text and respects `char_conversion=off`. Cross-text `multifile/1` and ordering constraints still require explicit shall-by-shall audit. |
|
|
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. Public/private and multi-text requirements still need complete mapping. |
|
|
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
39
|
| 7.9 — expression evaluation | audit | Arithmetic/evaluation/error suites, including Corrigenda. Exceptional-value/error-precedence matrix remains to be exhaustively enumerated. |
|
|
40
|
-
| 7.10 — input/output concepts | audit | Stream, character/byte I/O, read/write options, operator-sensitive write-back, Corrigendum 3 writer cases.
|
|
41
|
-
| 7.11 — flags |
|
|
42
|
-
| 7.12 — errors | audit | ISO `error(Error, Context)` envelope, type/domain/permission/representation/evaluation/syntax families and focused error cases.
|
|
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.
|
|
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 Part 1/Corrigendum 3 `read_term/2-3` and `write_term/2-3` option surfaces and ISO 8.14.1-8.14.2 prescribed error ordering now have dedicated strict tests; the wider stream/open/close option cross-product remains open. |
|
|
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 now covered for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3`; prescribed ordering for the remaining built-in families is still being mapped. |
|
|
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 8.14 term-I/O/operator subfamily now has dedicated strict option-surface and prescribed-error-order assertions, and 8.17 flag behavior is closed; the remaining mode/error matrix is not yet one-row-per-standard-row. |
|
|
44
44
|
| Clause 9 — evaluable functors | audit | Integer/float/rounding/transcendental/bitwise suites and corrigendum cases. Host floating-point representation choices remain documented implementation-defined behavior. |
|
|
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. |
|
|
@@ -59,10 +59,27 @@ conformance claims:
|
|
|
59
59
|
when the `char_conversion` flag is `on`, leaves quoted characters unchanged,
|
|
60
60
|
and feeds the same mapping into execution-time term input.
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
A follow-on audit closes the processor-character-set/collation choices rather
|
|
63
|
+
than leaving them implicit. `--iso-strict` now selects the 128-character ASCII
|
|
64
|
+
PCS U+0000..U+007F, classifies C0 controls and DEL as extended layout
|
|
65
|
+
characters, and uses the code point itself as each collating-sequence integer.
|
|
66
|
+
Characters/codes outside that PCS raise representation errors in strict
|
|
67
|
+
parsing, term input, character conversion, and character-code predicates. The
|
|
68
|
+
normal profile retains Unicode scalar character data as an explicit extension.
|
|
69
|
+
The complete WG17 syntax matrix remains green under this narrower strict
|
|
70
|
+
boundary.
|
|
71
|
+
|
|
72
|
+
A further issue #65 audit closes the Part 1 flag family and tightens the 8.14
|
|
73
|
+
term-I/O/operator error rules. The strict flag registry now distinguishes a
|
|
74
|
+
standard value that is valid but not selectable from a value outside the
|
|
75
|
+
standard domain: attempts to change fixed `bounded` or
|
|
76
|
+
`integer_rounding_function` to another standard value therefore reach the
|
|
77
|
+
required permission error. `read_term/3`, `write_term/3`, `op/3`, and
|
|
78
|
+
`current_op/3` now follow their prescribed error precedence, including cases
|
|
79
|
+
where more than one argument is erroneous. Strict `write_term/2-3` accepts only
|
|
80
|
+
the Part 1 plus Corrigendum 3 option surface; the normal-profile
|
|
81
|
+
`double_quotes/1` write option remains an explicitly documented EyeProlog
|
|
82
|
+
extension.
|
|
66
83
|
|
|
67
84
|
## Strict-core boundary
|
|
68
85
|
|
|
@@ -19,21 +19,21 @@ Status values are:
|
|
|
19
19
|
- **defined** — the current behavior is implemented and stated here;
|
|
20
20
|
- **not applicable** — the standard decision is conditional and the condition
|
|
21
21
|
is false for EyeProlog's selected profile;
|
|
22
|
-
- **audit gap** —
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
- **audit gap** — retained for any future implementation-defined choice whose
|
|
23
|
+
code/documentation boundary is still unresolved. Open *normative* shall-by-
|
|
24
|
+
shall work is tracked separately in `ISO-COMPLIANCE.md`.
|
|
25
25
|
|
|
26
26
|
## Explicit implementation-defined decisions
|
|
27
27
|
|
|
28
28
|
| Clause | Decision completed by ISO 5.4 documentation | EyeProlog choice | Status / implementation evidence |
|
|
29
29
|
| --- | --- | --- | --- |
|
|
30
30
|
| 5.5.11 | Reserved atoms and the effect of instantiating a variable to one | EyeProlog reserves no Prolog atom under 5.5.11. Atoms with implementation-looking names remain ordinary terms unless a particular predicate interprets them. | **defined** — term representation and built-ins in `src/term.js`, `src/iso.js`. |
|
|
31
|
-
| 6.5 | Processor character set (PCS) |
|
|
32
|
-
| 6.5 | Classification of additional/extended PCS characters |
|
|
33
|
-
| 6.6 | Collating-sequence integers |
|
|
34
|
-
| 6.6 | Collating values of control escapes and extended characters |
|
|
31
|
+
| 6.5 | Processor character set (PCS) | In `--iso-strict`, PCS is the 128-character 7-bit ASCII set U+0000..U+007F. Normal mode additionally accepts Unicode scalar values in character data as an implementation-specific extension. | **defined** — `src/iso-character.js`, strict parser/character-I/O guards, and strict-core/WG17 coverage. |
|
|
32
|
+
| 6.5 | Classification of additional/extended PCS characters | Printable ASCII uses the lexical classes specified by Part 1. ASCII C0 controls U+0000..U+001F and DEL U+007F are EyeProlog's extended **layout** characters; they may therefore separate tokens, while quoted control values are written/read through the ISO escape forms. No non-ASCII character belongs to strict PCS. | **defined** — `src/parser.js`, `src/syntax-scan.js`, `src/iso-character.js`. |
|
|
33
|
+
| 6.6 | Collating-sequence integers | In strict mode each PCS character's collating-sequence integer is its ASCII/Unicode code point, 0..127. Atom comparison is lexicographic by the same code-unit values, which coincide with those integers throughout strict PCS and satisfy the required capital-letter, small-letter, and decimal-digit constraints. | **defined** — `src/iso-character.js`, `src/term.js` (`compareTerms`), `src/iso.js` character-code predicates. |
|
|
34
|
+
| 6.6 | Collating values of control escapes and extended characters | Strict control/extended-layout characters use their ASCII code point as collating integer, so octal/hexadecimal escapes and character-code constants map to the same 0..127 PCS. Normal-mode Unicode character codes use Unicode scalar values; that broader ordering is outside the strict Part 1 profile. | **defined** — `src/iso-character.js`, parser escape handling, `char_code/2`, `atom_codes/2`, and WG17 escape cases. |
|
|
35
35
|
| 7.1.2.2 | Mapping between a character code and bytes | Text file streams decode and encode UTF-8. Binary streams expose bytes 0..255 directly. | **defined** — `src/io.js`. |
|
|
36
|
-
| 7.1.4.1 | Set `C` of characters represented by one-char atoms |
|
|
36
|
+
| 7.1.4.1 | Set `C` of characters represented by one-char atoms | In strict mode `C` is exactly the ASCII PCS U+0000..U+007F. Normal mode extends character predicates to Unicode scalar values U+0000..U+10FFFF excluding surrogates. | **defined** — `src/iso-character.js`, `src/iso.js` character-code validation. |
|
|
37
37
|
| 7.4.2.4 | Whether `op/3` directives affect other Prolog texts or execution | An `op/3` directive changes parsing of subsequent text loaded into the same `Program`; the resulting operator table is also used by execution-time term I/O. Separately created `Program` objects are independent. | **defined** — `src/parser.js`, `src/program.js`, `src/iso.js`. |
|
|
38
38
|
| 7.4.2.5 | Whether directive-created `Convc` affects other text/execution | Yes. A `char_conversion/2` directive updates preparation-time conversion for later unquoted source characters and the recorded mapping initializes execution-time term input. Quoted characters are not converted; `char_conversion=off` disables following preparation-time conversion. | **defined** — `src/parser.js`, `src/program.js`, `src/solver.js`; strict-core regression coverage. |
|
|
39
39
|
| 7.4.2.6 | Order of `initialization/1` goals | Initialization goals run once, in source/inclusion order, before requested goals; each must obtain a first solution. | **defined** — `Program.initializations`, `Solver.runInitializations()`. |
|
|
@@ -58,18 +58,18 @@ Status values are:
|
|
|
58
58
|
| 7.10.2.11 | Default `eof_action` | Newly opened file streams default to `error`. The predefined standard streams use `reset`. | **defined** — `src/io.js`. |
|
|
59
59
|
| 7.10.2.13 | `eof_action(Action)` property when the stream uses its default action | Reports the same selected action: `error` for ordinary opened files and `reset` for the predefined standard streams. | **defined** — `streamProperties()` in `src/iso.js`, defaults in `src/io.js`. |
|
|
60
60
|
| 7.10.2.11 | Whether `reposition(false)` streams may nevertheless be repositioned | No. `set_stream_position/2` raises a permission error unless the stream was created with `reposition(true)`. | **defined** — `src/iso.js`. |
|
|
61
|
-
| 7.11.1.1 | Default `bounded` flag | `false`: EyeProlog's Prolog integer model uses arbitrary-precision `BigInt`, subject to host resources. | **defined** — `defaultPrologFlags()` in `src/solver.js
|
|
61
|
+
| 7.11.1.1 | Default `bounded` flag | `false`: EyeProlog's Prolog integer model uses arbitrary-precision `BigInt`, subject to host resources. The standard values remain `true` and `false`; because this flag is fixed, selecting either value through `set_prolog_flag/2` reaches the standard non-changeable-flag rule rather than narrowing the value domain to the current choice. | **defined** — `defaultPrologFlags()` in `src/solver.js`; strict flag tests. |
|
|
62
62
|
| 7.11.1.2 | Default `max_integer` when `bounded=true` | Not applicable because EyeProlog selects `bounded=false`; consequently `current_prolog_flag(max_integer, _)` fails. | **not applicable** — `src/solver.js`, `current_prolog_flag/2`; strict-core regression coverage. |
|
|
63
63
|
| 7.11.1.3 | Default `min_integer` when `bounded=true` | Not applicable because EyeProlog selects `bounded=false`; consequently `current_prolog_flag(min_integer, _)` fails. | **not applicable** — `src/solver.js`, `current_prolog_flag/2`; strict-core regression coverage. |
|
|
64
|
-
| 7.11.1.4 | Default `integer_rounding_function` flag | `toward_zero`; `//` uses truncation toward zero. `div` is provided separately with downward/floor division semantics. | **defined** — `src/solver.js`, `src/iso-arithmetic.js
|
|
64
|
+
| 7.11.1.4 | Default `integer_rounding_function` flag | `toward_zero`; `//` uses truncation toward zero. The standard flag values are `down` and `toward_zero`, but the selected flag is non-changeable; an attempt to select the other valid value is therefore a permission error. `div` is provided separately with downward/floor division semantics. | **defined** — `src/solver.js`, `src/iso-arithmetic.js`; strict flag tests. |
|
|
65
65
|
| 9.1.3.1 | Integer division rounding function `rndI` | Truncation toward zero, matching the `integer_rounding_function=toward_zero` flag. | **defined** — BigInt division for `//` in `src/iso-arithmetic.js`. |
|
|
66
66
|
| 7.11.2.1 | Whether preparation-time `Convc` affects execution-time `Convc` | Yes: mappings created while Prolog text is prepared are retained and initialize the solver's execution-time conversion map. | **defined** — `src/parser.js`, `src/program.js`, `src/solver.js`. |
|
|
67
67
|
| 7.11.2.2 | Effect when `debug=on` | The flag is accepted and stored; it does not change goal semantics or enable a debugger. | **defined** — `src/solver.js`; no semantic branch depends on `debug`. |
|
|
68
68
|
| 7.11.2.3 | Default `max_arity` | `unbounded` in the Prolog model, subject to host memory and practical JavaScript array/index limits. | **defined** — `src/solver.js`; relevant guards report representation/resource errors. |
|
|
69
69
|
| 7.11.2.5 | Default `double_quotes` | `chars`. | **defined** — `src/solver.js`, parser flag state. |
|
|
70
70
|
| 7.12.1 | Second argument of `error/2` | The default context term is the atom `eyeprolog`. A few implementation-specific diagnostics may deliberately supply a more specific context term. | **defined** — `formalErrorTerm()` in `src/iso.js`. |
|
|
71
|
-
| 7.12.2(f) | Implementation-defined representation limits |
|
|
72
|
-
| 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` flag. With `bounded=false`, `max_integer` and `min_integer` have no current value and their `current_prolog_flag/2` queries fail. | **defined** — strict registry/flag filtering in `src/solver.js
|
|
71
|
+
| 7.12.2(f) | Implementation-defined representation limits | Strict character and character-code operations are limited to the selected ASCII PCS/collating integers 0..127. Normal mode extends characters to Unicode scalar values. Arity/integer values are modeled as unbounded but may hit host/resource limits. Float input overflow uses the implementation-specific `max_float`/`min_float` representation names documented by the STC-oriented tests. | **defined** — parser/ISO numeric and character guards. |
|
|
72
|
+
| 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` flag. With `bounded=false`, `max_integer` and `min_integer` have no current or selectable value and their `current_prolog_flag/2` queries fail. Valid alternative values of fixed standard flags are distinguished from invalid values so `set_prolog_flag/2` reports permission versus domain errors as prescribed. | **defined** — strict registry/flag filtering in `src/solver.js`; strict flag tests. |
|
|
73
73
|
| 8.17.3 | Other effects of `halt/0` | Terminates EyeProlog execution and returns host/process status `0`; it produces no Prolog solution. | **defined** — `HaltSignal`, `haltBuiltin()`, CLI/runner handling. |
|
|
74
74
|
| 8.17.4 | Meaning/effects of `halt(Status)` | Integer `Status` is converted to the host process/runner halt code; it produces no Prolog solution. | **defined** — `haltBuiltin()`, `src/execute.js`, `src/cli.js`. |
|
|
75
75
|
| 9.1.4.1 | Floating-point rounding function `rndF` | Floating values and operations use ECMAScript `Number` (IEEE-754 binary64) and the host's specified binary64 arithmetic/conversions. | **defined** — `src/iso-arithmetic.js`, `src/number-value.js`. |
|
|
@@ -105,7 +105,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
105
105
|
|
|
106
106
|
| Part 1 extension hook | EyeProlog normal-profile feature | Strict-core disposition |
|
|
107
107
|
| --- | --- | --- |
|
|
108
|
-
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion,
|
|
108
|
+
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, embedded quad syntax, and normal-mode Unicode character data | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax is rejected; strict character syntax/data is limited to the documented ASCII PCS. |
|
|
109
109
|
| 5.5.2 Predefined operators | Part 3 `|` and EyeProlog's labelable infix `(?-)/2`; CLP(Z) operators when that library is imported | Only the Part 1 operator table is predefined; a conforming `op/3` may still add permitted operators. |
|
|
110
110
|
| 5.5.3 Character-conversion mapping | No non-identity initial `Convc` extension | Identity initial mapping. |
|
|
111
111
|
| 5.5.4 Types | No additional runtime Prolog term type is exposed by the core solver | Only variable, integer, float, atom, and compound term ordering participates in strict mode. |
|
|
@@ -116,7 +116,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
116
116
|
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and interop autoloaded predicates | Strict registry contains only the Part 1 + Corrigenda core registry. |
|
|
117
117
|
| 5.5.10 Evaluable functors | Library/runtime extensions when registered outside the strict core | Strict arithmetic registry is the Part 1 + Corrigenda set. |
|
|
118
118
|
| 5.5.11 Reserved atoms | None | None. |
|
|
119
|
-
| Cor.3 5.5.12 Options | Extra library/host options may exist outside core option lists |
|
|
119
|
+
| Cor.3 5.5.12 Options | Extra library/host options may exist outside core option lists | Normal mode additionally accepts the EyeProlog `write_term/2-3` option `double_quotes(true|false)`. Strict core excludes that extension and accepts only the Part 1 plus Corrigendum 3 write-option surface; unknown extension options raise `domain_error(write_option,...)`. |
|
|
120
120
|
|
|
121
121
|
Normal mode provides documented module and DCG compatibility profiles whose
|
|
122
122
|
features overlap standardized Part 2 and Part 3 facilities. They are extensions
|
|
@@ -8,16 +8,16 @@ compliance audit and the remaining work before a full conformance claim.
|
|
|
8
8
|
|
|
9
9
|
| Standard area | Implementation | Representative executable coverage |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
-
| Clause 6 lexical and term syntax | tokenizer, operator parser, lists, curly terms, quotes, numeric syntax, comments | `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`, syntax error cases |
|
|
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
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 |
|
|
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` |
|
|
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 |
|
|
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, plus strict 8.14.1-8.14.4 option-surface/error-precedence tests for `read_term/3`, `write_term/3`, `op/3`, and `current_op/3` |
|
|
18
18
|
| 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin` |
|
|
19
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 | required
|
|
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 complete-flag audit in `run-iso-strict.mjs` |
|
|
21
21
|
| Clause 9 evaluable functors | integer, float, rounding, transcendental and bitwise operations | `arithmetic`, `corrigenda_arithmetic`, `corrigenda_atan2_zero`, `corrigenda_integer_negative_power` |
|
|
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 |
|
|
@@ -21,12 +21,17 @@ predicate indicators listed in Appendix B of the book across the Part 1 strict-c
|
|
|
21
21
|
target and the normal-mode module/DCG compatibility families. [ISO-COMPLIANCE.md](ISO-COMPLIANCE.md) is the explicit
|
|
22
22
|
processor-requirement ledger for the Part 1 strict-core audit. This suite is
|
|
23
23
|
still not an independent certification of every processor requirement, lexical
|
|
24
|
-
edge, option combination
|
|
24
|
+
edge, or option combination. Selected prescribed-error sequences are now
|
|
25
|
+
checked exactly (including ISO 8.14.1-8.14.4), while the remaining built-in
|
|
26
|
+
error-order matrix is tracked in the compliance ledger. Cases under `iso/`
|
|
25
27
|
identify standards-derived behavior; other directories cover EyeProlog host
|
|
26
28
|
contracts and extensions. EyeProlog-only execution features such as automatic
|
|
27
29
|
tabling and `tnot/1` well-founded negation are outside the Part 1 strict-core
|
|
28
30
|
claim. Their focused semantic coverage lives primarily in regression tests;
|
|
29
|
-
`tnot/1` is absent from the strict ISO registry.
|
|
31
|
+
`tnot/1` is absent from the strict ISO registry. The strict processor character
|
|
32
|
+
set is the documented 7-bit ASCII PCS with ASCII-code collation; normal-mode
|
|
33
|
+
Unicode character data is tested as an extension rather than folded into the
|
|
34
|
+
Part 1 claim.
|
|
30
35
|
|
|
31
36
|
All conformance files live under topic directories such as `arithmetic/`, `lists/`, `syntax/`, or `variables/`; new top-level numbered files should not be added. The report uses those directories as coverage categories.
|
|
32
37
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(
|
|
1
|
+
error(domain_error(operator_priority), 1.2)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(
|
|
1
|
+
error(domain_error(operator_priority), x)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(
|
|
1
|
+
error(domain_error(operator_specifier), 1)
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -46,6 +46,41 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
reporter.test('covers the Part 1 flag defaults, value domains, and changeability', () => {
|
|
50
|
+
const program = Program.parse('', { isoStrict: true });
|
|
51
|
+
const solver = new Solver(program, { isoStrict: true });
|
|
52
|
+
const answers = (text) => [...solver.solve([parseGoalText(text, { isoStrict: true })], new Env(), 0)].length;
|
|
53
|
+
for (const goalText of [
|
|
54
|
+
'current_prolog_flag(bounded,false)',
|
|
55
|
+
'current_prolog_flag(integer_rounding_function,toward_zero)',
|
|
56
|
+
'current_prolog_flag(char_conversion,on)',
|
|
57
|
+
'current_prolog_flag(debug,off)',
|
|
58
|
+
'current_prolog_flag(max_arity,unbounded)',
|
|
59
|
+
'current_prolog_flag(unknown,error)',
|
|
60
|
+
'current_prolog_flag(double_quotes,chars)',
|
|
61
|
+
]) equal(answers(goalText), 1, goalText);
|
|
62
|
+
|
|
63
|
+
for (const goalText of [
|
|
64
|
+
'set_prolog_flag(char_conversion,off)',
|
|
65
|
+
'set_prolog_flag(debug,on)',
|
|
66
|
+
'set_prolog_flag(unknown,fail)',
|
|
67
|
+
'set_prolog_flag(double_quotes,codes)',
|
|
68
|
+
]) run('', { isoStrict: true, goal: goalText });
|
|
69
|
+
|
|
70
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(bounded,true)' })).formal,
|
|
71
|
+
'permission_error(modify, flag)', 'bounded valid-but-fixed value');
|
|
72
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(integer_rounding_function,down)' })).formal,
|
|
73
|
+
'permission_error(modify, flag)', 'rounding valid-but-fixed value');
|
|
74
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(max_arity,unbounded)' })).formal,
|
|
75
|
+
'permission_error(modify, flag)', 'max_arity fixed value');
|
|
76
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(max_integer,unbounded)' })).formal,
|
|
77
|
+
'domain_error(flag_value)', 'max_integer has no value when unbounded');
|
|
78
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(unknown,maybe)' })).formal,
|
|
79
|
+
'domain_error(flag_value)', 'unknown value domain');
|
|
80
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'set_prolog_flag(not_a_flag,on)' })).formal,
|
|
81
|
+
'domain_error(prolog_flag)', 'unsupported flag');
|
|
82
|
+
});
|
|
83
|
+
|
|
49
84
|
reporter.test('preparation-time char_conversion affects later unquoted source only', () => {
|
|
50
85
|
const program = Program.parse(
|
|
51
86
|
":- char_conversion(x,y).\np(x).\nquoted('x').\n:- set_prolog_flag(char_conversion,off).\nraw(x).\n",
|
|
@@ -56,6 +91,128 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
56
91
|
equal(Boolean(program.findGroup('raw', 1)?.clauses.some((clause) => clause.head.args[0]?.name === 'x')), true, 'conversion disabled');
|
|
57
92
|
});
|
|
58
93
|
|
|
94
|
+
|
|
95
|
+
reporter.test('uses a documented 7-bit ASCII processor character set and collation', () => {
|
|
96
|
+
const program = Program.parse('', { isoStrict: true });
|
|
97
|
+
const solver = new Solver(program, { isoStrict: true });
|
|
98
|
+
const answers = (text) => [...solver.solve([parseGoalText(text, { isoStrict: true })], new Env(), 0)].length;
|
|
99
|
+
equal(answers("char_code('\\0\\',0)"), 1, 'NUL collating integer');
|
|
100
|
+
equal(answers("char_code('A',65)"), 1, 'A collating integer');
|
|
101
|
+
equal(answers("char_code('\\177\\',127)"), 1, 'DEL collating integer');
|
|
102
|
+
equal(answers("'\\0\\' @< 'A'"), 1, 'control before capital');
|
|
103
|
+
equal(answers("'A' @< 'a'"), 1, 'capital before small letter');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
reporter.test('follows the Part 1 standard term-type and atom ordering', () => {
|
|
108
|
+
const program = Program.parse('', { isoStrict: true });
|
|
109
|
+
const solver = new Solver(program, { isoStrict: true });
|
|
110
|
+
const answers = (text) => [...solver.solve([parseGoalText(text, { isoStrict: true })], new Env(), 0)].length;
|
|
111
|
+
equal(answers("X @< 1.0"), 1, 'variable before float');
|
|
112
|
+
equal(answers("1.0 @< 1"), 1, 'float before integer');
|
|
113
|
+
equal(answers("1 @< a"), 1, 'integer before atom');
|
|
114
|
+
equal(answers("a @< f(a)"), 1, 'atom before compound');
|
|
115
|
+
equal(answers("'' @< 'A'"), 1, 'null atom first');
|
|
116
|
+
equal(answers("'A' @< 'B'"), 1, 'atom collation');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
reporter.test('rejects characters outside the strict processor character set', () => {
|
|
120
|
+
const sourceError = capture(() => Program.parse("p('é').\n", { isoStrict: true }));
|
|
121
|
+
equal(sourceError.formal, 'representation_error(character)', 'source representation error');
|
|
122
|
+
|
|
123
|
+
const readError = capture(() => run('', {
|
|
124
|
+
isoStrict: true,
|
|
125
|
+
goal: 'read(X)',
|
|
126
|
+
ioOptions: { input: "'é'." },
|
|
127
|
+
}));
|
|
128
|
+
equal(readError.formal, 'representation_error(character)', 'read representation error');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
reporter.test('restricts strict character codes to the processor character set', () => {
|
|
132
|
+
const charCodeError = capture(() => run('', { isoStrict: true, goal: 'char_code(_,128)' }));
|
|
133
|
+
equal(charCodeError.formal, 'representation_error(character_code)', 'char_code/2');
|
|
134
|
+
const atomCodesError = capture(() => run('', { isoStrict: true, goal: 'atom_codes(_, [128])' }));
|
|
135
|
+
equal(atomCodesError.formal, 'representation_error(character_code)', 'atom_codes/2');
|
|
136
|
+
const putCodeError = capture(() => run('', { isoStrict: true, goal: 'put_code(128)' }));
|
|
137
|
+
equal(putCodeError.formal, 'representation_error(character_code)', 'put_code/1');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
reporter.test('keeps broader Unicode character handling as a normal-mode extension', () => {
|
|
141
|
+
const result = run('', { goal: "char_code('é',233)" });
|
|
142
|
+
equal(result.stdout, "char_code('é', 233).\n", 'normal Unicode char_code/2');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
reporter.test('keeps the strict write-option surface to Part 1 plus Corrigendum 3', () => {
|
|
146
|
+
const strictError = capture(() => run('', {
|
|
147
|
+
isoStrict: true,
|
|
148
|
+
goal: 'write_term(a,[double_quotes(true)])',
|
|
149
|
+
}));
|
|
150
|
+
equal(strictError.formal, 'domain_error(write_option)', 'strict extension rejection');
|
|
151
|
+
|
|
152
|
+
const normal = run('', { goal: 'write_term("ab",[double_quotes(true)])' });
|
|
153
|
+
includes(normal.stdout, '"ab"', 'normal-profile extension remains available');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
reporter.test('follows the ISO 8.14.1.3 read_term/3 error order', () => {
|
|
157
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'read_term(f(a),_,[X])' })).formal,
|
|
158
|
+
'instantiation_error', 'partial/variable option before stream domain');
|
|
159
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'read_term(f(a),_,foo)' })).formal,
|
|
160
|
+
'domain_error(stream_or_alias)', 'stream domain before non-list options');
|
|
161
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'read_term(user_output,_,foo)' })).formal,
|
|
162
|
+
'type_error(list)', 'non-list options before stream permission');
|
|
163
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'read_term(user_output,_,[bogus])' })).formal,
|
|
164
|
+
'domain_error(read_option)', 'invalid option before stream permission');
|
|
165
|
+
equal(capture(() => run('', { isoStrict: true, goal: "read_term('$stream'(999),_,[bogus])" })).formal,
|
|
166
|
+
'domain_error(read_option)', 'invalid option before stream existence');
|
|
167
|
+
|
|
168
|
+
const program = Program.parse('', { isoStrict: true });
|
|
169
|
+
const solver = new Solver(program, { isoStrict: true, ioOptions: { input: 'a.' } });
|
|
170
|
+
const error = capture(() => [...solver.solve([
|
|
171
|
+
parseGoalText('read_term(user_input,_,[bogus])', { isoStrict: true }),
|
|
172
|
+
], new Env(), 0)]);
|
|
173
|
+
equal(error.formal, 'domain_error(read_option)', 'invalid read option');
|
|
174
|
+
equal(solver.io.resolve('user_input').position, 0, 'invalid options are rejected before input');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
reporter.test('follows the ISO 8.14.2.3 write_term/3 error order', () => {
|
|
178
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'write_term(f(a),a,[X])' })).formal,
|
|
179
|
+
'instantiation_error', 'partial/variable option before stream domain');
|
|
180
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'write_term(f(a),a,foo)' })).formal,
|
|
181
|
+
'type_error(list)', 'non-list options before stream domain');
|
|
182
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'write_term(f(a),a,[bogus])' })).formal,
|
|
183
|
+
'domain_error(stream_or_alias)', 'stream domain before invalid option');
|
|
184
|
+
equal(capture(() => run('', { isoStrict: true, goal: "write_term('$stream'(999),a,[bogus])" })).formal,
|
|
185
|
+
'domain_error(write_option)', 'invalid option before stream existence');
|
|
186
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'write_term(user_input,a,[bogus])' })).formal,
|
|
187
|
+
'domain_error(write_option)', 'invalid option before stream permission');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
reporter.test('follows the ISO 8.14.3.3 op/3 error order', () => {
|
|
191
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(a,X,0)' })).formal,
|
|
192
|
+
'instantiation_error', 'specifier variable before priority type');
|
|
193
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(a,xfy,[X])' })).formal,
|
|
194
|
+
'instantiation_error', 'operator variable element before priority type');
|
|
195
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(a,1,0)' })).formal,
|
|
196
|
+
'type_error(integer)', 'priority type before specifier type');
|
|
197
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(1,1,0)' })).formal,
|
|
198
|
+
'type_error(atom)', 'specifier type before operator-list type');
|
|
199
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(1300,xfy,[1])' })).formal,
|
|
200
|
+
'type_error(atom)', 'operator element type before priority domain');
|
|
201
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(1300,foo,a)' })).formal,
|
|
202
|
+
'domain_error(operator_priority)', 'priority domain before specifier domain');
|
|
203
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'op(100,foo,a)' })).formal,
|
|
204
|
+
'domain_error(operator_specifier)', 'specifier domain');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
reporter.test('uses ISO 8.14.4.3 domain errors for current_op/3 filters', () => {
|
|
208
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'current_op(a,_,_)' })).formal,
|
|
209
|
+
'domain_error(operator_priority)', 'priority domain');
|
|
210
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'current_op(1,1,_)' })).formal,
|
|
211
|
+
'domain_error(operator_specifier)', 'specifier domain');
|
|
212
|
+
equal(capture(() => run('', { isoStrict: true, goal: 'current_op(1,fx,1)' })).formal,
|
|
213
|
+
'type_error(atom)', 'operator type');
|
|
214
|
+
});
|
|
215
|
+
|
|
59
216
|
reporter.test('uses the Part 1 predefined operator table', () => {
|
|
60
217
|
const program = Program.parse('', { isoStrict: true });
|
|
61
218
|
equal(program.operators.has('fx\u0000?-'), true, 'fx ?-');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5119,8 +5119,13 @@ this part makes control, reflection, state, operators, and streams explicit.
|
|
|
5119
5119
|
For Part 1 portability work, EyeProlog also provides a strict core mode:
|
|
5120
5120
|
`--iso-strict` on the CLI or `isoStrict: true` in the JavaScript API restricts
|
|
5121
5121
|
the language/runtime surface to ISO/IEC 13211-1:1995 plus Technical Corrigenda
|
|
5122
|
-
1–3.
|
|
5123
|
-
|
|
5122
|
+
1–3. Its processor character set is the 128-character ASCII set U+0000..U+007F;
|
|
5123
|
+
C0 controls and DEL are implementation-defined extended layout characters, and
|
|
5124
|
+
collating-sequence integers are the corresponding ASCII codes. Character data
|
|
5125
|
+
outside that PCS is rejected with a representation error in strict mode. Normal
|
|
5126
|
+
mode keeps EyeProlog's broader Unicode scalar character support as an explicit
|
|
5127
|
+
extension. Isolated mode and error cases live in `test/conformance/cases/iso/`.
|
|
5128
|
+
The examples here compose those operations into programs worth changing and
|
|
5124
5129
|
rerunning.
|
|
5125
5130
|
|
|
5126
5131
|
These facilities do not all have the same declarative character. Term
|
|
@@ -5420,7 +5425,10 @@ emitted compactly when no lexical ambiguity would arise. For example,
|
|
|
5420
5425
|
still retained where adjacent graphic tokens would otherwise merge, as in
|
|
5421
5426
|
`a+ -b`.
|
|
5422
5427
|
`write_term/2-3` supports `quoted/1`, `ignore_ops/1`, `numbervars/1`, and
|
|
5423
|
-
`variable_names/1`.
|
|
5428
|
+
`variable_names/1`. Normal mode additionally accepts the EyeProlog extension
|
|
5429
|
+
`double_quotes(true|false)`: `true` lets eligible character/code lists use the
|
|
5430
|
+
current `double_quotes` representation, while strict ISO mode rejects this
|
|
5431
|
+
non-standard write option.
|
|
5424
5432
|
|
|
5425
5433
|
Character operations are `get_char`, `peek_char`, `put_char`, `get_code`,
|
|
5426
5434
|
`peek_code`, and `put_code`; byte streams use the corresponding byte
|
|
@@ -5495,15 +5503,20 @@ normal-mode profiles are documented and tested compatibility surfaces; they are
|
|
|
5495
5503
|
not currently claimed as complete clause-by-clause certifications of Part 2 or
|
|
5496
5504
|
Part 3.
|
|
5497
5505
|
|
|
5498
|
-
Prolog source accepted by EyeProlog is UTF-8. `%` starts a line
|
|
5499
|
-
`/* ... */` delimits a block comment. Plain atoms begin with a
|
|
5506
|
+
Normal-mode Prolog source accepted by EyeProlog is UTF-8. `%` starts a line
|
|
5507
|
+
comment and `/* ... */` delimits a block comment. Plain atoms begin with a
|
|
5500
5508
|
lowercase ASCII letter. Variables begin with uppercase or underscore. The bare
|
|
5501
5509
|
`_` is fresh each time. Single quotes delimit quoted atoms; double quotes use
|
|
5502
|
-
ISO double-quoted-list notation. Integers, decimals, scientific notation,
|
|
5503
|
-
hexadecimal integers, and character-code constants are accepted.
|
|
5510
|
+
ISO double-quoted-list notation. Integers, decimals, scientific notation,
|
|
5511
|
+
binary/octal/hexadecimal integers, and character-code constants are accepted.
|
|
5504
5512
|
|
|
5505
|
-
|
|
5506
|
-
|
|
5513
|
+
For `--iso-strict`, the processor character set is deliberately narrower and
|
|
5514
|
+
fully documented: U+0000..U+007F. Printable ASCII uses the Part 1 lexical
|
|
5515
|
+
classes; C0 controls and DEL are extended layout characters; character-code and
|
|
5516
|
+
collation values are the same ASCII integers 0..127. Non-ASCII input is a
|
|
5517
|
+
representation error. In normal mode, unquoted names still deliberately use
|
|
5518
|
+
ASCII spelling while Unicode scalar values may appear inside quoted atoms and
|
|
5519
|
+
double-quoted lists:
|
|
5507
5520
|
|
|
5508
5521
|
```eyeprolog
|
|
5509
5522
|
|
|
@@ -6036,8 +6049,8 @@ quoted_atom("ab"). % quoted_atom(ab)
|
|
|
6036
6049
|
| `atom_length(+Atom,?Length)` | Counts Unicode code points, not UTF-16 code units. A supplied length must be a nonnegative integer. |
|
|
6037
6050
|
| `atom_concat(?Prefix,?Suffix,?Whole)` | Concatenates two atoms, removes a supplied prefix or suffix, or enumerates every split when only `Whole` is bound. At least `Whole`, or both parts, must determine the operation. |
|
|
6038
6051
|
| `sub_atom(+Atom,?Before,?Length,?After,?SubAtom)` | Enumerates substrings and their Unicode-code-point offsets. Supplied counts must be nonnegative integers. |
|
|
6039
|
-
| `atom_chars(?Atom,?Chars)`, `atom_codes(?Atom,?Codes)` | Convert between an atom and a proper list of one-character atoms or Unicode scalar
|
|
6040
|
-
| `char_code(?Character,?Code)` | Converts one character atom and
|
|
6052
|
+
| `atom_chars(?Atom,?Chars)`, `atom_codes(?Atom,?Codes)` | Convert between an atom and a proper list of one-character atoms or character codes. Strict mode uses its ASCII PCS/codes `0..127`; normal mode extends codes to Unicode scalar values. At least one side must be instantiated. |
|
|
6053
|
+
| `char_code(?Character,?Code)` | Converts one character atom and its collating/code value. Strict mode accepts only the ASCII PCS `0..127`; normal mode accepts Unicode scalar codes and rejects surrogates/out-of-range values. |
|
|
6041
6054
|
| `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list using ISO number and negative-number syntax, including radix integers, character-code constants, and leading layout. The input is not parsed as a general term: grouping such as `(0)` is a syntax error. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*. |
|
|
6042
6055
|
|
|
6043
6056
|
Conversions accept partial output lists when the atomic input is known, but
|
|
@@ -6078,12 +6091,12 @@ input or output.
|
|
|
6078
6091
|
| `at_end_of_stream`, `at_end_of_stream(+Stream)` | Succeeds when the current or selected input position is at or beyond its content. |
|
|
6079
6092
|
| `get_char(?Character)`, `get_char(+Stream,?Character)` | Reads one text character; end of input is `end_of_file`. |
|
|
6080
6093
|
| `peek_char(?Character)`, `peek_char(+Stream,?Character)` | Observes the next text character without advancing. |
|
|
6081
|
-
| `get_code(?Code)`, `get_code(+Stream,?Code)` | Reads a
|
|
6082
|
-
| `peek_code(?Code)`, `peek_code(+Stream,?Code)` | Observes the next
|
|
6094
|
+
| `get_code(?Code)`, `get_code(+Stream,?Code)` | Reads a character code; end of input is `-1`. Strict mode requires the ASCII PCS, while normal mode returns Unicode scalar codes. |
|
|
6095
|
+
| `peek_code(?Code)`, `peek_code(+Stream,?Code)` | Observes the next character code without advancing; strict mode requires the ASCII PCS. |
|
|
6083
6096
|
| `get_byte(?Byte)`, `get_byte(+Stream,?Byte)` | Reads one unit from a binary stream; end of input is `-1`. |
|
|
6084
6097
|
| `peek_byte(?Byte)`, `peek_byte(+Stream,?Byte)` | Observes the next binary unit without advancing. |
|
|
6085
6098
|
| `put_char(+Character)`, `put_char(+Stream,+Character)` | Writes one character atom to a text stream. |
|
|
6086
|
-
| `put_code(+Code)`, `put_code(+Stream,+Code)` | Writes one
|
|
6099
|
+
| `put_code(+Code)`, `put_code(+Stream,+Code)` | Writes one character code to a text stream. Strict mode accepts `0..127`; normal mode accepts Unicode scalar codes. |
|
|
6087
6100
|
| `put_byte(+Byte)`, `put_byte(+Stream,+Byte)` | Writes an integer in `0..255` to a binary stream. |
|
|
6088
6101
|
| `nl`, `nl(+Stream)` | Writes a newline to a text stream. |
|
|
6089
6102
|
|
|
@@ -7636,8 +7649,11 @@ conformance audit; it does **not** yet claim that every processor requirement
|
|
|
7636
7649
|
has been independently certified. `test/conformance/ISO-COMPLIANCE.md` tracks
|
|
7637
7650
|
the remaining normative audit work. The executable examples are mostly
|
|
7638
7651
|
EyeProlog-profile programs using host-supplied goals, character lists, explicit
|
|
7639
|
-
integrity relations, automatic tabling, and the EyeProlog library. The
|
|
7640
|
-
|
|
7652
|
+
integrity relations, automatic tabling, and the EyeProlog library. The strict audit also exercises the complete Part 1 flag
|
|
7653
|
+
set and the prescribed 8.14 error precedence for `read_term/3`, `write_term/3`,
|
|
7654
|
+
`op/3`, and `current_op/3`; these checks distinguish invalid values from
|
|
7655
|
+
valid-but-fixed flag values and reject normal-profile write options at the
|
|
7656
|
+
strict boundary. The remaining qualifications are:
|
|
7641
7657
|
|
|
7642
7658
|
- zero-arity compound syntax such as `ready()` is represented by the atom
|
|
7643
7659
|
`ready`;
|
|
@@ -7645,8 +7661,11 @@ remaining qualifications are:
|
|
|
7645
7661
|
- variables cannot occupy functor or predicate position;
|
|
7646
7662
|
- double-quoted text follows `double_quotes` exactly; the default `chars` value
|
|
7647
7663
|
matches Trealla and Scryer and may be changed to `codes` or `atom`;
|
|
7648
|
-
- `write_term/2-3` implements
|
|
7649
|
-
|
|
7664
|
+
- `write_term/2-3` implements the Part 1 plus Corrigendum 3 `quoted/1`,
|
|
7665
|
+
`ignore_ops/1`, `numbervars/1`, and `variable_names/1` option surface,
|
|
7666
|
+
including option validation and traversal rules; normal mode also offers
|
|
7667
|
+
`double_quotes(true|false)` as an explicitly non-standard extension, which
|
|
7668
|
+
strict mode rejects;
|
|
7650
7669
|
- unification consistently performs an occurs check, rejecting rational-tree
|
|
7651
7670
|
bindings accepted as extensions by some systems.
|
|
7652
7671
|
|
package/why-eyeprolog.md
CHANGED
|
@@ -19,7 +19,10 @@ syntax.
|
|
|
19
19
|
|
|
20
20
|
EyeProlog targets the Part 1 core together with Technical Corrigenda 1, 2,
|
|
21
21
|
and 3, and provides documented module and definite-clause-grammar compatibility
|
|
22
|
-
profiles for normal-mode programs.
|
|
22
|
+
profiles for normal-mode programs. Strict mode makes its processor character
|
|
23
|
+
model explicit: 7-bit ASCII is the PCS, ASCII code points are the collating
|
|
24
|
+
integers, and normal-mode Unicode character data is an extension rather than an
|
|
25
|
+
implicit part of the Part 1 claim. Its executable conformance matrix and tests
|
|
23
26
|
document the supported behavior, including an executable trace of the vendored
|
|
24
27
|
active WG17 syntax cases. This is extensive implementation evidence, not a
|
|
25
28
|
claim that every Part 1, Part 2, or Part 3 normative requirement has already
|