eyeprolog 1.3.44 → 1.3.46
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 +6 -1
- package/package.json +1 -1
- package/src/io.js +33 -10
- package/src/iso-arithmetic.js +12 -10
- package/src/iso-limits.js +8 -0
- package/src/iso.js +259 -81
- package/src/parser.js +2 -0
- package/src/program.js +130 -19
- package/src/solver.js +6 -1
- package/test/conformance/ISO-COMPLIANCE.md +36 -7
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +2 -2
- package/test/conformance/ISO-MATRIX.md +4 -4
- package/test/conformance/README.md +3 -2
- package/test/conformance/expected/iso/exceptions_and_flags.pl +1 -1
- package/test/conformance/expected-errors/iso/get_byte_text_stream.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_functor_compound_name.txt +1 -1
- package/test/run-iso-strict.mjs +97 -3
- package/the-art-of-eyeprolog.md +8 -2
package/src/iso.js
CHANGED
|
@@ -14,6 +14,7 @@ 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
16
|
import { CharacterRepresentationError, isStrictIsoPcsCharacter, isStrictIsoPcsCodePoint } from './iso-character.js';
|
|
17
|
+
import { ISO_MAX_ARITY } from './iso-limits.js';
|
|
17
18
|
import {
|
|
18
19
|
characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
|
|
19
20
|
} from './syntax-scan.js';
|
|
@@ -96,7 +97,7 @@ export const isoBuiltins = {
|
|
|
96
97
|
registry.add('current_output', 1, currentOutputBuiltin, { deterministic: true });
|
|
97
98
|
registry.add('set_input', 1, setCurrentStreamBuiltin('read'), { deterministic: true });
|
|
98
99
|
registry.add('set_output', 1, setCurrentStreamBuiltin('write'), { deterministic: true });
|
|
99
|
-
registry.add('flush_output', 0,
|
|
100
|
+
registry.add('flush_output', 0, flushOutputBuiltin, { deterministic: true });
|
|
100
101
|
registry.add('flush_output', 1, flushOutputBuiltin, { deterministic: true });
|
|
101
102
|
registry.add('stream_property', 2, streamPropertyBuiltin);
|
|
102
103
|
registry.add('set_stream_position', 2, setStreamPositionBuiltin, { deterministic: true });
|
|
@@ -346,10 +347,18 @@ function* keysortBuiltin({ goal, env }) {
|
|
|
346
347
|
validateListOutput(goal.args[1], env);
|
|
347
348
|
for (const item of items) {
|
|
348
349
|
const resolved = deref(item, env);
|
|
350
|
+
if (resolved.type === VAR) throw new PrologError('instantiation_error');
|
|
349
351
|
if (resolved.type !== COMPOUND || resolved.name !== '-' || resolved.arity !== 2) {
|
|
350
352
|
throw new PrologError('type_error(pair)', resolved);
|
|
351
353
|
}
|
|
352
354
|
}
|
|
355
|
+
const { items: outputPrefix } = listElements(goal.args[1], env);
|
|
356
|
+
for (const item of outputPrefix) {
|
|
357
|
+
if (item.type === VAR) continue;
|
|
358
|
+
if (item.type !== COMPOUND || item.name !== '-' || item.arity !== 2) {
|
|
359
|
+
throw new PrologError('type_error(pair)', item);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
353
362
|
// Modern ECMAScript specifies a stable Array#sort, as required by keysort/2.
|
|
354
363
|
// Keep one implementation-dependent variable order for this whole sorting
|
|
355
364
|
// operation, as required by ISO 7.2.1.
|
|
@@ -381,18 +390,26 @@ function* functorBuiltin({ goal, env }) {
|
|
|
381
390
|
if (unify(goal.args[1], name, next) && unify(goal.args[2], numberTerm(term.arity), next)) yield next;
|
|
382
391
|
return;
|
|
383
392
|
}
|
|
393
|
+
|
|
384
394
|
const name = deref(goal.args[1], env);
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
395
|
+
const arityTerm = deref(goal.args[2], env);
|
|
396
|
+
// ISO 8.5.1.3 orders construction-mode diagnostics. A compound Name is an
|
|
397
|
+
// atomic type error (before an Arity type error); only an atomic non-atom
|
|
398
|
+
// used for positive arity receives type_error(atom,...).
|
|
399
|
+
if (name.type === VAR) throw new PrologError('instantiation_error');
|
|
400
|
+
if (arityTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
401
|
+
if (name.type === COMPOUND) throw new PrologError('type_error(atomic)', name);
|
|
402
|
+
if (arityTerm.type !== NUMBER || !isDecimalInteger(arityTerm.name)) {
|
|
403
|
+
throw new PrologError('type_error(integer)', arityTerm);
|
|
404
|
+
}
|
|
405
|
+
const arity = BigInt(arityTerm.name);
|
|
406
|
+
if (arity > 0n && name.type !== ATOM) throw new PrologError('type_error(atom)', name);
|
|
407
|
+
if (arity > BigInt(ISO_MAX_ARITY)) throw new PrologError('representation_error(max_arity)');
|
|
408
|
+
if (arity < 0n) throw new PrologError('domain_error(not_less_than_zero)', arityTerm);
|
|
388
409
|
if (arity === 0n) {
|
|
389
|
-
if (name.type === VAR) throw new PrologError('instantiation_error');
|
|
390
|
-
if (name.type === COMPOUND) throw new PrologError('type_error(atomic)', name);
|
|
391
410
|
if (unify(goal.args[0], name, next)) yield next;
|
|
392
411
|
return;
|
|
393
412
|
}
|
|
394
|
-
if (name.type === VAR) throw new PrologError('instantiation_error');
|
|
395
|
-
if (name.type !== ATOM) throw new PrologError('type_error(atom)', name);
|
|
396
413
|
const id = ++isoFresh;
|
|
397
414
|
if (unify(goal.args[0], compound(name.name, Array.from({ length: Number(arity) }, (_, i) => variable(`__functor${id}_${i}`))), next)) yield next;
|
|
398
415
|
}
|
|
@@ -412,6 +429,11 @@ function* univBuiltin({ goal, env }) {
|
|
|
412
429
|
const term = deref(goal.args[0], env);
|
|
413
430
|
const next = env.clone();
|
|
414
431
|
if (term.type !== VAR) {
|
|
432
|
+
// 8.5.3.3(b) also applies in decomposition mode: a fixed second argument
|
|
433
|
+
// that is neither a list nor a partial list is an error, not failure.
|
|
434
|
+
if (listKind(goal.args[1], env) === 'nonlist') {
|
|
435
|
+
throw new PrologError('type_error(list)', deref(goal.args[1], env));
|
|
436
|
+
}
|
|
415
437
|
const items = term.type === COMPOUND ? [atom(term.name), ...term.args] : [term];
|
|
416
438
|
if (unify(goal.args[1], listFromItems(items), next)) yield next;
|
|
417
439
|
return;
|
|
@@ -430,6 +452,7 @@ function* univBuiltin({ goal, env }) {
|
|
|
430
452
|
return;
|
|
431
453
|
}
|
|
432
454
|
const name = requireAtom(items[0], env);
|
|
455
|
+
if (items.length - 1 > ISO_MAX_ARITY) throw new PrologError('representation_error(max_arity)');
|
|
433
456
|
if (unify(goal.args[0], compound(name.name, items.slice(1)), next)) yield next;
|
|
434
457
|
}
|
|
435
458
|
|
|
@@ -511,20 +534,15 @@ function* clauseBuiltin({ solver, goal, env }) {
|
|
|
511
534
|
const head = deref(goal.args[0], env);
|
|
512
535
|
if (head.type === VAR) throw new PrologError('instantiation_error');
|
|
513
536
|
if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
|
|
514
|
-
callableOrVariable(goal.args[1], env);
|
|
515
537
|
const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
516
|
-
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head)) {
|
|
517
|
-
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
518
|
-
}
|
|
519
538
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
// clauses inspectable for proof tooling; strict core mode restores the ISO
|
|
524
|
-
// access rule used by clause/2.
|
|
525
|
-
if (solver.isoStrict && !group.dynamic) {
|
|
539
|
+
// ISO 8.8.1.3 places private-procedure access before Body callability.
|
|
540
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head) ||
|
|
541
|
+
(solver.isoStrict && group && !group.dynamic)) {
|
|
526
542
|
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
527
543
|
}
|
|
544
|
+
callableOrVariable(goal.args[1], env);
|
|
545
|
+
if (!group) return;
|
|
528
546
|
for (const clause of group.clauses) {
|
|
529
547
|
const pair = compound('$clause', [clause.head, clauseBodyTerm(clause.body)]);
|
|
530
548
|
const copied = freshCopy(pair, new Env());
|
|
@@ -641,7 +659,7 @@ function predicateIndicatorParts(term, env) {
|
|
|
641
659
|
if (name.type !== ATOM) throw new PrologError('type_error(atom)', name);
|
|
642
660
|
const integer = BigInt(arity.name);
|
|
643
661
|
if (integer < 0n) throw new PrologError('domain_error(not_less_than_zero)', arity);
|
|
644
|
-
if (integer > BigInt(
|
|
662
|
+
if (integer > BigInt(ISO_MAX_ARITY)) throw new PrologError('representation_error(max_arity)');
|
|
645
663
|
return { name: name.name, arity: Number(integer), indicator };
|
|
646
664
|
}
|
|
647
665
|
|
|
@@ -680,7 +698,8 @@ function* setPrologFlagBuiltin({ solver, goal, env }) {
|
|
|
680
698
|
if (flag.type !== ATOM) throw new PrologError('type_error(atom)', flag);
|
|
681
699
|
const definition = solver.prologFlags.get(flag.name);
|
|
682
700
|
if (!definition) throw new PrologError('domain_error(prolog_flag)', flag);
|
|
683
|
-
|
|
701
|
+
const expectedType = definition.valueType ?? ATOM;
|
|
702
|
+
if (value.type !== expectedType || !definition.allowed.includes(value.name)) {
|
|
684
703
|
throw new PrologError('domain_error(flag_value)', compound('+', [flag, value]));
|
|
685
704
|
}
|
|
686
705
|
if (!definition.changeable) throw new PrologError('permission_error(modify, flag)', flag);
|
|
@@ -830,6 +849,16 @@ function streamReference(term, env) {
|
|
|
830
849
|
throw new PrologError('domain_error(stream_or_alias)', value);
|
|
831
850
|
}
|
|
832
851
|
|
|
852
|
+
function streamTermReference(term, env) {
|
|
853
|
+
const value = deref(term, env);
|
|
854
|
+
if (value.type === VAR) return null;
|
|
855
|
+
if (value.type === COMPOUND && value.name === '$stream' && value.arity === 1 &&
|
|
856
|
+
value.args[0].type === NUMBER && isDecimalInteger(value.args[0].name)) {
|
|
857
|
+
return Number(value.args[0].name);
|
|
858
|
+
}
|
|
859
|
+
throw new PrologError('domain_error(stream)', value);
|
|
860
|
+
}
|
|
861
|
+
|
|
833
862
|
function requireStream(solver, term, env, mode = null) {
|
|
834
863
|
const culprit = deref(term, env);
|
|
835
864
|
const stream = solver.io.resolve(streamReference(term, env));
|
|
@@ -886,13 +915,14 @@ function openOptions(term, env) {
|
|
|
886
915
|
if ((value = optionAtom(option, 'type'))) {
|
|
887
916
|
value = deref(value, env);
|
|
888
917
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
889
|
-
if (value.type !== ATOM
|
|
890
|
-
|
|
918
|
+
if (value.type !== ATOM || !['text', 'binary'].includes(value.name)) {
|
|
919
|
+
throw new PrologError('domain_error(stream_option)', option);
|
|
920
|
+
}
|
|
891
921
|
result.type = value.name;
|
|
892
922
|
} else if ((value = optionAtom(option, 'alias'))) {
|
|
893
923
|
value = deref(value, env);
|
|
894
924
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
895
|
-
if (value.type !== ATOM) throw new PrologError('
|
|
925
|
+
if (value.type !== ATOM) throw new PrologError('domain_error(stream_option)', option);
|
|
896
926
|
result.alias = value.name;
|
|
897
927
|
} else if ((value = optionAtom(option, 'reposition'))) {
|
|
898
928
|
value = deref(value, env);
|
|
@@ -911,19 +941,45 @@ function openOptions(term, env) {
|
|
|
911
941
|
return result;
|
|
912
942
|
}
|
|
913
943
|
|
|
944
|
+
function streamOptionHasRequiredVariable(option, env) {
|
|
945
|
+
option = deref(option, env);
|
|
946
|
+
if (option.type !== COMPOUND || option.arity !== 1 ||
|
|
947
|
+
!['type', 'alias', 'reposition', 'eof_action'].includes(option.name)) return false;
|
|
948
|
+
return deref(option.args[0], env).type === VAR;
|
|
949
|
+
}
|
|
950
|
+
|
|
914
951
|
function* openBuiltin({ solver, goal, env }) {
|
|
915
|
-
const
|
|
916
|
-
const mode =
|
|
917
|
-
if (!['read', 'write', 'append'].includes(mode.name)) throw new PrologError('domain_error(io_mode)', mode);
|
|
952
|
+
const source = deref(goal.args[0], env);
|
|
953
|
+
const mode = deref(goal.args[1], env);
|
|
918
954
|
const streamTarget = deref(goal.args[2], env);
|
|
955
|
+
const optionTerm = goal.arity === 3 ? emptyList() : goal.args[3];
|
|
956
|
+
|
|
957
|
+
// ISO 8.11.5.3 + Corrigenda 2/3. Keep each prescribed condition
|
|
958
|
+
// observable on its own and apply the published ordering for deterministic
|
|
959
|
+
// diagnostics when several arguments are erroneous.
|
|
960
|
+
if (source.type === VAR) throw new PrologError('instantiation_error');
|
|
961
|
+
if (mode.type === VAR) throw new PrologError('instantiation_error');
|
|
962
|
+
const preflight = preflightOptionList(optionTerm, env);
|
|
963
|
+
if (preflight.items.some((option) => streamOptionHasRequiredVariable(option, env))) {
|
|
964
|
+
throw new PrologError('instantiation_error');
|
|
965
|
+
}
|
|
966
|
+
if (mode.type !== ATOM) throw new PrologError('type_error(atom)', mode);
|
|
967
|
+
const optionItems = requireProperOptionList(preflight);
|
|
919
968
|
if (streamTarget.type !== VAR) throw new PrologError('uninstantiation_error', streamTarget);
|
|
920
|
-
|
|
921
|
-
if (
|
|
969
|
+
if (source.type !== ATOM) throw new PrologError('domain_error(source_sink)', source);
|
|
970
|
+
if (!['read', 'write', 'append'].includes(mode.name)) throw new PrologError('domain_error(io_mode)', mode);
|
|
971
|
+
const options = goal.arity === 3 ? {} : openOptions(optionTerm, env);
|
|
972
|
+
if (options.alias && solver.io.resolve(options.alias)) {
|
|
973
|
+
throw new PrologError('permission_error(open, source_sink)', atom(options.alias));
|
|
974
|
+
}
|
|
922
975
|
let stream;
|
|
923
976
|
try {
|
|
924
|
-
stream = solver.io.open(
|
|
925
|
-
} catch (
|
|
926
|
-
|
|
977
|
+
stream = solver.io.open(source.name, mode.name, options);
|
|
978
|
+
} catch (error) {
|
|
979
|
+
if (error?.code === 'ENOENT' && mode.name === 'read') {
|
|
980
|
+
throw new PrologError('existence_error(source_sink)', source);
|
|
981
|
+
}
|
|
982
|
+
throw new PrologError('permission_error(open, source_sink)', source);
|
|
927
983
|
}
|
|
928
984
|
const next = env.clone();
|
|
929
985
|
if (unify(goal.args[2], streamHandle(stream.id), next)) yield next;
|
|
@@ -931,22 +987,35 @@ function* openBuiltin({ solver, goal, env }) {
|
|
|
931
987
|
}
|
|
932
988
|
|
|
933
989
|
function* closeBuiltin({ solver, goal, env }) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
990
|
+
const reference = deref(goal.args[0], env);
|
|
991
|
+
if (reference.type === VAR) throw new PrologError('instantiation_error');
|
|
992
|
+
const optionTerm = goal.arity === 2 ? goal.args[1] : emptyList();
|
|
993
|
+
const preflight = preflightOptionList(optionTerm, env);
|
|
994
|
+
if (preflight.items.some((option) => {
|
|
995
|
+
const force = optionAtom(option, 'force');
|
|
996
|
+
return force != null && deref(force, env).type === VAR;
|
|
997
|
+
})) throw new PrologError('instantiation_error');
|
|
998
|
+
const options = requireProperOptionList(preflight);
|
|
999
|
+
streamReference(goal.args[0], env);
|
|
1000
|
+
let forceClose = false;
|
|
1001
|
+
for (const option of options) {
|
|
1002
|
+
const force = optionAtom(option, 'force');
|
|
1003
|
+
const value = force && deref(force, env);
|
|
1004
|
+
if (!value || value.type !== ATOM || !['true', 'false'].includes(value.name)) {
|
|
1005
|
+
throw new PrologError('domain_error(close_option)', option);
|
|
943
1006
|
}
|
|
1007
|
+
forceClose = value.name === 'true'; // rightmost contradictory option applies
|
|
944
1008
|
}
|
|
945
1009
|
const stream = requireStream(solver, goal.args[0], env);
|
|
946
1010
|
if (!stream.standard) {
|
|
1011
|
+
try {
|
|
1012
|
+
solver.io.close(stream);
|
|
1013
|
+
} catch (_) {
|
|
1014
|
+
if (!forceClose) throw new PrologError('system_error');
|
|
1015
|
+
solver.io.discard(stream);
|
|
1016
|
+
}
|
|
947
1017
|
if (solver.io.currentInput === stream.id) solver.io.currentInput = 0;
|
|
948
1018
|
if (solver.io.currentOutput === stream.id) solver.io.currentOutput = 1;
|
|
949
|
-
solver.io.close(stream);
|
|
950
1019
|
}
|
|
951
1020
|
yield env;
|
|
952
1021
|
}
|
|
@@ -954,7 +1023,8 @@ function* closeBuiltin({ solver, goal, env }) {
|
|
|
954
1023
|
function* currentInputBuiltin({ solver, goal, env }) {
|
|
955
1024
|
const value = deref(goal.args[0], env);
|
|
956
1025
|
if (value.type !== VAR) {
|
|
957
|
-
const
|
|
1026
|
+
const id = streamTermReference(goal.args[0], env);
|
|
1027
|
+
const stream = solver.io.resolve(id);
|
|
958
1028
|
if (!stream) throw new PrologError('domain_error(stream)', value);
|
|
959
1029
|
if (stream.id === solver.io.currentInput) yield env;
|
|
960
1030
|
return;
|
|
@@ -965,7 +1035,8 @@ function* currentInputBuiltin({ solver, goal, env }) {
|
|
|
965
1035
|
function* currentOutputBuiltin({ solver, goal, env }) {
|
|
966
1036
|
const value = deref(goal.args[0], env);
|
|
967
1037
|
if (value.type !== VAR) {
|
|
968
|
-
const
|
|
1038
|
+
const id = streamTermReference(goal.args[0], env);
|
|
1039
|
+
const stream = solver.io.resolve(id);
|
|
969
1040
|
if (!stream) throw new PrologError('domain_error(stream)', value);
|
|
970
1041
|
if (stream.id === solver.io.currentOutput) yield env;
|
|
971
1042
|
return;
|
|
@@ -973,6 +1044,7 @@ function* currentOutputBuiltin({ solver, goal, env }) {
|
|
|
973
1044
|
const next = env.clone();
|
|
974
1045
|
if (unify(goal.args[0], streamHandle(solver.io.currentOutput), next)) yield next;
|
|
975
1046
|
}
|
|
1047
|
+
|
|
976
1048
|
function setCurrentStreamBuiltin(mode) {
|
|
977
1049
|
return function* ({ solver, goal, env }) {
|
|
978
1050
|
const stream = requireStream(solver, goal.args[0], env, mode);
|
|
@@ -982,7 +1054,14 @@ function setCurrentStreamBuiltin(mode) {
|
|
|
982
1054
|
};
|
|
983
1055
|
}
|
|
984
1056
|
function* flushOutputBuiltin({ solver, goal, env }) {
|
|
985
|
-
|
|
1057
|
+
const stream = goal.arity === 0
|
|
1058
|
+
? solver.io.resolve(solver.io.currentOutput)
|
|
1059
|
+
: requireStream(solver, goal.args[0], env, 'write');
|
|
1060
|
+
try {
|
|
1061
|
+
solver.io.flush(stream);
|
|
1062
|
+
} catch (_) {
|
|
1063
|
+
throw new PrologError('system_error');
|
|
1064
|
+
}
|
|
986
1065
|
yield env;
|
|
987
1066
|
}
|
|
988
1067
|
|
|
@@ -1003,23 +1082,51 @@ function streamProperties(stream) {
|
|
|
1003
1082
|
return properties;
|
|
1004
1083
|
}
|
|
1005
1084
|
function* setStreamPositionBuiltin({ solver, goal, env }) {
|
|
1006
|
-
const
|
|
1007
|
-
if (
|
|
1085
|
+
const reference = deref(goal.args[0], env);
|
|
1086
|
+
if (reference.type === VAR) throw new PrologError('instantiation_error');
|
|
1008
1087
|
let position = deref(goal.args[1], env);
|
|
1009
|
-
if (position.type ===
|
|
1088
|
+
if (position.type === VAR) throw new PrologError('instantiation_error');
|
|
1089
|
+
streamReference(goal.args[0], env);
|
|
1090
|
+
if (!solver.isoStrict && position.type === COMPOUND && position.name === 'position' && position.arity === 1) {
|
|
1091
|
+
// Historical EyeProlog convenience form. Strict Part 1 mode uses only
|
|
1092
|
+
// the implementation-defined integer stream-position terms returned by
|
|
1093
|
+
// stream_property/2.
|
|
1010
1094
|
position = deref(position.args[0], env);
|
|
1011
1095
|
}
|
|
1012
|
-
if (position.type === VAR
|
|
1013
|
-
|
|
1096
|
+
if (position.type === VAR || position.type !== NUMBER || !isDecimalInteger(position.name)) {
|
|
1097
|
+
throw new PrologError('domain_error(stream_position)', position);
|
|
1098
|
+
}
|
|
1014
1099
|
const offset = BigInt(position.name);
|
|
1015
|
-
if (offset < 0n
|
|
1100
|
+
if (offset < 0n) throw new PrologError('domain_error(stream_position)', position);
|
|
1101
|
+
const stream = requireStream(solver, goal.args[0], env);
|
|
1102
|
+
if (!stream.reposition) throw new PrologError('permission_error(reposition, stream)', reference);
|
|
1103
|
+
if (offset > BigInt(stream.content.length)) throw new PrologError('domain_error(stream_position)', position);
|
|
1016
1104
|
stream.position = Number(offset);
|
|
1017
1105
|
stream.pastEnd = false;
|
|
1018
1106
|
yield env;
|
|
1019
1107
|
}
|
|
1108
|
+
|
|
1109
|
+
function isStreamPropertyPattern(value) {
|
|
1110
|
+
if (value.type === VAR) return true;
|
|
1111
|
+
if (value.type === ATOM) return value.name === 'input' || value.name === 'output';
|
|
1112
|
+
return value.type === COMPOUND && value.arity === 1 && [
|
|
1113
|
+
'file_name', 'mode', 'alias', 'position', 'end_of_stream', 'eof_action', 'reposition', 'type',
|
|
1114
|
+
].includes(value.name);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1020
1117
|
function* streamPropertyBuiltin({ solver, goal, env }) {
|
|
1021
1118
|
const reference = deref(goal.args[0], env);
|
|
1022
|
-
const
|
|
1119
|
+
const propertyPattern = deref(goal.args[1], env);
|
|
1120
|
+
if (reference.type !== VAR) {
|
|
1121
|
+
const id = streamTermReference(goal.args[0], env);
|
|
1122
|
+
if (!solver.io.resolve(id)) throw new PrologError('domain_error(stream)', reference);
|
|
1123
|
+
}
|
|
1124
|
+
if (!isStreamPropertyPattern(propertyPattern)) {
|
|
1125
|
+
throw new PrologError('domain_error(stream_property)', propertyPattern);
|
|
1126
|
+
}
|
|
1127
|
+
const streams = reference.type === VAR
|
|
1128
|
+
? [...solver.io.streams.values()]
|
|
1129
|
+
: [solver.io.resolve(streamTermReference(goal.args[0], env))];
|
|
1023
1130
|
for (const stream of streams) {
|
|
1024
1131
|
for (const property of streamProperties(stream)) {
|
|
1025
1132
|
const next = env.clone();
|
|
@@ -1035,27 +1142,69 @@ function outputStreamFor(solver, goal, env) {
|
|
|
1035
1142
|
return goal.arity === 1 ? solver.io.resolve(solver.io.currentOutput) : requireStream(solver, goal.args[0], env, 'write');
|
|
1036
1143
|
}
|
|
1037
1144
|
function* atEndBuiltin({ solver, goal, env }) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1145
|
+
// at_end_of_stream/1 is bootstrapped in Part 1 from the end_of_stream/1
|
|
1146
|
+
// stream property and therefore accepts any open stream-or-alias; it is not
|
|
1147
|
+
// an input-only operation.
|
|
1148
|
+
const stream = goal.arity === 0 ? solver.io.resolve(solver.io.currentInput) : requireStream(solver, goal.args[0], env);
|
|
1149
|
+
if (stream.pastEnd || stream.position >= stream.content.length) yield env;
|
|
1150
|
+
}
|
|
1151
|
+
function validStrictInputCharacterCode(value, solver) {
|
|
1152
|
+
if (value.type !== NUMBER || !isDecimalInteger(value.name)) return false;
|
|
1153
|
+
const code = BigInt(value.name);
|
|
1154
|
+
if (code === -1n) return true;
|
|
1155
|
+
if (code < 0n || code > 0x10ffffn || (code >= 0xd800n && code <= 0xdfffn)) return false;
|
|
1156
|
+
return !solver.isoStrict || isStrictIsoPcsCodePoint(Number(code));
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function preflightInputUnitTarget(name, target, env, solver) {
|
|
1160
|
+
const value = deref(target, env);
|
|
1161
|
+
if (name.endsWith('char')) {
|
|
1162
|
+
if (value.type !== VAR && !(oneChar(value) || (value.type === ATOM && value.name === 'end_of_file'))) {
|
|
1163
|
+
throw new PrologError('type_error(in_character)', value);
|
|
1164
|
+
}
|
|
1165
|
+
if (value.type === ATOM && oneChar(value) && solver.isoStrict && !isStrictIsoPcsCharacter(value.name)) {
|
|
1166
|
+
throw new PrologError('type_error(in_character)', value);
|
|
1167
|
+
}
|
|
1168
|
+
} else if (name.endsWith('code')) {
|
|
1169
|
+
if (value.type !== VAR && (value.type !== NUMBER || !isDecimalInteger(value.name))) {
|
|
1170
|
+
throw new PrologError('type_error(integer)', value);
|
|
1171
|
+
}
|
|
1172
|
+
if (value.type !== VAR && !validStrictInputCharacterCode(value, solver)) {
|
|
1173
|
+
throw new PrologError('representation_error(in_character_code)');
|
|
1174
|
+
}
|
|
1175
|
+
} else {
|
|
1176
|
+
if (value.type !== VAR) {
|
|
1177
|
+
if (value.type !== NUMBER || !isDecimalInteger(value.name)) throw new PrologError('type_error(in_byte)', value);
|
|
1178
|
+
const byte = BigInt(value.name);
|
|
1179
|
+
if (byte !== -1n && (byte < 0n || byte > 255n)) throw new PrologError('type_error(in_byte)', value);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return value;
|
|
1040
1183
|
}
|
|
1184
|
+
|
|
1041
1185
|
function inputUnitBuiltin(name) {
|
|
1042
1186
|
return function* ({ solver, goal, env }) {
|
|
1187
|
+
const explicit = goal.arity === 2;
|
|
1188
|
+
if (explicit && deref(goal.args[0], env).type === VAR) throw new PrologError('instantiation_error');
|
|
1189
|
+
const target = goal.args[goal.arity - 1];
|
|
1190
|
+
preflightInputUnitTarget(name, target, env, solver);
|
|
1043
1191
|
const stream = inputStreamFor(solver, goal, env);
|
|
1044
1192
|
const binary = name.endsWith('byte');
|
|
1045
|
-
if (binary
|
|
1193
|
+
if (binary && stream.type !== 'binary') {
|
|
1194
|
+
throw new PrologError('permission_error(input, text_stream)', streamHandle(stream.id));
|
|
1195
|
+
}
|
|
1196
|
+
if (!binary && stream.type !== 'text') {
|
|
1197
|
+
throw new PrologError('permission_error(input, binary_stream)', streamHandle(stream.id));
|
|
1198
|
+
}
|
|
1046
1199
|
if (stream.pastEnd && stream.eofAction === 'error') {
|
|
1047
1200
|
throw new PrologError('permission_error(input, past_end_of_stream)', streamHandle(stream.id));
|
|
1048
1201
|
}
|
|
1049
1202
|
if (stream.pastEnd && stream.eofAction === 'reset') {
|
|
1050
|
-
// A terminal EOF (Ctrl-D) is local to one input operation. The next
|
|
1051
|
-
// operation should wait for fresh terminal input rather than rewinding
|
|
1052
|
-
// and replaying the already-consumed interactive buffer.
|
|
1053
1203
|
if (typeof stream.interactiveReadUnit !== 'function') stream.position = 0;
|
|
1054
1204
|
stream.pastEnd = false;
|
|
1055
1205
|
}
|
|
1056
1206
|
const peek = name.startsWith('peek');
|
|
1057
|
-
if (stream.position >= stream.content.length &&
|
|
1058
|
-
typeof stream.interactiveReadUnit === 'function') {
|
|
1207
|
+
if (stream.position >= stream.content.length && typeof stream.interactiveReadUnit === 'function') {
|
|
1059
1208
|
const text = stream.interactiveReadUnit();
|
|
1060
1209
|
if (text != null) {
|
|
1061
1210
|
stream.content += String(text);
|
|
@@ -1066,50 +1215,68 @@ function inputUnitBuiltin(name) {
|
|
|
1066
1215
|
try {
|
|
1067
1216
|
unit = solver.io.readUnit(stream, peek);
|
|
1068
1217
|
} catch (error) {
|
|
1069
|
-
if (error?.name === 'InvalidCharacterEncodingError')
|
|
1070
|
-
throw new PrologError('representation_error(character)');
|
|
1071
|
-
}
|
|
1218
|
+
if (error?.name === 'InvalidCharacterEncodingError') throw new PrologError('representation_error(character)');
|
|
1072
1219
|
throw error;
|
|
1073
1220
|
}
|
|
1074
1221
|
if (unit == null && !peek) stream.pastEnd = true;
|
|
1075
1222
|
if (!binary && unit != null && solver.isoStrict && !isStrictIsoPcsCharacter(unit)) {
|
|
1076
1223
|
throw new PrologError('representation_error(character)');
|
|
1077
1224
|
}
|
|
1078
|
-
const result = unit == null
|
|
1225
|
+
const result = unit == null
|
|
1226
|
+
? (binary ? numberTerm(-1) : name.endsWith('code') ? numberTerm(-1) : atom('end_of_file'))
|
|
1079
1227
|
: binary ? numberTerm(unit) : name.endsWith('code') ? numberTerm(unit.codePointAt(0)) : atom(unit);
|
|
1080
|
-
const target = goal.args[goal.arity - 1];
|
|
1081
1228
|
const next = env.clone();
|
|
1082
1229
|
if (unify(target, result, next)) yield next;
|
|
1083
1230
|
};
|
|
1084
1231
|
}
|
|
1232
|
+
|
|
1233
|
+
function preflightOutputUnitValue(name, target, env) {
|
|
1234
|
+
const value = deref(target, env);
|
|
1235
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
1236
|
+
if (name === 'put_char') {
|
|
1237
|
+
if (!oneChar(value)) throw new PrologError('type_error(character)', value);
|
|
1238
|
+
return value;
|
|
1239
|
+
}
|
|
1240
|
+
if (name === 'put_byte') {
|
|
1241
|
+
if (value.type !== NUMBER || !isDecimalInteger(value.name)) throw new PrologError('type_error(byte)', value);
|
|
1242
|
+
const byte = BigInt(value.name);
|
|
1243
|
+
if (byte < 0n || byte > 255n) throw new PrologError('type_error(byte)', value);
|
|
1244
|
+
return value;
|
|
1245
|
+
}
|
|
1246
|
+
if (value.type !== NUMBER || !isDecimalInteger(value.name)) throw new PrologError('type_error(integer)', value);
|
|
1247
|
+
return value;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1085
1250
|
function outputUnitBuiltin(name) {
|
|
1086
1251
|
return function* ({ solver, goal, env }) {
|
|
1252
|
+
const explicit = goal.arity === 2;
|
|
1253
|
+
if (explicit && deref(goal.args[0], env).type === VAR) throw new PrologError('instantiation_error');
|
|
1254
|
+
const value = preflightOutputUnitValue(name, goal.args[goal.arity - 1], env);
|
|
1087
1255
|
const stream = outputStreamFor(solver, goal, env);
|
|
1088
|
-
const value = deref(goal.args[goal.arity - 1], env);
|
|
1089
|
-
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
1090
1256
|
if (name === 'put_char') {
|
|
1091
1257
|
if (stream.type !== 'text') throw new PrologError('permission_error(output, binary_stream)', streamHandle(stream.id));
|
|
1092
|
-
if (!oneChar(value)) throw new PrologError('type_error(character)', value);
|
|
1093
1258
|
if (solver.isoStrict && !isStrictIsoPcsCharacter(value.name)) {
|
|
1094
1259
|
throw new PrologError('representation_error(character)', value);
|
|
1095
1260
|
}
|
|
1096
1261
|
solver.io.writeUnit(stream, value.name);
|
|
1262
|
+
} else if (name === 'put_byte') {
|
|
1263
|
+
if (stream.type !== 'binary') throw new PrologError('permission_error(output, text_stream)', streamHandle(stream.id));
|
|
1264
|
+
solver.io.writeUnit(stream, Number(value.name));
|
|
1097
1265
|
} else {
|
|
1098
|
-
if (
|
|
1099
|
-
throw new PrologError('permission_error(output, stream)', streamHandle(stream.id));
|
|
1100
|
-
}
|
|
1101
|
-
if (value.type !== NUMBER || !isDecimalInteger(value.name)) throw new PrologError('type_error(integer)', value);
|
|
1266
|
+
if (stream.type !== 'text') throw new PrologError('permission_error(output, binary_stream)', streamHandle(stream.id));
|
|
1102
1267
|
const code = BigInt(value.name);
|
|
1103
|
-
|
|
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))) {
|
|
1268
|
+
if (code < 0n || code > 0x10ffffn || (code >= 0xd800n && code <= 0xdfffn)) {
|
|
1106
1269
|
throw new PrologError('representation_error(character_code)');
|
|
1107
1270
|
}
|
|
1108
|
-
solver.
|
|
1271
|
+
if (solver.isoStrict && !isStrictIsoPcsCodePoint(Number(code))) {
|
|
1272
|
+
throw new PrologError('representation_error(character_code)');
|
|
1273
|
+
}
|
|
1274
|
+
solver.io.writeUnit(stream, String.fromCodePoint(Number(code)));
|
|
1109
1275
|
}
|
|
1110
1276
|
yield env;
|
|
1111
1277
|
};
|
|
1112
1278
|
}
|
|
1279
|
+
|
|
1113
1280
|
function* nlBuiltin({ solver, goal, env }) {
|
|
1114
1281
|
const stream = goal.arity === 0
|
|
1115
1282
|
? solver.io.resolve(solver.io.currentOutput)
|
|
@@ -1274,6 +1441,13 @@ export function isCompleteReadTermText(text, solver) {
|
|
|
1274
1441
|
}
|
|
1275
1442
|
|
|
1276
1443
|
function readTermFromStream(stream, solver) {
|
|
1444
|
+
if (stream.pastEnd && stream.eofAction === 'error') {
|
|
1445
|
+
throw new PrologError('permission_error(input, past_end_of_stream)', streamHandle(stream.id));
|
|
1446
|
+
}
|
|
1447
|
+
if (stream.pastEnd && stream.eofAction === 'reset') {
|
|
1448
|
+
if (typeof stream.interactiveReadTerm !== 'function') stream.position = 0;
|
|
1449
|
+
stream.pastEnd = false;
|
|
1450
|
+
}
|
|
1277
1451
|
let requestedInteractiveTerm = false;
|
|
1278
1452
|
while (true) {
|
|
1279
1453
|
let sawCandidate = false;
|
|
@@ -1317,6 +1491,7 @@ function readTermFromStream(stream, solver) {
|
|
|
1317
1491
|
stream.position = source.length;
|
|
1318
1492
|
if (!sawCandidate) {
|
|
1319
1493
|
if (hasNonLayoutRemainder(source, remainderStart)) throw new PrologError('syntax_error(read_term)');
|
|
1494
|
+
stream.pastEnd = true;
|
|
1320
1495
|
return { term: atom('end_of_file'), variables: [] };
|
|
1321
1496
|
}
|
|
1322
1497
|
throw new PrologError('syntax_error(read_term)');
|
|
@@ -1789,14 +1964,14 @@ function canonicalNumberText(value) {
|
|
|
1789
1964
|
return text;
|
|
1790
1965
|
}
|
|
1791
1966
|
|
|
1792
|
-
function numberListText(list, env, kind, valueIsBound) {
|
|
1967
|
+
function numberListText(list, env, kind, valueIsBound, solver = null) {
|
|
1793
1968
|
const whole = deref(list, env);
|
|
1794
1969
|
const { items, tail } = listElements(list, env);
|
|
1795
1970
|
const proper = tail.type === ATOM && tail.name === '[]';
|
|
1796
1971
|
if (tail.type !== VAR && !proper) throw new PrologError('type_error(list)', whole);
|
|
1797
1972
|
|
|
1798
1973
|
const invalid = items.find((item) => item.type !== VAR &&
|
|
1799
|
-
(kind === 'chars' ? !oneChar(item) : !validCharacterCode(item)));
|
|
1974
|
+
(kind === 'chars' ? !oneChar(item) : !validCharacterCode(item, solver)));
|
|
1800
1975
|
if (invalid) {
|
|
1801
1976
|
if (kind === 'chars') throw new PrologError('type_error(character)', invalid);
|
|
1802
1977
|
if (invalid.type !== NUMBER || !isDecimalInteger(invalid.name)) {
|
|
@@ -1816,12 +1991,12 @@ function numberListText(list, env, kind, valueIsBound) {
|
|
|
1816
1991
|
const numberSyntaxError = new PrologError('syntax_error(number)');
|
|
1817
1992
|
|
|
1818
1993
|
function numberListBuiltin(kind) {
|
|
1819
|
-
return function* ({ goal, env }) {
|
|
1994
|
+
return function* ({ solver, goal, env }) {
|
|
1820
1995
|
const value = deref(goal.args[0], env);
|
|
1821
1996
|
if (value.type !== VAR && value.type !== NUMBER) throw new PrologError('type_error(number)', value);
|
|
1822
1997
|
const list = deref(goal.args[1], env);
|
|
1823
1998
|
if (value.type === VAR && list.type === VAR) throw new PrologError('instantiation_error');
|
|
1824
|
-
const text = numberListText(list, env, kind, value.type === NUMBER);
|
|
1999
|
+
const text = numberListText(list, env, kind, value.type === NUMBER, solver);
|
|
1825
2000
|
if (value.type === NUMBER) {
|
|
1826
2001
|
if (text != null) {
|
|
1827
2002
|
const parsed = parseIsoNumber(text);
|
|
@@ -1908,11 +2083,13 @@ function flatFindallTemplate(template) {
|
|
|
1908
2083
|
|
|
1909
2084
|
function* findallBuiltin({ solver, goal, env }) {
|
|
1910
2085
|
const [template, innerGoal, bag] = goal.args;
|
|
2086
|
+
// ISO 8.10.1.3 diagnoses Goal before Instances when both are erroneous.
|
|
2087
|
+
const invoked = callable(innerGoal, env);
|
|
1911
2088
|
assertListOrPartial(bag, env);
|
|
1912
2089
|
const collector = solver.cloneForInnerGoal(10000000);
|
|
1913
2090
|
let compact = flatFindallTemplate(template);
|
|
1914
2091
|
let collected = compact == null ? [] : null;
|
|
1915
|
-
for (const answerEnv of collector.solve([
|
|
2092
|
+
for (const answerEnv of collector.solve([invoked], env.clone(), 0)) {
|
|
1916
2093
|
if (compact != null && compact.push(template, answerEnv)) continue;
|
|
1917
2094
|
if (compact != null) {
|
|
1918
2095
|
collected = compact.materialize();
|
|
@@ -1971,8 +2148,9 @@ function sortedUnique(items) {
|
|
|
1971
2148
|
|
|
1972
2149
|
function allSolutionsBuiltin(asSet) {
|
|
1973
2150
|
return function* ({ solver, goal, env }) {
|
|
1974
|
-
|
|
2151
|
+
// ISO 8.10.2.3/8.10.3.3 diagnose the iterated goal before Instances.
|
|
1975
2152
|
const { iterated, quantified } = bagGoalParts(goal.args[1], env);
|
|
2153
|
+
assertListOrPartial(goal.args[2], env);
|
|
1976
2154
|
const free = freeVariables(iterated, goal.args[0], quantified, env);
|
|
1977
2155
|
const collector = solver.cloneForInnerGoal(10000000);
|
|
1978
2156
|
const groups = [];
|
package/src/parser.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { ATOM, COMPOUND, atom, compound, cons, emptyList, numberTerm, variable } from './term.js';
|
|
4
4
|
import { continuesGraphicToken, isTerminatingFullStop } from './syntax-scan.js';
|
|
5
5
|
import { CharacterRepresentationError, isStrictIsoPcsCodePoint } from './iso-character.js';
|
|
6
|
+
import { ISO_MAX_ARITY } from './iso-limits.js';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
export class NumberRepresentationError extends Error {
|
|
@@ -723,6 +724,7 @@ class Parser {
|
|
|
723
724
|
}
|
|
724
725
|
while (true) {
|
|
725
726
|
args.push(this.parseTerm(ARG_MIN_PRECEDENCE, false, false, true));
|
|
727
|
+
if (args.length > ISO_MAX_ARITY) throw new NumberRepresentationError('representation_error(max_arity)');
|
|
726
728
|
if (this.token.type !== TOK.COMMA) break;
|
|
727
729
|
this.advance();
|
|
728
730
|
}
|